opentelemetry_spanprocessor_any/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::sdk::trace::SpanLimits;
11use crate::sdk::{
12    trace::{
13        provider::{TracerProvider, TracerProviderInner},
14        span::{Span, SpanData},
15        Config, EvictedHashMap, EvictedQueue, SamplingDecision, SamplingResult,
16    },
17    InstrumentationLibrary,
18};
19use crate::trace::{
20    Link, SpanBuilder, SpanContext, SpanId, SpanKind, StatusCode, TraceContextExt, TraceFlags,
21    TraceId, TraceState,
22};
23use crate::{Context, KeyValue};
24use std::borrow::Cow;
25use std::fmt;
26use std::sync::Weak;
27
28/// `Tracer` implementation to create and manage spans
29#[derive(Clone)]
30pub struct Tracer {
31    instrumentation_lib: InstrumentationLibrary,
32    provider: Weak<TracerProviderInner>,
33}
34
35impl fmt::Debug for Tracer {
36    /// Formats the `Tracer` using the given formatter.
37    /// Omitting `provider` here is necessary to avoid cycles.
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("Tracer")
40            .field("name", &self.instrumentation_lib.name)
41            .field("version", &self.instrumentation_lib.version)
42            .finish()
43    }
44}
45
46impl Tracer {
47    /// Create a new tracer (used internally by `TracerProvider`s).
48    pub(crate) fn new(
49        instrumentation_lib: InstrumentationLibrary,
50        provider: Weak<TracerProviderInner>,
51    ) -> Self {
52        Tracer {
53            instrumentation_lib,
54            provider,
55        }
56    }
57
58    /// TracerProvider associated with this tracer.
59    pub fn provider(&self) -> Option<TracerProvider> {
60        self.provider.upgrade().map(TracerProvider::new)
61    }
62
63    /// instrumentation library information of this tracer.
64    pub fn instrumentation_library(&self) -> &InstrumentationLibrary {
65        &self.instrumentation_lib
66    }
67
68    /// Make a sampling decision using the provided sampler for the span and context.
69    #[allow(clippy::too_many_arguments)]
70    fn make_sampling_decision(
71        &self,
72        parent_cx: &Context,
73        trace_id: TraceId,
74        name: &str,
75        span_kind: &SpanKind,
76        attributes: &[KeyValue],
77        links: &[Link],
78        config: &Config,
79        instrumentation_library: &InstrumentationLibrary,
80    ) -> Option<(TraceFlags, Vec<KeyValue>, TraceState)> {
81        let sampling_result = config.sampler.should_sample(
82            Some(parent_cx),
83            trace_id,
84            name,
85            span_kind,
86            attributes,
87            links,
88            instrumentation_library,
89        );
90
91        self.process_sampling_result(sampling_result, parent_cx)
92    }
93
94    fn process_sampling_result(
95        &self,
96        sampling_result: SamplingResult,
97        parent_cx: &Context,
98    ) -> Option<(TraceFlags, Vec<KeyValue>, TraceState)> {
99        match sampling_result {
100            SamplingResult {
101                decision: SamplingDecision::Drop,
102                ..
103            } => None,
104            SamplingResult {
105                decision: SamplingDecision::RecordOnly,
106                attributes,
107                trace_state,
108            } => {
109                let trace_flags = parent_cx.span().span_context().trace_flags();
110                Some((trace_flags.with_sampled(false), attributes, trace_state))
111            }
112            SamplingResult {
113                decision: SamplingDecision::RecordAndSample,
114                attributes,
115                trace_state,
116            } => {
117                let trace_flags = parent_cx.span().span_context().trace_flags();
118                Some((trace_flags.with_sampled(true), attributes, trace_state))
119            }
120        }
121    }
122}
123
124impl crate::trace::Tracer for Tracer {
125    /// This implementation of `Tracer` produces `sdk::Span` instances.
126    type Span = Span;
127
128    /// Starts a new `Span` with a given context.
129    ///
130    /// Each span has zero or one parent spans and zero or more child spans, which
131    /// represent causally related operations. A tree of related spans comprises a
132    /// trace. A span is said to be a _root span_ if it does not have a parent. Each
133    /// trace includes a single root span, which is the shared ancestor of all other
134    /// spans in the trace.
135    fn start_with_context<T>(&self, name: T, parent_cx: &Context) -> Self::Span
136    where
137        T: Into<Cow<'static, str>>,
138    {
139        self.build_with_context(SpanBuilder::from_name(name), parent_cx)
140    }
141
142    /// Creates a span builder
143    ///
144    /// An ergonomic way for attributes to be configured before the `Span` is started.
145    fn span_builder<T>(&self, name: T) -> SpanBuilder
146    where
147        T: Into<Cow<'static, str>>,
148    {
149        SpanBuilder::from_name(name)
150    }
151
152    /// Starts a span from a `SpanBuilder`.
153    ///
154    /// Each span has zero or one parent spans and zero or more child spans, which
155    /// represent causally related operations. A tree of related spans comprises a
156    /// trace. A span is said to be a _root span_ if it does not have a parent. Each
157    /// trace includes a single root span, which is the shared ancestor of all other
158    /// spans in the trace.
159    fn build_with_context(&self, mut builder: SpanBuilder, parent_cx: &Context) -> Self::Span {
160        let provider = self.provider();
161        if provider.is_none() {
162            return Span::new(
163                SpanContext::empty_context(),
164                None,
165                self.clone(),
166                SpanLimits::default(),
167            );
168        }
169
170        let provider = provider.unwrap();
171        let config = provider.config();
172        let span_limits = config.span_limits;
173        let span_id = builder
174            .span_id
175            .take()
176            .unwrap_or_else(|| config.id_generator.new_span_id());
177        let span_kind = builder.span_kind.take().unwrap_or(SpanKind::Internal);
178        let mut attribute_options = builder.attributes.take().unwrap_or_default();
179        let mut link_options = builder.links.take();
180        let mut no_parent = true;
181        let mut remote_parent = false;
182        let mut parent_span_id = SpanId::INVALID;
183        let mut parent_trace_flags = TraceFlags::default();
184        let trace_id;
185
186        let parent_span = if parent_cx.has_active_span() {
187            Some(parent_cx.span())
188        } else {
189            None
190        };
191
192        // Build context for sampling decision
193        if let Some(sc) = parent_span.as_ref().map(|parent| parent.span_context()) {
194            no_parent = false;
195            remote_parent = sc.is_remote();
196            parent_span_id = sc.span_id();
197            parent_trace_flags = sc.trace_flags();
198            trace_id = sc.trace_id();
199        } else {
200            trace_id = builder
201                .trace_id
202                .unwrap_or_else(|| config.id_generator.new_trace_id());
203        };
204
205        // There are 3 paths for sampling.
206        //
207        // * Sampling has occurred elsewhere and is already stored in the builder
208        // * There is no parent or a remote parent, in which case make decision now
209        // * There is a local parent, in which case defer to the parent's decision
210        let sampling_decision = if let Some(sampling_result) = builder.sampling_result.take() {
211            self.process_sampling_result(sampling_result, parent_cx)
212        } else if no_parent || remote_parent {
213            self.make_sampling_decision(
214                parent_cx,
215                trace_id,
216                &builder.name,
217                &span_kind,
218                &attribute_options,
219                link_options.as_deref().unwrap_or(&[]),
220                provider.config(),
221                &self.instrumentation_lib,
222            )
223        } else {
224            // has parent that is local: use parent if sampled, or don't record.
225            parent_span
226                .filter(|span| span.span_context().is_sampled())
227                .map(|span| {
228                    (
229                        parent_trace_flags,
230                        Vec::new(),
231                        span.span_context().trace_state().clone(),
232                    )
233                })
234        };
235
236        let SpanBuilder {
237            name,
238            start_time,
239            end_time,
240            events,
241            status_code,
242            status_message,
243            ..
244        } = builder;
245
246        // Build optional inner context, `None` if not recording.
247        let mut span = if let Some((flags, mut extra_attrs, trace_state)) = sampling_decision {
248            if !extra_attrs.is_empty() {
249                attribute_options.append(&mut extra_attrs);
250            }
251            let mut attributes =
252                EvictedHashMap::new(span_limits.max_attributes_per_span, attribute_options.len());
253            for attribute in attribute_options {
254                attributes.insert(attribute);
255            }
256            let mut links = EvictedQueue::new(span_limits.max_links_per_span);
257            if let Some(link_options) = &mut link_options {
258                let link_attributes_limit = span_limits.max_attributes_per_link as usize;
259                for link in link_options.iter_mut() {
260                    let dropped_attributes_count =
261                        link.attributes.len().saturating_sub(link_attributes_limit);
262                    link.attributes.truncate(link_attributes_limit);
263                    link.dropped_attributes_count = dropped_attributes_count as u32;
264                }
265                links.append_vec(link_options);
266            }
267            let start_time = start_time.unwrap_or_else(crate::time::now);
268            let end_time = end_time.unwrap_or(start_time);
269            let mut events_queue = EvictedQueue::new(span_limits.max_events_per_span);
270            if let Some(mut events) = events {
271                let event_attributes_limit = span_limits.max_attributes_per_event as usize;
272                for event in events.iter_mut() {
273                    let dropped_attributes_count = event
274                        .attributes
275                        .len()
276                        .saturating_sub(event_attributes_limit);
277                    event.attributes.truncate(event_attributes_limit);
278                    event.dropped_attributes_count = dropped_attributes_count as u32;
279                }
280                events_queue.append_vec(&mut events);
281            }
282            let status_code = status_code.unwrap_or(StatusCode::Unset);
283            let status_message = status_message.unwrap_or(Cow::Borrowed(""));
284
285            let span_context = SpanContext::new(trace_id, span_id, flags, false, trace_state);
286            Span::new(
287                span_context,
288                Some(SpanData {
289                    parent_span_id,
290                    span_kind,
291                    name,
292                    start_time,
293                    end_time,
294                    attributes,
295                    events: events_queue,
296                    links,
297                    status_code,
298                    status_message,
299                }),
300                self.clone(),
301                span_limits,
302            )
303        } else {
304            let span_context = SpanContext::new(
305                trace_id,
306                span_id,
307                TraceFlags::default(),
308                false,
309                Default::default(),
310            );
311            Span::new(span_context, None, self.clone(), span_limits)
312        };
313
314        // Call `on_start` for all processors
315        for processor in provider.span_processors() {
316            processor.on_start(&mut span, parent_cx)
317        }
318
319        span
320    }
321}
322
323#[cfg(all(test, feature = "testing", feature = "trace"))]
324mod tests {
325    use crate::{
326        sdk::{
327            self,
328            trace::{Config, Sampler, SamplingDecision, SamplingResult, ShouldSample},
329            InstrumentationLibrary,
330        },
331        testing::trace::TestSpan,
332        trace::{
333            Link, Span, SpanContext, SpanId, SpanKind, TraceContextExt, TraceFlags, TraceId,
334            TraceState, Tracer, TracerProvider,
335        },
336        Context, KeyValue,
337    };
338
339    #[derive(Debug)]
340    struct TestSampler {}
341
342    impl ShouldSample for TestSampler {
343        fn should_sample(
344            &self,
345            parent_context: Option<&Context>,
346            _trace_id: TraceId,
347            _name: &str,
348            _span_kind: &SpanKind,
349            _attributes: &[KeyValue],
350            _links: &[Link],
351            _instrumentation_library: &InstrumentationLibrary,
352        ) -> SamplingResult {
353            let trace_state = parent_context
354                .unwrap()
355                .span()
356                .span_context()
357                .trace_state()
358                .clone();
359            SamplingResult {
360                decision: SamplingDecision::RecordAndSample,
361                attributes: Vec::new(),
362                trace_state: trace_state.insert("foo", "notbar").unwrap(),
363            }
364        }
365    }
366
367    #[test]
368    fn allow_sampler_to_change_trace_state() {
369        // Setup
370        let sampler = TestSampler {};
371        let config = Config::default().with_sampler(sampler);
372        let tracer_provider = sdk::trace::TracerProvider::builder()
373            .with_config(config)
374            .build();
375        let tracer = tracer_provider.tracer("test");
376        let trace_state = TraceState::from_key_value(vec![("foo", "bar")]).unwrap();
377
378        let parent_context = Context::new().with_span(TestSpan(SpanContext::new(
379            TraceId::from_u128(128),
380            SpanId::from_u64(64),
381            TraceFlags::SAMPLED,
382            true,
383            trace_state,
384        )));
385
386        // Test sampler should change trace state
387        let span = tracer.start_with_context("foo", &parent_context);
388        let span_context = span.span_context();
389        let expected = span_context.trace_state();
390        assert_eq!(expected.get("foo"), Some("notbar"))
391    }
392
393    #[test]
394    fn drop_parent_based_children() {
395        let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
396        let config = Config::default().with_sampler(sampler);
397        let tracer_provider = sdk::trace::TracerProvider::builder()
398            .with_config(config)
399            .build();
400
401        let context = Context::current_with_span(TestSpan(SpanContext::empty_context()));
402        let tracer = tracer_provider.tracer("test");
403        let span = tracer.start_with_context("must_not_be_sampled", &context);
404
405        assert!(!span.span_context().is_sampled());
406    }
407
408    #[test]
409    fn uses_current_context_for_builders_if_unset() {
410        let sampler = Sampler::ParentBased(Box::new(Sampler::AlwaysOn));
411        let config = Config::default().with_sampler(sampler);
412        let tracer_provider = sdk::trace::TracerProvider::builder()
413            .with_config(config)
414            .build();
415        let tracer = tracer_provider.tracer("test");
416
417        let _attached = Context::current_with_span(TestSpan(SpanContext::empty_context())).attach();
418        let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
419        assert!(!span.span_context().is_sampled());
420
421        let _attached = Context::current()
422            .with_remote_span_context(SpanContext::new(
423                TraceId::from_u128(1),
424                SpanId::from_u64(1),
425                TraceFlags::default(),
426                true,
427                Default::default(),
428            ))
429            .attach();
430        let span = tracer.span_builder("must_not_be_sampled").start(&tracer);
431
432        assert!(!span.span_context().is_sampled());
433    }
434}