Skip to main content

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.

Overview

The opentelemetry-appender-log crate provides a bridge between the log crate and OpenTelemetry. It implements the log::Log trait to capture logs emitted through the log crate’s macros (error!, warn!, info!, debug!, trace!) and forwards them to OpenTelemetry exporters.

Installation

Add the following to your Cargo.toml:
[dependencies]
log = "0.4"
opentelemetry = { version = "0.27", features = ["logs"] }
opentelemetry-appender-log = "0.27"
opentelemetry-sdk = { version = "0.27", features = ["logs"] }
opentelemetry-stdout = { version = "0.27", features = ["logs"] }

Quick Start

1

Create a LoggerProvider

Set up the OpenTelemetry logger provider with an exporter:
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_stdout::LogExporter;

let exporter = LogExporter::default();
let provider = SdkLoggerProvider::builder()
    .with_simple_exporter(exporter)
    .build();
2

Install the log appender

Create and register the OpenTelemetry log bridge:
use opentelemetry_appender_log::OpenTelemetryLogBridge;
use log::Level;

let appender = OpenTelemetryLogBridge::new(&provider);
log::set_boxed_logger(Box::new(appender)).unwrap();
log::set_max_level(Level::Info.to_level_filter());
3

Emit logs

Use standard log macros:
use log::{error, warn, info};

error!("Application error occurred");
warn!("Warning message");
info!("Application started successfully");
4

Shutdown

Flush remaining logs before exit:
provider.shutdown().unwrap();

Complete Example

use log::{error, info, warn, Level};
use opentelemetry_appender_log::OpenTelemetryLogBridge;
use opentelemetry_sdk::{logs::SdkLoggerProvider, Resource};
use opentelemetry_stdout::LogExporter;

#[tokio::main]
async fn main() {
    // Create an exporter that writes to stdout
    let exporter = LogExporter::default();

    // Create a LoggerProvider with resource attributes
    let provider = SdkLoggerProvider::builder()
        .with_resource(
            Resource::builder()
                .with_service_name("my-service")
                .build(),
        )
        .with_simple_exporter(exporter)
        .build();

    // Set up the log appender
    let appender = OpenTelemetryLogBridge::new(&provider);
    log::set_boxed_logger(Box::new(appender)).unwrap();
    log::set_max_level(Level::Info.to_level_filter());

    // Emit logs with structured key-values
    let fruit = "apple";
    let price = 2.99;

    error!(fruit, price; "hello from {fruit}. My price is {price}");
    warn!("Warning message");
    info!("Application started");

    // Shutdown to ensure all logs are flushed
    provider.shutdown().unwrap();
}

Field Mapping

The appender maps log::Record fields to OpenTelemetry LogRecord fields:

Basic Fields

log FieldOpenTelemetry FieldNotes
args()bodyThe formatted log message
level()severity_numberMapped using severity table below
level()severity_textString representation (“ERROR”, “INFO”)
target()targetModule/component identifier
key_values()attributesStructured key-value pairs

Severity Mapping

log::LevelSeverity TextSeverity Number
ErrorERROR17
WarnWARN13
InfoINFO9
DebugDEBUG5
TraceTRACE1

Metadata Attributes (Experimental)

With the experimental_metadata_attributes feature, source code metadata is captured:
[dependencies]
opentelemetry-appender-log = { version = "0.27", features = ["experimental_metadata_attributes"] }
This adds attributes:
log FieldAttribute KeyExample Value
file()code.filepathsrc/main.rs
line()code.lineno42
module_path()code.namespacemy_app::module

Key-Value Attributes

The log crate supports structured logging with key-value pairs:
use log::info;

info!(
    user_id = 12345,
    action = "login",
    duration_ms = 150;
    "User logged in successfully"
);
These are converted to OpenTelemetry attributes based on their type:

Type Mapping

Rust TypeAnyValue TypeNotes
i8-i64Int
u8-u64IntConverted to i64 if possible, else String
i128, u128Int or StringUses Int if fits in i64, else stringified
f32, f64Double
boolBoolean
&str, StringString
Other typesStringFormatted using Debug

With Serde Support

Enable the with-serde feature for complex types:
[dependencies]
opentelemetry-appender-log = { version = "0.27", features = ["with-serde"] }
With this feature enabled: | Type | Result | Notes | |-------------------|---------------|------------------------------------|| | Sequences | ListAny | Vectors, arrays, etc. | | Maps | Map | HashMaps, BTreeMaps, etc. | | Structs | Map | Serialized as key-value maps | | Enums | Various | Depends on variant type | | Bytes | Bytes | Raw byte arrays | | Option::None | — | Discarded | | Option::Some(T) | T | Uses inner value | | () | — | Discarded | Example:
use log::info;
use serde::Serialize;

#[derive(Serialize)]
struct UserInfo {
    id: u64,
    name: String,
    roles: Vec<String>,
}

let user = UserInfo {
    id: 12345,
    name: "alice".to_string(),
    roles: vec!["admin".to_string(), "user".to_string()],
};

info!(user = log::kv::Value::from_serde(&user); "User loaded");
Without with-serde, complex types are formatted using Debug:
// Without with-serde feature
info!(data = vec![1, 2, 3]; "Processing data");
// Attribute value: "[1, 2, 3]" (string)

Usage with Batch Processor

For production use, configure a batch processor:
use opentelemetry_sdk::logs::BatchLogProcessor;
use std::time::Duration;

let exporter = opentelemetry_stdout::LogExporter::default();

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

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

Integration with Traces

When logs are emitted within an active OpenTelemetry span context, the trace context is automatically attached:
use opentelemetry::trace::{Tracer, TracerProvider};
use opentelemetry_sdk::trace::SdkTracerProvider;
use log::error;

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

tracer.in_span("process-request", |_cx| {
    // This log will have trace_id and span_id from the active span
    error!("Request processing failed");
});
The emitted log will include:
  • trace_id: The ID of the distributed trace
  • span_id: The ID of the current span
  • trace_flags: Sampling flags

Performance Considerations

Filtering

Set appropriate log levels to avoid overhead:
// Only process Info and above
log::set_max_level(log::Level::Info.to_level_filter());

event_enabled

The appender implements enabled() to check if a log should be processed:
// This check happens before expensive formatting
if log::log_enabled!(log::Level::Debug) {
    let expensive_data = compute_expensive_data();
    log::debug!(data = expensive_data; "Debug information");
}

Batching

Use BatchLogProcessor in production to amortize export costs:
  • Reduces network overhead
  • Improves throughput
  • Adds minimal latency (configurable)
See Log Processors for configuration details.

Comparison with tracing Appender

Featurelog Appendertracing Appender
Target Cratelogtracing
Structured LoggingKey-valuesFields + spans
Span ContextManualAutomatic
Async-awareNoYes
FilteringLevel-basedTarget + level-based
Event NamesNot supportedSupported
Best ForSimple applicationsAsync applications
Choose the log appender if:
  • You have existing code using the log crate
  • You need simple, synchronous logging
  • You don’t need advanced filtering or span correlation
Choose the tracing appender if:
  • You’re building async applications
  • You want hierarchical span context
  • You need advanced filtering capabilities
  • You want automatic trace correlation

Feature Flags

FeatureDescription
with-serdeSupport complex types via serde serialization
experimental_metadata_attributesCapture source code location as attributes

Troubleshooting

Logs not appearing

  1. Check log level: Ensure set_max_level() is set appropriately
    log::set_max_level(log::LevelFilter::Debug);
    
  2. Flush on shutdown: Always call provider.shutdown()
    provider.shutdown().unwrap();
    
  3. Check processor configuration: Verify the exporter is configured correctly

Complex types not serialized

Enable the with-serde feature:
opentelemetry-appender-log = { version = "0.27", features = ["with-serde"] }

Trace context not attached

Ensure logs are emitted within an active span context:
tracer.in_span("my-operation", |_cx| {
    log::info!("This log will have trace context");
});

See Also

tracing Appender

Alternative appender for the tracing crate

Log Processors

Configure batch and simple processors

Bridge API

Understanding the underlying API

Overview

Return to logs overview