reqwest_tracing/
otel.rs

1use reqwest::header::{HeaderName, HeaderValue};
2use reqwest::Request;
3use std::str::FromStr;
4use tracing::Span;
5
6/// Injects the given OpenTelemetry Context into a reqwest::Request headers to allow propagation downstream.
7pub fn inject_opentelemetry_context_into_request(mut request: Request) -> Request {
8    #[cfg(feature = "opentelemetry_0_20")]
9    opentelemetry_0_20_pkg::global::get_text_map_propagator(|injector| {
10        use tracing_opentelemetry_0_21_pkg::OpenTelemetrySpanExt;
11        let context = Span::current().context();
12        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
13    });
14
15    #[cfg(feature = "opentelemetry_0_21")]
16    opentelemetry_0_21_pkg::global::get_text_map_propagator(|injector| {
17        use tracing_opentelemetry_0_22_pkg::OpenTelemetrySpanExt;
18        let context = Span::current().context();
19        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
20    });
21
22    #[cfg(feature = "opentelemetry_0_22")]
23    opentelemetry_0_22_pkg::global::get_text_map_propagator(|injector| {
24        use tracing_opentelemetry_0_23_pkg::OpenTelemetrySpanExt;
25        let context = Span::current().context();
26        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
27    });
28
29    #[cfg(feature = "opentelemetry_0_23")]
30    opentelemetry_0_23_pkg::global::get_text_map_propagator(|injector| {
31        use tracing_opentelemetry_0_24_pkg::OpenTelemetrySpanExt;
32        let context = Span::current().context();
33        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
34    });
35
36    #[cfg(feature = "opentelemetry_0_24")]
37    opentelemetry_0_24_pkg::global::get_text_map_propagator(|injector| {
38        use tracing_opentelemetry_0_25_pkg::OpenTelemetrySpanExt;
39        let context = Span::current().context();
40        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
41    });
42
43    #[cfg(feature = "opentelemetry_0_25")]
44    opentelemetry_0_25_pkg::global::get_text_map_propagator(|injector| {
45        use tracing_opentelemetry_0_26_pkg::OpenTelemetrySpanExt;
46        let context = Span::current().context();
47        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
48    });
49
50    #[cfg(feature = "opentelemetry_0_26")]
51    opentelemetry_0_26_pkg::global::get_text_map_propagator(|injector| {
52        use tracing_opentelemetry_0_27_pkg::OpenTelemetrySpanExt;
53        let context = Span::current().context();
54        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
55    });
56
57    #[cfg(feature = "opentelemetry_0_27")]
58    opentelemetry_0_27_pkg::global::get_text_map_propagator(|injector| {
59        use tracing_opentelemetry_0_28_pkg::OpenTelemetrySpanExt;
60        let context = Span::current().context();
61        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
62    });
63
64    #[cfg(feature = "opentelemetry_0_28")]
65    opentelemetry_0_28_pkg::global::get_text_map_propagator(|injector| {
66        use tracing_opentelemetry_0_29_pkg::OpenTelemetrySpanExt;
67        let context = Span::current().context();
68        injector.inject_context(&context, &mut RequestCarrier::new(&mut request))
69    });
70
71    request
72}
73
74// "traceparent" => https://www.w3.org/TR/trace-context/#trace-context-http-headers-format
75
76/// Injector used via opentelemetry propagator to tell the extractor how to insert the "traceparent" header value
77/// This will allow the propagator to inject opentelemetry context into a standard data structure. Will basically
78/// insert a "traceparent" string value "{version}-{trace_id}-{span_id}-{trace-flags}" of the spans context into the headers.
79/// Listeners can then re-hydrate the context to add additional spans to the same trace.
80struct RequestCarrier<'a> {
81    request: &'a mut Request,
82}
83
84impl<'a> RequestCarrier<'a> {
85    pub fn new(request: &'a mut Request) -> Self {
86        RequestCarrier { request }
87    }
88}
89
90impl RequestCarrier<'_> {
91    fn set_inner(&mut self, key: &str, value: String) {
92        let header_name = HeaderName::from_str(key).expect("Must be header name");
93        let header_value = HeaderValue::from_str(&value).expect("Must be a header value");
94        self.request.headers_mut().insert(header_name, header_value);
95    }
96}
97
98#[cfg(feature = "opentelemetry_0_20")]
99impl opentelemetry_0_20_pkg::propagation::Injector for RequestCarrier<'_> {
100    fn set(&mut self, key: &str, value: String) {
101        self.set_inner(key, value)
102    }
103}
104
105#[cfg(feature = "opentelemetry_0_21")]
106impl opentelemetry_0_21_pkg::propagation::Injector for RequestCarrier<'_> {
107    fn set(&mut self, key: &str, value: String) {
108        self.set_inner(key, value)
109    }
110}
111
112#[cfg(feature = "opentelemetry_0_22")]
113impl opentelemetry_0_22_pkg::propagation::Injector for RequestCarrier<'_> {
114    fn set(&mut self, key: &str, value: String) {
115        self.set_inner(key, value)
116    }
117}
118
119#[cfg(feature = "opentelemetry_0_23")]
120impl opentelemetry_0_23_pkg::propagation::Injector for RequestCarrier<'_> {
121    fn set(&mut self, key: &str, value: String) {
122        self.set_inner(key, value)
123    }
124}
125
126#[cfg(feature = "opentelemetry_0_24")]
127impl opentelemetry_0_24_pkg::propagation::Injector for RequestCarrier<'_> {
128    fn set(&mut self, key: &str, value: String) {
129        self.set_inner(key, value)
130    }
131}
132
133#[cfg(feature = "opentelemetry_0_25")]
134impl opentelemetry_0_25_pkg::propagation::Injector for RequestCarrier<'_> {
135    fn set(&mut self, key: &str, value: String) {
136        self.set_inner(key, value)
137    }
138}
139
140#[cfg(feature = "opentelemetry_0_26")]
141impl opentelemetry_0_26_pkg::propagation::Injector for RequestCarrier<'_> {
142    fn set(&mut self, key: &str, value: String) {
143        self.set_inner(key, value)
144    }
145}
146
147#[cfg(feature = "opentelemetry_0_27")]
148impl opentelemetry_0_27_pkg::propagation::Injector for RequestCarrier<'_> {
149    fn set(&mut self, key: &str, value: String) {
150        self.set_inner(key, value)
151    }
152}
153
154#[cfg(feature = "opentelemetry_0_28")]
155impl opentelemetry_0_28_pkg::propagation::Injector for RequestCarrier<'_> {
156    fn set(&mut self, key: &str, value: String) {
157        self.set_inner(key, value)
158    }
159}
160
161#[cfg(test)]
162mod test {
163    use std::sync::OnceLock;
164
165    use crate::{DisableOtelPropagation, TracingMiddleware};
166    use reqwest::Response;
167    use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, Extension};
168    use tracing::{info_span, Instrument, Level};
169
170    use tracing_subscriber::{filter, layer::SubscriberExt, Registry};
171    use wiremock::{matchers::any, Mock, MockServer, ResponseTemplate};
172
173    async fn make_echo_request_in_otel_context(client: ClientWithMiddleware) -> Response {
174        static TELEMETRY: OnceLock<()> = OnceLock::new();
175
176        TELEMETRY.get_or_init(|| {
177            let subscriber = Registry::default().with(
178                filter::Targets::new().with_target("reqwest_tracing::otel::test", Level::DEBUG),
179            );
180
181            #[cfg(feature = "opentelemetry_0_20")]
182            let subscriber = {
183                use opentelemetry_0_20_pkg::trace::TracerProvider;
184                use opentelemetry_stdout_0_1::SpanExporterBuilder;
185
186                let exporter = SpanExporterBuilder::default()
187                    .with_writer(std::io::sink())
188                    .build();
189
190                let provider = opentelemetry_0_20_pkg::sdk::trace::TracerProvider::builder()
191                    .with_simple_exporter(exporter)
192                    .build();
193
194                let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
195                let _ = opentelemetry_0_20_pkg::global::set_tracer_provider(provider);
196                opentelemetry_0_20_pkg::global::set_text_map_propagator(
197                    opentelemetry_0_20_pkg::sdk::propagation::TraceContextPropagator::new(),
198                );
199
200                let telemetry = tracing_opentelemetry_0_21_pkg::layer().with_tracer(tracer);
201                subscriber.with(telemetry)
202            };
203
204            #[cfg(feature = "opentelemetry_0_21")]
205            let subscriber = {
206                use opentelemetry_0_21_pkg::trace::TracerProvider;
207                use opentelemetry_stdout_0_2::SpanExporterBuilder;
208
209                let exporter = SpanExporterBuilder::default()
210                    .with_writer(std::io::sink())
211                    .build();
212
213                let provider = opentelemetry_sdk_0_21::trace::TracerProvider::builder()
214                    .with_simple_exporter(exporter)
215                    .build();
216
217                let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
218                let _ = opentelemetry_0_21_pkg::global::set_tracer_provider(provider);
219                opentelemetry_0_21_pkg::global::set_text_map_propagator(
220                    opentelemetry_sdk_0_21::propagation::TraceContextPropagator::new(),
221                );
222
223                let telemetry = tracing_opentelemetry_0_22_pkg::layer().with_tracer(tracer);
224                subscriber.with(telemetry)
225            };
226
227            #[cfg(feature = "opentelemetry_0_22")]
228            let subscriber = {
229                use opentelemetry_0_22_pkg::trace::TracerProvider;
230                use opentelemetry_stdout_0_3::SpanExporterBuilder;
231
232                let exporter = SpanExporterBuilder::default()
233                    .with_writer(std::io::sink())
234                    .build();
235
236                let provider = opentelemetry_sdk_0_22::trace::TracerProvider::builder()
237                    .with_simple_exporter(exporter)
238                    .build();
239
240                let tracer = provider.versioned_tracer("reqwest", None::<&str>, None::<&str>, None);
241                let _ = opentelemetry_0_22_pkg::global::set_tracer_provider(provider);
242                opentelemetry_0_22_pkg::global::set_text_map_propagator(
243                    opentelemetry_sdk_0_22::propagation::TraceContextPropagator::new(),
244                );
245
246                let telemetry = tracing_opentelemetry_0_23_pkg::layer().with_tracer(tracer);
247                subscriber.with(telemetry)
248            };
249
250            #[cfg(feature = "opentelemetry_0_23")]
251            let subscriber = {
252                use opentelemetry_0_23_pkg::trace::TracerProvider;
253                use opentelemetry_stdout_0_4::SpanExporterBuilder;
254
255                let exporter = SpanExporterBuilder::default()
256                    .with_writer(std::io::sink())
257                    .build();
258
259                let provider = opentelemetry_sdk_0_23::trace::TracerProvider::builder()
260                    .with_simple_exporter(exporter)
261                    .build();
262
263                let tracer = provider.tracer_builder("reqwest").build();
264                let _ = opentelemetry_0_23_pkg::global::set_tracer_provider(provider);
265                opentelemetry_0_23_pkg::global::set_text_map_propagator(
266                    opentelemetry_sdk_0_23::propagation::TraceContextPropagator::new(),
267                );
268
269                let telemetry = tracing_opentelemetry_0_24_pkg::layer().with_tracer(tracer);
270                subscriber.with(telemetry)
271            };
272
273            #[cfg(feature = "opentelemetry_0_24")]
274            let subscriber = {
275                use opentelemetry_0_24_pkg::trace::TracerProvider;
276                use opentelemetry_stdout_0_5::SpanExporterBuilder;
277
278                let exporter = SpanExporterBuilder::default()
279                    .with_writer(std::io::sink())
280                    .build();
281
282                let provider = opentelemetry_sdk_0_24::trace::TracerProvider::builder()
283                    .with_simple_exporter(exporter)
284                    .build();
285
286                let tracer = provider.tracer_builder("reqwest").build();
287                let _ = opentelemetry_0_24_pkg::global::set_tracer_provider(provider);
288                opentelemetry_0_24_pkg::global::set_text_map_propagator(
289                    opentelemetry_sdk_0_24::propagation::TraceContextPropagator::new(),
290                );
291
292                let telemetry = tracing_opentelemetry_0_25_pkg::layer().with_tracer(tracer);
293                subscriber.with(telemetry)
294            };
295
296            #[cfg(feature = "opentelemetry_0_25")]
297            let subscriber = {
298                use opentelemetry_0_25_pkg::trace::TracerProvider;
299
300                let provider = opentelemetry_sdk_0_25::trace::TracerProvider::builder().build();
301
302                let tracer = provider.tracer_builder("reqwest").build();
303                let _ = opentelemetry_0_25_pkg::global::set_tracer_provider(provider);
304                opentelemetry_0_25_pkg::global::set_text_map_propagator(
305                    opentelemetry_sdk_0_25::propagation::TraceContextPropagator::new(),
306                );
307
308                let telemetry = tracing_opentelemetry_0_26_pkg::layer().with_tracer(tracer);
309                subscriber.with(telemetry)
310            };
311
312            #[cfg(feature = "opentelemetry_0_26")]
313            let subscriber = {
314                use opentelemetry_0_26_pkg::trace::TracerProvider;
315
316                let provider = opentelemetry_sdk_0_26::trace::TracerProvider::builder().build();
317
318                let tracer = provider.tracer_builder("reqwest").build();
319                let _ = opentelemetry_0_26_pkg::global::set_tracer_provider(provider);
320                opentelemetry_0_26_pkg::global::set_text_map_propagator(
321                    opentelemetry_sdk_0_26::propagation::TraceContextPropagator::new(),
322                );
323
324                let telemetry = tracing_opentelemetry_0_27_pkg::layer().with_tracer(tracer);
325                subscriber.with(telemetry)
326            };
327
328            #[cfg(feature = "opentelemetry_0_27")]
329            let subscriber = {
330                use opentelemetry_0_27_pkg::trace::TracerProvider;
331
332                let provider = opentelemetry_sdk_0_27::trace::TracerProvider::builder().build();
333
334                let tracer = provider.tracer("reqwest");
335                let _ = opentelemetry_0_27_pkg::global::set_tracer_provider(provider);
336                opentelemetry_0_27_pkg::global::set_text_map_propagator(
337                    opentelemetry_sdk_0_27::propagation::TraceContextPropagator::new(),
338                );
339
340                let telemetry = tracing_opentelemetry_0_28_pkg::layer().with_tracer(tracer);
341                subscriber.with(telemetry)
342            };
343
344            #[cfg(feature = "opentelemetry_0_28")]
345            let subscriber = {
346                use opentelemetry_0_28_pkg::trace::TracerProvider;
347
348                let provider = opentelemetry_sdk_0_28::trace::SdkTracerProvider::builder().build();
349
350                let tracer = provider.tracer("reqwest");
351                let _ = opentelemetry_0_28_pkg::global::set_tracer_provider(provider);
352                opentelemetry_0_28_pkg::global::set_text_map_propagator(
353                    opentelemetry_sdk_0_28::propagation::TraceContextPropagator::new(),
354                );
355
356                let telemetry = tracing_opentelemetry_0_29_pkg::layer().with_tracer(tracer);
357                subscriber.with(telemetry)
358            };
359
360            tracing::subscriber::set_global_default(subscriber).unwrap();
361        });
362
363        // Mock server - sends all request headers back in the response
364        let server = MockServer::start().await;
365        Mock::given(any())
366            .respond_with(|req: &wiremock::Request| {
367                req.headers
368                    .iter()
369                    .fold(ResponseTemplate::new(200), |resp, (k, v)| {
370                        resp.append_header(k.clone(), v.clone())
371                    })
372            })
373            .mount(&server)
374            .await;
375
376        client
377            .get(server.uri())
378            .send()
379            .instrument(info_span!("some_span"))
380            .await
381            .unwrap()
382    }
383
384    #[tokio::test]
385    async fn tracing_middleware_propagates_otel_data_even_when_the_span_is_disabled() {
386        let client = ClientBuilder::new(reqwest::Client::new())
387            .with(TracingMiddleware::default())
388            .build();
389
390        let resp = make_echo_request_in_otel_context(client).await;
391
392        assert!(
393            resp.headers().contains_key("traceparent"),
394            "by default, the tracing middleware will propagate otel contexts"
395        );
396    }
397
398    #[tokio::test]
399    async fn context_no_propagated() {
400        let client = ClientBuilder::new(reqwest::Client::new())
401            .with_init(Extension(DisableOtelPropagation))
402            .with(TracingMiddleware::default())
403            .build();
404
405        let resp = make_echo_request_in_otel_context(client).await;
406
407        assert!(
408            !resp.headers().contains_key("traceparent"),
409            "request should not contain traceparent if context propagation is disabled"
410        );
411    }
412}