Skip to main content
The trace module provides types for tracking the progression of a single request while it is handled by services that make up an application. A trace is a tree of Spans which are objects that represent the work being done by individual services or components involved in a request as it flows through a system.

Core Concepts

OpenTelemetry tracing consists of three main traits:
  • TracerProvider - The entry point of the API that provides access to Tracers
  • Tracer - Responsible for creating Spans
  • Span - Represents a single operation within a trace

Getting Started

Here’s a basic example of instrumenting an application:
use opentelemetry::{global, trace::Tracer, KeyValue};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_stdout::SpanExporter;

fn init_tracer() -> SdkTracerProvider {
    let provider = SdkTracerProvider::builder()
        .with_simple_exporter(SpanExporter::default())
        .build();

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

fn do_work() {
    let tracer = global::tracer("my-component");

    tracer.in_span("doing_work", |cx| {
        // Your application logic here
        println!("Work is being traced!");
    });
}

fn main() {
    let provider = init_tracer();
    do_work();
    provider.shutdown().unwrap();
}

Trace Structure

A trace is a tree of spans. Each trace contains:
  • A root span - The top-level span with no parent
  • Zero or more child spans - Spans that have a parent span
Spans can be nested to form a trace tree:
let tracer = global::tracer("my-tracer");

tracer.in_span("parent_operation", |_cx| {
    // This is the root span

    tracer.in_span("child_operation", |_cx| {
        // This is a child span
    });
});

Synchronous vs Asynchronous Code

Synchronous Code

In synchronous code, use in_span for automatic span management:
use opentelemetry::{global, trace::Tracer};

let tracer = global::tracer("my-component");

tracer.in_span("operation", |_cx| {
    // Span is active here
    // Span ends automatically when closure returns
});

Asynchronous Code

For async code, use the FutureExt trait to attach context to futures:
use opentelemetry::{trace::FutureExt, Context};

let cx = Context::current();

let my_future = async {
    // Async work here
};

my_future.with_context(cx).await;
Do not use mark_span_as_active directly in async blocks, as the guard will remain active for the entire lifetime of the future, not just when it’s being polled.

Working with the Global TracerProvider

OpenTelemetry provides a global TracerProvider singleton:
use opentelemetry::global;

// Get a tracer anywhere in your application
let tracer = global::tracer("my-component");

// Or with version information
let tracer = global::tracer_with_version(
    "my-component",
    env!("CARGO_PKG_VERSION")
);

Complete Example with OTLP

Here’s a complete example using the OTLP exporter:
use opentelemetry::{global, trace::{Tracer, TraceContextExt}, KeyValue};
use opentelemetry_otlp::SpanExporter;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let exporter = SpanExporter::builder()
        .with_tonic()
        .build()?;

    let provider = SdkTracerProvider::builder()
        .with_resource(Resource::builder()
            .with_service_name("my-service")
            .build())
        .with_batch_exporter(exporter)
        .build();

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

    let tracer = global::tracer("my-component");

    tracer.in_span("main_operation", |cx| {
        let span = cx.span();
        span.add_event(
            "Processing started",
            vec![KeyValue::new("items", 100)]
        );
        span.set_attribute(KeyValue::new("user.id", "12345"));

        // Your application logic
    });

    provider.shutdown()?;
    Ok(())
}

Next Steps

Tracers

Learn how to create and configure tracers

Spans

Understand span lifecycle, attributes, and events

Context Propagation

Propagate trace context across service boundaries

Sampling

Control which traces are recorded and exported

Build docs developers (and LLMs) love