Skip to main content
The Zipkin exporter sends OpenTelemetry trace data to Zipkin, a popular distributed tracing system. Use this exporter when you have existing Zipkin infrastructure or need compatibility with Zipkin’s trace format.
The Zipkin exporter only supports traces. For metrics and logs, use the OTLP exporter.

Installation

Add the dependency to your Cargo.toml:
[dependencies]
opentelemetry = "0.31"
opentelemetry_sdk = { version = "0.31", features = ["trace"] }
opentelemetry-zipkin = "0.31"

Feature Flags

Default features (blocking HTTP client):
opentelemetry-zipkin = { version = "0.31", default-features = true }
# Includes: reqwest-blocking-client
Async HTTP client (recommended for async applications):
opentelemetry-zipkin = { version = "0.31", features = ["reqwest-client"], default-features = false }
With Rustls TLS:
opentelemetry-zipkin = { version = "0.31", features = ["reqwest-rustls"] }

Quick Start

1. Run Zipkin

Start Zipkin using Docker:
docker run -d -p 9411:9411 openzipkin/zipkin
Zipkin UI will be available at: http://localhost:9411

2. Basic Setup

use opentelemetry::global;
use opentelemetry::trace::Tracer;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_zipkin::ZipkinExporter;

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    // Set Zipkin propagator
    global::set_text_map_propagator(opentelemetry_zipkin::Propagator::new());

    // Create Zipkin exporter
    let exporter = ZipkinExporter::builder().build()?;

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

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

    // Get a tracer and create spans
    let tracer = global::tracer("my-tracer");
    
    tracer.in_span("operation", |_cx| {
        // Your application logic
    });

    // Shutdown to ensure all spans are sent
    provider.shutdown()?;
    Ok(())
}

3. View Traces

Open http://localhost:9411 in your browser to view traces in the Zipkin UI.

Complete Example

Here’s a complete example with nested spans and attributes:
use opentelemetry::{
    global,
    trace::{Span, Tracer},
    InstrumentationScope,
    KeyValue,
};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry_zipkin::ZipkinExporter;
use std::thread;
use std::time::Duration;

fn process_request(tracer: &impl Tracer) {
    let mut span = tracer.start("process_request");
    span.set_attribute(KeyValue::new("request.id", "12345"));
    span.set_attribute(KeyValue::new("user.id", "user_456"));
    
    thread::sleep(Duration::from_millis(10));
    
    span.add_event(
        "Request validated",
        vec![KeyValue::new("validation.time_ms", 5)],
    );
    
    span.end();
}

fn database_query(tracer: &impl Tracer) {
    let mut span = tracer.start("database_query");
    span.set_attribute(KeyValue::new("db.system", "postgresql"));
    span.set_attribute(KeyValue::new("db.statement", "SELECT * FROM users"));
    
    thread::sleep(Duration::from_millis(15));
    span.end();
}

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    // Set Zipkin propagator for context propagation
    global::set_text_map_propagator(opentelemetry_zipkin::Propagator::new());

    // Create exporter
    let exporter = ZipkinExporter::builder().build()?;

    // Create provider with resource information
    let provider = SdkTracerProvider::builder()
        .with_simple_exporter(exporter)
        .with_resource(
            Resource::builder()
                .with_service_name("zipkin-demo")
                .with_service_version("1.0.0")
                .with_attributes([
                    KeyValue::new("environment", "development"),
                ])
                .build()
        )
        .build();

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

    // Create instrumentation scope
    let scope = InstrumentationScope::builder("my-app")
        .with_version(env!("CARGO_PKG_VERSION"))
        .with_attributes([KeyValue::new("scope-key", "scope-value")])
        .build();

    let tracer = global::tracer_with_scope(scope);

    // Create nested spans
    tracer.in_span("main_operation", |_cx| {
        thread::sleep(Duration::from_millis(5));
        
        process_request(&tracer);
        database_query(&tracer);
        
        thread::sleep(Duration::from_millis(5));
    });

    // Ensure all spans are sent
    provider.shutdown()?;
    Ok(())
}

Configuration

Custom Endpoint

By default, the exporter sends to http://localhost:9411/api/v2/spans. Customize the endpoint:
use opentelemetry_zipkin::ZipkinExporter;

let exporter = ZipkinExporter::builder()
    .with_collector_endpoint("https://zipkin.example.com:9411/api/v2/spans")
    .build()?;

Service Name Configuration

Set the service name via Resource:
use opentelemetry_sdk::Resource;

let resource = Resource::builder()
    .with_service_name("my-service")
    .with_service_version("1.2.3")
    .with_service_namespace("production")
    .build();

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

Environment Variables

The exporter supports these environment variables:
  • OTEL_EXPORTER_ZIPKIN_ENDPOINT - Zipkin collector endpoint
  • OTEL_SERVICE_NAME - Service name

Performance Optimization

Use Batch Exporter

For production applications, use batch processing for better performance:
use opentelemetry_sdk::trace::{BatchSpanProcessor, BatchConfigBuilder};
use opentelemetry_zipkin::ZipkinExporter;

let exporter = ZipkinExporter::builder().build()?;

// Configure batching
let batch_config = BatchConfigBuilder::default()
    .with_max_queue_size(4096)
    .with_max_export_batch_size(512)
    .with_scheduled_delay(std::time::Duration::from_secs(5))
    .with_max_export_timeout(std::time::Duration::from_secs(30))
    .build();

let batch_processor = BatchSpanProcessor::builder(exporter)
    .with_batch_config(batch_config)
    .build();

let provider = SdkTracerProvider::builder()
    .with_span_processor(batch_processor)
    .build();

Async HTTP Client

For async applications, use the non-blocking client:
[dependencies]
opentelemetry-zipkin = { version = "0.31", features = ["reqwest-client"], default-features = false }
No code changes needed - the async client is used automatically.

Context Propagation

Zipkin uses B3 propagation for distributing trace context across service boundaries.

Set B3 Propagator

use opentelemetry::global;
use opentelemetry_zipkin::Propagator;

// Set the Zipkin B3 propagator
global::set_text_map_propagator(Propagator::new());

Injecting Context (Outgoing Requests)

use opentelemetry::global;
use opentelemetry::propagation::Injector;
use std::collections::HashMap;

// Your HTTP headers
let mut headers = HashMap::new();

// Inject trace context into headers
struct HeaderInjector<'a>(&'a mut HashMap<String, String>);

impl<'a> Injector for HeaderInjector<'a> {
    fn set(&mut self, key: &str, value: String) {
        self.0.insert(key.to_string(), value);
    }
}

let cx = opentelemetry::Context::current();
global::get_text_map_propagator(|propagator| {
    propagator.inject_context(&cx, &mut HeaderInjector(&mut headers));
});

// Now headers contains B3 trace context (X-B3-TraceId, X-B3-SpanId, etc.)

Extracting Context (Incoming Requests)

use opentelemetry::global;
use opentelemetry::propagation::Extractor;
use std::collections::HashMap;

struct HeaderExtractor<'a>(&'a HashMap<String, String>);

impl<'a> Extractor for HeaderExtractor<'a> {
    fn get(&self, key: &str) -> Option<&str> {
        self.0.get(key).map(|v| v.as_str())
    }

    fn keys(&self) -> Vec<&str> {
        self.0.keys().map(|k| k.as_str()).collect()
    }
}

let headers: HashMap<String, String> = /* incoming request headers */;
let parent_cx = global::get_text_map_propagator(|propagator| {
    propagator.extract(&HeaderExtractor(&headers))
});

// Use parent_cx when creating spans to maintain trace continuity

Integration Examples

With Actix-Web

use actix_web::{web, App, HttpServer, HttpResponse};
use opentelemetry::trace::Tracer;
use opentelemetry::global;

async fn index() -> HttpResponse {
    let tracer = global::tracer("actix-web");
    
    tracer.in_span("handle_request", |_cx| {
        // Handler logic
        HttpResponse::Ok().body("Hello, World!")
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Initialize Zipkin exporter
    let exporter = opentelemetry_zipkin::ZipkinExporter::builder()
        .build()
        .expect("Failed to create exporter");
    
    let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
        .with_batch_exporter(exporter)
        .build();
    
    global::set_tracer_provider(provider);

    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

With Tokio

use opentelemetry::trace::Tracer;
use opentelemetry::global;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
    let exporter = opentelemetry_zipkin::ZipkinExporter::builder().build()?;
    
    let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
        .with_batch_exporter(exporter)
        .build();
    
    global::set_tracer_provider(provider.clone());

    let tracer = global::tracer("tokio-app");

    tracer.in_span("async_operation", |_cx| async {
        sleep(Duration::from_millis(100)).await;
        println!("Async work done");
    }).await;

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

Troubleshooting

Traces Not Appearing

  1. Verify Zipkin is running:
    curl http://localhost:9411/api/v2/services
    
  2. Check endpoint configuration:
    • Default: http://localhost:9411/api/v2/spans
    • Ensure no typos in custom endpoints
  3. Ensure shutdown is called:
    provider.shutdown()?;  // Flushes all pending spans
    

Missing Span Attributes

Attributes must be set before the span ends:
let mut span = tracer.start("operation");
span.set_attribute(KeyValue::new("key", "value"));  // Before end()
span.end();

Context Not Propagating

Ensure the Zipkin propagator is set:
global::set_text_map_propagator(opentelemetry_zipkin::Propagator::new());

When to Use Zipkin Exporter

Use Zipkin exporter when:
  • You have existing Zipkin infrastructure
  • You need B3 propagation compatibility
  • You’re integrating with Zipkin-native tools
  • You’re migrating gradually from Zipkin to OpenTelemetry
Use OTLP exporter when:
  • Starting a new project
  • You need metrics and logs in addition to traces
  • You want vendor-neutral telemetry
  • You need maximum flexibility in backend choice

Zipkin vs OTLP

FeatureZipkin ExporterOTLP Exporter
Traces
Metrics
Logs
ProtocolHTTP/JSONgRPC, HTTP/protobuf, HTTP/JSON
MaintenanceActiveActive
Use CaseZipkin backendsUniversal

Next Steps

OTLP Exporter

Export all signals with OTLP

Tracing Guide

Learn about distributed tracing

Build docs developers (and LLMs) love