Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Logs

At a glance

  • Builds a logger provider that writes log records to stdout and connects tracing events to it.
  • Uses opentelemetry 0.33.1, opentelemetry_sdk 0.33.1, opentelemetry-stdout 0.33.1, opentelemetry-appender-tracing 0.33.1, tracing-opentelemetry 0.34.0, tracing 0.1.44, and tracing-subscriber 0.3.23. Other guide versions: opentelemetry-otlp 0.33.1, tokio 1.54.1, rusqlite 0.42.0, and libsql 0.10.0.
  • Run with cargo run -p logs.
  • The last line is ok: log records emitted and providers shut down.

Dependencies

The example uses Rust edition 2024. The versions below are pinned in its manifest.

[package]
name = "logs"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
opentelemetry = { version = "=0.33.1", default-features = false, features = ["trace"] }
opentelemetry_sdk = { version = "=0.33.1", default-features = false, features = ["logs", "trace"] }
opentelemetry-stdout = { version = "=0.33.1", default-features = false, features = ["logs"] }
opentelemetry-appender-tracing = "=0.33.1"
tracing-opentelemetry = "=0.34.0"
tracing = "=0.1.44"
tracing-subscriber = { version = "=0.3.23", default-features = false, features = ["registry"] }

Code

use std::io::{self, Write};

use opentelemetry::trace::TracerProvider;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_sdk::{logs::SdkLoggerProvider, trace::SdkTracerProvider};
use tracing_subscriber::layer::SubscriberExt;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let logger_provider = SdkLoggerProvider::builder()
        .with_simple_exporter(opentelemetry_stdout::LogExporter::default())
        .build();
    let tracer_provider = SdkTracerProvider::builder().build();
    let tracer = tracer_provider.tracer("logs-example");

    let subscriber = tracing_subscriber::registry()
        .with(tracing_opentelemetry::layer().with_tracer(tracer))
        .with(OpenTelemetryTracingBridge::new(&logger_provider));
    tracing::subscriber::set_global_default(subscriber)?;

    tracing::info!(operation = "start", message = "starting log example");
    tracing::warn!(retry_count = 2, message = "retry scheduled");

    let span = tracing::info_span!("request", request_id = "request-42");
    let entered = span.enter();
    tracing::error!(error = "simulated failure", message = "request failed");
    drop(entered);

    logger_provider.shutdown()?;
    tracer_provider.shutdown()?;

    writeln!(
        io::stdout().lock(),
        "ok: log records emitted and providers shut down"
    )?;
    Ok(())
}

Run it

Run:

cargo run -p logs

The stdout exporter prints the records; their formatting can vary. The final line is:

ok: log records emitted and providers shut down

How it works

The logger provider owns the log pipeline. Its simple exporter writes each record to stdout.

OpenTelemetryTracingBridge is a tracing-subscriber layer. It turns tracing events into OpenTelemetry log records. The event levels map to log severity, and event fields become record attributes.

The example also adds a tracing-opentelemetry layer backed by an SDK tracer provider. When the error event occurs inside the active span, the bridge can add that span’s trace context to the log record. This lets a backend correlate the log with a trace when both signals are available.

Finally, the example shuts down both providers before returning.

Pitfalls

  • A span alone does not create OpenTelemetry trace context. Install the tracing-opentelemetry layer and emit the event while the span is entered.
  • Install one global tracing subscriber. Setting a second global subscriber fails.
  • The stdout exporter writes locally; it does not send records to an OTLP endpoint.
  • Shut down providers before exit so buffered telemetry can be flushed.