| Crates.io | otel |
| lib.rs | otel |
| version | 0.6.0 |
| created_at | 2025-12-09 07:59:16.785074+00 |
| updated_at | 2025-12-29 06:52:23.175631+00 |
| description | Ergonomic macros for OpenTelemetry tracing in Rust |
| homepage | |
| repository | https://github.com/gold-build/otel |
| max_upload_size | |
| id | 1974994 |
| size | 151,990 |
Just a few macros for when you're working directly with OpenTelemetry crates,
and not tracing.
in |cx| { ... } syntax for automatic span lifetime managementLazyLock for lazy initializationBefore any spans can be created, configure and install a tracer provider:
fn main() {
// Example: Stdout exporter for development
let provider = opentelemetry_stdout::new_pipeline()
.install_simple();
opentelemetry::global::set_tracer_provider(provider);
run_app();
opentelemetry::global::shutdown_tracer_provider();
}
// In src/lib.rs or src/main.rs
otel::tracer!();
This creates a TRACER static available throughout your crate.
fn process_data(items: &[Item]) {
otel::span!(
"data.process",
"item.count" = items.len() as i64
);
// Your code here - execution is traced
// Span ends automatically at end of scope
}
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 |
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.
Spans are automatically managed through lexical scoping:
fn process_batch(items: &[Item]) {
otel::span!("batch.process");
for item in items {
// Child span - automatically parented to batch.process
otel::span!("item.process");
process_item(item);
}
// Spans end automatically at end of scope
}
For automatic span lifetime management with return values, use the in keyword:
fn compute_result(input: &Data) -> Result<Output> {
otel::span!("computation.execute", in |cx| {
validate(input)?;
let processed = process(input)?;
Ok(processed)
})
}
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:
fn fetch_user(user_id: u64) -> Result<User> {
otel::span!(
"user.fetch",
"user.id" = user_id as i64,
in |cx| {
database.get_user(user_id)
}
)
}
When to use closure syntax:
?When to use manual guard syntax:
Use closure syntax with [FutureExt::with_context] to propagate context across .await points:
use opentelemetry::trace::FutureExt;
async fn fetch_user(id: u64) -> Result<User> {
otel::span!("user.fetch", "user.id" = id as i64, in |cx| {
db.get_user(id)
.with_context(cx)
}).await
}
For multiple awaits, use the detached form to get a context variable:
async fn process_order(id: u64) -> Result<()> {
let cx = otel::span!(^ "order.process");
let order = fetch_order(id).with_context(cx.clone()).await?;
validate(&order).with_context(cx.clone()).await?;
submit(&order).with_context(cx).await
}
See [span!] documentation for more async patterns including spawned tasks and
concurrent operations.
Your application must initialize an OpenTelemetry tracer provider before using these macros. See the OpenTelemetry documentation for setup instructions.
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:
[profile.dev]
trim-paths = "all"
[profile.release]
trim-paths = "all"
This affects the code.file.path attribute on all spans. Without this setting,
paths will be absolute and vary across build environments.