tracing_opentelemetry/
layer.rs

1use crate::{OtelData, PreSampledTracer};
2use once_cell::unsync;
3use opentelemetry::{
4    trace::{self as otel, noop, SpanBuilder, SpanKind, Status, TraceContextExt},
5    Context as OtelContext, Key, KeyValue, StringValue, Value,
6};
7use std::fmt;
8use std::marker;
9use std::thread;
10#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))]
11use std::time::Instant;
12use std::{any::TypeId, borrow::Cow};
13use tracing_core::span::{self, Attributes, Id, Record};
14use tracing_core::{field, Event, Subscriber};
15#[cfg(feature = "tracing-log")]
16use tracing_log::NormalizeEvent;
17use tracing_subscriber::layer::Context;
18use tracing_subscriber::registry::LookupSpan;
19use tracing_subscriber::Layer;
20#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
21use web_time::Instant;
22
23const SPAN_NAME_FIELD: &str = "otel.name";
24const SPAN_KIND_FIELD: &str = "otel.kind";
25const SPAN_STATUS_CODE_FIELD: &str = "otel.status_code";
26const SPAN_STATUS_MESSAGE_FIELD: &str = "otel.status_message";
27
28const EVENT_EXCEPTION_NAME: &str = "exception";
29const FIELD_EXCEPTION_MESSAGE: &str = "exception.message";
30const FIELD_EXCEPTION_STACKTRACE: &str = "exception.stacktrace";
31
32/// An [OpenTelemetry] propagation layer for use in a project that uses
33/// [tracing].
34///
35/// [OpenTelemetry]: https://opentelemetry.io
36/// [tracing]: https://github.com/tokio-rs/tracing
37pub struct OpenTelemetryLayer<S, T> {
38    tracer: T,
39    location: bool,
40    tracked_inactivity: bool,
41    with_threads: bool,
42    with_level: bool,
43    sem_conv_config: SemConvConfig,
44    get_context: WithContext,
45    _registry: marker::PhantomData<S>,
46}
47
48impl<S> Default for OpenTelemetryLayer<S, noop::NoopTracer>
49where
50    S: Subscriber + for<'span> LookupSpan<'span>,
51{
52    fn default() -> Self {
53        OpenTelemetryLayer::new(noop::NoopTracer::new())
54    }
55}
56
57/// Construct a layer to track spans via [OpenTelemetry].
58///
59/// [OpenTelemetry]: https://opentelemetry.io
60///
61/// # Examples
62///
63/// ```rust,no_run
64/// use tracing_subscriber::layer::SubscriberExt;
65/// use tracing_subscriber::Registry;
66///
67/// // Use the tracing subscriber `Registry`, or any other subscriber
68/// // that impls `LookupSpan`
69/// let subscriber = Registry::default().with(tracing_opentelemetry::layer());
70/// # drop(subscriber);
71/// ```
72pub fn layer<S>() -> OpenTelemetryLayer<S, noop::NoopTracer>
73where
74    S: Subscriber + for<'span> LookupSpan<'span>,
75{
76    OpenTelemetryLayer::default()
77}
78
79// this function "remembers" the types of the subscriber so that we
80// can downcast to something aware of them without knowing those
81// types at the callsite.
82//
83// See https://github.com/tokio-rs/tracing/blob/4dad420ee1d4607bad79270c1520673fa6266a3d/tracing-error/src/layer.rs
84pub(crate) struct WithContext(
85    #[allow(clippy::type_complexity)]
86    fn(&tracing::Dispatch, &span::Id, f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer)),
87);
88
89impl WithContext {
90    // This function allows a function to be called in the context of the
91    // "remembered" subscriber.
92    pub(crate) fn with_context(
93        &self,
94        dispatch: &tracing::Dispatch,
95        id: &span::Id,
96        mut f: impl FnMut(&mut OtelData, &dyn PreSampledTracer),
97    ) {
98        (self.0)(dispatch, id, &mut f)
99    }
100}
101
102fn str_to_span_kind(s: &str) -> Option<otel::SpanKind> {
103    match s {
104        s if s.eq_ignore_ascii_case("server") => Some(otel::SpanKind::Server),
105        s if s.eq_ignore_ascii_case("client") => Some(otel::SpanKind::Client),
106        s if s.eq_ignore_ascii_case("producer") => Some(otel::SpanKind::Producer),
107        s if s.eq_ignore_ascii_case("consumer") => Some(otel::SpanKind::Consumer),
108        s if s.eq_ignore_ascii_case("internal") => Some(otel::SpanKind::Internal),
109        _ => None,
110    }
111}
112
113fn str_to_status(s: &str) -> otel::Status {
114    match s {
115        s if s.eq_ignore_ascii_case("ok") => otel::Status::Ok,
116        s if s.eq_ignore_ascii_case("error") => otel::Status::error(""),
117        _ => otel::Status::Unset,
118    }
119}
120
121#[derive(Default)]
122struct SpanBuilderUpdates {
123    name: Option<Cow<'static, str>>,
124    span_kind: Option<SpanKind>,
125    status: Option<Status>,
126    attributes: Option<Vec<KeyValue>>,
127}
128
129impl SpanBuilderUpdates {
130    fn update(self, span_builder: &mut SpanBuilder) {
131        let Self {
132            name,
133            span_kind,
134            status,
135            attributes,
136        } = self;
137
138        if let Some(name) = name {
139            span_builder.name = name;
140        }
141        if let Some(span_kind) = span_kind {
142            span_builder.span_kind = Some(span_kind);
143        }
144        if let Some(status) = status {
145            span_builder.status = status;
146        }
147        if let Some(attributes) = attributes {
148            if let Some(builder_attributes) = &mut span_builder.attributes {
149                builder_attributes.extend(attributes);
150            } else {
151                span_builder.attributes = Some(attributes);
152            }
153        }
154    }
155}
156
157struct SpanEventVisitor<'a, 'b> {
158    event_builder: &'a mut otel::Event,
159    span_builder_updates: &'b mut Option<SpanBuilderUpdates>,
160    sem_conv_config: SemConvConfig,
161}
162
163impl field::Visit for SpanEventVisitor<'_, '_> {
164    /// Record events on the underlying OpenTelemetry [`Span`] from `bool` values.
165    ///
166    /// [`Span`]: opentelemetry::trace::Span
167    fn record_bool(&mut self, field: &field::Field, value: bool) {
168        match field.name() {
169            "message" => self.event_builder.name = value.to_string().into(),
170            // Skip fields that are actually log metadata that have already been handled
171            #[cfg(feature = "tracing-log")]
172            name if name.starts_with("log.") => (),
173            name => {
174                self.event_builder
175                    .attributes
176                    .push(KeyValue::new(name, value));
177            }
178        }
179    }
180
181    /// Record events on the underlying OpenTelemetry [`Span`] from `f64` values.
182    ///
183    /// [`Span`]: opentelemetry::trace::Span
184    fn record_f64(&mut self, field: &field::Field, value: f64) {
185        match field.name() {
186            "message" => self.event_builder.name = value.to_string().into(),
187            // Skip fields that are actually log metadata that have already been handled
188            #[cfg(feature = "tracing-log")]
189            name if name.starts_with("log.") => (),
190            name => {
191                self.event_builder
192                    .attributes
193                    .push(KeyValue::new(name, value));
194            }
195        }
196    }
197
198    /// Record events on the underlying OpenTelemetry [`Span`] from `i64` values.
199    ///
200    /// [`Span`]: opentelemetry::trace::Span
201    fn record_i64(&mut self, field: &field::Field, value: i64) {
202        match field.name() {
203            "message" => self.event_builder.name = value.to_string().into(),
204            // Skip fields that are actually log metadata that have already been handled
205            #[cfg(feature = "tracing-log")]
206            name if name.starts_with("log.") => (),
207            name => {
208                self.event_builder
209                    .attributes
210                    .push(KeyValue::new(name, value));
211            }
212        }
213    }
214
215    /// Record events on the underlying OpenTelemetry [`Span`] from `&str` values.
216    ///
217    /// [`Span`]: opentelemetry::trace::Span
218    fn record_str(&mut self, field: &field::Field, value: &str) {
219        match field.name() {
220            "message" => self.event_builder.name = value.to_string().into(),
221            // While tracing supports the error primitive, the instrumentation macro does not
222            // use the primitive and instead uses the debug or display primitive.
223            // In both cases, an event with an empty name and with an error attribute is created.
224            "error" if self.event_builder.name.is_empty() => {
225                if self.sem_conv_config.error_events_to_status {
226                    self.span_builder_updates
227                        .get_or_insert_with(SpanBuilderUpdates::default)
228                        .status
229                        .replace(otel::Status::error(format!("{:?}", value)));
230                }
231                if self.sem_conv_config.error_events_to_exceptions {
232                    self.event_builder.name = EVENT_EXCEPTION_NAME.into();
233                    self.event_builder.attributes.push(KeyValue::new(
234                        FIELD_EXCEPTION_MESSAGE,
235                        format!("{:?}", value),
236                    ));
237                } else {
238                    self.event_builder
239                        .attributes
240                        .push(KeyValue::new("error", format!("{:?}", value)));
241                }
242            }
243            // Skip fields that are actually log metadata that have already been handled
244            #[cfg(feature = "tracing-log")]
245            name if name.starts_with("log.") => (),
246            name => {
247                self.event_builder
248                    .attributes
249                    .push(KeyValue::new(name, value.to_string()));
250            }
251        }
252    }
253
254    /// Record events on the underlying OpenTelemetry [`Span`] from values that
255    /// implement Debug.
256    ///
257    /// [`Span`]: opentelemetry::trace::Span
258    fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) {
259        match field.name() {
260            "message" => self.event_builder.name = format!("{:?}", value).into(),
261            // While tracing supports the error primitive, the instrumentation macro does not
262            // use the primitive and instead uses the debug or display primitive.
263            // In both cases, an event with an empty name and with an error attribute is created.
264            "error" if self.event_builder.name.is_empty() => {
265                if self.sem_conv_config.error_events_to_status {
266                    self.span_builder_updates
267                        .get_or_insert_with(SpanBuilderUpdates::default)
268                        .status
269                        .replace(otel::Status::error(format!("{:?}", value)));
270                }
271                if self.sem_conv_config.error_events_to_exceptions {
272                    self.event_builder.name = EVENT_EXCEPTION_NAME.into();
273                    self.event_builder.attributes.push(KeyValue::new(
274                        FIELD_EXCEPTION_MESSAGE,
275                        format!("{:?}", value),
276                    ));
277                } else {
278                    self.event_builder
279                        .attributes
280                        .push(KeyValue::new("error", format!("{:?}", value)));
281                }
282            }
283            // Skip fields that are actually log metadata that have already been handled
284            #[cfg(feature = "tracing-log")]
285            name if name.starts_with("log.") => (),
286            name => {
287                self.event_builder
288                    .attributes
289                    .push(KeyValue::new(name, format!("{:?}", value)));
290            }
291        }
292    }
293
294    /// Set attributes on the underlying OpenTelemetry [`Span`] using a [`std::error::Error`]'s
295    /// [`std::fmt::Display`] implementation. Also adds the `source` chain as an extra field
296    ///
297    /// [`Span`]: opentelemetry::trace::Span
298    fn record_error(
299        &mut self,
300        field: &tracing_core::Field,
301        value: &(dyn std::error::Error + 'static),
302    ) {
303        let mut chain: Vec<StringValue> = Vec::new();
304        let mut next_err = value.source();
305
306        while let Some(err) = next_err {
307            chain.push(err.to_string().into());
308            next_err = err.source();
309        }
310
311        let error_msg = value.to_string();
312
313        if self.sem_conv_config.error_fields_to_exceptions {
314            self.event_builder.attributes.push(KeyValue::new(
315                Key::new(FIELD_EXCEPTION_MESSAGE),
316                Value::String(StringValue::from(error_msg.clone())),
317            ));
318
319            // NOTE: This is actually not the stacktrace of the exception. This is
320            // the "source chain". It represents the heirarchy of errors from the
321            // app level to the lowest level such as IO. It does not represent all
322            // of the callsites in the code that led to the error happening.
323            // `std::error::Error::backtrace` is a nightly-only API and cannot be
324            // used here until the feature is stabilized.
325            self.event_builder.attributes.push(KeyValue::new(
326                Key::new(FIELD_EXCEPTION_STACKTRACE),
327                Value::Array(chain.clone().into()),
328            ));
329        }
330
331        if self.sem_conv_config.error_records_to_exceptions {
332            let attributes = self
333                .span_builder_updates
334                .get_or_insert_with(SpanBuilderUpdates::default)
335                .attributes
336                .get_or_insert_with(Vec::new);
337
338            attributes.push(KeyValue::new(
339                FIELD_EXCEPTION_MESSAGE,
340                Value::String(error_msg.clone().into()),
341            ));
342
343            // NOTE: This is actually not the stacktrace of the exception. This is
344            // the "source chain". It represents the heirarchy of errors from the
345            // app level to the lowest level such as IO. It does not represent all
346            // of the callsites in the code that led to the error happening.
347            // `std::error::Error::backtrace` is a nightly-only API and cannot be
348            // used here until the feature is stabilized.
349            attributes.push(KeyValue::new(
350                FIELD_EXCEPTION_STACKTRACE,
351                Value::Array(chain.clone().into()),
352            ));
353        }
354
355        self.event_builder.attributes.push(KeyValue::new(
356            Key::new(field.name()),
357            Value::String(StringValue::from(error_msg)),
358        ));
359        self.event_builder.attributes.push(KeyValue::new(
360            Key::new(format!("{}.chain", field.name())),
361            Value::Array(chain.into()),
362        ));
363    }
364}
365
366/// Control over the mapping between tracing fields/events and OpenTelemetry conventional status/exception fields
367#[derive(Clone, Copy)]
368struct SemConvConfig {
369    /// If an error value is recorded on an event/span, should the otel fields
370    /// be added
371    ///
372    /// Note that this uses tracings `record_error` which is only implemented for `(dyn Error + 'static)`.
373    error_fields_to_exceptions: bool,
374
375    /// If an error value is recorded on an event, should the otel fields be
376    /// added to the corresponding span
377    ///
378    /// Note that this uses tracings `record_error` which is only implemented for `(dyn Error + 'static)`.
379    error_records_to_exceptions: bool,
380
381    /// If a function is instrumented and returns a `Result`, should the error
382    /// value be propagated to the span status.
383    ///
384    /// Without this enabled, the span status will be "Error" with an empty description
385    /// when at least one error event is recorded in the span.
386    ///
387    /// Note: the instrument macro will emit an error event if the function returns the `Err` variant.
388    /// This is not affected by this setting. Disabling this will only affect the span status.
389    error_events_to_status: bool,
390
391    /// If an event with an empty name and a field named `error` is recorded,
392    /// should the event be rewritten to have the name `exception` and the field `exception.message`
393    ///
394    /// Follows the semantic conventions for exceptions.
395    ///
396    /// Note: the instrument macro will emit an error event if the function returns the `Err` variant.
397    /// This is not affected by this setting. Disabling this will only affect the created fields on the OTel span.
398    error_events_to_exceptions: bool,
399}
400
401struct SpanAttributeVisitor<'a> {
402    span_builder_updates: &'a mut SpanBuilderUpdates,
403    sem_conv_config: SemConvConfig,
404}
405
406impl SpanAttributeVisitor<'_> {
407    fn record(&mut self, attribute: KeyValue) {
408        self.span_builder_updates
409            .attributes
410            .get_or_insert_with(Vec::new)
411            .push(KeyValue::new(attribute.key, attribute.value));
412    }
413}
414
415impl field::Visit for SpanAttributeVisitor<'_> {
416    /// Set attributes on the underlying OpenTelemetry [`Span`] from `bool` values.
417    ///
418    /// [`Span`]: opentelemetry::trace::Span
419    fn record_bool(&mut self, field: &field::Field, value: bool) {
420        self.record(KeyValue::new(field.name(), value));
421    }
422
423    /// Set attributes on the underlying OpenTelemetry [`Span`] from `f64` values.
424    ///
425    /// [`Span`]: opentelemetry::trace::Span
426    fn record_f64(&mut self, field: &field::Field, value: f64) {
427        self.record(KeyValue::new(field.name(), value));
428    }
429
430    /// Set attributes on the underlying OpenTelemetry [`Span`] from `i64` values.
431    ///
432    /// [`Span`]: opentelemetry::trace::Span
433    fn record_i64(&mut self, field: &field::Field, value: i64) {
434        self.record(KeyValue::new(field.name(), value));
435    }
436
437    /// Set attributes on the underlying OpenTelemetry [`Span`] from `&str` values.
438    ///
439    /// [`Span`]: opentelemetry::trace::Span
440    fn record_str(&mut self, field: &field::Field, value: &str) {
441        match field.name() {
442            SPAN_NAME_FIELD => self.span_builder_updates.name = Some(value.to_string().into()),
443            SPAN_KIND_FIELD => self.span_builder_updates.span_kind = str_to_span_kind(value),
444            SPAN_STATUS_CODE_FIELD => self.span_builder_updates.status = Some(str_to_status(value)),
445            SPAN_STATUS_MESSAGE_FIELD => {
446                self.span_builder_updates.status = Some(otel::Status::error(value.to_string()))
447            }
448            _ => self.record(KeyValue::new(field.name(), value.to_string())),
449        }
450    }
451
452    /// Set attributes on the underlying OpenTelemetry [`Span`] from values that
453    /// implement Debug.
454    ///
455    /// [`Span`]: opentelemetry::trace::Span
456    fn record_debug(&mut self, field: &field::Field, value: &dyn fmt::Debug) {
457        match field.name() {
458            SPAN_NAME_FIELD => self.span_builder_updates.name = Some(format!("{:?}", value).into()),
459            SPAN_KIND_FIELD => {
460                self.span_builder_updates.span_kind = str_to_span_kind(&format!("{:?}", value))
461            }
462            SPAN_STATUS_CODE_FIELD => {
463                self.span_builder_updates.status = Some(str_to_status(&format!("{:?}", value)))
464            }
465            SPAN_STATUS_MESSAGE_FIELD => {
466                self.span_builder_updates.status = Some(otel::Status::error(format!("{:?}", value)))
467            }
468            _ => self.record(KeyValue::new(
469                Key::new(field.name()),
470                Value::String(format!("{:?}", value).into()),
471            )),
472        }
473    }
474
475    /// Set attributes on the underlying OpenTelemetry [`Span`] using a [`std::error::Error`]'s
476    /// [`std::fmt::Display`] implementation. Also adds the `source` chain as an extra field
477    ///
478    /// [`Span`]: opentelemetry::trace::Span
479    fn record_error(
480        &mut self,
481        field: &tracing_core::Field,
482        value: &(dyn std::error::Error + 'static),
483    ) {
484        let mut chain: Vec<StringValue> = Vec::new();
485        let mut next_err = value.source();
486
487        while let Some(err) = next_err {
488            chain.push(err.to_string().into());
489            next_err = err.source();
490        }
491
492        let error_msg = value.to_string();
493
494        if self.sem_conv_config.error_fields_to_exceptions {
495            self.record(KeyValue::new(
496                Key::new(FIELD_EXCEPTION_MESSAGE),
497                Value::from(error_msg.clone()),
498            ));
499
500            // NOTE: This is actually not the stacktrace of the exception. This is
501            // the "source chain". It represents the heirarchy of errors from the
502            // app level to the lowest level such as IO. It does not represent all
503            // of the callsites in the code that led to the error happening.
504            // `std::error::Error::backtrace` is a nightly-only API and cannot be
505            // used here until the feature is stabilized.
506            self.record(KeyValue::new(
507                Key::new(FIELD_EXCEPTION_STACKTRACE),
508                Value::Array(chain.clone().into()),
509            ));
510        }
511
512        self.record(KeyValue::new(
513            Key::new(field.name()),
514            Value::String(error_msg.into()),
515        ));
516        self.record(KeyValue::new(
517            Key::new(format!("{}.chain", field.name())),
518            Value::Array(chain.into()),
519        ));
520    }
521}
522
523impl<S, T> OpenTelemetryLayer<S, T>
524where
525    S: Subscriber + for<'span> LookupSpan<'span>,
526    T: otel::Tracer + PreSampledTracer + 'static,
527{
528    /// Set the [`Tracer`] that this layer will use to produce and track
529    /// OpenTelemetry [`Span`]s.
530    ///
531    /// [`Tracer`]: opentelemetry::trace::Tracer
532    /// [`Span`]: opentelemetry::trace::Span
533    ///
534    /// # Examples
535    ///
536    /// ```no_run
537    /// use tracing_opentelemetry::OpenTelemetryLayer;
538    /// use tracing_subscriber::layer::SubscriberExt;
539    /// use opentelemetry::trace::TracerProvider as _;
540    /// use tracing_subscriber::Registry;
541    ///
542    /// // Create an OTLP pipeline exporter for a `trace_demo` service.
543    ///
544    /// let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
545    ///     .with_tonic()
546    ///     .build()
547    ///     .unwrap();
548    ///
549    /// let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
550    ///     .with_simple_exporter(otlp_exporter)
551    ///     .build()
552    ///     .tracer("trace_demo");
553    ///
554    /// // Create a layer with the configured tracer
555    /// let otel_layer = OpenTelemetryLayer::new(tracer);
556    ///
557    /// // Use the tracing subscriber `Registry`, or any other subscriber
558    /// // that impls `LookupSpan`
559    /// let subscriber = Registry::default().with(otel_layer);
560    /// # drop(subscriber);
561    /// ```
562    pub fn new(tracer: T) -> Self {
563        OpenTelemetryLayer {
564            tracer,
565            location: true,
566            tracked_inactivity: true,
567            with_threads: true,
568            with_level: false,
569            sem_conv_config: SemConvConfig {
570                error_fields_to_exceptions: true,
571                error_records_to_exceptions: true,
572                error_events_to_exceptions: true,
573                error_events_to_status: true,
574            },
575
576            get_context: WithContext(Self::get_context),
577            _registry: marker::PhantomData,
578        }
579    }
580
581    /// Set the [`Tracer`] that this layer will use to produce and track
582    /// OpenTelemetry [`Span`]s.
583    ///
584    /// [`Tracer`]: opentelemetry::trace::Tracer
585    /// [`Span`]: opentelemetry::trace::Span
586    ///
587    /// # Examples
588    ///
589    /// ```no_run
590    /// use tracing_subscriber::layer::SubscriberExt;
591    /// use tracing_subscriber::Registry;
592    /// use opentelemetry::trace::TracerProvider;
593    ///
594    /// // Create an OTLP pipeline exporter for a `trace_demo` service.
595    ///
596    /// let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
597    ///     .with_tonic()
598    ///     .build()
599    ///     .unwrap();
600    ///
601    /// let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
602    ///     .with_simple_exporter(otlp_exporter)
603    ///     .build()
604    ///     .tracer("trace_demo");
605    ///
606    /// // Create a layer with the configured tracer
607    /// let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
608    ///
609    /// // Use the tracing subscriber `Registry`, or any other subscriber
610    /// // that impls `LookupSpan`
611    /// let subscriber = Registry::default().with(otel_layer);
612    /// # drop(subscriber);
613    /// ```
614    pub fn with_tracer<Tracer>(self, tracer: Tracer) -> OpenTelemetryLayer<S, Tracer>
615    where
616        Tracer: otel::Tracer + PreSampledTracer + 'static,
617    {
618        OpenTelemetryLayer {
619            tracer,
620            location: self.location,
621            tracked_inactivity: self.tracked_inactivity,
622            with_threads: self.with_threads,
623            with_level: self.with_level,
624            sem_conv_config: self.sem_conv_config,
625            get_context: WithContext(OpenTelemetryLayer::<S, Tracer>::get_context),
626            _registry: self._registry,
627            // cannot use ``..self` here due to different generics
628        }
629    }
630
631    /// Sets whether or not span and event metadata should include OpenTelemetry
632    /// exception fields such as `exception.message` and `exception.backtrace`
633    /// when an `Error` value is recorded. If multiple error values are recorded
634    /// on the same span/event, only the most recently recorded error value will
635    /// show up under these fields.
636    ///
637    /// These attributes follow the [OpenTelemetry semantic conventions for
638    /// exceptions][conv].
639    ///
640    /// By default, these attributes are recorded.
641    /// Note that this only works for `(dyn Error + 'static)`.
642    /// See [Implementations on Foreign Types of tracing::Value][impls] or [`OpenTelemetryLayer::with_error_events_to_exceptions`]
643    ///
644    /// [conv]: https://github.com/open-telemetry/semantic-conventions/tree/main/docs/exceptions/
645    /// [impls]: https://docs.rs/tracing/0.1.37/tracing/trait.Value.html#foreign-impls
646    pub fn with_error_fields_to_exceptions(self, error_fields_to_exceptions: bool) -> Self {
647        Self {
648            sem_conv_config: SemConvConfig {
649                error_fields_to_exceptions,
650                ..self.sem_conv_config
651            },
652            ..self
653        }
654    }
655
656    /// Sets whether or not an event considered for exception mapping (see [`OpenTelemetryLayer::with_error_recording`])
657    /// should be propagated to the span status error description.
658    ///
659    ///
660    /// By default, these events do set the span status error description.
661    pub fn with_error_events_to_status(self, error_events_to_status: bool) -> Self {
662        Self {
663            sem_conv_config: SemConvConfig {
664                error_events_to_status,
665                ..self.sem_conv_config
666            },
667            ..self
668        }
669    }
670
671    /// Sets whether or not a subset of events following the described schema are mapped to
672    /// events following the [OpenTelemetry semantic conventions for
673    /// exceptions][conv].
674    ///
675    /// * Only events without a message field (unnamed events) and at least one field with the name error
676    ///   are considered for mapping.
677    ///
678    /// By default, these events are mapped.
679    ///
680    /// [conv]: https://github.com/open-telemetry/semantic-conventions/tree/main/docs/exceptions/
681    pub fn with_error_events_to_exceptions(self, error_events_to_exceptions: bool) -> Self {
682        Self {
683            sem_conv_config: SemConvConfig {
684                error_events_to_exceptions,
685                ..self.sem_conv_config
686            },
687            ..self
688        }
689    }
690
691    /// Sets whether or not reporting an `Error` value on an event will
692    /// propagate the OpenTelemetry exception fields such as `exception.message`
693    /// and `exception.backtrace` to the corresponding span. You do not need to
694    /// enable `with_exception_fields` in order to enable this. If multiple
695    /// error values are recorded on the same span/event, only the most recently
696    /// recorded error value will show up under these fields.
697    ///
698    /// These attributes follow the [OpenTelemetry semantic conventions for
699    /// exceptions][conv].
700    ///
701    /// By default, these attributes are propagated to the span. Note that this only works for `(dyn Error + 'static)`.
702    /// See [Implementations on Foreign Types of tracing::Value][impls] or [`OpenTelemetryLayer::with_error_events_to_exceptions`]
703    ///
704    /// [conv]: https://github.com/open-telemetry/semantic-conventions/tree/main/docs/exceptions/
705    /// [impls]: https://docs.rs/tracing/0.1.37/tracing/trait.Value.html#foreign-impls
706    pub fn with_error_records_to_exceptions(self, error_records_to_exceptions: bool) -> Self {
707        Self {
708            sem_conv_config: SemConvConfig {
709                error_records_to_exceptions,
710                ..self.sem_conv_config
711            },
712            ..self
713        }
714    }
715
716    /// Sets whether or not span and event metadata should include OpenTelemetry
717    /// attributes with location information, such as the file, module and line number.
718    ///
719    /// These attributes follow the [OpenTelemetry semantic conventions for
720    /// source locations][conv].
721    ///
722    /// By default, locations are enabled.
723    ///
724    /// [conv]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attributes.md#source-code-attributes/
725    pub fn with_location(self, location: bool) -> Self {
726        Self { location, ..self }
727    }
728
729    /// Sets whether or not spans metadata should include the _busy time_
730    /// (total time for which it was entered), and _idle time_ (total time
731    /// the span existed but was not entered).
732    ///
733    /// By default, inactivity tracking is enabled.
734    pub fn with_tracked_inactivity(self, tracked_inactivity: bool) -> Self {
735        Self {
736            tracked_inactivity,
737            ..self
738        }
739    }
740
741    /// Sets whether or not spans record additional attributes for the thread
742    /// name and thread ID of the thread they were created on, following the
743    /// [OpenTelemetry semantic conventions for threads][conv].
744    ///
745    /// By default, thread attributes are enabled.
746    ///
747    /// [conv]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attributes.md#general-thread-attributes/
748    pub fn with_threads(self, threads: bool) -> Self {
749        Self {
750            with_threads: threads,
751            ..self
752        }
753    }
754
755    /// Sets whether or not span metadata should include the `tracing` verbosity level information as a `level` field.
756    ///
757    /// The level is always added to events, and based on [`OpenTelemetryLayer::with_error_events_to_status`]
758    /// error-level events will mark the span status as an error.
759    ///
760    /// By default, level information is disabled.
761    pub fn with_level(self, level: bool) -> Self {
762        Self {
763            with_level: level,
764            ..self
765        }
766    }
767
768    /// Retrieve the parent OpenTelemetry [`Context`] from the current tracing
769    /// [`span`] through the [`Registry`]. This [`Context`] links spans to their
770    /// parent for proper hierarchical visualization.
771    ///
772    /// [`Context`]: opentelemetry::Context
773    /// [`span`]: tracing::Span
774    /// [`Registry`]: tracing_subscriber::Registry
775    fn parent_context(&self, attrs: &Attributes<'_>, ctx: &Context<'_, S>) -> OtelContext {
776        if let Some(parent) = attrs.parent() {
777            // A span can have an _explicit_ parent that is NOT seen by this `Layer` (for which
778            // `Context::span` returns `None`. This happens if the parent span is filtered away
779            // from the layer by a per-layer filter. In that case, we fall-through to the `else`
780            // case, and consider this span a root span.
781            //
782            // This is likely rare, as most users who use explicit parents will configure their
783            // filters so that children and parents are both seen, but it's not guaranteed. Also,
784            // if users configure their filter with a `reload` filter, it's possible that a parent
785            // and child have different filters as they are created with a filter change
786            // in-between.
787            //
788            // In these case, we prefer to emit a smaller span tree instead of panicking.
789            if let Some(span) = ctx.span(parent) {
790                let mut extensions = span.extensions_mut();
791                return extensions
792                    .get_mut::<OtelData>()
793                    .map(|builder| self.tracer.sampled_context(builder))
794                    .unwrap_or_default();
795            }
796        }
797
798        // Else if the span is inferred from context, look up any available current span.
799        if attrs.is_contextual() {
800            ctx.lookup_current()
801                .and_then(|span| {
802                    let mut extensions = span.extensions_mut();
803                    extensions
804                        .get_mut::<OtelData>()
805                        .map(|builder| self.tracer.sampled_context(builder))
806                })
807                .unwrap_or_else(OtelContext::current)
808        // Explicit root spans should have no parent context.
809        } else {
810            OtelContext::new()
811        }
812    }
813
814    fn get_context(
815        dispatch: &tracing::Dispatch,
816        id: &span::Id,
817        f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer),
818    ) {
819        let subscriber = dispatch
820            .downcast_ref::<S>()
821            .expect("subscriber should downcast to expected type; this is a bug!");
822        let span = subscriber
823            .span(id)
824            .expect("registry should have a span for the current ID");
825        let layer = dispatch
826            .downcast_ref::<OpenTelemetryLayer<S, T>>()
827            .expect("layer should downcast to expected type; this is a bug!");
828
829        let mut extensions = span.extensions_mut();
830        if let Some(builder) = extensions.get_mut::<OtelData>() {
831            f(builder, &layer.tracer);
832        }
833    }
834
835    fn extra_span_attrs(&self) -> usize {
836        let mut extra_attrs = 0;
837        if self.location {
838            extra_attrs += 3;
839        }
840        if self.with_threads {
841            extra_attrs += 2;
842        }
843        if self.with_level {
844            extra_attrs += 1;
845        }
846        extra_attrs
847    }
848}
849
850thread_local! {
851    static THREAD_ID: unsync::Lazy<u64> = unsync::Lazy::new(|| {
852        // OpenTelemetry's semantic conventions require the thread ID to be
853        // recorded as an integer, but `std::thread::ThreadId` does not expose
854        // the integer value on stable, so we have to convert it to a `usize` by
855        // parsing it. Since this requires allocating a `String`, store it in a
856        // thread local so we only have to do this once.
857        // TODO(eliza): once `std::thread::ThreadId::as_u64` is stabilized
858        // (https://github.com/rust-lang/rust/issues/67939), just use that.
859        thread_id_integer(thread::current().id())
860    });
861}
862
863impl<S, T> Layer<S> for OpenTelemetryLayer<S, T>
864where
865    S: Subscriber + for<'span> LookupSpan<'span>,
866    T: otel::Tracer + PreSampledTracer + 'static,
867{
868    /// Creates an [OpenTelemetry `Span`] for the corresponding [tracing `Span`].
869    ///
870    /// [OpenTelemetry `Span`]: opentelemetry::trace::Span
871    /// [tracing `Span`]: tracing::Span
872    fn on_new_span(&self, attrs: &Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
873        let span = ctx.span(id).expect("Span not found, this is a bug");
874        let mut extensions = span.extensions_mut();
875
876        if self.tracked_inactivity && extensions.get_mut::<Timings>().is_none() {
877            extensions.insert(Timings::new());
878        }
879
880        let parent_cx = self.parent_context(attrs, &ctx);
881        let mut builder = self
882            .tracer
883            .span_builder(attrs.metadata().name())
884            .with_start_time(crate::time::now())
885            // Eagerly assign span id so children have stable parent id
886            .with_span_id(self.tracer.new_span_id());
887
888        // Record new trace id if there is no active parent span
889        if !parent_cx.has_active_span() {
890            builder.trace_id = Some(self.tracer.new_trace_id());
891        }
892
893        let builder_attrs = builder.attributes.get_or_insert(Vec::with_capacity(
894            attrs.fields().len() + self.extra_span_attrs(),
895        ));
896
897        if self.location {
898            let meta = attrs.metadata();
899
900            if let Some(filename) = meta.file() {
901                builder_attrs.push(KeyValue::new("code.filepath", filename));
902            }
903
904            if let Some(module) = meta.module_path() {
905                builder_attrs.push(KeyValue::new("code.namespace", module));
906            }
907
908            if let Some(line) = meta.line() {
909                builder_attrs.push(KeyValue::new("code.lineno", line as i64));
910            }
911        }
912
913        if self.with_threads {
914            THREAD_ID.with(|id| builder_attrs.push(KeyValue::new("thread.id", **id as i64)));
915            if let Some(name) = std::thread::current().name() {
916                // TODO(eliza): it's a bummer that we have to allocate here, but
917                // we can't easily get the string as a `static`. it would be
918                // nice if `opentelemetry` could also take `Arc<str>`s as
919                // `String` values...
920                builder_attrs.push(KeyValue::new("thread.name", name.to_string()));
921            }
922        }
923
924        if self.with_level {
925            builder_attrs.push(KeyValue::new("level", attrs.metadata().level().as_str()));
926        }
927
928        let mut updates = SpanBuilderUpdates::default();
929        attrs.record(&mut SpanAttributeVisitor {
930            span_builder_updates: &mut updates,
931            sem_conv_config: self.sem_conv_config,
932        });
933
934        updates.update(&mut builder);
935        extensions.insert(OtelData { builder, parent_cx });
936    }
937
938    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
939        if !self.tracked_inactivity {
940            return;
941        }
942
943        let span = ctx.span(id).expect("Span not found, this is a bug");
944        let mut extensions = span.extensions_mut();
945
946        if let Some(timings) = extensions.get_mut::<Timings>() {
947            let now = Instant::now();
948            timings.idle += (now - timings.last).as_nanos() as i64;
949            timings.last = now;
950        }
951    }
952
953    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
954        let span = ctx.span(id).expect("Span not found, this is a bug");
955        let mut extensions = span.extensions_mut();
956
957        if let Some(otel_data) = extensions.get_mut::<OtelData>() {
958            otel_data.builder.end_time = Some(crate::time::now());
959        }
960
961        if !self.tracked_inactivity {
962            return;
963        }
964
965        if let Some(timings) = extensions.get_mut::<Timings>() {
966            let now = Instant::now();
967            timings.busy += (now - timings.last).as_nanos() as i64;
968            timings.last = now;
969        }
970    }
971
972    /// Record OpenTelemetry [`attributes`] for the given values.
973    ///
974    /// [`attributes`]: opentelemetry::trace::SpanBuilder::attributes
975    fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
976        let span = ctx.span(id).expect("Span not found, this is a bug");
977        let mut updates = SpanBuilderUpdates::default();
978        values.record(&mut SpanAttributeVisitor {
979            span_builder_updates: &mut updates,
980            sem_conv_config: self.sem_conv_config,
981        });
982        let mut extensions = span.extensions_mut();
983        if let Some(data) = extensions.get_mut::<OtelData>() {
984            updates.update(&mut data.builder);
985        }
986    }
987
988    fn on_follows_from(&self, id: &Id, follows: &Id, ctx: Context<S>) {
989        let span = ctx.span(id).expect("Span not found, this is a bug");
990        let mut extensions = span.extensions_mut();
991        let data = extensions
992            .get_mut::<OtelData>()
993            .expect("Missing otel data span extensions");
994
995        // The follows span may be filtered away (or closed), from this layer,
996        // in which case we just drop the data, as opposed to panicking. This
997        // uses the same reasoning as `parent_context` above.
998        if let Some(follows_span) = ctx.span(follows) {
999            let mut follows_extensions = follows_span.extensions_mut();
1000            let follows_data = follows_extensions
1001                .get_mut::<OtelData>()
1002                .expect("Missing otel data span extensions");
1003
1004            let follows_context = self
1005                .tracer
1006                .sampled_context(follows_data)
1007                .span()
1008                .span_context()
1009                .clone();
1010            let follows_link = otel::Link::with_context(follows_context);
1011            if let Some(ref mut links) = data.builder.links {
1012                links.push(follows_link);
1013            } else {
1014                data.builder.links = Some(vec![follows_link]);
1015            }
1016        }
1017    }
1018
1019    /// Records OpenTelemetry [`Event`] data on event.
1020    ///
1021    /// Note: an [`ERROR`]-level event will also set the OpenTelemetry span status code to
1022    /// [`Error`], signaling that an error has occurred.
1023    ///
1024    /// [`Event`]: opentelemetry::trace::Event
1025    /// [`ERROR`]: tracing::Level::ERROR
1026    /// [`Error`]: opentelemetry::trace::StatusCode::Error
1027    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
1028        // Ignore events that are not in the context of a span
1029        if let Some(span) = event.parent().and_then(|id| ctx.span(id)).or_else(|| {
1030            event
1031                .is_contextual()
1032                .then(|| ctx.lookup_current())
1033                .flatten()
1034        }) {
1035            // Performing read operations before getting a write lock to avoid a deadlock
1036            // See https://github.com/tokio-rs/tracing/issues/763
1037            #[cfg(feature = "tracing-log")]
1038            let normalized_meta = event.normalized_metadata();
1039            #[cfg(feature = "tracing-log")]
1040            let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
1041            #[cfg(not(feature = "tracing-log"))]
1042            let meta = event.metadata();
1043
1044            let target = Key::new("target");
1045
1046            #[cfg(feature = "tracing-log")]
1047            let target = if normalized_meta.is_some() {
1048                KeyValue::new(target, Value::String(meta.target().to_owned().into()))
1049            } else {
1050                KeyValue::new(target, Value::String(event.metadata().target().into()))
1051            };
1052
1053            #[cfg(not(feature = "tracing-log"))]
1054            let target = KeyValue::new(target, Value::String(meta.target().into()));
1055
1056            let mut otel_event = otel::Event::new(
1057                String::new(),
1058                crate::time::now(),
1059                vec![
1060                    KeyValue::new(
1061                        Key::new("level"),
1062                        Value::String(meta.level().as_str().into()),
1063                    ),
1064                    target,
1065                ],
1066                0,
1067            );
1068
1069            let mut builder_updates = None;
1070            event.record(&mut SpanEventVisitor {
1071                event_builder: &mut otel_event,
1072                span_builder_updates: &mut builder_updates,
1073                sem_conv_config: self.sem_conv_config,
1074            });
1075
1076            // If the event name is still empty, then there was no special handling of error fields.
1077            // It should be safe to set the event name to the name provided by tracing.
1078            // This is a hack but there are existing hacks that depend on the name being empty, so to avoid breaking those the event name is set here.
1079            // Ideally, the name should be set above when the event is constructed.
1080            // see: https://github.com/tokio-rs/tracing-opentelemetry/pull/28
1081            if otel_event.name.is_empty() {
1082                otel_event.name = std::borrow::Cow::Borrowed(event.metadata().name());
1083            }
1084
1085            let mut extensions = span.extensions_mut();
1086            let otel_data = extensions.get_mut::<OtelData>();
1087
1088            if let Some(otel_data) = otel_data {
1089                let builder = &mut otel_data.builder;
1090
1091                if builder.status == otel::Status::Unset
1092                    && *meta.level() == tracing_core::Level::ERROR
1093                {
1094                    builder.status = otel::Status::error("")
1095                }
1096
1097                if let Some(builder_updates) = builder_updates {
1098                    builder_updates.update(builder);
1099                }
1100
1101                if self.location {
1102                    #[cfg(not(feature = "tracing-log"))]
1103                    let normalized_meta: Option<tracing_core::Metadata<'_>> = None;
1104                    let (file, module) = match &normalized_meta {
1105                        Some(meta) => (
1106                            meta.file().map(|s| Value::from(s.to_owned())),
1107                            meta.module_path().map(|s| Value::from(s.to_owned())),
1108                        ),
1109                        None => (
1110                            event.metadata().file().map(Value::from),
1111                            event.metadata().module_path().map(Value::from),
1112                        ),
1113                    };
1114
1115                    if let Some(file) = file {
1116                        otel_event
1117                            .attributes
1118                            .push(KeyValue::new("code.filepath", file));
1119                    }
1120                    if let Some(module) = module {
1121                        otel_event
1122                            .attributes
1123                            .push(KeyValue::new("code.namespace", module));
1124                    }
1125                    if let Some(line) = meta.line() {
1126                        otel_event
1127                            .attributes
1128                            .push(KeyValue::new("code.lineno", line as i64));
1129                    }
1130                }
1131
1132                if let Some(ref mut events) = builder.events {
1133                    events.push(otel_event);
1134                } else {
1135                    builder.events = Some(vec![otel_event]);
1136                }
1137            }
1138        };
1139    }
1140
1141    /// Exports an OpenTelemetry [`Span`] on close.
1142    ///
1143    /// [`Span`]: opentelemetry::trace::Span
1144    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
1145        let span = ctx.span(&id).expect("Span not found, this is a bug");
1146        let (otel_data, timings) = {
1147            let mut extensions = span.extensions_mut();
1148            let timings = if self.tracked_inactivity {
1149                extensions.remove::<Timings>()
1150            } else {
1151                None
1152            };
1153            (extensions.remove::<OtelData>(), timings)
1154        };
1155
1156        if let Some(OtelData {
1157            mut builder,
1158            parent_cx,
1159        }) = otel_data
1160        {
1161            // Append busy/idle timings when enabled.
1162            if let Some(timings) = timings {
1163                let busy_ns = Key::new("busy_ns");
1164                let idle_ns = Key::new("idle_ns");
1165
1166                let attributes = builder
1167                    .attributes
1168                    .get_or_insert_with(|| Vec::with_capacity(2));
1169                attributes.push(KeyValue::new(busy_ns, timings.busy));
1170                attributes.push(KeyValue::new(idle_ns, timings.idle));
1171            }
1172
1173            // Build and start span, drop span to export
1174            builder.start_with_context(&self.tracer, &parent_cx);
1175        }
1176    }
1177
1178    // SAFETY: this is safe because the `WithContext` function pointer is valid
1179    // for the lifetime of `&self`.
1180    unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
1181        match id {
1182            id if id == TypeId::of::<Self>() => Some(self as *const _ as *const ()),
1183            id if id == TypeId::of::<WithContext>() => {
1184                Some(&self.get_context as *const _ as *const ())
1185            }
1186            _ => None,
1187        }
1188    }
1189}
1190
1191struct Timings {
1192    idle: i64,
1193    busy: i64,
1194    last: Instant,
1195}
1196
1197impl Timings {
1198    fn new() -> Self {
1199        Self {
1200            idle: 0,
1201            busy: 0,
1202            last: Instant::now(),
1203        }
1204    }
1205}
1206
1207fn thread_id_integer(id: thread::ThreadId) -> u64 {
1208    let thread_id = format!("{:?}", id);
1209    thread_id
1210        .trim_start_matches("ThreadId(")
1211        .trim_end_matches(')')
1212        .parse::<u64>()
1213        .expect("thread ID should parse as an integer")
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218    use super::*;
1219    use opentelemetry::trace::{SpanContext, TraceFlags};
1220    use std::{
1221        collections::HashMap,
1222        error::Error,
1223        fmt::Display,
1224        sync::{Arc, Mutex},
1225        time::SystemTime,
1226    };
1227    use tracing_subscriber::prelude::*;
1228
1229    #[derive(Debug, Clone)]
1230    struct TestTracer(Arc<Mutex<Option<OtelData>>>);
1231    impl otel::Tracer for TestTracer {
1232        type Span = noop::NoopSpan;
1233        fn start_with_context<T>(&self, _name: T, _context: &OtelContext) -> Self::Span
1234        where
1235            T: Into<Cow<'static, str>>,
1236        {
1237            noop::NoopSpan::DEFAULT
1238        }
1239        fn span_builder<T>(&self, name: T) -> otel::SpanBuilder
1240        where
1241            T: Into<Cow<'static, str>>,
1242        {
1243            otel::SpanBuilder::from_name(name)
1244        }
1245        fn build_with_context(
1246            &self,
1247            builder: otel::SpanBuilder,
1248            parent_cx: &OtelContext,
1249        ) -> Self::Span {
1250            *self.0.lock().unwrap() = Some(OtelData {
1251                builder,
1252                parent_cx: parent_cx.clone(),
1253            });
1254            noop::NoopSpan::DEFAULT
1255        }
1256    }
1257
1258    impl PreSampledTracer for TestTracer {
1259        fn sampled_context(&self, _builder: &mut crate::OtelData) -> OtelContext {
1260            OtelContext::new()
1261        }
1262        fn new_trace_id(&self) -> otel::TraceId {
1263            otel::TraceId::INVALID
1264        }
1265        fn new_span_id(&self) -> otel::SpanId {
1266            otel::SpanId::INVALID
1267        }
1268    }
1269
1270    impl TestTracer {
1271        fn with_data<T>(&self, f: impl FnOnce(&OtelData) -> T) -> T {
1272            let lock = self.0.lock().unwrap();
1273            let data = lock.as_ref().expect("no span data has been recorded yet");
1274            f(data)
1275        }
1276    }
1277
1278    #[derive(Debug, Clone)]
1279    struct TestSpan(otel::SpanContext);
1280    impl otel::Span for TestSpan {
1281        fn add_event_with_timestamp<T: Into<Cow<'static, str>>>(
1282            &mut self,
1283            _: T,
1284            _: SystemTime,
1285            _: Vec<KeyValue>,
1286        ) {
1287        }
1288        fn span_context(&self) -> &otel::SpanContext {
1289            &self.0
1290        }
1291        fn is_recording(&self) -> bool {
1292            false
1293        }
1294        fn set_attribute(&mut self, _attribute: KeyValue) {}
1295        fn set_status(&mut self, _status: otel::Status) {}
1296        fn update_name<T: Into<Cow<'static, str>>>(&mut self, _new_name: T) {}
1297        fn add_link(&mut self, _span_context: SpanContext, _attributes: Vec<KeyValue>) {}
1298        fn end_with_timestamp(&mut self, _timestamp: SystemTime) {}
1299    }
1300
1301    #[derive(Debug)]
1302    struct TestDynError {
1303        msg: &'static str,
1304        source: Option<Box<TestDynError>>,
1305    }
1306    impl Display for TestDynError {
1307        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1308            write!(f, "{}", self.msg)
1309        }
1310    }
1311    impl Error for TestDynError {
1312        fn source(&self) -> Option<&(dyn Error + 'static)> {
1313            match &self.source {
1314                Some(source) => Some(source),
1315                None => None,
1316            }
1317        }
1318    }
1319    impl TestDynError {
1320        fn new(msg: &'static str) -> Self {
1321            Self { msg, source: None }
1322        }
1323        fn with_parent(self, parent_msg: &'static str) -> Self {
1324            Self {
1325                msg: parent_msg,
1326                source: Some(Box::new(self)),
1327            }
1328        }
1329    }
1330
1331    #[test]
1332    fn dynamic_span_names() {
1333        let dynamic_name = "GET http://example.com".to_string();
1334        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1335        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1336
1337        tracing::subscriber::with_default(subscriber, || {
1338            tracing::debug_span!("static_name", otel.name = dynamic_name.as_str());
1339        });
1340
1341        let recorded_name = tracer
1342            .0
1343            .lock()
1344            .unwrap()
1345            .as_ref()
1346            .map(|b| b.builder.name.clone());
1347        assert_eq!(recorded_name, Some(dynamic_name.into()))
1348    }
1349
1350    #[test]
1351    fn span_kind() {
1352        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1353        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1354
1355        tracing::subscriber::with_default(subscriber, || {
1356            tracing::debug_span!("request", otel.kind = "server");
1357        });
1358
1359        let recorded_kind = tracer.with_data(|data| data.builder.span_kind.clone());
1360        assert_eq!(recorded_kind, Some(otel::SpanKind::Server))
1361    }
1362
1363    #[test]
1364    fn span_status_code() {
1365        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1366        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1367
1368        tracing::subscriber::with_default(subscriber, || {
1369            tracing::debug_span!("request", otel.status_code = ?otel::Status::Ok);
1370        });
1371
1372        let recorded_status = tracer.with_data(|data| data.builder.status.clone());
1373        assert_eq!(recorded_status, otel::Status::Ok)
1374    }
1375
1376    #[test]
1377    fn span_status_message() {
1378        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1379        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1380
1381        let message = "message";
1382
1383        tracing::subscriber::with_default(subscriber, || {
1384            tracing::debug_span!("request", otel.status_message = message);
1385        });
1386
1387        let recorded_status_message = tracer
1388            .0
1389            .lock()
1390            .unwrap()
1391            .as_ref()
1392            .unwrap()
1393            .builder
1394            .status
1395            .clone();
1396
1397        assert_eq!(recorded_status_message, otel::Status::error(message))
1398    }
1399
1400    #[test]
1401    fn trace_id_from_existing_context() {
1402        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1403        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1404        let trace_id = otel::TraceId::from(42u128);
1405        let existing_cx = OtelContext::current_with_span(TestSpan(otel::SpanContext::new(
1406            trace_id,
1407            otel::SpanId::from(1u64),
1408            TraceFlags::default(),
1409            false,
1410            Default::default(),
1411        )));
1412        let _g = existing_cx.attach();
1413
1414        tracing::subscriber::with_default(subscriber, || {
1415            tracing::debug_span!("request", otel.kind = "server");
1416        });
1417
1418        let recorded_trace_id =
1419            tracer.with_data(|data| data.parent_cx.span().span_context().trace_id());
1420        assert_eq!(recorded_trace_id, trace_id)
1421    }
1422
1423    #[test]
1424    fn includes_timings() {
1425        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1426        let subscriber = tracing_subscriber::registry().with(
1427            layer()
1428                .with_tracer(tracer.clone())
1429                .with_tracked_inactivity(true),
1430        );
1431
1432        tracing::subscriber::with_default(subscriber, || {
1433            tracing::debug_span!("request");
1434        });
1435
1436        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1437        let keys = attributes
1438            .iter()
1439            .map(|kv| kv.key.as_str())
1440            .collect::<Vec<&str>>();
1441        assert!(keys.contains(&"idle_ns"));
1442        assert!(keys.contains(&"busy_ns"));
1443    }
1444
1445    #[test]
1446    fn records_error_fields() {
1447        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1448        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1449
1450        let err = TestDynError::new("base error")
1451            .with_parent("intermediate error")
1452            .with_parent("user error");
1453
1454        tracing::subscriber::with_default(subscriber, || {
1455            tracing::debug_span!(
1456                "request",
1457                error = &err as &(dyn std::error::Error + 'static)
1458            );
1459        });
1460
1461        let attributes = tracer
1462            .0
1463            .lock()
1464            .unwrap()
1465            .as_ref()
1466            .unwrap()
1467            .builder
1468            .attributes
1469            .as_ref()
1470            .unwrap()
1471            .clone();
1472
1473        let key_values = attributes
1474            .into_iter()
1475            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1476            .collect::<HashMap<_, _>>();
1477
1478        assert_eq!(key_values["error"].as_str(), "user error");
1479        assert_eq!(
1480            key_values["error.chain"],
1481            Value::Array(
1482                vec![
1483                    StringValue::from("intermediate error"),
1484                    StringValue::from("base error")
1485                ]
1486                .into()
1487            )
1488        );
1489
1490        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1491        assert_eq!(
1492            key_values[FIELD_EXCEPTION_STACKTRACE],
1493            Value::Array(
1494                vec![
1495                    StringValue::from("intermediate error"),
1496                    StringValue::from("base error")
1497                ]
1498                .into()
1499            )
1500        );
1501    }
1502
1503    #[test]
1504    fn records_event_name() {
1505        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1506        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1507
1508        tracing::subscriber::with_default(subscriber, || {
1509            tracing::debug_span!("test span").in_scope(|| {
1510                tracing::event!(tracing::Level::INFO, "event name 1"); // this is equivalent to 'message = "event name 1"'
1511                tracing::event!(name: "event name 2", tracing::Level::INFO, field1 = "field1");
1512                tracing::event!(name: "event name 3", tracing::Level::INFO, error = "field2");
1513                tracing::event!(name: "event name 4", tracing::Level::INFO, message = "field3");
1514                tracing::event!(name: "event name 5", tracing::Level::INFO, name = "field4");
1515            });
1516        });
1517
1518        let events = tracer
1519            .0
1520            .lock()
1521            .unwrap()
1522            .as_ref()
1523            .unwrap()
1524            .builder
1525            .events
1526            .as_ref()
1527            .unwrap()
1528            .clone();
1529
1530        let mut iter = events.iter();
1531
1532        assert_eq!(iter.next().unwrap().name, "event name 1");
1533        assert_eq!(iter.next().unwrap().name, "event name 2");
1534        assert_eq!(iter.next().unwrap().name, "exception"); // error attribute is handled specially
1535        assert_eq!(iter.next().unwrap().name, "field3"); // message attribute is handled specially
1536        assert_eq!(iter.next().unwrap().name, "event name 5"); // name attribute should not conflict with event name.
1537    }
1538
1539    #[test]
1540    fn records_no_error_fields() {
1541        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1542        let subscriber = tracing_subscriber::registry().with(
1543            layer()
1544                .with_error_records_to_exceptions(false)
1545                .with_tracer(tracer.clone()),
1546        );
1547
1548        let err = TestDynError::new("base error")
1549            .with_parent("intermediate error")
1550            .with_parent("user error");
1551
1552        tracing::subscriber::with_default(subscriber, || {
1553            tracing::debug_span!(
1554                "request",
1555                error = &err as &(dyn std::error::Error + 'static)
1556            );
1557        });
1558
1559        let attributes = tracer
1560            .0
1561            .lock()
1562            .unwrap()
1563            .as_ref()
1564            .unwrap()
1565            .builder
1566            .attributes
1567            .as_ref()
1568            .unwrap()
1569            .clone();
1570
1571        let key_values = attributes
1572            .into_iter()
1573            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1574            .collect::<HashMap<_, _>>();
1575
1576        assert_eq!(key_values["error"].as_str(), "user error");
1577        assert_eq!(
1578            key_values["error.chain"],
1579            Value::Array(
1580                vec![
1581                    StringValue::from("intermediate error"),
1582                    StringValue::from("base error")
1583                ]
1584                .into()
1585            )
1586        );
1587
1588        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1589        assert_eq!(
1590            key_values[FIELD_EXCEPTION_STACKTRACE],
1591            Value::Array(
1592                vec![
1593                    StringValue::from("intermediate error"),
1594                    StringValue::from("base error")
1595                ]
1596                .into()
1597            )
1598        );
1599    }
1600
1601    #[test]
1602    fn includes_span_location() {
1603        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1604        let subscriber = tracing_subscriber::registry()
1605            .with(layer().with_tracer(tracer.clone()).with_location(true));
1606
1607        tracing::subscriber::with_default(subscriber, || {
1608            tracing::debug_span!("request");
1609        });
1610
1611        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1612        let keys = attributes
1613            .iter()
1614            .map(|kv| kv.key.as_str())
1615            .collect::<Vec<&str>>();
1616        assert!(keys.contains(&"code.filepath"));
1617        assert!(keys.contains(&"code.namespace"));
1618        assert!(keys.contains(&"code.lineno"));
1619    }
1620
1621    #[test]
1622    fn excludes_span_location() {
1623        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1624        let subscriber = tracing_subscriber::registry()
1625            .with(layer().with_tracer(tracer.clone()).with_location(false));
1626
1627        tracing::subscriber::with_default(subscriber, || {
1628            tracing::debug_span!("request");
1629        });
1630
1631        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1632        let keys = attributes
1633            .iter()
1634            .map(|kv| kv.key.as_str())
1635            .collect::<Vec<&str>>();
1636        assert!(!keys.contains(&"code.filepath"));
1637        assert!(!keys.contains(&"code.namespace"));
1638        assert!(!keys.contains(&"code.lineno"));
1639    }
1640
1641    #[test]
1642    fn includes_thread() {
1643        let thread = thread::current();
1644        let expected_name = thread
1645            .name()
1646            .map(|name| Value::String(name.to_owned().into()));
1647        let expected_id = Value::I64(thread_id_integer(thread.id()) as i64);
1648
1649        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1650        let subscriber = tracing_subscriber::registry()
1651            .with(layer().with_tracer(tracer.clone()).with_threads(true));
1652
1653        tracing::subscriber::with_default(subscriber, || {
1654            tracing::debug_span!("request");
1655        });
1656
1657        let attributes = tracer
1658            .with_data(|data| data.builder.attributes.as_ref().unwrap().clone())
1659            .drain(..)
1660            .map(|kv| (kv.key.as_str().to_string(), kv.value))
1661            .collect::<HashMap<_, _>>();
1662        assert_eq!(attributes.get("thread.name"), expected_name.as_ref());
1663        assert_eq!(attributes.get("thread.id"), Some(&expected_id));
1664    }
1665
1666    #[test]
1667    fn excludes_thread() {
1668        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1669        let subscriber = tracing_subscriber::registry()
1670            .with(layer().with_tracer(tracer.clone()).with_threads(false));
1671
1672        tracing::subscriber::with_default(subscriber, || {
1673            tracing::debug_span!("request");
1674        });
1675
1676        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1677        let keys = attributes
1678            .iter()
1679            .map(|kv| kv.key.as_str())
1680            .collect::<Vec<&str>>();
1681        assert!(!keys.contains(&"thread.name"));
1682        assert!(!keys.contains(&"thread.id"));
1683    }
1684
1685    #[test]
1686    fn includes_level() {
1687        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1688        let subscriber = tracing_subscriber::registry()
1689            .with(layer().with_tracer(tracer.clone()).with_level(true));
1690
1691        tracing::subscriber::with_default(subscriber, || {
1692            tracing::debug_span!("request");
1693        });
1694
1695        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1696        let keys = attributes
1697            .iter()
1698            .map(|kv| kv.key.as_str())
1699            .collect::<Vec<&str>>();
1700        assert!(keys.contains(&"level"));
1701    }
1702
1703    #[test]
1704    fn excludes_level() {
1705        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1706        let subscriber = tracing_subscriber::registry()
1707            .with(layer().with_tracer(tracer.clone()).with_level(false));
1708
1709        tracing::subscriber::with_default(subscriber, || {
1710            tracing::debug_span!("request");
1711        });
1712
1713        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1714        let keys = attributes
1715            .iter()
1716            .map(|kv| kv.key.as_str())
1717            .collect::<Vec<&str>>();
1718        assert!(!keys.contains(&"level"));
1719    }
1720
1721    #[test]
1722    fn propagates_error_fields_from_event_to_span() {
1723        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1724        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1725
1726        let err = TestDynError::new("base error")
1727            .with_parent("intermediate error")
1728            .with_parent("user error");
1729
1730        tracing::subscriber::with_default(subscriber, || {
1731            let _guard = tracing::debug_span!("request",).entered();
1732
1733            tracing::error!(
1734                error = &err as &(dyn std::error::Error + 'static),
1735                "request error!"
1736            )
1737        });
1738
1739        let attributes = tracer
1740            .0
1741            .lock()
1742            .unwrap()
1743            .as_ref()
1744            .unwrap()
1745            .builder
1746            .attributes
1747            .as_ref()
1748            .unwrap()
1749            .clone();
1750
1751        let key_values = attributes
1752            .into_iter()
1753            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1754            .collect::<HashMap<_, _>>();
1755
1756        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1757        assert_eq!(
1758            key_values[FIELD_EXCEPTION_STACKTRACE],
1759            Value::Array(
1760                vec![
1761                    StringValue::from("intermediate error"),
1762                    StringValue::from("base error")
1763                ]
1764                .into()
1765            )
1766        );
1767    }
1768
1769    #[test]
1770    fn propagates_no_error_fields_from_event_to_span() {
1771        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1772        let subscriber = tracing_subscriber::registry().with(
1773            layer()
1774                .with_error_fields_to_exceptions(false)
1775                .with_tracer(tracer.clone()),
1776        );
1777
1778        let err = TestDynError::new("base error")
1779            .with_parent("intermediate error")
1780            .with_parent("user error");
1781
1782        tracing::subscriber::with_default(subscriber, || {
1783            let _guard = tracing::debug_span!("request",).entered();
1784
1785            tracing::error!(
1786                error = &err as &(dyn std::error::Error + 'static),
1787                "request error!"
1788            )
1789        });
1790
1791        let attributes = tracer
1792            .0
1793            .lock()
1794            .unwrap()
1795            .as_ref()
1796            .unwrap()
1797            .builder
1798            .attributes
1799            .as_ref()
1800            .unwrap()
1801            .clone();
1802
1803        let key_values = attributes
1804            .into_iter()
1805            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1806            .collect::<HashMap<_, _>>();
1807
1808        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1809        assert_eq!(
1810            key_values[FIELD_EXCEPTION_STACKTRACE],
1811            Value::Array(
1812                vec![
1813                    StringValue::from("intermediate error"),
1814                    StringValue::from("base error")
1815                ]
1816                .into()
1817            )
1818        );
1819    }
1820
1821    #[test]
1822    fn tracing_error_compatibility() {
1823        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1824        let subscriber = tracing_subscriber::registry()
1825            .with(
1826                layer()
1827                    .with_error_fields_to_exceptions(false)
1828                    .with_tracer(tracer.clone()),
1829            )
1830            .with(tracing_error::ErrorLayer::default());
1831
1832        tracing::subscriber::with_default(subscriber, || {
1833            let span = tracing::info_span!("Blows up!", exception = tracing::field::Empty);
1834            let _entered = span.enter();
1835            let context = tracing_error::SpanTrace::capture();
1836
1837            // This can cause a deadlock if `on_record` locks extensions while attributes are visited
1838            span.record("exception", tracing::field::debug(&context));
1839            // This can cause a deadlock if `on_event` locks extensions while the event is visited
1840            tracing::info!(exception = &tracing::field::debug(&context), "hello");
1841        });
1842
1843        // No need to assert anything, as long as this finished (and did not panic), everything is ok.
1844    }
1845}