← Week 1: Distributed Tracing

Day 5: Context Propagation

Phase 6 · Sep 6, 2026

← Week 1: Distributed Tracing

Agenda (2–3 hours)

  • Read (45 min): W3C TraceContext specification; W3C Baggage specification; OpenTelemetry context propagation documentation
  • Study (45 min): Trace a traceparent header through an HTTP proxy, a message queue, and a Lambda invocation. What breaks at each boundary?
  • Practice (45 min): Propagate trace context across an HTTP call using reqwest + opentelemetry-http; verify that child spans appear under the correct parent in Jaeger
  • Challenge (30 min): Inject user_id into Baggage at the API gateway; propagate it through 3 downstream services; read it in each span's attributes. What are the security implications of Baggage?
← Week 1: Distributed Tracing

W3C TraceContext

Standard headers for cross-service trace propagation:

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │  │                                │                │
             │  trace-id (128 bit)               span-id (64 bit) │
             version                                              flags (01=sampled)

tracestate: vendor-specific key=value pairs (optional)

Every downstream service reads traceparent and creates a child span with the same trace ID.

← Week 1: Distributed Tracing

Baggage

User-defined key-value pairs propagated with the trace:

baggage: user_id=alice,tenant_id=acme
use opentelemetry::baggage::BaggageExt;
use opentelemetry::Context;

// At API gateway — set baggage
let cx = Context::current_with_baggage(vec![
    KeyValue::new("user_id", user_id.to_string()),
]);

// In downstream service — read baggage
let user_id = cx.baggage().get("user_id");

Warning: Baggage is visible to all services in the call graph — never put secrets in Baggage.

← Week 1: Distributed Tracing

Async Message Queue Propagation

HTTP headers don't survive a queue. Inject context into message attributes:

// Producer: inject trace context into SQS message attributes
let mut carrier = HashMap::new();
global::get_text_map_propagator(|p| p.inject_context(&cx, &mut carrier));

sqs.send_message()
    .message_attributes(...)
    .set_message_attributes(Some(carrier_to_sqs_attrs(carrier)))
    .send().await?;

// Consumer: extract trace context from message attributes
let cx = global::get_text_map_propagator(|p|
    p.extract(&sqs_attrs_to_carrier(&msg.message_attributes))
);
← Week 1: Distributed Tracing

Key Takeaways

  • traceparent is the W3C standard — 128-bit trace ID + 64-bit span ID + sampling flag
  • Baggage propagates user-defined key-value pairs; treat it as public data (no secrets)
  • Queue and Lambda boundaries break automatic header propagation — inject/extract manually
  • opentelemetry-http provides reqwest/hyper middleware for automatic HTTP propagation

Tomorrow: instrumenting real services — adding spans to a full Axum + tonic + DynamoDB app.