opentelemetry_sdk/trace/
tracer.rs

1//! # Tracer
2//!
3//! The OpenTelemetry library achieves in-process context propagation of
4//! `Span`s by way of the `Tracer`.
5//!
6//! The `Tracer` is responsible for tracking the currently active `Span`,
7//! and exposes methods for creating and activating new `Spans`.
8//!
9//! Docs: <https://github.com/open-telemetry/opentelemetry-specification/blob/v1.3.0/specification/trace/api.md#tracer>
10use crate::trace::{
11    provider::SdkTracerProvider,
12    span::{Span, SpanData},
13    IdGenerator, ShouldSample, SpanEvents, SpanLimits, SpanLinks,
14};
15use opentelemetry::{
16    trace::{SamplingDecision, SpanBuilder, SpanContext, SpanKind, TraceContextExt, TraceFlags},
17    Context, InstrumentationScope, KeyValue,
18};
19use std::fmt;
20
21/// `Tracer` implementation to create and manage spans
22#[derive(Clone)]
23pub struct SdkTracer {
24    scope: InstrumentationScope,
25    provider: SdkTracerProvider,
26}
27
28impl fmt::Debug for SdkTracer {
29    /// Formats the `Tracer` using the given formatter.
30    /// Omitting `provider` here is necessary to avoid cycles.
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("Tracer")
33            .field("name", &self.scope.name())
34            .field("version", &self.scope.version())
35            .finish()
36    }
37}
38
39impl SdkTracer {
40    /// Create a new tracer (used internally by `TracerProvider`s).
41    pub(crate) fn new(scope: InstrumentationScope, provider: SdkTracerProvider) -> Self {
42        SdkTracer { scope, provider }
43    }
44
45    /// TracerProvider associated with this tracer.
46    pub(crate) fn provider(&self) -> &SdkTracerProvider {
47        &self.provider
48    }
49
50    /// Instrumentation scope of this tracer.
51    pub(crate) fn instrumentation_scope(&self) -> &InstrumentationScope {
52        &self.scope
53    }
54
55    fn build_recording_span(
56        &self,
57        psc: &SpanContext,
58        sc: SpanContext,
59        mut builder: SpanBuilder,
60        attrs: Vec<KeyValue>,
61        span_limits: SpanLimits,
62    ) -> Span {
63        let mut attribute_options = builder.attributes.take().unwrap_or_default();
64        for extra_attr in attrs {
65            attribute_options.push(extra_attr);
66        }
67        let span_attributes_limit = span_limits.max_attributes_per_span as usize;
68        let dropped_attributes_count = attribute_options
69            .len()
70            .saturating_sub(span_attributes_limit);
71        attribute_options.truncate(span_attributes_limit);
72        let dropped_attributes_count = dropped_attributes_count as u32;
73
74        // Links are available as Option<Vec<Link>> in the builder
75        // If it is None, then there are no links to process.
76        // In that case Span.Links will be default (empty Vec<Link>, 0 drop count)
77        // Otherwise, truncate Vec<Link> to keep until limits and use that in Span.Links.
78        // Store the count of excess links into Span.Links.dropped_count.
79        // There is no ability today to add Links after Span creation,
80        // but such a capability will be needed in the future
81        // once the spec for that stabilizes.
82
83        let spans_links_limit = span_limits.max_links_per_span as usize;
84        let span_links: SpanLinks = if let Some(mut links) = builder.links.take() {
85            let dropped_count = links.len().saturating_sub(spans_links_limit);
86            links.truncate(spans_links_limit);
87            let link_attributes_limit = span_limits.max_attributes_per_link as usize;
88            for link in links.iter_mut() {
89                let dropped_attributes_count =
90                    link.attributes.len().saturating_sub(link_attributes_limit);
91                link.attributes.truncate(link_attributes_limit);
92                link.dropped_attributes_count = dropped_attributes_count as u32;
93            }
94            SpanLinks {
95                links,
96                dropped_count: dropped_count as u32,
97            }
98        } else {
99            SpanLinks::default()
100        };
101
102        let SpanBuilder {
103            name,
104            start_time,
105            end_time,
106            events,
107            status,
108            ..
109        } = builder;
110
111        let start_time = start_time.unwrap_or_else(opentelemetry::time::now);
112        let end_time = end_time.unwrap_or(start_time);
113        let spans_events_limit = span_limits.max_events_per_span as usize;
114        let span_events: SpanEvents = if let Some(mut events) = events {
115            let dropped_count = events.len().saturating_sub(spans_events_limit);
116            events.truncate(spans_events_limit);
117            let event_attributes_limit = span_limits.max_attributes_per_event as usize;
118            for event in events.iter_mut() {
119                let dropped_attributes_count = event
120                    .attributes
121                    .len()
122                    .saturating_sub(event_attributes_limit);
123                event.attributes.truncate(event_attributes_limit);
124                event.dropped_attributes_count = dropped_attributes_count as u32;
125            }
126            SpanEvents {
127                events,
128                dropped_count: dropped_count as u32,
129            }
130        } else {
131            SpanEvents::default()
132        };
133        Span::new(
134            sc,
135            Some(SpanData {
136                parent_span_id: psc.span_id(),
137                span_kind: builder.span_kind.take().unwrap_or(SpanKind::Internal),
138                name,
139                start_time,
140                end_time,
141                attributes: attribute_options,
142                dropped_attributes_count,
143                events: span_events,
144                links: span_links,
145                status,
146            }),
147            self.clone(),
148            span_limits,
149        )
150    }
151
152    /// The [`IdGenerator`] associated with this tracer.
153    ///
154    // Note: this is necessary for tracing-opentelemetry's `PreSampledTracer`.
155    #[doc(hidden)]
156    pub fn id_generator(&self) -> &dyn IdGenerator {
157        &*self.provider.config().id_generator
158    }
159
160    /// The [`ShouldSample`] associated with this tracer.
161    ///
162    // Note: this is necessary for tracing-opentelemetry's `PreSampledTracer`.
163    #[doc(hidden)]
164    pub fn should_sample(&self) -> &dyn ShouldSample {
165        &*self.provider.config().sampler
166    }
167}
168
169impl opentelemetry::trace::Tracer for SdkTracer {
170    /// This implementation of `Tracer` produces `sdk::Span` instances.
171    type Span = Span;
172
173    /// Starts a span from a `SpanBuilder`.
174    ///
175    /// Each span has zero or one parent spans and zero or more child spans, which
176    /// represent causally related operations. A tree of related spans comprises a
177    /// trace. A span is said to be a _root span_ if it does not have a parent. Each
178    /// trace includes a single root span, which is the shared ancestor of all other
179    /// spans in the trace.
180    fn build_with_context(&self, mut builder: SpanBuilder, parent_cx: &Context) -> Self::Span {
181        let provider = self.provider();
182        // no point start a span if the tracer provider has already being shutdown
183        if provider.is_shutdown() {
184            return Span::new(
185                SpanContext::empty_context(),
186                None,
187                self.clone(),
188                SpanLimits::default(),
189            );
190        }
191
192        let config = provider.config();
193        let span_id = builder
194            .span_id
195            .take()
196            .unwrap_or_else(|| config.id_generator.new_span_id());
197        let trace_id;
198        let mut psc = &SpanContext::empty_context();
199
200        let parent_span = if parent_cx.has_active_span() {
201            Some(parent_cx.span())
202        } else {
203            None
204        };
205
206        // Build context for sampling decision
207        if let Some(sc) = parent_span.as_ref().map(|parent| parent.span_context()) {
208            trace_id = sc.trace_id();
209            psc = sc;
210        } else {
211            trace_id = builder
212                .trace_id
213                .unwrap_or_else(|| config.id_generator.new_trace_id());
214        };
215
216        // In order to accommodate use cases like `tracing-opentelemetry` we there is the ability
217        // to use pre-sampling. Otherwise, the standard method of sampling is followed.
218        let samplings_result = if let Some(sr) = builder.sampling_result.take() {
219            sr
220        } else {
221            config.sampler.should_sample(
222                Some(parent_cx),
223                trace_id,
224                &builder.name,
225                builder.span_kind.as_ref().unwrap_or(&SpanKind::Internal),
226                builder.attributes.as_ref().unwrap_or(&Vec::new()),
227                builder.links.as_deref().unwrap_or(&[]),
228            )
229        };
230
231        let trace_flags = parent_cx.span().span_context().trace_flags();
232        let trace_state = samplings_result.trace_state;
233        let span_limits = config.span_limits;
234        // Build optional inner context, `None` if not recording.
235        let mut span = match samplings_result.decision {
236            SamplingDecision::RecordAndSample => {
237                let sc = SpanContext::new(
238                    trace_id,
239                    span_id,
240                    trace_flags.with_sampled(true),
241                    false,
242                    trace_state,
243                );
244                self.build_recording_span(
245                    psc,
246                    sc,
247                    builder,
248                    samplings_result.attributes,
249                    span_limits,
250                )
251            }
252            SamplingDecision::RecordOnly => {
253                let sc = SpanContext::new(
254                    trace_id,
255                    span_id,
256                    trace_flags.with_sampled(false),
257                    false,
258                    trace_state,
259                );
260                self.build_recording_span(
261                    psc,
262                    sc,
263                    builder,
264                    samplings_result.attributes,
265                    span_limits,
266                )
267            }
268            SamplingDecision::Drop => {
269                let span_context =
270                    SpanContext::new(trace_id, span_id, TraceFlags::default(), false, trace_state);
271                Span::new(span_context, None, self.clone(), span_limits)
272            }
273        };
274
275        // Call `on_start` for all processors
276        for processor in provider.span_processors() {
277            processor.on_start(&mut span, parent_cx)
278        }
279
280        span
281    }
282}
283
284#[cfg(all(test, feature = "testing", feature = "trace"))]
285mod tests {
286    use crate::{
287        testing::trace::TestSpan,
288        trace::{Sampler, ShouldSample},
289    };
290    use opentelemetry::{
291        trace::{
292            Link, SamplingDecision, SamplingResult, Span, SpanContext, SpanId, SpanKind,
293            TraceContextExt, TraceFlags, TraceId, TraceState, Tracer, TracerProvider,
294        },
295        Context, KeyValue,
296    };
297
298    #[derive(Clone, Debug)]
299    struct TestSampler {}
300
301    impl ShouldSample for TestSampler {
302        fn should_sample(
303            &self,
304            parent_context: Option<&Context>,
305            _trace_id: TraceId,
306            _name: &str,
307            _span_kind: &SpanKind,
308            _attributes: &[KeyValue],
309            _links: &[Link],
310        ) -> SamplingResult {
311            let trace_state = parent_context
312                .unwrap()
313                .span()
314                .span_context()
315                .trace_state()
316                .clone();
317            SamplingResult {
318                decision: SamplingDecision::RecordAndSample,
319                attributes: Vec::new(),
320                trace_state: trace_state.insert("foo", "notbar").unwrap(),
321            }
322        }
323    }
324
325    #[test]
326    fn allow_sampler_to_change_trace_state() {
327        // Setup
328        let sampler = TestSampler {};
329        let tracer_provider = crate::trace::SdkTracerProvider::builder()
330            .with_sampler(sampler)
331            .build();
332        let tracer = tracer_provider.tracer("test");
333        let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
334
335        let parent_context = Context::new().with_span(TestSpan(SpanContext::new(
336            TraceId::from_u128(128),
337            SpanId::from_u64(64),
338            TraceFlags::SAMPLED,
339            true,
340            trace_state,
341        )));
342
343        // Test sampler should change trace state
344        let span = tracer.start_with_context("foo", &parent_context);
345        let span_context = span.span_context();
346        let expected = span_context.trace_state();
347        assert_eq!(expected.get("foo"), Some("notbar"))
348    }
349
350    #[test]
351    fn drop_parent_based_children() {
352        let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
353        let tracer_provider = crate::trace::SdkTracerProvider::builder()
354            .with_sampler(sampler)
355            .build();
356
357        let context = Context::current_with_span(TestSpan(SpanContext::empty_context()));
358        let tracer = tracer_provider.tracer("test");
359        let span = tracer.start_with_context("must_not_be_sampled", &context);
360
361        assert!(!span.span_context().is_sampled());
362    }
363
364    #[test]
365    fn uses_current_context_for_builders_if_unset() {
366        let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
367        let tracer_provider = crate::trace::SdkTracerProvider::builder()
368            .with_sampler(sampler)
369            .build();
370        let tracer = tracer_provider.tracer("test");
371
372        let _attached = Context::current_with_span(TestSpan(SpanContext::empty_context())).attach();
373        let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
374        assert!(!span.span_context().is_sampled());
375
376        let context = Context::map_current(|cx| {
377            cx.with_remote_span_context(SpanContext::new(
378                TraceId::from_u128(1),
379                SpanId::from_u64(1),
380                TraceFlags::default(),
381                true,
382                Default::default(),
383            ))
384        });
385        let _attached = context.attach();
386        let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
387
388        assert!(!span.span_context().is_sampled());
389    }
390}