rust 68 lines · 8 steps

Propagating trace context in Axum extractors

A custom Axum extractor pulls W3C trace headers off incoming requests and forwards them onto outbound HTTP calls.

Explained by highlit
1use axum::{
2 async_trait,
3 extract::FromRequestParts,
4 http::{header::HeaderValue, request::Parts, HeaderName},
5};
6use reqwest::Client;
7use serde::Deserialize;
8 
9const TRACEPARENT: HeaderName = HeaderName::from_static("traceparent");
10const TRACESTATE: HeaderName = HeaderName::from_static("tracestate");
11 
12#[derive(Clone, Debug)]
13pub struct TraceContext {
14 traceparent: HeaderValue,
15 tracestate: Option<HeaderValue>,
16}
17 
18#[async_trait]
19impl<S> FromRequestParts<S> for TraceContext
20where
21 S: Send + Sync,
22{
23 type Rejection = std::convert::Infallible;
24 
25 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
26 let traceparent = parts
27 .headers
28 .get(TRACEPARENT)
29 .cloned()
30 .unwrap_or_else(|| HeaderValue::from_static("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"));
31 
32 Ok(Self {
33 traceparent,
34 tracestate: parts.headers.get(TRACESTATE).cloned(),
35 })
36 }
37}
38 
39impl TraceContext {
40 pub fn apply(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
41 let mut req = req.header(TRACEPARENT, self.traceparent.clone());
42 if let Some(state) = &self.tracestate {
43 req = req.header(TRACESTATE, state.clone());
44 }
45 req
46 }
47}
48 
49#[derive(Deserialize)]
50pub struct Quote {
51 pub symbol: String,
52 pub price: f64,
53}
54 
55pub async fn fetch_quote(
56 trace: TraceContext,
57 client: Client,
58 symbol: &str,
59) -> reqwest::Result<Quote> {
60 let request = client.get(format!("https://prices.internal/v1/quotes/{symbol}"));
61 trace
62 .apply(request)
63 .send()
64 .await?
65 .error_for_status()?
66 .json()
67 .await
68}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Implementing FromRequestParts turns any struct into an Axum handler argument extracted straight from the request.
  2. 2Using Infallible as the rejection type guarantees extraction always succeeds, so handlers never see an error path.
  3. 3Forwarding traceparent and tracestate onto outbound clients is what stitches services into a single distributed trace.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Propagating trace context in Axum extractors — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code