Overview
Tracing allows you to track the flow of requests through your application. This guide shows you how to set up basic tracing with the OpenTelemetry Rust SDK using the stdout exporter.
Dependencies
Add these dependencies to your Cargo.toml:
[dependencies]
opentelemetry = { version = "*", features = ["trace"] }
opentelemetry_sdk = { version = "*", features = ["trace", "rt-tokio"] }
opentelemetry-stdout = { version = "*", features = ["trace"] }
tokio = { version = "1", features = ["full"] }
Complete Example
Initialize the Tracer Provider
Set up the tracer provider with a stdout exporter and configure the trace context propagator.use opentelemetry::global;
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::SdkTracerProvider};
use opentelemetry_stdout::SpanExporter;
fn init_tracer() -> SdkTracerProvider {
global::set_text_map_propagator(TraceContextPropagator::new());
// Install stdout exporter pipeline to be able to retrieve the collected spans.
let provider = SdkTracerProvider::builder()
.with_simple_exporter(SpanExporter::default())
.build();
global::set_tracer_provider(provider.clone());
provider
}
Create and Start Spans
Create spans to track operations in your application.use opentelemetry::{
trace::{SpanKind, Tracer},
KeyValue,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let tracer_provider = init_tracer();
let tracer = global::tracer("example/client");
// Create a span with SpanKind::Client
let span = tracer
.span_builder("my_operation")
.with_kind(SpanKind::Client)
.start(&tracer);
// Your application logic here
// Shutdown the provider to flush all spans
tracer_provider.shutdown()?;
Ok(())
}
Add Span Attributes
Enrich your spans with additional context using attributes.use opentelemetry::trace::{Span, Tracer};
let mut span = tracer
.span_builder("database_query")
.with_kind(SpanKind::Internal)
.with_attributes([
KeyValue::new("db.system", "postgresql"),
KeyValue::new("db.operation", "SELECT"),
])
.start(&tracer);
// Add events to spans
span.add_event("Processing started", vec![]);
Parent-Child Span Relationships
Create nested spans to represent hierarchical operations.use opentelemetry::{Context, trace::TraceContextExt};
// Create parent span
let parent_span = tracer
.span_builder("parent_operation")
.with_kind(SpanKind::Server)
.start(&tracer);
let parent_cx = Context::current_with_span(parent_span);
// Create child span with parent context
let child_span = tracer
.span_builder("child_operation")
.with_kind(SpanKind::Internal)
.start_with_context(&tracer, &parent_cx);
Span Kinds
OpenTelemetry defines different span kinds to categorize operations:
SpanKind::Client - Represents a request to a remote service
SpanKind::Server - Represents handling an incoming request
SpanKind::Internal - Represents an internal operation
SpanKind::Producer - Represents a message producer
SpanKind::Consumer - Represents a message consumer
Always call shutdown() on the tracer provider before your application exits to ensure all spans are properly flushed to the exporter.
Output
When you run your application, the stdout exporter will print span data in JSON format to the console, showing:
- Trace ID and Span ID
- Span name and kind
- Start and end timestamps
- Attributes and events
- Parent-child relationships
For production use, replace the stdout exporter with an OTLP exporter to send traces to a backend like Jaeger, Zipkin, or a cloud observability platform. See the OTLP HTTP and OTLP gRPC examples.