← Back to Blog

Observability as a Compiler Pass

April 2026 observabilityotel

Every language eventually grows an OTEL library. Most look the same: you wrap every function by hand with tracer.start_span(...), you forget to close spans on error, you never instrument pipeline stages because it's too noisy. Lateralus takes a different path — the instrumentation *is* a compiler pass.

◉ The attribute

#[traced(name = "billing.settle")]
fn settle_invoice(inv: Invoice) -> Result<Receipt, Error> {
    inv
        |> validate()
        |> charge_card()
        |> mark_paid()
        |> issue_receipt()
}

After compilation, the body is wrapped:

let __span = std::otel::start_span("billing.settle", Sampler::parent_based())
try {
    ...original body...
} recover err {
    std::otel::set_status(__span, err)
    raise err
}
std::otel::end_span(__span)

You never type those wrapping lines. You never forget end_span. You never leak a span on a panic.

◉ Pipeline-stage tracing

The real win is #[traced_pipeline]. Applied to a let binding whose RHS is a pipeline, it emits one span per stage:

#[traced_pipeline]
let report = txns
    |> filter(is_recent)
    |> group_by(category)
    |> summarize()

Flame graphs now show filter, group_by, summarize as sibling spans — which is exactly what you want when chasing a slow stage.

◉ Why not runtime reflection?

Two reasons:

No runtime tax when tracing is off

If the binary is built with --features=otel=off, the pass is skipped and there is zero residue. No branches, no checks, no dead instrumentation.

Sampler decisions at compile-time

ratio(0.01) in production can be always in dev. The sampler is a single expression emitted once per #[traced], not an indirection through a runtime registry.

◉ The whole pass is ~120 lines

See otel_emit.ltl. It walks module.items, looks for has_attr("traced"), rewrites the body. Everything else is AST helpers you already had.

◉ Integrations

- std::otel exports to OTLP/HTTP by default, gRPC if you bring a transport - Auto-correlates with std::log structured output - trace_id flows into std::http response headers for edge-to-edge tracing

The lesson: if you put it in the compiler, it's free.

Try it in the playground

Every snippet on this page runs in your browser. No install, no signup.

▶ Open Playground Star on GitHub