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}