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

Exporters

At a glance

  • Exports one span to a local OpenTelemetry Collector using OTLP.
  • Example crates: opentelemetry 0.33.1, opentelemetry_sdk 0.33.1, opentelemetry-otlp 0.33.1, and tokio 1.54.1. Book crate versions: opentelemetry-stdout 0.33.1, opentelemetry-appender-tracing 0.33.1, tracing-opentelemetry 0.34.0, tracing 0.1.44, tracing-subscriber 0.3.23, rusqlite 0.42.0, and libsql 0.10.0.
  • Run with cargo run -p otlp-export -- grpc or choose http; an optional second argument overrides the endpoint.
  • Expected last line: ok: one span flushed over grpc; look for it in the collector's output (or over http). Check the Collector output to confirm delivery.

Dependencies

The example uses Rust edition 2024.

[package]
name = "otlp-export"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
opentelemetry = { version = "=0.33.1", features = ["trace"] }
opentelemetry_sdk = { version = "=0.33.1", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "=0.33.1", default-features = false, features = ["trace", "grpc-tonic", "http-proto", "reqwest-blocking-client"] }
tokio = { version = "=1.54.1", features = ["macros", "rt-multi-thread"] }

Code

use std::io::Write;
use std::time::Duration;

use opentelemetry::trace::{Tracer, TracerProvider};
use opentelemetry_otlp::{SpanExporter, WithExportConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args_os().skip(1);
    let use_http = match args.next() {
        None => false,
        Some(argument) if argument == std::ffi::OsStr::new("grpc") => false,
        Some(argument) if argument == std::ffi::OsStr::new("http") => true,
        Some(_) => {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "choose grpc or http",
            )
            .into());
        }
    };

    let endpoint = match args.next() {
        Some(endpoint) => endpoint.into_string().map_err(|_| {
            std::io::Error::new(std::io::ErrorKind::InvalidInput, "endpoint must be valid Unicode")
        })?,
        None if use_http => "http://localhost:4318/v1/traces".to_owned(),
        None => "http://localhost:4317".to_owned(),
    };
    if args.next().is_some() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "usage: otlp-export [grpc|http] [endpoint]",
        )
        .into());
    }

    let exporter = if use_http {
        SpanExporter::builder()
            .with_http()
            .with_endpoint(endpoint)
            .with_timeout(Duration::from_secs(5))
            .build()?
    } else {
        SpanExporter::builder()
            .with_tonic()
            .with_endpoint(endpoint)
            .with_timeout(Duration::from_secs(5))
            .build()?
    };

    let provider = SdkTracerProvider::builder()
        .with_batch_exporter(exporter)
        .build();
    let tracer = provider.tracer("otlp-export");
    let span = tracer.start("example operation");
    drop(span);
    provider.shutdown()?;

    let protocol = if use_http { "http" } else { "grpc" };
    // Shutdown flushes the batch. It does not prove delivery: the span arrives only if a collector is listening.
    writeln!(
        std::io::stdout(),
        "ok: one span flushed over {protocol}; look for it in the collector's output"
    )?;
    Ok(())
}

Run it

Start a Collector with the configuration below, then run the example:

cargo run -p otlp-export -- grpc

To use HTTP protobuf instead, run:

cargo run -p otlp-export -- http

The protocol defaults to grpc when omitted. You can pass an endpoint as the second argument, for example http http://localhost:4318/v1/traces. The final line is ok: one span flushed over grpc; look for it in the collector's output for the first command, and the same with http for the second. Delivery is proved at the collector, whose debug exporter prints the span; the program’s own line only says the batch was flushed.

This minimal Collector configuration accepts both protocols and prints received spans with the debug exporter:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]

How it works

OTLP is the OpenTelemetry protocol for sending telemetry between services and collectors. The example creates an OTLP span exporter and a batch span processor, then ends one span. Shutting down the provider flushes the batch before the program exits.

The grpc argument selects gRPC on http://localhost:4317. The http argument selects HTTP with protobuf encoding on http://localhost:4318/v1/traces. These are the default endpoints; an optional second argument replaces the selected endpoint. The Collector configuration must enable the matching receiver protocol.

The OTLP exporter enables grpc-tonic, http-proto, and reqwest-blocking-client: the default batch processor exports on its own thread without a Tokio runtime, so its HTTP client must be blocking rather than async.

gRPC and HTTP protobuf both carry OTLP data. Choose gRPC when your Collector and network support it; choose HTTP protobuf when HTTP-based routing or infrastructure is a better fit.

The example sets a five-second export timeout. In production, use TLS when telemetry crosses an untrusted network. Configure the exporter and Collector consistently: an HTTPS endpoint alone does not configure certificate trust. When authentication is required, configure matching headers on the exporter and Collector. Keep credentials out of source code.

Timeouts bound how long an export attempt can take. Retries can help with transient failures, but they add delay and load; use bounded retries and coordinate their limits with the timeout and shutdown period.

Pitfalls

  • A Collector must be running and listening on the selected port and protocol.
  • HTTP OTLP uses the /v1/traces path; the gRPC endpoint does not.
  • A successful shutdown flushes pending spans, but it cannot deliver them if the Collector is unreachable.
  • TLS and authentication settings must match at both ends.