tracing_opentelemetry/
span_ext.rs

1use crate::layer::WithContext;
2use opentelemetry::{trace::SpanContext, Context, Key, KeyValue, Value};
3
4/// Utility functions to allow tracing [`Span`]s to accept and return
5/// [OpenTelemetry] [`Context`]s.
6///
7/// [`Span`]: tracing::Span
8/// [OpenTelemetry]: https://opentelemetry.io
9/// [`Context`]: opentelemetry::Context
10pub trait OpenTelemetrySpanExt {
11    /// Associates `self` with a given OpenTelemetry trace, using the provided
12    /// parent [`Context`].
13    ///
14    /// [`Context`]: opentelemetry::Context
15    ///
16    /// # Examples
17    ///
18    /// ```rust
19    /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt};
20    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
21    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
22    /// use std::collections::HashMap;
23    /// use tracing::Span;
24    ///
25    /// // Example carrier, could be a framework header map that impls otel's `Extractor`.
26    /// let mut carrier = HashMap::new();
27    ///
28    /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc.
29    /// let propagator = TraceContextPropagator::new();
30    ///
31    /// // Extract otel parent context via the chosen propagator
32    /// let parent_context = propagator.extract(&carrier);
33    ///
34    /// // Generate a tracing span as usual
35    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
36    ///
37    /// // Assign parent trace from external context
38    /// app_root.set_parent(parent_context.clone());
39    ///
40    /// // Or if the current span has been created elsewhere:
41    /// Span::current().set_parent(parent_context);
42    /// ```
43    fn set_parent(&self, cx: Context);
44
45    /// Associates `self` with a given OpenTelemetry trace, using the provided
46    /// followed span [`SpanContext`].
47    ///
48    /// [`SpanContext`]: opentelemetry::trace::SpanContext
49    ///
50    /// # Examples
51    ///
52    /// ```rust
53    /// use opentelemetry::{propagation::TextMapPropagator, trace::TraceContextExt};
54    /// use opentelemetry_sdk::propagation::TraceContextPropagator;
55    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
56    /// use std::collections::HashMap;
57    /// use tracing::Span;
58    ///
59    /// // Example carrier, could be a framework header map that impls otel's `Extractor`.
60    /// let mut carrier = HashMap::new();
61    ///
62    /// // Propagator can be swapped with b3 propagator, jaeger propagator, etc.
63    /// let propagator = TraceContextPropagator::new();
64    ///
65    /// // Extract otel context of linked span via the chosen propagator
66    /// let linked_span_otel_context = propagator.extract(&carrier);
67    ///
68    /// // Extract the linked span context from the otel context
69    /// let linked_span_context = linked_span_otel_context.span().span_context().clone();
70    ///
71    /// // Generate a tracing span as usual
72    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
73    ///
74    /// // Assign linked trace from external context
75    /// app_root.add_link(linked_span_context);
76    ///
77    /// // Or if the current span has been created elsewhere:
78    /// let linked_span_context = linked_span_otel_context.span().span_context().clone();
79    /// Span::current().add_link(linked_span_context);
80    /// ```
81    fn add_link(&self, cx: SpanContext);
82
83    /// Associates `self` with a given OpenTelemetry trace, using the provided
84    /// followed span [`SpanContext`] and attributes.
85    ///
86    /// [`SpanContext`]: opentelemetry::trace::SpanContext
87    fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec<KeyValue>);
88
89    /// Extracts an OpenTelemetry [`Context`] from `self`.
90    ///
91    /// [`Context`]: opentelemetry::Context
92    ///
93    /// # Examples
94    ///
95    /// ```rust
96    /// use opentelemetry::Context;
97    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
98    /// use tracing::Span;
99    ///
100    /// fn make_request(cx: Context) {
101    ///     // perform external request after injecting context
102    ///     // e.g. if the request's headers impl `opentelemetry::propagation::Injector`
103    ///     // then `propagator.inject_context(cx, request.headers_mut())`
104    /// }
105    ///
106    /// // Generate a tracing span as usual
107    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
108    ///
109    /// // To include tracing context in client requests from _this_ app,
110    /// // extract the current OpenTelemetry context.
111    /// make_request(app_root.context());
112    ///
113    /// // Or if the current span has been created elsewhere:
114    /// make_request(Span::current().context())
115    /// ```
116    fn context(&self) -> Context;
117
118    /// Sets an OpenTelemetry attribute directly for this span, bypassing `tracing`.
119    /// If fields set here conflict with `tracing` fields, the `tracing` fields will supersede fields set with `set_attribute`.
120    /// This allows for more than 32 fields.
121    ///
122    /// # Examples
123    ///
124    /// ```rust
125    /// use opentelemetry::Context;
126    /// use tracing_opentelemetry::OpenTelemetrySpanExt;
127    /// use tracing::Span;
128    ///
129    /// // Generate a tracing span as usual
130    /// let app_root = tracing::span!(tracing::Level::INFO, "app_start");
131    ///
132    /// // Set the `http.request.header.x_forwarded_for` attribute to `example`.
133    /// app_root.set_attribute("http.request.header.x_forwarded_for", "example");
134    /// ```
135    fn set_attribute(&self, key: impl Into<Key>, value: impl Into<Value>);
136}
137
138impl OpenTelemetrySpanExt for tracing::Span {
139    fn set_parent(&self, cx: Context) {
140        let mut cx = Some(cx);
141        self.with_subscriber(move |(id, subscriber)| {
142            if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
143                get_context.with_context(subscriber, id, move |data, _tracer| {
144                    if let Some(cx) = cx.take() {
145                        data.parent_cx = cx;
146                        data.builder.sampling_result = None;
147                    }
148                });
149            }
150        });
151    }
152
153    fn add_link(&self, cx: SpanContext) {
154        self.add_link_with_attributes(cx, Vec::new())
155    }
156
157    fn add_link_with_attributes(&self, cx: SpanContext, attributes: Vec<KeyValue>) {
158        if cx.is_valid() {
159            let mut cx = Some(cx);
160            let mut att = Some(attributes);
161            self.with_subscriber(move |(id, subscriber)| {
162                if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
163                    get_context.with_context(subscriber, id, move |data, _tracer| {
164                        if let Some(cx) = cx.take() {
165                            let attr = att.take().unwrap_or_default();
166                            let follows_link = opentelemetry::trace::Link::new(cx, attr, 0);
167                            data.builder
168                                .links
169                                .get_or_insert_with(|| Vec::with_capacity(1))
170                                .push(follows_link);
171                        }
172                    });
173                }
174            });
175        }
176    }
177
178    fn context(&self) -> Context {
179        let mut cx = None;
180        self.with_subscriber(|(id, subscriber)| {
181            if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
182                get_context.with_context(subscriber, id, |builder, tracer| {
183                    cx = Some(tracer.sampled_context(builder));
184                })
185            }
186        });
187
188        cx.unwrap_or_default()
189    }
190
191    fn set_attribute(&self, key: impl Into<Key>, value: impl Into<Value>) {
192        self.with_subscriber(move |(id, subscriber)| {
193            if let Some(get_context) = subscriber.downcast_ref::<WithContext>() {
194                let mut key = Some(key.into());
195                let mut value = Some(value.into());
196                get_context.with_context(subscriber, id, move |builder, _| {
197                    if builder.builder.attributes.is_none() {
198                        builder.builder.attributes = Some(Default::default());
199                    }
200                    builder
201                        .builder
202                        .attributes
203                        .as_mut()
204                        .unwrap()
205                        .push(KeyValue::new(key.take().unwrap(), value.take().unwrap()));
206                })
207            }
208        });
209    }
210}