• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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