Skip to main content
The stdout exporter writes telemetry data to standard output, making it ideal for development, debugging, and learning. It supports all three OpenTelemetry signals: traces, metrics, and logs.
The stdout exporter is designed for development and debugging only. It is not optimized for production use, and the output format may change between versions.

Installation

Add the dependency to your Cargo.toml:
[dependencies]
opentelemetry = "0.31"
opentelemetry_sdk = "0.31"
opentelemetry-stdout = "0.31"

Feature Flags

The stdout exporter supports selective signal features:
# Default (all signals)
opentelemetry-stdout = "0.31"

# Traces only
opentelemetry-stdout = { version = "0.31", features = ["trace"], default-features = false }

# Metrics only
opentelemetry-stdout = { version = "0.31", features = ["metrics"], default-features = false }

# Logs only
opentelemetry-stdout = { version = "0.31", features = ["logs"], default-features = false }

Quick Start

Traces

use opentelemetry::global;
use opentelemetry::trace::Tracer;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_stdout::SpanExporter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create stdout exporter for traces
    let exporter = SpanExporter::default();

    // Create tracer provider
    let provider = SdkTracerProvider::builder()
        .with_simple_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("stdout-example")
                .build()
        )
        .build();

    global::set_tracer_provider(provider.clone());

    // Create and use a tracer
    let tracer = global::tracer("my-tracer");
    
    tracer.in_span("example-operation", |_cx| {
        println!("Doing some work...");
    });

    // Shutdown to flush spans
    provider.shutdown()?;
    Ok(())
}

Metrics

use opentelemetry::global;
use opentelemetry::KeyValue;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_stdout::MetricExporter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create stdout exporter for metrics
    let exporter = MetricExporter::default();

    // Create meter provider
    let provider = SdkMeterProvider::builder()
        .with_periodic_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("stdout-example")
                .build()
        )
        .build();

    global::set_meter_provider(provider.clone());

    // Create and use a meter
    let meter = global::meter("my-meter");
    let counter = meter.u64_counter("requests").build();
    
    counter.add(1, &[KeyValue::new("endpoint", "/api/users")]);

    // Shutdown to flush metrics
    provider.shutdown()?;
    Ok(())
}

Logs

use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_stdout::LogExporter;
use tracing::info;
use tracing_subscriber::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create stdout exporter for logs
    let exporter = LogExporter::default();

    // Create logger provider
    let provider = SdkLoggerProvider::builder()
        .with_simple_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("stdout-example")
                .build()
        )
        .build();

    // Bridge tracing to OpenTelemetry
    let layer = OpenTelemetryTracingBridge::new(&provider);
    tracing_subscriber::registry().with(layer).init();

    // Use tracing macros
    info!("Application started");
    info!(user_id = "12345", "User logged in");

    // Shutdown to flush logs
    provider.shutdown()?;
    Ok(())
}

Complete Example (All Signals)

Here’s a comprehensive example that demonstrates all three signals:
use once_cell::sync::Lazy;
use opentelemetry::{global, trace::{TraceContextExt, Tracer}, InstrumentationScope, KeyValue};
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_sdk::{
    logs::SdkLoggerProvider,
    metrics::SdkMeterProvider,
    trace::SdkTracerProvider,
    Resource,
};
use tracing::info;
use tracing_subscriber::prelude::*;

static RESOURCE: Lazy<Resource> = Lazy::new(|| {
    Resource::builder()
        .with_service_name("demo-app")
        .with_service_version("1.0.0")
        .build()
});

fn init_traces() -> SdkTracerProvider {
    let exporter = opentelemetry_stdout::SpanExporter::default();
    let provider = SdkTracerProvider::builder()
        .with_simple_exporter(exporter)
        .with_resource(RESOURCE.clone())
        .build();
    global::set_tracer_provider(provider.clone());
    provider
}

fn init_metrics() -> SdkMeterProvider {
    let exporter = opentelemetry_stdout::MetricExporter::default();
    let provider = SdkMeterProvider::builder()
        .with_periodic_exporter(exporter)
        .with_resource(RESOURCE.clone())
        .build();
    global::set_meter_provider(provider.clone());
    provider
}

fn init_logs() -> SdkLoggerProvider {
    let exporter = opentelemetry_stdout::LogExporter::default();
    let provider = SdkLoggerProvider::builder()
        .with_simple_exporter(exporter)
        .with_resource(RESOURCE.clone())
        .build();
    let layer = OpenTelemetryTracingBridge::new(&provider);
    tracing_subscriber::registry().with(layer).init();
    provider
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize all providers
    let tracer_provider = init_traces();
    let meter_provider = init_metrics();
    let logger_provider = init_logs();

    // Create instrumentation scope
    let scope = InstrumentationScope::builder("demo-app")
        .with_version("1.0")
        .with_attributes([KeyValue::new("environment", "development")])
        .build();

    let tracer = global::tracer_with_scope(scope.clone());
    let meter = global::meter_with_scope(scope);

    // Create metrics
    let request_counter = meter
        .u64_counter("http_requests_total")
        .with_description("Total HTTP requests")
        .build();

    let request_duration = meter
        .f64_histogram("http_request_duration_seconds")
        .with_description("HTTP request duration")
        .build();

    // Record some metrics
    request_counter.add(1, &[
        KeyValue::new("method", "GET"),
        KeyValue::new("endpoint", "/api/users"),
    ]);
    request_duration.record(0.123, &[
        KeyValue::new("method", "GET"),
        KeyValue::new("status", "200"),
    ]);

    // Create traces with logs
    tracer.in_span("handle_request", |cx| {
        let span = cx.span();
        span.set_attribute(KeyValue::new("user.id", "12345"));
        span.set_attribute(KeyValue::new("request.path", "/api/users"));
        
        info!("Processing user request");
        
        span.add_event(
            "Request validated",
            vec![KeyValue::new("validation.time_ms", 5)],
        );
        
        tracer.in_span("database_query", |cx| {
            let span = cx.span();
            span.set_attribute(KeyValue::new("db.system", "postgresql"));
            info!("Querying database");
        });
        
        info!("Request completed successfully");
    });

    // Shutdown all providers
    tracer_provider.shutdown()?;
    meter_provider.shutdown()?;
    logger_provider.shutdown()?;

    Ok(())
}

Output Format

The stdout exporter prints telemetry in a human-readable JSON-like format:

Trace Output Example

Span {
    name: "handle_request",
    trace_id: 5b8aa5a2d2c872e8137a28db9d321b50,
    span_id: 5fb397be34d26b51,
    parent_span_id: 0000000000000000,
    start_time: 2026-03-02T12:00:00.123456Z,
    end_time: 2026-03-02T12:00:00.234567Z,
    attributes: {
        "user.id": "12345",
        "request.path": "/api/users"
    },
    events: [
        Event {
            name: "Request validated",
            timestamp: 2026-03-02T12:00:00.150000Z,
            attributes: {"validation.time_ms": 5}
        }
    ],
    status: Ok
}

Metric Output Example

Metric {
    name: "http_requests_total",
    description: "Total HTTP requests",
    unit: "",
    data: Sum {
        value: 1,
        attributes: {
            "method": "GET",
            "endpoint": "/api/users"
        }
    }
}

Log Output Example

LogRecord {
    timestamp: 2026-03-02T12:00:00.123456Z,
    severity: Info,
    body: "Processing user request",
    attributes: {},
    trace_id: 5b8aa5a2d2c872e8137a28db9d321b50,
    span_id: 5fb397be34d26b51
}

Configuration

Custom Writer

By default, output goes to stdout. You can customize the destination:
use std::io::Write;
use opentelemetry_stdout::SpanExporter;

// Example: Write to a file
let file = std::fs::File::create("traces.log")?;
let exporter = SpanExporter::new(file);

Resource Configuration

Add resource attributes that appear in all telemetry:
use opentelemetry::KeyValue;
use opentelemetry_sdk::Resource;

let resource = Resource::builder()
    .with_service_name("my-service")
    .with_service_version("1.2.3")
    .with_attributes([
        KeyValue::new("environment", "development"),
        KeyValue::new("region", "local"),
        KeyValue::new("host.name", "laptop"),
    ])
    .build();

let provider = SdkTracerProvider::builder()
    .with_simple_exporter(exporter)
    .with_resource(resource)
    .build();

Use Cases

Local Development

Quickly verify instrumentation without setting up a backend:
let exporter = opentelemetry_stdout::SpanExporter::default();
let provider = SdkTracerProvider::builder()
    .with_simple_exporter(exporter)
    .build();

Testing

Validate telemetry data in tests:
#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_instrumentation() {
        let exporter = opentelemetry_stdout::SpanExporter::default();
        let provider = SdkTracerProvider::builder()
            .with_simple_exporter(exporter)
            .build();
        
        // Your test code that generates spans
        // Spans will be printed to stdout for inspection
    }
}

Learning and Education

Understand the structure of OpenTelemetry data:
// Students can see exactly what data is being collected
let exporter = opentelemetry_stdout::SpanExporter::default();

Debugging Production Issues Locally

Reproduce issues with realistic data:
// Switch to stdout exporter temporarily for debugging
#[cfg(debug_assertions)]
let exporter = opentelemetry_stdout::SpanExporter::default();

#[cfg(not(debug_assertions))]
let exporter = opentelemetry_otlp::SpanExporter::builder()
    .with_http()
    .build()?;

Performance Considerations

The stdout exporter writes synchronously and can block your application. It is not suitable for production use.

Impact on Application Performance

  • Each telemetry item is written immediately
  • I/O operations block the calling thread
  • Large volumes of telemetry can slow down your application
  • No batching or buffering

Alternatives for Production

For production, use:

Integration with Other Tools

Piping to jq

Format output with jq for better readability:
cargo run | jq '.'

Filtering Output

Filter specific telemetry:
# Only show errors
cargo run 2>&1 | grep -i error

# Only show spans
cargo run 2>&1 | grep "Span {"

Saving to File

Capture output for later analysis:
cargo run > telemetry.log 2>&1

Troubleshooting

No Output Appearing

  1. Ensure shutdown is called:
    provider.shutdown()?;  // Flushes all pending data
    
  2. Check feature flags:
    opentelemetry-stdout = { version = "0.31", features = ["trace"] }
    
  3. Verify exporter is set:
    let provider = SdkTracerProvider::builder()
        .with_simple_exporter(exporter)  // Must set exporter
        .build();
    

Output Mixed with Application Logs

Redirect stderr separately:
cargo run 2>telemetry.log 1>app.log
Or use a custom writer:
let file = std::fs::File::create("telemetry.log")?;
let exporter = opentelemetry_stdout::SpanExporter::new(file);

Performance Degradation

The stdout exporter is not optimized for high-volume telemetry. For production:
// Switch to OTLP for production
let exporter = opentelemetry_otlp::SpanExporter::builder()
    .with_http()
    .build()?;

Comparison with Other Exporters

FeatureStdoutOTLPZipkin
Traces
Metrics
Logs
Production Ready
Batching
Backend Required
Use CaseDevelopmentProductionZipkin backends

Next Steps

OTLP Exporter

Production-ready exporter for all signals

Tracing Guide

Learn how to instrument your application

Build docs developers (and LLMs) love