OpenTelemetry instrumentation macros for distributed tracing.
Just a few macros for when you're working directly with OpenTelemetry crates,
and not tracing.
Features
- Simple tracer creation: Declare static tracers with a single macro call
- Ergonomic span creation: Create spans with minimal boilerplate
- Closure-based spans: Use
in |cx| { ... }syntax for automatic span lifetime management - Automatic code location: All spans include file, line, and column attributes
- Flexible API: Use default or named tracers, add custom attributes
- Zero runtime overhead: Uses
LazyLockfor lazy initialization
Quick Start
1. Initialize OpenTelemetry in your application
Before any spans can be created, configure and install a tracer provider:
2. Declare a tracer at your crate root
// In src/lib.rs or src/main.rs
tracer!;
This creates a TRACER static available throughout your crate.
3. Create spans in your code
Syntax Reference
The span! macro supports multiple forms depending on your use case:
| Syntax | Returns | Use Case |
|---|---|---|
span!("name") |
() |
Default tracer, creates internal guard |
span!("name", "k" = v, ...) |
() |
With custom attributes, internal guard |
span!("name", in |cx| { ... }) |
T |
With closure (auto-managed lifetime) |
span!("name", "k" = v, in |cx| { ... }) |
T |
Closure + attributes |
span!(@TRACER, "name") |
() |
Explicit tracer, internal guard |
span!(@TRACER, "name", "k" = v) |
() |
Explicit tracer + attributes, internal guard |
span!(@TRACER, "name", in |cx| { ... }) |
T |
Explicit tracer with closure |
span!(@TRACER, "name", "k" = v, in |cx| { ... }) |
T |
Explicit tracer + attributes + closure |
span!(^ "name") |
Context |
Detached span (no automatic guard) |
span!(^ "name", "k" = v) |
Context |
Detached with attributes |
span!(^ @TRACER, "name") |
Context |
Detached with explicit tracer |
Synchronous vs Asynchronous Usage
OpenTelemetry context is stored in thread-local storage. This works naturally in
synchronous code, but async tasks can migrate between threads at .await points.
You must explicitly propagate context through async boundaries.
Synchronous Code
Spans are automatically managed through lexical scoping:
Closure-Based Spans
For automatic span lifetime management with return values, use the in keyword:
The closure receives the Context as a parameter and can return any value. The span automatically ends when the closure completes or panics.
With attributes:
When to use closure syntax:
- The span lifetime matches a single expression or block
- You want automatic cleanup even on early return or
? - You need to return a value from the span
When to use manual guard syntax:
- The span covers multiple statements with complex control flow
- You need the context variable for explicit async propagation
- You're wrapping a large function body
Asynchronous Code
Use closure syntax with [FutureExt::with_context] to propagate context across .await points:
use FutureExt;
async
For multiple awaits, use the detached form to get a context variable:
async
See [span!] documentation for more async patterns including spawned tasks and
concurrent operations.
Requirements
Your application must initialize an OpenTelemetry tracer provider before using these macros. See the OpenTelemetry documentation for setup instructions.
Build Configuration
For clean file paths in span attributes (e.g., src/lib.rs instead of
/home/user/project/src/lib.rs), enable path trimming in Cargo.toml:
[]
= "all"
[]
= "all"
This affects the code.file.path attribute on all spans. Without this setting,
paths will be absolute and vary across build environments.