tracing-opentelemetry-instrumentation-sdk

repository·main·Indexed 42 days ago

https://github.com/davidb/tracing-opentelemetry-instrumentation-sdk

A collection of Rust crates for integrating OpenTelemetry and the tracing ecosystem into Rust applications, with a focus on Axum web services. Includes axum-tracing-opentelemetry v0.39.1 for distributed tracing and Prometheus metrics, init-tracing-opentelemetry for simplified subscriber initialization via TracingConfig presets, and a fake-opentelemetry-collector for testing spans.

Tokens
27.2K
Snippets
95
Records
117
Agent score
54%

What's inside tracing-opentelemetry-instrumentation-sdk

  1. Overview of tracing-opentelemetry-instrumentation-sdk

    main

    This project provides a collection of Rust crates designed to simplify working with tracing and opentelemetry in Rust applications.

    Key crates include:

    • init-tracing-opentelemetry: Helpers for initializing tracing and opentelemetry configurations, allowing you to use opinionated presets or compose your own.
    • axum-tracing-opentelemetry: Middlewares and tools specifically for integrating axum with tracing and opentelemetry.
    • fake-opentelemetry-collector: A basic fake OpenTelemetry collector used for testing collected telemetry data.
  2. Compare axum-tracing-opentelemetry with tower_http::trace

    main

    While both provide middleware for Axum, they serve different purposes:

    Featuretower_http::trace
    Primary GoalGeneral-purpose logging (emits tracing events for lifecycle phases).
    Tracing FocusNot focused on distributed tracing.
    ContextCustom span fields.
    Body LoggingCan log request/response bodies.
    Featureaxum-tracing-opentelemetry
    Primary GoalOpenTelemetry distributed tracing.
    Context PropagationExtracts W3C traceparent/tracestate headers to connect traces across services.
    AttributesPopulates spans with [OTel HTTP semantic convention] attributes (e.g., http.request.method, url.path).
    Response InjectionOtelInResponseLayer injects the actual OTel trace_id into response headers.
    Body LoggingDoes not log bodies or emit per-phase log events.

    Best Practice: Use them together. Use tower_http::trace for human-readable logs and OtelAxumLayer for distributed trace correlation.

  3. Use re-exported OpenTelemetry crates to avoid version mismatches

    main

    To prevent breaking the OpenTelemetry global state caused by multiple versions of core crates in your dependency tree, do not add opentelemetry, opentelemetry_sdk, or tracing-opentelemetry as direct dependencies in your Cargo.toml.

    Instead, add init-tracing-opentelemetry and access the pinned versions through its re-exports. This ensures all parts of your application use the exact same versions of the OpenTelemetry SDK.

    [dependencies]
    init-tracing-opentelemetry = { version = "...", features = ["otlp"] }
    // Use these instead of direct dependencies
    use init_tracing_opentelemetry::opentelemetry;
    use init_tracing_opentelemetry::opentelemetry_sdk;
    use init_tracing_opentelemetry::tracing_opentelemetry;
  4. Important constraints when using tracing-opentelemetry

    main

    When using tracing-opentelemetry to interoperate with OpenTelemetry, be aware of these behaviors:

    • Span Creation Timing: An OpenTelemetry span is only created when the tracing span is closed. Do not attempt to interact with the OpenTelemetry Span or SpanBuilder directly from inside a tracing span.
    • Context and Trace ID: The OTel parent Context (and trace_id) is created on a NEW span or inherited from a parent. Because the parent context can be overwritten after creation, a tracing log might report no trace_id or a trace_id that hasn't been set yet during the NEW event.
    • Defining Span Metadata: To define the kind, name, etc., of an OpenTelemetry span via tracing, use specific record names such as otel.name, otel.kind, etc.
    • Field Initialization: Since fields in a tracing span should be defined at creation time, use tracing::field::Empty for fields that will be updated later during the span's lifecycle.
  5. How OpenTelemetry instrumentation works with tracing

    main

    This SDK provides helpers to build OpenTelemetry (OTel) instrumentation using the tracing crate, adhering to OpenTelemetry Trace Semantic Conventions.

    Instrumentation Patterns

    Caller Side (e.g., making an HTTP/gRPC request):

    1. Start a span with attributes (using tracing::field::Empty for values to be filled later).
    2. Inject propagation data into the call (e.g., via headers).
    3. Execute the call.
    4. Update span attributes with the response (e.g., status code).

    Callee Side (e.g., receiving an HTTP/gRPC request):

    1. Extract propagated info from headers to create an OpenTelemetry Context.
    2. Start a span with attributes.
    3. Attach the extracted context as the parent of the new span.
    4. Process the request.
    5. Update span attributes with the response (e.g., status code).
  6. Configure OTLP exporter environment variables

    main

    To use the OTLP exporter with the examples-axum-otlp example (or similar OpenTelemetry-instrumented applications), you must configure the following environment variables based on your preferred transport protocol (gRPC or HTTP/protobuf).

    For gRPC

    Use these settings to export traces via gRPC:

    • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: Set to your collector endpoint (e.g., http://localhost:4317).
    • OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: Set to grpc.
    • OTEL_TRACES_SAMPLER: Set to always_on to ensure all traces are captured.

    For HTTP

    Use these settings to export traces via HTTP/protobuf:

    • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: Set to your collector endpoint path (e.g., http://127.0.0.1:4318/v1/traces).
    • OTEL_EXPORTER_OTLP_TRACES_PROTOCOL: Set to http/protobuf.
    • OTEL_TRACES_SAMPLER: Set to always_on to ensure all traces are captured.
    # For GRPC:
    export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4317"
    export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="grpc"
    export OTEL_TRACES_SAMPLER="always_on"
    
    # For HTTP:
    export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://127.0.0.1:4318/v1/traces"
    export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL="http/protobuf"
    export OTEL_TRACES_SAMPLER="always_on"
  7. Integrate OpenTelemetry tracing into Axum

    main

    Use axum-tracing-opentelemetry to provide distributed tracing for Axum applications. The middleware handles:

    • Reading OpenTelemetry headers from incoming requests.
    • Starting a new trace if no trace context is found.
    • Attaching the trace to the tracing span.
    • Creating an OpenTelemetry Span when the tracing span closes.

    To use it, apply OtelAxumLayer to your router to start traces on incoming requests, and optionally apply OtelInResponseLayer to inject the trace context back into response headers.

    use axum_tracing_opentelemetry::middleware::{OtelAxumLayer, OtelInResponseLayer};
    
    // ... setup tracing using init-tracing-opentelemetry ...
    
    fn app() -> Router {
        Router::new()
            .route("/", get(index)) 
            // include trace context as header into the response
            .layer(OtelInResponseLayer::default())
            // start OpenTelemetry trace on incoming request
            .layer(OtelAxumLayer::default())
            .route("/health", get(health))
    }
  8. Use fake-opentelemetry-collector for testing spans

    main

    The fake-opentelemetry-collector is a basic OpenTelemetry collector designed for testing what is collected by OpenTelemetry. It allows you to start a mock collector server, initialize a TracerProvider that points to it, and then inspect the exported spans to verify your instrumentation logic. This is particularly useful for snapshot testing with tools like insta.

    use std::time::Duration;
    use fake_opentelemetry_collector::{setup_tracer_provider, FakeCollectorServer};
    use opentelemetry::trace::{TracerProvider, SpanKind};
    
    #[tokio::test(flavor = "multi_thread")]
    async fn demo_fake_tracer_and_collector() {
        // 1. Start the fake collector server
        let mut fake_collector = FakeCollectorServer::start()
            .await
            .expect("fake collector setup and started");
    
        // 2. Initialize the TracerProvider linked to the fake collector
        let tracer_provider = setup_tracer_provider(&fake_collector).await;
        let tracer = tracer_provider.tracer("test");
    
        // 3. Create and end a span
        let mut span = tracer
            .span_builder("my-test-span")
            .with_kind(SpanKind::Server)
            .start(&tracer);
        span.add_event("my-test-event", vec![]);
        span.end();
    
        // 4. Flush and shutdown the provider to ensure spans are sent
        let _ = tracer_provider.force_flush();
        tracer_provider
            .shutdown()
            .expect("no error during shutdown");
        drop(tracer_provider);
    
        // 5. Retrieve and inspect the exported spans
        let otel_spans = fake_collector
            .exported_spans(1, Duration::from_secs(20))
            .await;
        
        // Example assertion using insta
        // insta::assert_yaml_snapshot!(otel_spans, ...);
    }
  9. Configure OpenTelemetry Metrics

    main

    To enable OpenTelemetry metrics, enable the metrics feature in your Cargo.toml. This initializes a global SdkMeterProvider.

    The preferred way to produce metrics is to create instruments directly using the OpenTelemetry API. This keeps metric cardinality independent of log volume and avoids the overhead of emitting log lines.

    Optional: Tracing-Macro Bridge

    If the metrics feature is enabled, a MetricsLayer is added. This allows you to emit metrics by using specific field names in tracing macros (e.g., tracing::info!(monotonic_counter.name = 1)). Use this only when you are already emitting a trace/log for that event, as it couples metric emission to log volume.

    use init_tracing_opentelemetry::opentelemetry::{KeyValue, global};
    
    let http_requests = global::meter("my-service").u64_counter("http_requests").build();
    
    http_requests.add(1, &[KeyValue::new("method", "GET"), KeyValue::new("path", "/")]);
  10. Set up Otel Desktop Viewer for local trace visualization

    main

    Otel Desktop Viewer is a desktop collector that allows you to collect and visualize traces locally. You can install it via Homebrew or run it as a Docker container.

    To run via Docker (AMD64):

    1. Run the container mapping ports 8000 (UI), 4317 (gRPC), and 4318 (HTTP).
    2. Open http://localhost:8000 in your browser.
    # also available via `brew install --cask ctrlspice/tap/otel-desktop-viewer`
    # For AMD64 (most common)
    # docker/nerdctl/podman or any container runner
    docker run -p 8000:8000 -p 4317:4317 -p 4318:4318 ghcr.io/ctrlspice/otel-desktop-viewer:latest
    
    open http://localhost:8000
  11. Configure OpenTelemetry Logs

    main

    To enable OpenTelemetry log export, enable the logs feature. This initializes a SdkLoggerProvider and bridges tracing events to the OpenTelemetry log pipeline. Standard tracing macros (e.g., info!, error!) will be automatically exported via OTLP.

    Enabling/Disabling Logs

    You can toggle log export at runtime using .with_logs(bool) on your TracingConfig.

    [dependencies]
    init-tracing-opentelemetry = { version = "*", features = ["otlp", "logs"] }
    use init_tracing_opentelemetry::TracingConfig;
    
    TracingConfig::default()
        .with_logs(false)   // disable log export (default: enabled when feature is active)
        .init_subscriber()
        .expect("valid tracing configuration");