otel 0.5.0

Ergonomic macros for OpenTelemetry tracing in Rust
Documentation
# 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 `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,ignore
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. Create spans in your code

```rust,ignore
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
}
```

# 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:

```rust,ignore
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
}
```

### Closure-Based Spans

For automatic span lifetime management with return values, use the `in` keyword:

```rust,ignore
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:

```rust,ignore
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:**
- 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:

```rust,ignore
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:

```rust,ignore
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.

# 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.