otel 0.2.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
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);
    }
}
```

### Closure-Based Spans

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

```rust
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
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 [`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.