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}