1 use opentelemetry::sdk::trace::{Tracer, TracerProvider};
2 use opentelemetry::trace::OrderMap;
3 use opentelemetry::{
4 trace as otel,
5 trace::{
6 noop, SamplingDecision, SamplingResult, SpanBuilder, SpanContext, SpanId, SpanKind,
7 TraceContextExt, TraceFlags, TraceId, TraceState,
8 },
9 Context as OtelContext,
10 };
11
12 /// An interface for authors of OpenTelemetry SDKs to build pre-sampled tracers.
13 ///
14 /// The OpenTelemetry spec does not allow trace ids to be updated after a span
15 /// has been created. In order to associate extracted parent trace ids with
16 /// existing `tracing` spans, `tracing-opentelemetry` builds up otel span data
17 /// using a [`SpanBuilder`] instead, and creates / exports full otel spans only
18 /// when the associated `tracing` span is closed. However, in order to properly
19 /// inject otel [`Context`] information to downstream requests, the sampling
20 /// state must now be known _before_ the otel span has been created.
21 ///
22 /// The logic for coming to a sampling decision and creating an injectable span
23 /// context from a [`SpanBuilder`] is encapsulated in the
24 /// [`PreSampledTracer::sampled_context`] method and has been implemented
25 /// for the standard OpenTelemetry SDK, but this trait may be implemented by
26 /// authors of alternate OpenTelemetry SDK implementations if they wish to have
27 /// `tracing` compatibility.
28 ///
29 /// See the [`OpenTelemetrySpanExt::set_parent`] and
30 /// [`OpenTelemetrySpanExt::context`] methods for example usage.
31 ///
32 /// [`Tracer`]: opentelemetry::trace::Tracer
33 /// [`SpanBuilder`]: opentelemetry::trace::SpanBuilder
34 /// [`PreSampledTracer::sampled_span_context`]: crate::PreSampledTracer::sampled_span_context
35 /// [`OpenTelemetrySpanExt::set_parent`]: crate::OpenTelemetrySpanExt::set_parent
36 /// [`OpenTelemetrySpanExt::context`]: crate::OpenTelemetrySpanExt::context
37 /// [`Context`]: opentelemetry::Context
38 pub trait PreSampledTracer {
39 /// Produce an otel context containing an active and pre-sampled span for
40 /// the given span builder data.
41 ///
42 /// The sampling decision, span context information, and parent context
43 /// values must match the values recorded when the tracing span is closed.
sampled_context(&self, data: &mut crate::OtelData) -> OtelContext44 fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext;
45
46 /// Generate a new trace id.
new_trace_id(&self) -> otel::TraceId47 fn new_trace_id(&self) -> otel::TraceId;
48
49 /// Generate a new span id.
new_span_id(&self) -> otel::SpanId50 fn new_span_id(&self) -> otel::SpanId;
51 }
52
53 impl PreSampledTracer for noop::NoopTracer {
sampled_context(&self, data: &mut crate::OtelData) -> OtelContext54 fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext {
55 data.parent_cx.clone()
56 }
57
new_trace_id(&self) -> otel::TraceId58 fn new_trace_id(&self) -> otel::TraceId {
59 otel::TraceId::INVALID
60 }
61
new_span_id(&self) -> otel::SpanId62 fn new_span_id(&self) -> otel::SpanId {
63 otel::SpanId::INVALID
64 }
65 }
66
67 impl PreSampledTracer for Tracer {
sampled_context(&self, data: &mut crate::OtelData) -> OtelContext68 fn sampled_context(&self, data: &mut crate::OtelData) -> OtelContext {
69 // Ensure tracing pipeline is still installed.
70 if self.provider().is_none() {
71 return OtelContext::new();
72 }
73 let provider = self.provider().unwrap();
74 let parent_cx = &data.parent_cx;
75 let builder = &mut data.builder;
76
77 // Gather trace state
78 let (trace_id, parent_trace_flags) = current_trace_state(builder, parent_cx, &provider);
79
80 // Sample or defer to existing sampling decisions
81 let (flags, trace_state) = if let Some(result) = &builder.sampling_result {
82 process_sampling_result(result, parent_trace_flags)
83 } else {
84 builder.sampling_result = Some(provider.config().sampler.should_sample(
85 Some(parent_cx),
86 trace_id,
87 &builder.name,
88 builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal),
89 builder.attributes.as_ref().unwrap_or(&OrderMap::default()),
90 builder.links.as_deref().unwrap_or(&[]),
91 self.instrumentation_library(),
92 ));
93
94 process_sampling_result(
95 builder.sampling_result.as_ref().unwrap(),
96 parent_trace_flags,
97 )
98 }
99 .unwrap_or_default();
100
101 let span_id = builder.span_id.unwrap_or(SpanId::INVALID);
102 let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state);
103 parent_cx.with_remote_span_context(span_context)
104 }
105
new_trace_id(&self) -> otel::TraceId106 fn new_trace_id(&self) -> otel::TraceId {
107 self.provider()
108 .map(|provider| provider.config().id_generator.new_trace_id())
109 .unwrap_or(otel::TraceId::INVALID)
110 }
111
new_span_id(&self) -> otel::SpanId112 fn new_span_id(&self) -> otel::SpanId {
113 self.provider()
114 .map(|provider| provider.config().id_generator.new_span_id())
115 .unwrap_or(otel::SpanId::INVALID)
116 }
117 }
118
current_trace_state( builder: &SpanBuilder, parent_cx: &OtelContext, provider: &TracerProvider, ) -> (TraceId, TraceFlags)119 fn current_trace_state(
120 builder: &SpanBuilder,
121 parent_cx: &OtelContext,
122 provider: &TracerProvider,
123 ) -> (TraceId, TraceFlags) {
124 if parent_cx.has_active_span() {
125 let span = parent_cx.span();
126 let sc = span.span_context();
127 (sc.trace_id(), sc.trace_flags())
128 } else {
129 (
130 builder
131 .trace_id
132 .unwrap_or_else(|| provider.config().id_generator.new_trace_id()),
133 Default::default(),
134 )
135 }
136 }
137
process_sampling_result( sampling_result: &SamplingResult, trace_flags: TraceFlags, ) -> Option<(TraceFlags, TraceState)>138 fn process_sampling_result(
139 sampling_result: &SamplingResult,
140 trace_flags: TraceFlags,
141 ) -> Option<(TraceFlags, TraceState)> {
142 match sampling_result {
143 SamplingResult {
144 decision: SamplingDecision::Drop,
145 ..
146 } => None,
147 SamplingResult {
148 decision: SamplingDecision::RecordOnly,
149 trace_state,
150 ..
151 } => Some((trace_flags & !TraceFlags::SAMPLED, trace_state.clone())),
152 SamplingResult {
153 decision: SamplingDecision::RecordAndSample,
154 trace_state,
155 ..
156 } => Some((trace_flags | TraceFlags::SAMPLED, trace_state.clone())),
157 }
158 }
159
160 #[cfg(test)]
161 mod tests {
162 use super::*;
163 use crate::OtelData;
164 use opentelemetry::sdk::trace::{config, Sampler, TracerProvider};
165 use opentelemetry::trace::{SpanBuilder, SpanId, TracerProvider as _};
166
167 #[test]
assigns_default_trace_id_if_missing()168 fn assigns_default_trace_id_if_missing() {
169 let provider = TracerProvider::default();
170 let tracer = provider.tracer("test");
171 let mut builder = SpanBuilder::from_name("empty".to_string());
172 builder.span_id = Some(SpanId::from(1u64.to_be_bytes()));
173 builder.trace_id = None;
174 let parent_cx = OtelContext::new();
175 let cx = tracer.sampled_context(&mut OtelData { builder, parent_cx });
176 let span = cx.span();
177 let span_context = span.span_context();
178
179 assert!(span_context.is_valid());
180 }
181
182 #[rustfmt::skip]
sampler_data() -> Vec<(&'static str, Sampler, OtelContext, Option<SamplingResult>, bool)>183 fn sampler_data() -> Vec<(&'static str, Sampler, OtelContext, Option<SamplingResult>, bool)> {
184 vec![
185 // No parent samples
186 ("empty_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new(), None, true),
187 ("empty_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new(), None, false),
188
189 // Remote parent samples
190 ("remote_parent_cx_always_on", Sampler::AlwaysOn, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true),
191 ("remote_parent_cx_always_off", Sampler::AlwaysOff, OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, false),
192 ("sampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOff)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::SAMPLED, true)), None, true),
193 ("unsampled_remote_parent_cx_parent_based", Sampler::ParentBased(Box::new(Sampler::AlwaysOn)), OtelContext::new().with_remote_span_context(span_context(TraceFlags::default(), true)), None, false),
194
195 // Existing sampling result defers
196 ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false),
197 ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true),
198
199 // Existing local parent, defers
200 ("previous_drop_result_always_on", Sampler::AlwaysOn, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::Drop, attributes: vec![], trace_state: Default::default() }), false),
201 ("previous_record_and_sample_result_always_off", Sampler::AlwaysOff, OtelContext::new(), Some(SamplingResult { decision: SamplingDecision::RecordAndSample, attributes: vec![], trace_state: Default::default() }), true),
202 ]
203 }
204
205 #[test]
sampled_context()206 fn sampled_context() {
207 for (name, sampler, parent_cx, previous_sampling_result, is_sampled) in sampler_data() {
208 let provider = TracerProvider::builder()
209 .with_config(config().with_sampler(sampler))
210 .build();
211 let tracer = provider.tracer("test");
212 let mut builder = SpanBuilder::from_name("parent".to_string());
213 builder.sampling_result = previous_sampling_result;
214 let sampled = tracer.sampled_context(&mut OtelData { builder, parent_cx });
215
216 assert_eq!(
217 sampled.span().span_context().is_sampled(),
218 is_sampled,
219 "{}",
220 name
221 )
222 }
223 }
224
span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext225 fn span_context(trace_flags: TraceFlags, is_remote: bool) -> SpanContext {
226 SpanContext::new(
227 TraceId::from(1u128.to_be_bytes()),
228 SpanId::from(1u64.to_be_bytes()),
229 trace_flags,
230 is_remote,
231 Default::default(),
232 )
233 }
234 }
235