> ## 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

> Understand how to propagate trace context across service boundaries and async operations in OpenTelemetry Rust

Context propagation allows trace information to flow across service boundaries and through async operations, maintaining the parent-child relationships between spans.

## Context Basics

The `Context` type stores the currently active span and other contextual information. The `TraceContextExt` trait provides methods for working with spans in a context.

### TraceContextExt Trait

From `opentelemetry/src/trace/context.rs:223-299`:

```rust theme={null}
pub trait TraceContextExt {
    fn current_with_span<T: Span + Send + Sync + 'static>(span: T) -> Self;
    fn with_span<T: Span + Send + Sync + 'static>(&self, span: T) -> Self;
    fn span(&self) -> SpanRef<'_>;
    fn has_active_span(&self) -> bool;
    fn with_remote_span_context(&self, span_context: SpanContext) -> Self;
}
```

## Managing Active Spans

### Creating Context with Spans

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

let tracer = global::tracer("my-component");
let span = tracer.start("operation");

// Create a new context containing this span
let cx = Context::current_with_span(span);

// Or add a span to an existing context
let parent_cx = Context::current();
let child_span = tracer.start("child");
let child_cx = parent_cx.with_span(child_span);
```

### Accessing the Active Span

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

let cx = Context::current();
let span = cx.span();

// Use the span reference
span.add_event("something happened", vec![
    KeyValue::new("detail", "important")
]);
```

### Using get\_active\_span

The `get_active_span` function provides access to the current thread's active span:

```rust theme={null}
use opentelemetry::trace::{get_active_span, Tracer};
use opentelemetry::{global, KeyValue};

fn my_function() {
    // Access the active span without needing a context reference
    get_active_span(|span| {
        span.add_event(
            "my_function called",
            vec![KeyValue::new("timestamp", "2024-01-01")]
        );
    });
}

let tracer = global::tracer("my-component");
tracer.in_span("parent", |_cx| {
    // The span is active, my_function can access it
    my_function();
});
```

## Propagating Context

### Thread-Local Context Storage

OpenTelemetry uses thread-local storage to manage the active context:

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

// Get the current context
let cx = Context::current();

// Attach a context and get a guard
let new_cx = Context::new();
let _guard = new_cx.attach();

// Context is active until guard is dropped
```

### Manual Context Attachment

For explicit control over context lifetime:

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

let tracer = global::tracer("my-component");
let span = tracer.start("operation");
let cx = Context::current_with_span(span);

// Attach and get guard
let _guard = cx.attach();

// Context is active for the current thread
// Guard detaches context when dropped
```

### Using mark\_span\_as\_active

From `opentelemetry/src/trace/context.rs:327-361`:

```rust theme={null}
use opentelemetry::trace::{mark_span_as_active, Tracer, Span};
use opentelemetry::global;

let tracer = global::tracer("my-component");
let span = tracer.start("parent_span");

// Mark as active
let parent_active = mark_span_as_active(span);

{
    let child = tracer.start("child_span");
    let _child_active = mark_span_as_active(child);
    // Child is active here
}
// Parent is active again

drop(parent_active);
// No active span
```

## Async Context Propagation

### The Problem with Async

Standard context guards don't work correctly with async code:

```rust theme={null}
// ❌ INCORRECT - Don't do this!
async {
    let _guard = mark_span_as_active(span);
    // Guard stays active for entire future lifetime,
    // not just during polling!
};
```

From `opentelemetry/src/trace/tracer.rs:79-95`:

> The context guard `_g` will not exit until the future generated by the async block is complete. Since futures can be entered and exited multiple times without them completing, the span remains active for as long as the future exists.

### Using FutureExt

The correct way to propagate context in async code:

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

let cx = Context::current();

let my_future = async {
    // Async work here
};

// Attach context to the future
my_future.with_context(cx).await;
```

### Complete Async Example

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

async fn fetch_data(id: u64) -> Result<String, Box<dyn std::error::Error>> {
    let tracer = global::tracer("my-service");

    tracer.in_span("fetch_data", |cx| async move {
        let span = cx.span();
        span.set_attribute(KeyValue::new("data.id", id as i64));

        // Propagate context to nested async operation
        let result = fetch_from_db(id)
            .with_context(cx.clone())
            .await?;

        span.add_event("data_fetched", vec![]);
        Ok(result)
    }).await
}
```

## Remote Context Propagation

### Extracting Context for Propagation

When making HTTP requests or RPC calls, inject context into headers:

```rust theme={null}
use opentelemetry::{global, propagation::Injector};
use opentelemetry::trace::TraceContextExt;

// Custom injector for your HTTP library
struct HeaderInjector<'a>(&'a mut http::HeaderMap);

impl Injector for HeaderInjector<'_> {
    fn set(&mut self, key: &str, value: String) {
        if let Ok(name) = http::header::HeaderName::from_bytes(key.as_bytes()) {
            if let Ok(val) = http::header::HeaderValue::from_str(&value) {
                self.0.insert(name, val);
            }
        }
    }
}

// Inject context into request headers
let cx = Context::current();
let mut headers = http::HeaderMap::new();

global::get_text_map_propagator(|propagator| {
    propagator.inject_context(&cx, &mut HeaderInjector(&mut headers))
});
```

### Real-world gRPC Example

From `examples/tracing-grpc/src/client.rs:23-34`:

```rust theme={null}
use opentelemetry::propagation::Injector;
use tonic::metadata::MetadataMap;

struct MetadataMap<'a>(&'a mut tonic::metadata::MetadataMap);

impl Injector for MetadataMap<'_> {
    fn set(&mut self, key: &str, value: String) {
        if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.as_bytes()) {
            if let Ok(val) = tonic::metadata::MetadataValue::try_from(&value) {
                self.0.insert(key, val);
            }
        }
    }
}

// Inject into gRPC request
let mut request = tonic::Request::new(HelloRequest {
    name: "Tonic".into(),
});

global::get_text_map_propagator(|propagator| {
    propagator.inject_context(&cx, &mut MetadataMap(request.metadata_mut()))
});
```

### Extracting Context from Incoming Requests

```rust theme={null}
use opentelemetry::{global, propagation::Extractor};

// Custom extractor for your HTTP library
struct HeaderExtractor<'a>(&'a http::HeaderMap);

impl Extractor for HeaderExtractor<'_> {
    fn get(&self, key: &str) -> Option<&str> {
        self.0.get(key).and_then(|v| v.to_str().ok())
    }

    fn keys(&self) -> Vec<&str> {
        self.0.keys().map(|k| k.as_str()).collect()
    }
}

// Extract parent context from incoming request
let parent_cx = global::get_text_map_propagator(|propagator| {
    propagator.extract(&HeaderExtractor(&headers))
});

// Create child span with remote parent
let span = tracer.start_with_context("handle_request", &parent_cx);
```

### W3C Trace Context Propagator

Configure the global text map propagator:

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

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

## Remote Span Context

When you receive a span context from another service:

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

// Create a context with a remote span context
let remote_span_context: SpanContext = /* extracted from headers */;
let cx = Context::current().with_remote_span_context(remote_span_context);

// Create a child span
let span = tracer.start_with_context("local_operation", &cx);
```

## Context in Span Processors

<Warning>
  Do not rely on `Context::current()` in `SpanProcessor::on_end`. The context at cleanup time is unrelated to the span being ended.
</Warning>

From `opentelemetry-sdk/src/trace/span_processor.rs:87-118`:

```rust theme={null}
// ❌ INCORRECT - Don't access current context in on_end
impl SpanProcessor for MyProcessor {
    fn on_end(&self, span: SpanData) {
        // Context::current() is NOT related to this span!
        let cx = Context::current();
    }
}

// ✅ CORRECT - Extract info in on_start and store as attributes
impl SpanProcessor for MyProcessor {
    fn on_start(&self, span: &mut Span, cx: &Context) {
        // Extract baggage and store as span attribute
        if let Some(value) = cx.baggage().get("my-key") {
            span.set_attribute(KeyValue::new("my-key", value.to_string()));
        }
    }

    fn on_end(&self, span: SpanData) {
        // Access the attribute stored in on_start
        let my_value = span.attributes.iter()
            .find(|kv| kv.key.as_str() == "my-key");
    }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always use FutureExt for async code">
    Never use context guards directly in async blocks. Use `.with_context()` to attach context to futures.
  </Accordion>

  <Accordion title="Inject context at service boundaries">
    When making network calls, inject the current context into request metadata so downstream services can continue the trace.
  </Accordion>

  <Accordion title="Extract context from incoming requests">
    For server-side code, extract the parent span context from incoming request headers to maintain trace continuity.
  </Accordion>

  <Accordion title="Use the global propagator">
    Configure a single text map propagator (like W3C Trace Context) globally to ensure consistent propagation across your application.
  </Accordion>

  <Accordion title="Avoid Context::current() in processors">
    In span processors, extract needed information during `on_start` and store it as span attributes.
  </Accordion>
</AccordionGroup>

## Complete Example

Here's a complete HTTP client/server example:

```rust theme={null}
use opentelemetry::{
    global,
    propagation::{Injector, Extractor},
    trace::{Tracer, TraceContextExt, SpanKind},
    Context, KeyValue,
};

// Client side - inject context
async fn make_request() -> Result<(), Box<dyn std::error::Error>> {
    let tracer = global::tracer("http-client");

    tracer.in_span("http_request", |cx| async move {
        let mut request = http::Request::builder()
            .uri("https://api.example.com/data")
            .body(())?;

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

        // Make the request...
        Ok(())
    }).await
}

// Server side - extract context
async fn handle_request(req: http::Request<()>) -> http::Response<()> {
    // Extract parent context from headers
    let parent_cx = global::get_text_map_propagator(|propagator| {
        propagator.extract(&HeaderExtractor(req.headers()))
    });

    let tracer = global::tracer("http-server");

    // Create span with remote 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 request with context
    // ...

    http::Response::new(())
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Sampling" icon="filter" href="/traces/sampling">
    Learn how to control which traces are recorded
  </Card>

  <Card title="Span Processors" icon="gears" href="/traces/span-processors">
    Configure how spans are processed and exported
  </Card>
</CardGroup>
