> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/open-telemetry/opentelemetry-rust/llms.txt
> Use this file to discover all available pages before exploring further.

# Context Propagation

> Learn how OpenTelemetry propagates context within a process and across service boundaries to maintain trace continuity in distributed systems.

## Overview

Context propagation is fundamental to distributed tracing. It allows OpenTelemetry to:

1. **Link related operations** - Connect spans within a single process
2. **Trace across services** - Maintain trace continuity as requests flow between services
3. **Share metadata** - Propagate baggage and other contextual information

OpenTelemetry provides two types of context propagation:

* **In-process propagation** - Using the `Context` API
* **Cross-service propagation** - Using propagators to inject/extract context from message headers

## In-Process Context Propagation

The `Context` type is an immutable, execution-scoped collection of values that flows through your application.

### Creating and Using Context

```rust theme={null}
use opentelemetry::Context;

#[derive(Debug, PartialEq)]
struct UserId(u64);

#[derive(Debug, PartialEq)]
struct RequestId(String);

// Create a context with values
let ctx = Context::new()
    .with_value(UserId(123))
    .with_value(RequestId("req-abc".to_string()));

// Retrieve values
assert_eq!(ctx.get::<UserId>(), Some(&UserId(123)));
assert_eq!(ctx.get::<RequestId>(), Some(&RequestId("req-abc".to_string())));
```

<Note>
  Contexts are immutable. Operations like `with_value()` return a new context containing both the original and new values.
</Note>

### Attaching Context to the Current Thread

Use `attach()` to make a context active for the current thread:

```rust theme={null}
use opentelemetry::Context;

#[derive(Debug, PartialEq)]
struct UserId(u64);

let ctx = Context::new().with_value(UserId(123));

// Attach the context
let _guard = ctx.attach();

// Now it's accessible via Context::current()
assert_eq!(Context::current().get::<UserId>(), Some(&UserId(123)));

// The guard automatically restores the previous context when dropped
```

<Warning>
  You must assign the guard to a variable (not `_`) or Rust will drop it immediately, restoring the previous context.
</Warning>

```rust theme={null}
// ❌ Wrong - guard is immediately dropped
Context::new().with_value(UserId(123)).attach();

// ✅ Correct - guard lives for the scope
let _guard = Context::new().with_value(UserId(123)).attach();
```

### Nested Contexts

Contexts can be nested, creating a stack of active contexts:

```rust theme={null}
use opentelemetry::Context;

#[derive(Debug, PartialEq)]
struct ValueA(&'static str);

#[derive(Debug, PartialEq)]
struct ValueB(u64);

let _outer = Context::new().with_value(ValueA("a")).attach();

// Only ValueA is set
assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA("a")));
assert_eq!(Context::current().get::<ValueB>(), None);

{
    let _inner = Context::current_with_value(ValueB(42)).attach();
    
    // Both values are accessible
    assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA("a")));
    assert_eq!(Context::current().get::<ValueB>(), Some(&ValueB(42)));
}

// Inner guard dropped, only ValueA remains
assert_eq!(Context::current().get::<ValueA>(), Some(&ValueA("a")));
assert_eq!(Context::current().get::<ValueB>(), None);
```

### Context with Spans

Spans are automatically stored in the context:

```rust theme={null}
use opentelemetry::{global, trace::{Tracer, TraceContextExt}, Context};

let tracer = global::tracer("my_service");
let span = tracer.start("operation");

// Create context with the span
let ctx = Context::current_with_span(span);

// Access the span from context
ctx.span().set_attribute(KeyValue::new("key", "value"));
```

### Async Context Propagation

For async code, use `FutureExt` to propagate context across `.await` points:

```rust theme={null}
use opentelemetry::{Context, trace::FutureExt};

async fn fetch_data() -> String {
    // This operation can access the context
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
    "data".to_string()
}

async fn process_request() {
    let ctx = Context::new().with_value(UserId(123));
    
    // Propagate context to async operation
    let result = fetch_data().with_context(ctx.clone()).await;
    
    // Context is available throughout the async chain
}
```

## Cross-Service Propagation

When a request crosses service boundaries (e.g., HTTP, gRPC), context must be serialized into message headers and deserialized on the receiving side.

### Propagators

Propagators handle serialization and deserialization of context. OpenTelemetry provides several standard propagators:

* **TraceContext** - W3C Trace Context standard (recommended)
* **Baggage** - W3C Baggage standard
* **Jaeger** - Jaeger-specific format
* **B3** - Zipkin B3 format

### Setting Up Propagation

```rust theme={null}
use opentelemetry::global;
use opentelemetry_sdk::propagation::TraceContextPropagator;

// Set the global propagator
global::set_text_map_propagator(TraceContextPropagator::new());
```

### Injecting Context (Client Side)

When making an outbound request, inject the current context into headers:

```rust theme={null}
use opentelemetry::{global, Context, trace::{TraceContextExt, Tracer}};
use opentelemetry_http::HeaderInjector;
use hyper::Request;

let tracer = global::tracer("example/client");

// Create a span for the outbound request
let span = tracer
    .span_builder("http_request")
    .with_kind(opentelemetry::trace::SpanKind::Client)
    .start(&tracer);

let cx = Context::current_with_span(span);

// Create HTTP request
let mut req = Request::builder().uri("https://api.example.com/data");

// Inject context into headers
global::get_text_map_propagator(|propagator| {
    propagator.inject_context(
        &cx,
        &mut HeaderInjector(req.headers_mut().unwrap())
    );
});

let request = req.body("request body").unwrap();
// Send the request...
```

This adds headers like:

```
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: vendor1=value1,vendor2=value2
```

### Extracting Context (Server Side)

When receiving a request, extract the context from headers:

```rust theme={null}
use opentelemetry::{global, trace::{Tracer, SpanKind}};
use opentelemetry_http::HeaderExtractor;
use hyper::Request;

fn handle_request(req: Request<Body>) {
    // Extract context from incoming headers
    let parent_cx = global::get_text_map_propagator(|propagator| {
        propagator.extract(&HeaderExtractor(req.headers()))
    });
    
    let tracer = global::tracer("example/server");
    
    // Create a span that continues the trace
    let span = tracer
        .span_builder("handle_request")
        .with_kind(SpanKind::Server)
        .start_with_context(&tracer, &parent_cx);
    
    let cx = parent_cx.with_span(span);
    
    // Process the request with the extracted context...
}
```

### Complete HTTP Example

Here's a complete example of context propagation between HTTP client and server:

**Client:**

```rust theme={null}
use opentelemetry::{global, trace::{SpanKind, TraceContextExt, Tracer}, Context};
use opentelemetry_http::HeaderInjector;
use opentelemetry_sdk::propagation::TraceContextPropagator;

// Configure propagator
global::set_text_map_propagator(TraceContextPropagator::new());

async fn send_request(url: &str, body: &str) -> Result<(), Box<dyn std::error::Error>> {
    let tracer = global::tracer("example/client");
    
    // Create client span
    let span = tracer
        .span_builder("http_request")
        .with_kind(SpanKind::Client)
        .start(&tracer);
    
    let cx = Context::current_with_span(span);
    
    // Build request and inject context
    let mut req = hyper::Request::builder().uri(url);
    
    global::get_text_map_propagator(|propagator| {
        propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut().unwrap()));
    });
    
    let request = req.body(body.to_string())?;
    
    // Send request...
    
    Ok(())
}
```

**Server:**

```rust theme={null}
use opentelemetry::{global, trace::{FutureExt, Span, SpanKind, TraceContextExt, Tracer}, Context};
use opentelemetry_http::HeaderExtractor;
use opentelemetry_sdk::propagation::TraceContextPropagator;
use hyper::{Request, Response, Body};

// Configure propagator
global::set_text_map_propagator(TraceContextPropagator::new());

async fn handle_request(req: Request<Body>) -> Response<Body> {
    // Extract context from headers
    let parent_cx = global::get_text_map_propagator(|propagator| {
        propagator.extract(&HeaderExtractor(req.headers()))
    });
    
    let tracer = global::tracer("example/server");
    
    // Create server span with extracted context as parent
    let span = tracer
        .span_builder("handle_request")
        .with_kind(SpanKind::Server)
        .start_with_context(&tracer, &parent_cx);
    
    let cx = parent_cx.with_span(span);
    
    // Process with context
    async {
        // Your request handling logic
        Response::new(Body::from("Response"))
    }
    .with_context(cx)
    .await
}
```

## Baggage

Baggage allows you to propagate arbitrary key-value pairs across service boundaries. Unlike span attributes, baggage is propagated to all downstream services.

### Using Baggage

```rust theme={null}
use opentelemetry::{baggage::BaggageExt, Context, KeyValue};

// Add baggage to context
let cx = Context::current()
    .with_baggage(vec![
        KeyValue::new("user.id", "123"),
        KeyValue::new("tenant.id", "acme-corp"),
    ]);

let _guard = cx.attach();

// Access baggage
let current_cx = Context::current();
for (key, (value, _metadata)) in current_cx.baggage() {
    println!("{}: {}", key, value);
}
```

### Propagating Baggage Across Services

To propagate baggage, use a composite propagator:

```rust theme={null}
use opentelemetry::{global, propagation::TextMapCompositePropagator};
use opentelemetry_sdk::propagation::{BaggagePropagator, TraceContextPropagator};

let composite = TextMapCompositePropagator::new(vec![
    Box::new(TraceContextPropagator::new()),
    Box::new(BaggagePropagator::new()),
]);

global::set_text_map_propagator(composite);
```

Now both trace context and baggage will be propagated via HTTP headers:

```
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
baggage: user.id=123,tenant.id=acme-corp
```

### Baggage in Spans and Logs

Baggage is not automatically added to spans or logs. You need custom processors:

```rust theme={null}
use opentelemetry::{baggage::BaggageExt, Context, KeyValue};
use opentelemetry_sdk::trace::SpanProcessor;

#[derive(Debug)]
struct EnrichWithBaggageSpanProcessor;

impl SpanProcessor for EnrichWithBaggageSpanProcessor {
    fn on_start(&self, span: &mut opentelemetry_sdk::trace::Span, cx: &Context) {
        // Add all baggage items as span attributes
        for (key, (value, _metadata)) in cx.baggage().iter() {
            span.set_attribute(KeyValue::new(key.clone(), value.clone()));
        }
    }
    
    fn on_end(&self, _span: opentelemetry_sdk::trace::SpanData) {}
    
    fn force_flush(&self) -> opentelemetry_sdk::error::OTelSdkResult {
        Ok(())
    }
    
    fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> opentelemetry_sdk::error::OTelSdkResult {
        Ok(())
    }
}

// Register the processor
use opentelemetry_sdk::trace::SdkTracerProvider;

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

<Warning>
  **Use baggage sparingly**: Baggage is propagated to all downstream services and can add significant overhead. Only include essential key-value pairs.
</Warning>

## Custom Propagators

You can implement custom propagators for proprietary formats:

```rust theme={null}
use opentelemetry::propagation::{TextMapPropagator, Extractor, Injector};
use opentelemetry::Context;

struct CustomPropagator;

impl TextMapPropagator for CustomPropagator {
    fn inject_context(&self, cx: &Context, injector: &mut dyn Injector) {
        // Serialize context into custom headers
        if let Some(span) = cx.span().span_context() {
            injector.set("x-custom-trace-id", span.trace_id().to_string());
            injector.set("x-custom-span-id", span.span_id().to_string());
        }
    }
    
    fn extract_with_context(&self, cx: &Context, extractor: &dyn Extractor) -> Context {
        // Deserialize context from custom headers
        // Implementation details...
        cx.clone()
    }
    
    fn fields(&self) -> Vec<String> {
        vec![
            "x-custom-trace-id".to_string(),
            "x-custom-span-id".to_string(),
        ]
    }
}
```

## Telemetry Suppression

OpenTelemetry components can suppress telemetry generation to prevent infinite loops:

```rust theme={null}
use opentelemetry::Context;

// Enter a suppressed scope (used internally by exporters)
let _guard = Context::enter_telemetry_suppressed_scope();

// Check if telemetry is suppressed
if Context::is_current_telemetry_suppressed() {
    // Skip telemetry generation
    return;
}
```

This is primarily used by OpenTelemetry SDK components (exporters, processors) to prevent generating telemetry about telemetry operations.

## Best Practices

<Tip>
  **Use W3C Trace Context**: `TraceContextPropagator` is the recommended standard for interoperability.
</Tip>

<Tip>
  **Always inject and extract**: Client-side code should inject context, server-side code should extract it.
</Tip>

<Tip>
  **Propagate context in async code**: Use `with_context()` to ensure context flows across `.await` points.
</Tip>

<Tip>
  **Minimize baggage**: Only propagate essential information as baggage—it's sent with every request.
</Tip>

<Warning>
  **Don't forget the guard variable**: `let _guard = ctx.attach()` not just `ctx.attach()`.
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="Signals" icon="signal" href="/concepts/signals">
    Learn about traces, metrics, and logs
  </Card>

  <Card title="Exporters" icon="upload" href="/exporters/overview">
    Configure where to send your telemetry data
  </Card>
</CardGroup>
