tracing_opentelemetry/
span_ext.rs

1use crate::layer::WithContext;
2use opentelemetry::{
3    time,
4    trace::{SpanContext, Status},
5    Context, Key, KeyValue, Value,
6};
7use std::{borrow::Cow, time::SystemTime};
8
9/// Utility functions to allow tracing [`Span`]s to accept and return
10/// [OpenTelemetry] [`Context`]s.
11///
12/// [`Span`]: tracing::Span
13/// [OpenTelemetry]: https://opentelemetry.io
14/// [`Context`]: opentelemetry::Context
15pub trait OpenTelemetrySpanExt {
16    /// Associates `self` with a given OpenTelemetry trace, using the provided
17    /// parent [`Context`].
18    ///
19    /// [`Context`]: opentelemetry::Context
20    ///
21    /// # Examples
22    ///
23    /// ```rust
24    /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt};
25    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
26    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
27    /// use std::collections::HashMap;
28    /// use tracing::Span;
29    ///
30    /// // Example carrier, could be a framework header map that impls otel's `Extractor`.
31    /// let mut carrier = HashMap::new();
32    ///
33    /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc.
34    /// let propagator = TraceContextPropagator::new();
35    ///
36    /// // Extract otel parent context via the chosen propagator
37    /// let parent_context = propagator.extract(&carrier);
38    ///
39    /// // Generate a tracing span as usual
40    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
41    ///
42    /// // Assign parent trace from external context
43    /// app_root.set_parent(parent_context.clone());
44    ///
45    /// // Or if the current span has been created elsewhere:
46    /// Span::current().set_parent(parent_context);
47    /// ```
48    fn set_parent(&self, cx: Context);
49
50    /// Associates `self` with a given OpenTelemetry trace, using the provided
51    /// followed span [`SpanContext`].
52    ///
53    /// [`SpanContext`]: opentelemetry::trace::SpanContext
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt};
59    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
60    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
61    /// use std::collections::HashMap;
62    /// use tracing::Span;
63    ///
64    /// // Example carrier, could be a framework header map that impls otel's `Extractor`.
65    /// let mut carrier = HashMap::new();
66    ///
67    /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc.
68    /// let propagator = TraceContextPropagator::new();
69    ///
70    /// // Extract otel context of linked span via the chosen propagator
71    /// let linked_span_otel_context = propagator.extract(&carrier);
72    ///
73    /// // Extract the linked span context from the otel context
74    /// let linked_span_context = linked_span_otel_context.span().span_context().clone();
75    ///
76    /// // Generate a tracing span as usual
77    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
78    ///
79    /// // Assign linked trace from external context
80    /// app_root.add_link(linked_span_context);
81    ///
82    /// // Or if the current span has been created elsewhere:
83    /// let linked_span_context = linked_span_otel_context.span().span_context().clone();
84    /// Span::current().add_link(linked_span_context);
85    /// ```
86    fn add_link(&self, cx: SpanContext);
87
88    /// Associates `self` with a given OpenTelemetry trace, using the provided
89    /// followed span [`SpanContext`] and attributes.
90    ///
91    /// [`SpanContext`]: opentelemetry::trace::SpanContext
92    fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec<KeyValue>);
93
94    /// Extracts an OpenTelemetry [`Context`] from `self`.
95    ///
96    /// [`Context`]: opentelemetry::Context
97    ///
98    /// # Examples
99    ///
100    /// ```rust
101    /// use opentelemetry::Context;
102    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
103    /// use tracing::Span;
104    ///
105    /// fn make_request(cx: Context) {
106    ///     // perform external request after injecting context
107    ///     // e.g. if the request's headers impl `opentelemetry::propagation::Injector`
108    ///     // then `propagator.inject_context(cx, request.headers_mut())`
109    /// }
110    ///
111    /// // Generate a tracing span as usual
112    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
113    ///
114    /// // To include tracing context in client requests from _this_ app,
115    /// // extract the current OpenTelemetry context.
116    /// make_request(app_root.context());
117    ///
118    /// // Or if the current span has been created elsewhere:
119    /// make_request(Span::current().context())
120    /// ```
121    fn context(&self) -> Context;
122
123    /// Sets an OpenTelemetry attribute directly for this span, bypassing `tracing`.
124    /// If fields set here conflict with `tracing` fields, the `tracing` fields will supersede fields set with `set_attribute`.
125    /// This allows for more than 32 fields.
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use opentelemetry::Context;
131    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
132    /// use tracing::Span;
133    ///
134    /// // Generate a tracing span as usual
135    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
136    ///
137    /// // Set the `http.request.header.x_forwarded_for` attribute to `example`.
138    /// app_root.set_attribute("http.request.header.x_forwarded_for", "example");
139    /// ```
140    fn set_attribute(&self, key: impl Into<Key>, value: impl Into<Value>);
141
142    /// Sets an OpenTelemetry status for this span.
143    /// This is useful for setting the status of a span that was created by a library that does not declare
144    /// the otel.status_code field of the span in advance.
145    ///
146    /// # Examples
147    ///
148    /// ```rust
149    /// use opentelemetry::trace::Status;
150    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
151    /// use tracing::Span;
152    ///
153    /// /// // Generate a tracing span as usual
154    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
155    ///
156    /// // Set the Status of the span to `Status::Ok`.
157    /// app_root.set_status(Status::Ok);
158    /// ```            
159    fn set_status(&self, status: Status);
160
161    /// Adds an OpenTelemetry event directly to this span, bypassing `tracing::event!`.
162    /// This allows for adding events with dynamic attribute keys, similar to `set_attribute` for span attributes.
163    /// Events are added with the current timestamp.
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use opentelemetry::{KeyValue};
169    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
170    /// use tracing::Span;
171    ///
172    /// let app_root = tracing::span!(tracing::Level::INFO, "processing_request");
173    ///
174    /// let dynamic_attrs = vec![
175    ///     KeyValue::new("job_id", "job-123"),
176    ///     KeyValue::new("user.id", "user-xyz"),
177    /// ];
178    ///
179    /// // Add event using the extension method
180    /// app_root.add_event("job_started".to_string(), dynamic_attrs);
181    ///
182    /// // ... perform work ...
183    ///
184    /// app_root.add_event("job_completed", vec![KeyValue::new("status", "success")]);
185    /// ```
186    fn add_event(&self, name: impl Into<Cow<'static, str>>, attributes: Vec<KeyValue>);
187
188    /// Adds an OpenTelemetry event with a specific timestamp directly to this span.
189    /// Similar to `add_event`, but allows overriding the event timestamp.
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// use opentelemetry::{KeyValue};
195    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
196    /// use tracing::Span;
197    /// use std::time::{Duration, SystemTime};
198    /// use std::borrow::Cow;
199    ///
200    /// let app_root = tracing::span!(tracing::Level::INFO, "historical_event_processing");
201    ///
202    /// let event_time = SystemTime::now() - Duration::from_secs(60);
203    /// let event_attrs = vec![KeyValue::new("record_id", "rec-456")];
204    /// let event_name: Cow<'static, str> = "event_from_past".into();
205    ///
206    /// app_root.add_event_with_timestamp(event_name, event_time, event_attrs);
207    /// ```
208    fn add_event_with_timestamp(
209        &self,
210        name: impl Into<Cow<'static, str>>,
211        timestamp: SystemTime,
212        attributes: Vec<KeyValue>,
213    );
214}
215
216impl OpenTelemetrySpanExt for tracing::Span {
217    fn set_parent(&self, cx: Context) {
218        let mut cx = Some(cx);
219        self.with_subscriber(move |(id, subscriber)| {
220            let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
221                return;
222            };
223            get_context.with_context(subscriber, id, move |data, _tracer| {
224                let Some(cx) = cx.take() else {
225                    return;
226                };
227                data.parent_cx = cx;
228                data.builder.sampling_result = None;
229            });
230        });
231    }
232
233    fn add_link(&self, cx: SpanContext) {
234        self.add_link_with_attributes(cx, Vec::new())
235    }
236
237    fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec<KeyValue>) {
238        if cx.is_valid() {
239            let mut cx = Some(cx);
240            let mut att = Some(attributes);
241            self.with_subscriber(move |(id, subscriber)| {
242                let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
243                    return;
244                };
245                get_context.with_context(subscriber, id, move |data, _tracer| {
246                    let Some(cx) = cx.take() else {
247                        return;
248                    };
249                    let attr = att.take().unwrap_or_default();
250                    let follows_link = opentelemetry::trace::Link::new(cx, attr, 0);
251                    data.builder
252                        .links
253                        .get_or_insert_with(|| Vec::with_capacity(1))
254                        .push(follows_link);
255                });
256            });
257        }
258    }
259
260    fn context(&self) -> Context {
261        let mut cx = None;
262        self.with_subscriber(|(id, subscriber)| {
263            let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
264                return;
265            };
266            get_context.with_context(subscriber, id, |builder, tracer| {
267                cx = Some(tracer.sampled_context(builder));
268            })
269        });
270
271        cx.unwrap_or_default()
272    }
273
274    fn set_attribute(&self, key: impl Into<Key>, value: impl Into<Value>) {
275        self.with_subscriber(move |(id, subscriber)| {
276            let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
277                return;
278            };
279            let mut key = Some(key.into());
280            let mut value = Some(value.into());
281            get_context.with_context(subscriber, id, move |builder, _| {
282                if builder.builder.attributes.is_none() {
283                    builder.builder.attributes = Some(Default::default());
284                }
285                builder
286                    .builder
287                    .attributes
288                    .as_mut()
289                    .unwrap()
290                    .push(KeyValue::new(key.take().unwrap(), value.take().unwrap()));
291            })
292        });
293    }
294
295    fn set_status(&self, status: Status) {
296        self.with_subscriber(move |(id, subscriber)| {
297            let mut status = Some(status);
298            let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
299                return;
300            };
301            get_context.with_context(subscriber, id, move |builder, _| {
302                builder.builder.status = status.take().unwrap();
303            });
304        });
305    }
306
307    fn add_event(&self, name: impl Into<Cow<'static, str>>, attributes: Vec<KeyValue>) {
308        self.add_event_with_timestamp(name, time::now(), attributes);
309    }
310
311    fn add_event_with_timestamp(
312        &self,
313        name: impl Into<Cow<'static, str>>,
314        timestamp: SystemTime,
315        attributes: Vec<KeyValue>,
316    ) {
317        self.with_subscriber(move |(id, subscriber)| {
318            let mut event = Some(opentelemetry::trace::Event::new(
319                name, timestamp, attributes, 0,
320            ));
321            let Some(get_context) = subscriber.downcast_ref::<WithContext>() else {
322                return;
323            };
324            get_context.with_context(subscriber, id, move |data, _tracer| {
325                let Some(event) = event.take() else {
326                    return;
327                };
328                data.builder.events.get_or_insert_with(Vec::new).push(event);
329            });
330        });
331    }
332}