Skip to content

Rust OpenTelemetry instrumentation

Instrument a Rust service with OpenTelemetry by bridging the tracing crate to an OTLP exporter. Spans you record with tracing — and spans that libraries like Axum and Actix Web emit through it — are exported to Osuite. This guide uses opentelemetry-otlp with the Tonic (gRPC) transport and tracing-opentelemetry as the bridge.

  • A Rust project using the tracing crate for instrumentation.
  • A Tokio async runtime (Axum, Actix Web, and most async services provide one).
  • Your Osuite ingest token and region.
  1. Add the OpenTelemetry crates to Cargo.toml. These versions are the current 0.32 release line paired with tracing-opentelemetry 0.33.

    [dependencies]
    axum = "0.8"
    tower-http = { version = "0.6", features = ["trace"] }
    tokio = { version = "1", features = ["full"] }
    tracing = "0.1"
    tracing-subscriber = { version = "0.3", features = ["env-filter"] }
    tracing-opentelemetry = "0.33"
    opentelemetry = "0.32"
    opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"] }
    opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"] }
  2. Add a telemetry init function that builds an OTLP tracer provider and registers it with the tracing subscriber. Call init_telemetry() at the top of main, and call provider.shutdown() before exit so buffered spans are flushed.

    use opentelemetry::trace::TracerProvider as _;
    use opentelemetry::KeyValue;
    use opentelemetry_otlp::WithExportConfig;
    use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::SdkTracerProvider, Resource};
    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    fn init_telemetry() -> Result<SdkTracerProvider, Box<dyn std::error::Error>> {
    opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
    let exporter = opentelemetry_otlp::SpanExporter::builder()
    .with_tonic()
    .build()?;
    let provider = SdkTracerProvider::builder()
    .with_batch_exporter(exporter)
    .with_resource(
    Resource::builder_empty()
    .with_attribute(KeyValue::new("service.name", "<service-name>"))
    .with_attribute(KeyValue::new("service.environment", "production"))
    .build(),
    )
    .build();
    let tracer = provider.tracer("<service-name>");
    tracing_subscriber::registry()
    .with(tracing_subscriber::EnvFilter::from_default_env())
    .with(tracing_opentelemetry::layer().with_tracer(tracer))
    .with(tracing_subscriber::fmt::layer())
    .init();
    Ok(provider)
    }
  3. Set the exporter environment variables. The Tonic exporter reads its endpoint and headers from these.

    Terminal window
    export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.<region>.osuite.io:443
    export OTEL_EXPORTER_OTLP_HEADERS=x-osuite-ingest-token=<your-ingest-token>

    The https:// scheme is required — the exporter takes a URL and Osuite only accepts OTLP over TLS.

Call init_telemetry first, then add the framework’s tracing middleware so every request opens a span. For Axum, use tower-http’s TraceLayer:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = init_telemetry()?;
let app = axum::Router::new()
.route("/", axum::routing::get(|| async { "ok" }))
.layer(tower_http::trace::TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
provider.shutdown()?;
Ok(())
}

What you should see

Send a request to any route. The service appears in APM within a minute with a trace per request, showing the request span and any nested spans you record with #[tracing::instrument] or tracing::span!.

If no traces appear, confirm init_telemetry runs before any request is served and that provider.shutdown() is called on exit — without it, short-lived processes can exit before the batch exporter flushes. Check that the endpoint includes the https:// scheme and port 443.

No data in Osuite after a few minutes? Work through these checks.

  • Endpoint — confirm the exporter targets exactly ingest.<region>.osuite.io:443 for your region, over TLS.
  • Token — confirm the ingest header carries a valid <your-ingest-token> and has not been rotated.
  • Pipeline — confirm the signal you expect (traces, logs, or metrics) is wired into an active pipeline with the otlp/osuite exporter attached.
  • Export errors — check the application or Collector logs for OTLP export failures (auth, DNS, TLS, connection refused).
  • Network — confirm the host has outbound access to the ingest endpoint on port 443.
  • Timing — allow up to a minute for the first data to appear before assuming a failure.

Still stuck? Ask the Investigation Agent or contact support.