# 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
- **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 `LazyLock` for lazy initialization
# Quick Start
## 1. Initialize OpenTelemetry in your application
Before any spans can be created, configure and install a tracer provider:
```rust
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();
}
```
## 2. Declare a tracer at your crate root
```rust
// In src/lib.rs or src/main.rs
otel::tracer!();
```
This creates a `TRACER` static available throughout your crate.
## 3. Import and create spans
```rust
use crate::TRACER;
fn process_data(items: &[Item]) {
let (_cx, _guard) = otel::span!(
"data.process",
"item.count" => items.len() as i64
);
// Your code here - execution is traced
// Span ends when _guard drops
}
```
# 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
The guard automatically manages span lifetime:
```rust
fn process_batch(items: &[Item]) {
let (_cx, _guard) = otel::span!("batch.process");
for item in items {
// Child span - automatically parented to batch.process
let (_cx, _guard) = otel::span!("item.process");
process_item(item);
}
}
```
## Asynchronous Code
Use [`FutureExt::with_context`] to propagate context across `.await` points:
```rust
use opentelemetry::trace::FutureExt;
async fn fetch_user(id: u64) -> Result<User> {
let (cx, _guard) = otel::span!("user.fetch", "user.id" => id as i64);
db.get_user(id)
.with_context(cx)
.await
}
```
For multiple awaits, clone the context:
```rust
async fn process_order(id: u64) -> Result<()> {
let (cx, _guard) = 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.
# Requirements
Your application must initialize an OpenTelemetry tracer provider before using these macros.
See the [OpenTelemetry documentation](https://docs.rs/opentelemetry) 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`:
```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.