> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/open-telemetry/opentelemetry-rust/llms.txt
> Use this file to discover all available pages before exploring further.

# tracing Appender

> Bridge logs from the tracing crate to OpenTelemetry

## Overview

The `opentelemetry-appender-tracing` crate bridges the [`tracing`](https://docs.rs/tracing) crate to OpenTelemetry logs. It provides a `tracing::Layer` implementation that converts `tracing` events into OpenTelemetry `LogRecord`s, enabling seamless integration for async Rust applications.

<Note>
  Unlike traces and metrics, OpenTelemetry does not provide a dedicated logging API for end-users. Instead, it recommends using existing logging libraries like `tracing` and bridging them to OpenTelemetry logs.
</Note>

## Key Features

* Integrates as a `tracing-subscriber` `Layer` alongside other layers (e.g., `fmt`)
* Automatically attaches OpenTelemetry trace context (`TraceId`, `SpanId`, `TraceFlags`) to logs
* Automatically associates OpenTelemetry `Resource` to logs
* Supports exporting to OpenTelemetry-compatible backends (OTLP, stdout, etc.)
* Optional: Capture span attributes in log records (experimental)

## Installation

Add the following to your `Cargo.toml`:

```toml theme={null}
[dependencies]
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["registry", "std"] }
opentelemetry = { version = "0.27", features = ["logs"] }
opentelemetry-sdk = { version = "0.27", features = ["logs"] }
opentelemetry-appender-tracing = "0.27"
opentelemetry-stdout = { version = "0.27", features = ["logs"] }
```

## Quick Start

<Steps>
  <Step title="Create a LoggerProvider">
    Set up the OpenTelemetry logger provider:

    ```rust theme={null}
    use opentelemetry_sdk::logs::SdkLoggerProvider;
    use opentelemetry_stdout::LogExporter;

    let exporter = LogExporter::default();
    let provider = SdkLoggerProvider::builder()
        .with_simple_exporter(exporter)
        .build();
    ```
  </Step>

  <Step title="Create the tracing bridge layer">
    Create the `OpenTelemetryTracingBridge` layer:

    ```rust theme={null}
    use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;

    let otel_layer = OpenTelemetryTracingBridge::new(&provider);
    ```
  </Step>

  <Step title="Register with tracing subscriber">
    Combine with other layers and initialize:

    ```rust theme={null}
    use tracing_subscriber::prelude::*;

    tracing_subscriber::registry()
        .with(otel_layer)
        .with(tracing_subscriber::fmt::layer())
        .init();
    ```
  </Step>

  <Step title="Emit logs">
    Use standard `tracing` macros:

    ```rust theme={null}
    use tracing::error;

    error!(
        name: "user-login-failed",
        target: "auth-service",
        user_id = 12345,
        message = "Invalid credentials"
    );
    ```
  </Step>

  <Step title="Shutdown">
    Flush remaining logs:

    ```rust theme={null}
    provider.shutdown().unwrap();
    ```
  </Step>
</Steps>

## Complete Example

```rust theme={null}
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_sdk::Resource;
use tracing::{error, info};
use tracing_subscriber::{prelude::*, EnvFilter};

fn main() {
    // Create exporter and provider
    let exporter = opentelemetry_stdout::LogExporter::default();
    let provider = SdkLoggerProvider::builder()
        .with_resource(
            Resource::builder()
                .with_service_name("my-service")
                .build(),
        )
        .with_simple_exporter(exporter)
        .build();

    // Create OpenTelemetry layer with filtering
    let filter_otel = EnvFilter::new("info")
        .add_directive("hyper=off".parse().unwrap())
        .add_directive("tonic=off".parse().unwrap());
    let otel_layer = OpenTelemetryTracingBridge::new(&provider)
        .with_filter(filter_otel);

    // Create fmt layer for console output
    let fmt_layer = tracing_subscriber::fmt::layer()
        .with_thread_names(true);

    // Initialize subscriber with both layers
    tracing_subscriber::registry()
        .with(otel_layer)
        .with(fmt_layer)
        .init();

    // Emit logs
    info!("Application started");
    error!(
        name: "my-event-name",
        target: "my-system",
        event_id = 20,
        user_name = "otel",
        user_email = "otel@opentelemetry.io",
        message = "Example error message"
    );

    // Shutdown
    provider.shutdown().unwrap();
}
```

## Field Mapping

The appender maps `tracing::Event` to OpenTelemetry `LogRecord`:

### Basic Mapping

| tracing Field   | OpenTelemetry Field | Notes                                                      |
| --------------- | ------------------- | ---------------------------------------------------------- |
| event name      | `event_name`        | Events in tracing become OTel Events                       |
| target          | `target`            | Groups logs by module/crate; used as instrumentation scope |
| level           | `severity_number`   | Mapped using severity table below                          |
| level           | `severity_text`     | String representation ("ERROR", "INFO")                    |
| fields          | `attributes`        | Converted to key-value attributes                          |
| "message" field | `body`              | If present, used as log body                               |

<Note>
  If a field named `message` exists, it's used as the log body. Otherwise, the body is empty or derived from the event's formatted message.
</Note>

### Severity Mapping

| tracing::Level | Severity Text | Severity Number |
| -------------- | ------------- | --------------- |
| `ERROR`        | ERROR         | 17              |
| `WARN`         | WARN          | 13              |
| `INFO`         | INFO          | 9               |
| `DEBUG`        | DEBUG         | 5               |
| `TRACE`        | TRACE         | 1               |

### Type Mapping

tracing field types are mapped to `AnyValue`:

| tracing Type   | AnyValue Type     | Notes                                   |
| -------------- | ----------------- | --------------------------------------- |
| `i64`          | `Int`             |                                         |
| `u64`          | `Int` or `String` | If fits in `i64`, else stringified      |
| `u128`, `i128` | `Int` or `String` | If fits in `i64`, else stringified      |
| `f32`, `f64`   | `Double`          |                                         |
| `&str`         | `String`          |                                         |
| `bool`         | `Boolean`         |                                         |
| `&[u8]`        | `Bytes`           |                                         |
| `&dyn Debug`   | `String`          | Formatted via `Debug`                   |
| `&dyn Error`   | `String`          | Stored as `exception.message` attribute |

## Event Names and Targets

### Event Names

You can specify event names explicitly:

```rust theme={null}
use tracing::error;

error!(name: "user-login-failed", user_id = 123, "Login attempt failed");
```

Without an explicit name, tracing generates a default based on source location:

```rust theme={null}
error!(user_id = 123, "Login attempt failed");
// Event name: "event src/main.rs:42" (example)
```

### Targets

Targets group logs by module:

```rust theme={null}
error!(target: "auth-service", "Authentication failed");
```

Without an explicit target, tracing uses the module path:

```rust theme={null}
// In module my_app::auth
error!("Authentication failed");
// Target: "my_app::auth"
```

Exporters use the `target` field as the OpenTelemetry instrumentation scope name.

## Trace Context Integration

When logs are emitted within an active OpenTelemetry span, the trace context is automatically attached:

```rust theme={null}
use opentelemetry::trace::{Tracer, TracerProvider};
use opentelemetry_sdk::trace::SdkTracerProvider;
use tracing::error;

let tracer_provider = SdkTracerProvider::builder().build();
let tracer = tracer_provider.tracer("my-app");

tracer.in_span("process-request", |_cx| {
    // This log will include trace_id, span_id, and trace_flags
    error!(request_id = "req-123", "Request processing failed");
});
```

The log record will contain:

* **trace\_id**: Links the log to the distributed trace
* **span\_id**: Links the log to the specific span
* **trace\_flags**: Sampling information

## Span Attributes (Experimental)

With the `experimental_span_attributes` feature, span fields are automatically captured as log attributes:

```toml theme={null}
[dependencies]
opentelemetry-appender-tracing = { version = "0.27", features = ["experimental_span_attributes"] }
```

**Example:**

```rust theme={null}
use tracing::{error, info_span};

let span = info_span!(
    "process_user",
    user_id = 12345,
    session_id = "abc-def"
);
let _enter = span.enter();

// This log will include user_id and session_id from the span
error!(status = 500, "User processing failed");
```

The log attributes will include:

* `user_id = 12345` (from span)
* `session_id = "abc-def"` (from span)
* `status = 500` (from event)

### Nested Spans

Attributes from all parent spans are collected:

```rust theme={null}
use tracing::{error, info_span};

let outer = info_span!("request", request_id = "req-123");
let _outer_guard = outer.enter();

let inner = info_span!("database", query = "SELECT * FROM users");
let _inner_guard = inner.enter();

// Log includes both request_id and query
error!("Database query failed");
```

### Attribute Allowlist

Filter which span attributes to capture:

```rust theme={null}
let layer = OpenTelemetryTracingBridge::builder(&provider)
    .with_span_attribute_allowlist(["user_id", "request_id"])
    .build();
```

Only `user_id` and `request_id` span attributes will be captured in logs.

## Filtering

### By Level

Use `tracing-subscriber` filters:

```rust theme={null}
use tracing_subscriber::{EnvFilter, prelude::*};

let filter = EnvFilter::new("info")
    .add_directive("my_app=debug".parse().unwrap());

let layer = OpenTelemetryTracingBridge::new(&provider)
    .with_filter(filter);
```

### Suppress Telemetry Loops

When using OTLP exporters, prevent telemetry-induced-telemetry loops:

```rust theme={null}
let filter = EnvFilter::new("info")
    .add_directive("hyper=off".parse().unwrap())
    .add_directive("tonic=off".parse().unwrap())
    .add_directive("h2=off".parse().unwrap())
    .add_directive("reqwest=off".parse().unwrap());
```

This filters out logs from HTTP/gRPC libraries used by the OTLP exporter.

<Warning>
  This filtering also drops logs from these libraries when used outside of the exporter. For more targeted filtering, see the [open issue](https://github.com/open-telemetry/opentelemetry-rust/issues/2877).
</Warning>

## Advanced Configuration

### Builder Pattern

Use the builder for advanced configuration:

```rust theme={null}
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;

let layer = OpenTelemetryTracingBridge::builder(&provider)
    .with_span_attribute_allowlist(["user_id", "session_id"])
    .build();
```

### Multiple Layers

Combine with other `tracing` layers:

```rust theme={null}
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

let otel_layer = OpenTelemetryTracingBridge::new(&provider);

let fmt_layer = fmt::layer()
    .with_thread_names(true)
    .with_file(true)
    .with_line_number(true);

tracing_subscriber::registry()
    .with(otel_layer)
    .with(fmt_layer)
    .init();
```

### Batch Processing

For production, use batch processing:

```rust theme={null}
use opentelemetry_sdk::logs::BatchLogProcessor;
use std::time::Duration;

let processor = BatchLogProcessor::builder(exporter)
    .with_batch_config(
        opentelemetry_sdk::logs::BatchConfigBuilder::default()
            .with_max_queue_size(2048)
            .with_scheduled_delay(Duration::from_secs(5))
            .build(),
    )
    .build();

let provider = SdkLoggerProvider::builder()
    .with_log_processor(processor)
    .build();
```

## Custom Log Processor

You can create custom processors to enrich logs:

```rust theme={null}
use opentelemetry::logs::Severity;
use opentelemetry::InstrumentationScope;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord};
use opentelemetry_sdk::Resource;

#[derive(Debug)]
struct EnrichmentLogProcessor<P: LogProcessor> {
    delegate: P,
}

impl<P: LogProcessor> LogProcessor for EnrichmentLogProcessor<P> {
    fn emit(&self, record: &mut SdkLogRecord, scope: &InstrumentationScope) {
        // Add custom attribute
        record.add_attribute("environment", "production");
        // Forward to delegate
        self.delegate.emit(record, scope);
    }

    fn force_flush(&self) -> OTelSdkResult {
        self.delegate.force_flush()
    }

    fn shutdown_with_timeout(&self, timeout: std::time::Duration) -> OTelSdkResult {
        self.delegate.shutdown_with_timeout(timeout)
    }

    fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool {
        self.delegate.event_enabled(level, target, name)
    }

    fn set_resource(&mut self, resource: &Resource) {
        self.delegate.set_resource(resource);
    }
}
```

Use it in the provider:

```rust theme={null}
use opentelemetry_sdk::logs::SimpleLogProcessor;

let simple = SimpleLogProcessor::new(exporter);
let enriching = EnrichmentLogProcessor { delegate: simple };

let provider = SdkLoggerProvider::builder()
    .with_log_processor(enriching)
    .build();
```

## Comparison with log Appender

| Feature                | tracing Appender   | log Appender        |
| ---------------------- | ------------------ | ------------------- |
| **Target Crate**       | `tracing`          | `log`               |
| **Structured Logging** | Fields + spans     | Key-values          |
| **Span Context**       | Automatic          | Manual              |
| **Async-aware**        | Yes                | No                  |
| **Filtering**          | Target + level     | Level only          |
| **Event Names**        | Supported          | Not supported       |
| **Best For**           | Async applications | Simple applications |

Choose the `tracing` appender if:

* You're building async applications (tokio, async-std)
* You want hierarchical span context
* You need advanced filtering capabilities
* You want automatic trace correlation

Choose the [`log` appender](/logs/appender-log) if:

* You have existing code using the `log` crate
* You need simple, synchronous logging
* You don't need span correlation

## Feature Flags

| Feature                            | Description                                |
| ---------------------------------- | ------------------------------------------ |
| `experimental_span_attributes`     | Capture span fields as log attributes      |
| `experimental_metadata_attributes` | Capture source code location as attributes |

## Limitations

1. **No Valuable support**: The appender does not currently support the `valuable` crate for efficient serialization. See [issue #2819](https://github.com/open-telemetry/opentelemetry-rust/issues/2819).

2. **tracing-opentelemetry**: This appender converts `tracing` events into logs, not spans. For converting `tracing` spans to OpenTelemetry spans, use the third-party [`tracing-opentelemetry`](https://docs.rs/tracing-opentelemetry) crate.

## Troubleshooting

### Logs not appearing

1. Check that the layer is registered:
   ```rust theme={null}
   tracing_subscriber::registry()
       .with(otel_layer)
       .init();
   ```

2. Verify filtering isn't too restrictive:
   ```rust theme={null}
   let filter = EnvFilter::new("debug");
   ```

3. Always shutdown the provider:
   ```rust theme={null}
   provider.shutdown().unwrap();
   ```

### Trace context not attached

Ensure you're using OpenTelemetry's tracing, not just `tracing`:

```rust theme={null}
use opentelemetry::trace::{Tracer, TracerProvider};

let tracer = tracer_provider.tracer("my-app");
tracer.in_span("operation", |_cx| {
    tracing::info!("This log will have trace context");
});
```

### Span attributes not captured

Enable the feature flag:

```toml theme={null}
opentelemetry-appender-tracing = { version = "0.27", features = ["experimental_span_attributes"] }
```

## See Also

<CardGroup cols={2}>
  <Card title="log Appender" icon="file-code" href="/logs/appender-log">
    Alternative appender for the `log` crate
  </Card>

  <Card title="Log Processors" icon="gears" href="/logs/log-processors">
    Configure batch and simple processors
  </Card>

  <Card title="Bridge API" icon="bridge" href="/logs/bridge-api">
    Understanding the underlying API
  </Card>

  <Card title="Overview" icon="book" href="/logs/overview">
    Return to logs overview
  </Card>
</CardGroup>
