Skip to main content

opentelemetry-appender-log

Version: 0.31.0 The opentelemetry-appender-log crate provides a log appender that bridges the log crate to OpenTelemetry’s Logs Bridge API, enabling log events to be exported via OpenTelemetry.

Installation

[dependencies]
opentelemetry-appender-log = "0.31"
log = { version = "0.4", features = ["kv", "std"] }

Feature Flags

  • with-serde: Support complex values as attributes without stringifying (enables serde serialization)
  • experimental_metadata_attributes: Include code location metadata (file path, line number, module) as attributes

Quick Start

Basic Setup

use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider};
use opentelemetry_appender_log::OpenTelemetryLogBridge;

// 1. Create OpenTelemetry logger provider with an exporter
let exporter = opentelemetry_stdout::LogExporter::default();
let logger_provider = SdkLoggerProvider::builder()
    .with_log_processor(BatchLogProcessor::builder(exporter).build())
    .build();

// 2. Create the log bridge
let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);

// 3. Set as the global logger
log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();
log::set_max_level(log::LevelFilter::Info);

// 4. Use the log crate as normal
log::info!("Application started");
log::error!("Something went wrong");

With Key-Value Attributes

use log::info;

info!(
    target: "my_system",
    user_id = 42,
    user_name = "Alice";
    "User logged in"
);

Core Types

OpenTelemetryLogBridge<P, L>
struct
Log appender that implements log::Log trait and forwards log records to OpenTelemetry
use opentelemetry_appender_log::OpenTelemetryLogBridge;
use opentelemetry_sdk::logs::SdkLoggerProvider;

let provider: SdkLoggerProvider = /* ... */;
let bridge = OpenTelemetryLogBridge::new(&provider);

Field Mapping

This section describes how log records map to OpenTelemetry log records:

Body

The stringified message from log::Record::args() becomes the log body.
log::info!("Request processed");
// -> LogRecord.body = "Request processed"

Severity

log::Level maps to OpenTelemetry severity:
log::LevelSeverity TextSeverity Number
ErrorError17
WarnWarn13
InfoInfo9
DebugDebug5
TraceTrace1

Target

The log::Record::target() becomes the OpenTelemetry InstrumentationScope name.
log::info!(target: "my_module", "Event occurred");
// -> InstrumentationScope.name = "my_module"

Attributes

Key-value pairs from log::Record::key_values() are converted to attributes.

Type Conversion

Without with-serde Feature

TypeOpenTelemetry TypeNotes
i8-i64AnyValue::Int
u8-u64AnyValue::IntIf value fits in i64, otherwise stringified
i128, u128AnyValue::Int or StringConverted to i64 if possible, else stringified
f32, f64AnyValue::Double
boolAnyValue::Boolean
&strAnyValue::String
OtherAnyValue::StringStringified via Debug trait

With with-serde Feature

Enables rich type support using serde:
TypeOpenTelemetry TypeNotes
PrimitivesAs aboveSame as without serde
BytesAnyValue::BytesBinary data
Option::None-Discarded
Option::SomeAnyUses inner value
SequencesAnyValue::ListAnyArrays, slices, tuples
Maps/StructsAnyValue::MapStructured data
Enum variantsAnyValue::String or MapUnit variants as string, others as map

Metadata Attributes

With the experimental_metadata_attributes feature:
[dependencies]
opentelemetry-appender-log = { version = "0.31", features = ["experimental_metadata_attributes"] }
opentelemetry-semantic-conventions = "0.31"
log::warn!("Configuration issue detected");
// Automatically includes:
// - code.filepath: "src/main.rs"
// - code.line_number: 42
// - code.function_name: "my_module::function"

Examples

Basic Logging

use log::{info, warn, error};

info!("Server started on port 8080");
warn!("High memory usage detected");
error!("Database connection failed");

Structured Logging

use log::info;

info!(
    user_id = 12345,
    action = "purchase",
    amount = 99.99;
    "User completed purchase"
);

With Custom Target

use log::info;

info!(target: "http_server", 
    method = "GET",
    path = "/api/users",
    status = 200;
    "Request processed"
);

Complex Types (with serde)

use log::info;
use serde::Serialize;

#[derive(Serialize)]
struct UserContext {
    id: u64,
    role: String,
}

let user = UserContext { 
    id: 42, 
    role: "admin".to_string() 
};

info!(
    user = log::kv::Value::from_serde(&user);
    "User authenticated"
);

Performance Considerations

  • The bridge evaluates enabled() before processing log records
  • Use appropriate log levels to avoid unnecessary overhead
  • Consider using BatchLogProcessor for better performance
  • The with-serde feature adds overhead for complex types

Integration with OTLP

use opentelemetry_otlp::LogExporter;
use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider};
use opentelemetry_appender_log::OpenTelemetryLogBridge;

let exporter = LogExporter::builder()
    .with_http()
    .build()?;

let logger_provider = SdkLoggerProvider::builder()
    .with_log_processor(BatchLogProcessor::builder(exporter).build())
    .build();

let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);
log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();

Differences from Direct Logging

This bridge is designed for applications already using the log crate. If you’re starting fresh, consider using the tracing crate with opentelemetry-appender-tracing, which provides better structured logging support.

Documentation

Full API Documentation

View complete API reference on docs.rs

log Crate

Documentation for the log crate

Minimum Rust Version

MSRV: 1.75.0

Build docs developers (and LLMs) love