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    pub fn with_tracked_inactivity(self, tracked_inactivity: bool) -> Self {
733        Self {
734            tracked_inactivity,
735            ..self
736        }
737    }
738
739    /// Sets whether or not spans record additional attributes for the thread
740    /// name and thread ID of the thread they were created on, following the
741    /// [OpenTelemetry semantic conventions for threads][conv].
742    ///
743    /// By default, thread attributes are enabled.
744    ///
745    /// [conv]: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/attributes.md#general-thread-attributes/
746    pub fn with_threads(self, threads: bool) -> Self {
747        Self {
748            with_threads: threads,
749            ..self
750        }
751    }
752
753    /// Sets whether or not span metadata should include the `tracing` verbosity level information as a `level` field.
754    ///
755    /// The level is always added to events, and based on [`OpenTelemetryLayer::with_error_events_to_status`]
756    /// error-level events will mark the span status as an error.
757    ///
758    /// By default, level information is disabled.
759    pub fn with_level(self, level: bool) -> Self {
760        Self {
761            with_level: level,
762            ..self
763        }
764    }
765
766    /// Retrieve the parent OpenTelemetry [`Context`] from the current tracing
767    /// [`span`] through the [`Registry`]. This [`Context`] links spans to their
768    /// parent for proper hierarchical visualization.
769    ///
770    /// [`Context`]: opentelemetry::Context
771    /// [`span`]: tracing::Span
772    /// [`Registry`]: tracing_subscriber::Registry
773    fn parent_context(&self, attrs: &Attributes<'_>, ctx: &Context<'_, S>) -> OtelContext {
774        if let Some(parent) = attrs.parent() {
775            // A span can have an _explicit_ parent that is NOT seen by this `Layer` (for which
776            // `Context::span` returns `None`. This happens if the parent span is filtered away
777            // from the layer by a per-layer filter. In that case, we fall-through to the `else`
778            // case, and consider this span a root span.
779            //
780            // This is likely rare, as most users who use explicit parents will configure their
781            // filters so that children and parents are both seen, but it's not guaranteed. Also,
782            // if users configure their filter with a `reload` filter, it's possible that a parent
783            // and child have different filters as they are created with a filter change
784            // in-between.
785            //
786            // In these case, we prefer to emit a smaller span tree instead of panicking.
787            if let Some(span) = ctx.span(parent) {
788                let mut extensions = span.extensions_mut();
789                return extensions
790                    .get_mut::<OtelData>()
791                    .map(|builder| self.tracer.sampled_context(builder))
792                    .unwrap_or_default();
793            }
794        }
795
796        // Else if the span is inferred from context, look up any available current span.
797        if attrs.is_contextual() {
798            ctx.lookup_current()
799                .and_then(|span| {
800                    let mut extensions = span.extensions_mut();
801                    extensions
802                        .get_mut::<OtelData>()
803                        .map(|builder| self.tracer.sampled_context(builder))
804                })
805                .unwrap_or_else(OtelContext::current)
806        // Explicit root spans should have no parent context.
807        } else {
808            OtelContext::new()
809        }
810    }
811
812    fn get_context(
813        dispatch: &tracing::Dispatch,
814        id: &span::Id,
815        f: &mut dyn FnMut(&mut OtelData, &dyn PreSampledTracer),
816    ) {
817        let subscriber = dispatch
818            .downcast_ref::<S>()
819            .expect("subscriber should downcast to expected type; this is a bug!");
820        let span = subscriber
821            .span(id)
822            .expect("registry should have a span for the current ID");
823        let layer = dispatch
824            .downcast_ref::<OpenTelemetryLayer<S, T>>()
825            .expect("layer should downcast to expected type; this is a bug!");
826
827        let mut extensions = span.extensions_mut();
828        if let Some(builder) = extensions.get_mut::<OtelData>() {
829            f(builder, &layer.tracer);
830        }
831    }
832
833    fn extra_span_attrs(&self) -> usize {
834        let mut extra_attrs = 0;
835        if self.location {
836            extra_attrs += 3;
837        }
838        if self.with_threads {
839            extra_attrs += 2;
840        }
841        if self.with_level {
842            extra_attrs += 1;
843        }
844        extra_attrs
845    }
846}
847
848thread_local! {
849    static THREAD_ID: unsync::Lazy<u64> = unsync::Lazy::new(|| {
850        // OpenTelemetry's semantic conventions require the thread ID to be
851        // recorded as an integer, but `std::thread::ThreadId` does not expose
852        // the integer value on stable, so we have to convert it to a `usize` by
853        // parsing it. Since this requires allocating a `String`, store it in a
854        // thread local so we only have to do this once.
855        // TODO(eliza): once `std::thread::ThreadId::as_u64` is stabilized
856        // (https://github.com/rust-lang/rust/issues/67939), just use that.
857        thread_id_integer(thread::current().id())
858    });
859}
860
861impl<S, T> Layer<S> for OpenTelemetryLayer<S, T>
862where
863    S: Subscriber + for<'span> LookupSpan<'span>,
864    T: otel::Tracer + PreSampledTracer + 'static,
865{
866    /// Creates an [OpenTelemetry `Span`] for the corresponding [tracing `Span`].
867    ///
868    /// [OpenTelemetry `Span`]: opentelemetry::trace::Span
869    /// [tracing `Span`]: tracing::Span
870    fn on_new_span(&self, attrs: &Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
871        let span = ctx.span(id).expect("Span not found, this is a bug");
872        let mut extensions = span.extensions_mut();
873
874        if self.tracked_inactivity && extensions.get_mut::<Timings>().is_none() {
875            extensions.insert(Timings::new());
876        }
877
878        let parent_cx = self.parent_context(attrs, &ctx);
879        let mut builder = self
880            .tracer
881            .span_builder(attrs.metadata().name())
882            .with_start_time(crate::time::now())
883            // Eagerly assign span id so children have stable parent id
884            .with_span_id(self.tracer.new_span_id());
885
886        // Record new trace id if there is no active parent span
887        if !parent_cx.has_active_span() {
888            builder.trace_id = Some(self.tracer.new_trace_id());
889        }
890
891        let builder_attrs = builder.attributes.get_or_insert(Vec::with_capacity(
892            attrs.fields().len() + self.extra_span_attrs(),
893        ));
894
895        if self.location {
896            let meta = attrs.metadata();
897
898            if let Some(filename) = meta.file() {
899                builder_attrs.push(KeyValue::new("code.filepath", filename));
900            }
901
902            if let Some(module) = meta.module_path() {
903                builder_attrs.push(KeyValue::new("code.namespace", module));
904            }
905
906            if let Some(line) = meta.line() {
907                builder_attrs.push(KeyValue::new("code.lineno", line as i64));
908            }
909        }
910
911        if self.with_threads {
912            THREAD_ID.with(|id| builder_attrs.push(KeyValue::new("thread.id", **id as i64)));
913            if let Some(name) = std::thread::current().name() {
914                // TODO(eliza): it's a bummer that we have to allocate here, but
915                // we can't easily get the string as a `static`. it would be
916                // nice if `opentelemetry` could also take `Arc<str>`s as
917                // `String` values...
918                builder_attrs.push(KeyValue::new("thread.name", name.to_string()));
919            }
920        }
921
922        if self.with_level {
923            builder_attrs.push(KeyValue::new("level", attrs.metadata().level().as_str()));
924        }
925
926        let mut updates = SpanBuilderUpdates::default();
927        attrs.record(&mut SpanAttributeVisitor {
928            span_builder_updates: &mut updates,
929            sem_conv_config: self.sem_conv_config,
930        });
931
932        updates.update(&mut builder);
933        extensions.insert(OtelData { builder, parent_cx });
934    }
935
936    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
937        if !self.tracked_inactivity {
938            return;
939        }
940
941        let span = ctx.span(id).expect("Span not found, this is a bug");
942        let mut extensions = span.extensions_mut();
943
944        if let Some(timings) = extensions.get_mut::<Timings>() {
945            let now = Instant::now();
946            timings.idle += (now - timings.last).as_nanos() as i64;
947            timings.last = now;
948        }
949    }
950
951    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
952        let span = ctx.span(id).expect("Span not found, this is a bug");
953        let mut extensions = span.extensions_mut();
954
955        if let Some(otel_data) = extensions.get_mut::<OtelData>() {
956            otel_data.builder.end_time = Some(crate::time::now());
957        }
958
959        if !self.tracked_inactivity {
960            return;
961        }
962
963        if let Some(timings) = extensions.get_mut::<Timings>() {
964            let now = Instant::now();
965            timings.busy += (now - timings.last).as_nanos() as i64;
966            timings.last = now;
967        }
968    }
969
970    /// Record OpenTelemetry [`attributes`] for the given values.
971    ///
972    /// [`attributes`]: opentelemetry::trace::SpanBuilder::attributes
973    fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
974        let span = ctx.span(id).expect("Span not found, this is a bug");
975        let mut updates = SpanBuilderUpdates::default();
976        values.record(&mut SpanAttributeVisitor {
977            span_builder_updates: &mut updates,
978            sem_conv_config: self.sem_conv_config,
979        });
980        let mut extensions = span.extensions_mut();
981        if let Some(data) = extensions.get_mut::<OtelData>() {
982            updates.update(&mut data.builder);
983        }
984    }
985
986    fn on_follows_from(&self, id: &Id, follows: &Id, ctx: Context<S>) {
987        let span = ctx.span(id).expect("Span not found, this is a bug");
988        let mut extensions = span.extensions_mut();
989        let data = extensions
990            .get_mut::<OtelData>()
991            .expect("Missing otel data span extensions");
992
993        // The follows span may be filtered away (or closed), from this layer,
994        // in which case we just drop the data, as opposed to panicking. This
995        // uses the same reasoning as `parent_context` above.
996        if let Some(follows_span) = ctx.span(follows) {
997            let mut follows_extensions = follows_span.extensions_mut();
998            let follows_data = follows_extensions
999                .get_mut::<OtelData>()
1000                .expect("Missing otel data span extensions");
1001
1002            let follows_context = self
1003                .tracer
1004                .sampled_context(follows_data)
1005                .span()
1006                .span_context()
1007                .clone();
1008            let follows_link = otel::Link::with_context(follows_context);
1009            if let Some(ref mut links) = data.builder.links {
1010                links.push(follows_link);
1011            } else {
1012                data.builder.links = Some(vec![follows_link]);
1013            }
1014        }
1015    }
1016
1017    /// Records OpenTelemetry [`Event`] data on event.
1018    ///
1019    /// Note: an [`ERROR`]-level event will also set the OpenTelemetry span status code to
1020    /// [`Error`], signaling that an error has occurred.
1021    ///
1022    /// [`Event`]: opentelemetry::trace::Event
1023    /// [`ERROR`]: tracing::Level::ERROR
1024    /// [`Error`]: opentelemetry::trace::StatusCode::Error
1025    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
1026        // Ignore events that are not in the context of a span
1027        if let Some(span) = event.parent().and_then(|id| ctx.span(id)).or_else(|| {
1028            event
1029                .is_contextual()
1030                .then(|| ctx.lookup_current())
1031                .flatten()
1032        }) {
1033            // Performing read operations before getting a write lock to avoid a deadlock
1034            // See https://github.com/tokio-rs/tracing/issues/763
1035            #[cfg(feature = "tracing-log")]
1036            let normalized_meta = event.normalized_metadata();
1037            #[cfg(feature = "tracing-log")]
1038            let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
1039            #[cfg(not(feature = "tracing-log"))]
1040            let meta = event.metadata();
1041
1042            let target = Key::new("target");
1043
1044            #[cfg(feature = "tracing-log")]
1045            let target = if normalized_meta.is_some() {
1046                KeyValue::new(target, Value::String(meta.target().to_owned().into()))
1047            } else {
1048                KeyValue::new(target, Value::String(event.metadata().target().into()))
1049            };
1050
1051            #[cfg(not(feature = "tracing-log"))]
1052            let target = KeyValue::new(target, Value::String(meta.target().into()));
1053
1054            let mut otel_event = otel::Event::new(
1055                String::new(),
1056                crate::time::now(),
1057                vec![
1058                    KeyValue::new(
1059                        Key::new("level"),
1060                        Value::String(meta.level().as_str().into()),
1061                    ),
1062                    target,
1063                ],
1064                0,
1065            );
1066
1067            let mut builder_updates = None;
1068            event.record(&mut SpanEventVisitor {
1069                event_builder: &mut otel_event,
1070                span_builder_updates: &mut builder_updates,
1071                sem_conv_config: self.sem_conv_config,
1072            });
1073
1074            // If the event name is still empty, then there was no special handling of error fields.
1075            // It should be safe to set the event name to the name provided by tracing.
1076            // 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.
1077            // Ideally, the name should be set above when the event is constructed.
1078            // see: https://github.com/tokio-rs/tracing-opentelemetry/pull/28
1079            if otel_event.name.is_empty() {
1080                otel_event.name = std::borrow::Cow::Borrowed(event.metadata().name());
1081            }
1082
1083            let mut extensions = span.extensions_mut();
1084            let otel_data = extensions.get_mut::<OtelData>();
1085
1086            if let Some(otel_data) = otel_data {
1087                let builder = &mut otel_data.builder;
1088
1089                if builder.status == otel::Status::Unset
1090                    && *meta.level() == tracing_core::Level::ERROR
1091                {
1092                    builder.status = otel::Status::error("")
1093                }
1094
1095                if let Some(builder_updates) = builder_updates {
1096                    builder_updates.update(builder);
1097                }
1098
1099                if self.location {
1100                    #[cfg(not(feature = "tracing-log"))]
1101                    let normalized_meta: Option<tracing_core::Metadata<'_>> = None;
1102                    let (file, module) = match &normalized_meta {
1103                        Some(meta) => (
1104                            meta.file().map(|s| Value::from(s.to_owned())),
1105                            meta.module_path().map(|s| Value::from(s.to_owned())),
1106                        ),
1107                        None => (
1108                            event.metadata().file().map(Value::from),
1109                            event.metadata().module_path().map(Value::from),
1110                        ),
1111                    };
1112
1113                    if let Some(file) = file {
1114                        otel_event
1115                            .attributes
1116                            .push(KeyValue::new("code.filepath", file));
1117                    }
1118                    if let Some(module) = module {
1119                        otel_event
1120                            .attributes
1121                            .push(KeyValue::new("code.namespace", module));
1122                    }
1123                    if let Some(line) = meta.line() {
1124                        otel_event
1125                            .attributes
1126                            .push(KeyValue::new("code.lineno", line as i64));
1127                    }
1128                }
1129
1130                if let Some(ref mut events) = builder.events {
1131                    events.push(otel_event);
1132                } else {
1133                    builder.events = Some(vec![otel_event]);
1134                }
1135            }
1136        };
1137    }
1138
1139    /// Exports an OpenTelemetry [`Span`] on close.
1140    ///
1141    /// [`Span`]: opentelemetry::trace::Span
1142    fn on_close(&self, id: span::Id, ctx: Context<'_, S>) {
1143        let span = ctx.span(&id).expect("Span not found, this is a bug");
1144        let (otel_data, timings) = {
1145            let mut extensions = span.extensions_mut();
1146            let timings = if self.tracked_inactivity {
1147                extensions.remove::<Timings>()
1148            } else {
1149                None
1150            };
1151            (extensions.remove::<OtelData>(), timings)
1152        };
1153
1154        if let Some(OtelData {
1155            mut builder,
1156            parent_cx,
1157        }) = otel_data
1158        {
1159            // Append busy/idle timings when enabled.
1160            if let Some(timings) = timings {
1161                let busy_ns = Key::new("busy_ns");
1162                let idle_ns = Key::new("idle_ns");
1163
1164                let attributes = builder
1165                    .attributes
1166                    .get_or_insert_with(|| Vec::with_capacity(2));
1167                attributes.push(KeyValue::new(busy_ns, timings.busy));
1168                attributes.push(KeyValue::new(idle_ns, timings.idle));
1169            }
1170
1171            // Build and start span, drop span to export
1172            builder.start_with_context(&self.tracer, &parent_cx);
1173        }
1174    }
1175
1176    // SAFETY: this is safe because the `WithContext` function pointer is valid
1177    // for the lifetime of `&self`.
1178    unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> {
1179        match id {
1180            id if id == TypeId::of::<Self>() => Some(self as *const _ as *const ()),
1181            id if id == TypeId::of::<WithContext>() => {
1182                Some(&self.get_context as *const _ as *const ())
1183            }
1184            _ => None,
1185        }
1186    }
1187}
1188
1189struct Timings {
1190    idle: i64,
1191    busy: i64,
1192    last: Instant,
1193}
1194
1195impl Timings {
1196    fn new() -> Self {
1197        Self {
1198            idle: 0,
1199            busy: 0,
1200            last: Instant::now(),
1201        }
1202    }
1203}
1204
1205fn thread_id_integer(id: thread::ThreadId) -> u64 {
1206    let thread_id = format!("{:?}", id);
1207    thread_id
1208        .trim_start_matches("ThreadId(")
1209        .trim_end_matches(')')
1210        .parse::<u64>()
1211        .expect("thread ID should parse as an integer")
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216    use super::*;
1217    use opentelemetry::trace::{SpanContext, TraceFlags};
1218    use std::{
1219        collections::HashMap,
1220        error::Error,
1221        fmt::Display,
1222        sync::{Arc, Mutex},
1223        time::SystemTime,
1224    };
1225    use tracing_subscriber::prelude::*;
1226
1227    #[derive(Debug, Clone)]
1228    struct TestTracer(Arc<Mutex<Option<OtelData>>>);
1229    impl otel::Tracer for TestTracer {
1230        type Span = noop::NoopSpan;
1231        fn start_with_context<T>(&self, _name: T, _context: &OtelContext) -> Self::Span
1232        where
1233            T: Into<Cow<'static, str>>,
1234        {
1235            noop::NoopSpan::DEFAULT
1236        }
1237        fn span_builder<T>(&self, name: T) -> otel::SpanBuilder
1238        where
1239            T: Into<Cow<'static, str>>,
1240        {
1241            otel::SpanBuilder::from_name(name)
1242        }
1243        fn build_with_context(
1244            &self,
1245            builder: otel::SpanBuilder,
1246            parent_cx: &OtelContext,
1247        ) -> Self::Span {
1248            *self.0.lock().unwrap() = Some(OtelData {
1249                builder,
1250                parent_cx: parent_cx.clone(),
1251            });
1252            noop::NoopSpan::DEFAULT
1253        }
1254    }
1255
1256    impl PreSampledTracer for TestTracer {
1257        fn sampled_context(&self, _builder: &mut crate::OtelData) -> OtelContext {
1258            OtelContext::new()
1259        }
1260        fn new_trace_id(&self) -> otel::TraceId {
1261            otel::TraceId::INVALID
1262        }
1263        fn new_span_id(&self) -> otel::SpanId {
1264            otel::SpanId::INVALID
1265        }
1266    }
1267
1268    impl TestTracer {
1269        fn with_data<T>(&self, f: impl FnOnce(&OtelData) -> T) -> T {
1270            let lock = self.0.lock().unwrap();
1271            let data = lock.as_ref().expect("no span data has been recorded yet");
1272            f(data)
1273        }
1274    }
1275
1276    #[derive(Debug, Clone)]
1277    struct TestSpan(otel::SpanContext);
1278    impl otel::Span for TestSpan {
1279        fn add_event_with_timestamp<T: Into<Cow<'static, str>>>(
1280            &mut self,
1281            _: T,
1282            _: SystemTime,
1283            _: Vec<KeyValue>,
1284        ) {
1285        }
1286        fn span_context(&self) -> &otel::SpanContext {
1287            &self.0
1288        }
1289        fn is_recording(&self) -> bool {
1290            false
1291        }
1292        fn set_attribute(&mut self, _attribute: KeyValue) {}
1293        fn set_status(&mut self, _status: otel::Status) {}
1294        fn update_name<T: Into<Cow<'static, str>>>(&mut self, _new_name: T) {}
1295        fn add_link(&mut self, _span_context: SpanContext, _attributes: Vec<KeyValue>) {}
1296        fn end_with_timestamp(&mut self, _timestamp: SystemTime) {}
1297    }
1298
1299    #[derive(Debug)]
1300    struct TestDynError {
1301        msg: &'static str,
1302        source: Option<Box<TestDynError>>,
1303    }
1304    impl Display for TestDynError {
1305        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1306            write!(f, "{}", self.msg)
1307        }
1308    }
1309    impl Error for TestDynError {
1310        fn source(&self) -> Option<&(dyn Error + 'static)> {
1311            match &self.source {
1312                Some(source) => Some(source),
1313                None => None,
1314            }
1315        }
1316    }
1317    impl TestDynError {
1318        fn new(msg: &'static str) -> Self {
1319            Self { msg, source: None }
1320        }
1321        fn with_parent(self, parent_msg: &'static str) -> Self {
1322            Self {
1323                msg: parent_msg,
1324                source: Some(Box::new(self)),
1325            }
1326        }
1327    }
1328
1329    #[test]
1330    fn dynamic_span_names() {
1331        let dynamic_name = "GET http://example.com".to_string();
1332        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1333        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1334
1335        tracing::subscriber::with_default(subscriber, || {
1336            tracing::debug_span!("static_name", otel.name = dynamic_name.as_str());
1337        });
1338
1339        let recorded_name = tracer
1340            .0
1341            .lock()
1342            .unwrap()
1343            .as_ref()
1344            .map(|b| b.builder.name.clone());
1345        assert_eq!(recorded_name, Some(dynamic_name.into()))
1346    }
1347
1348    #[test]
1349    fn span_kind() {
1350        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1351        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1352
1353        tracing::subscriber::with_default(subscriber, || {
1354            tracing::debug_span!("request", otel.kind = "server");
1355        });
1356
1357        let recorded_kind = tracer.with_data(|data| data.builder.span_kind.clone());
1358        assert_eq!(recorded_kind, Some(otel::SpanKind::Server))
1359    }
1360
1361    #[test]
1362    fn span_status_code() {
1363        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1364        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1365
1366        tracing::subscriber::with_default(subscriber, || {
1367            tracing::debug_span!("request", otel.status_code = ?otel::Status::Ok);
1368        });
1369
1370        let recorded_status = tracer.with_data(|data| data.builder.status.clone());
1371        assert_eq!(recorded_status, otel::Status::Ok)
1372    }
1373
1374    #[test]
1375    fn span_status_message() {
1376        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1377        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1378
1379        let message = "message";
1380
1381        tracing::subscriber::with_default(subscriber, || {
1382            tracing::debug_span!("request", otel.status_message = message);
1383        });
1384
1385        let recorded_status_message = tracer
1386            .0
1387            .lock()
1388            .unwrap()
1389            .as_ref()
1390            .unwrap()
1391            .builder
1392            .status
1393            .clone();
1394
1395        assert_eq!(recorded_status_message, otel::Status::error(message))
1396    }
1397
1398    #[test]
1399    fn trace_id_from_existing_context() {
1400        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1401        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1402        let trace_id = otel::TraceId::from(42u128);
1403        let existing_cx = OtelContext::current_with_span(TestSpan(otel::SpanContext::new(
1404            trace_id,
1405            otel::SpanId::from(1u64),
1406            TraceFlags::default(),
1407            false,
1408            Default::default(),
1409        )));
1410        let _g = existing_cx.attach();
1411
1412        tracing::subscriber::with_default(subscriber, || {
1413            tracing::debug_span!("request", otel.kind = "server");
1414        });
1415
1416        let recorded_trace_id =
1417            tracer.with_data(|data| data.parent_cx.span().span_context().trace_id());
1418        assert_eq!(recorded_trace_id, trace_id)
1419    }
1420
1421    #[test]
1422    fn includes_timings() {
1423        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1424        let subscriber = tracing_subscriber::registry().with(
1425            layer()
1426                .with_tracer(tracer.clone())
1427                .with_tracked_inactivity(true),
1428        );
1429
1430        tracing::subscriber::with_default(subscriber, || {
1431            tracing::debug_span!("request");
1432        });
1433
1434        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1435        let keys = attributes
1436            .iter()
1437            .map(|kv| kv.key.as_str())
1438            .collect::<Vec<&str>>();
1439        assert!(keys.contains(&"idle_ns"));
1440        assert!(keys.contains(&"busy_ns"));
1441    }
1442
1443    #[test]
1444    fn records_error_fields() {
1445        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1446        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1447
1448        let err = TestDynError::new("base error")
1449            .with_parent("intermediate error")
1450            .with_parent("user error");
1451
1452        tracing::subscriber::with_default(subscriber, || {
1453            tracing::debug_span!(
1454                "request",
1455                error = &err as &(dyn std::error::Error + 'static)
1456            );
1457        });
1458
1459        let attributes = tracer
1460            .0
1461            .lock()
1462            .unwrap()
1463            .as_ref()
1464            .unwrap()
1465            .builder
1466            .attributes
1467            .as_ref()
1468            .unwrap()
1469            .clone();
1470
1471        let key_values = attributes
1472            .into_iter()
1473            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1474            .collect::<HashMap<_, _>>();
1475
1476        assert_eq!(key_values["error"].as_str(), "user error");
1477        assert_eq!(
1478            key_values["error.chain"],
1479            Value::Array(
1480                vec![
1481                    StringValue::from("intermediate error"),
1482                    StringValue::from("base error")
1483                ]
1484                .into()
1485            )
1486        );
1487
1488        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1489        assert_eq!(
1490            key_values[FIELD_EXCEPTION_STACKTRACE],
1491            Value::Array(
1492                vec![
1493                    StringValue::from("intermediate error"),
1494                    StringValue::from("base error")
1495                ]
1496                .into()
1497            )
1498        );
1499    }
1500
1501    #[test]
1502    fn records_event_name() {
1503        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1504        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1505
1506        tracing::subscriber::with_default(subscriber, || {
1507            tracing::debug_span!("test span").in_scope(|| {
1508                tracing::event!(tracing::Level::INFO, "event name 1"); // this is equivalent to 'message = "event name 1"'
1509                tracing::event!(name: "event name 2", tracing::Level::INFO, field1 = "field1");
1510                tracing::event!(name: "event name 3", tracing::Level::INFO, error = "field2");
1511                tracing::event!(name: "event name 4", tracing::Level::INFO, message = "field3");
1512                tracing::event!(name: "event name 5", tracing::Level::INFO, name = "field4");
1513            });
1514        });
1515
1516        let events = tracer
1517            .0
1518            .lock()
1519            .unwrap()
1520            .as_ref()
1521            .unwrap()
1522            .builder
1523            .events
1524            .as_ref()
1525            .unwrap()
1526            .clone();
1527
1528        let mut iter = events.iter();
1529
1530        assert_eq!(iter.next().unwrap().name, "event name 1");
1531        assert_eq!(iter.next().unwrap().name, "event name 2");
1532        assert_eq!(iter.next().unwrap().name, "exception"); // error attribute is handled specially
1533        assert_eq!(iter.next().unwrap().name, "field3"); // message attribute is handled specially
1534        assert_eq!(iter.next().unwrap().name, "event name 5"); // name attribute should not conflict with event name.
1535    }
1536
1537    #[test]
1538    fn records_no_error_fields() {
1539        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1540        let subscriber = tracing_subscriber::registry().with(
1541            layer()
1542                .with_error_records_to_exceptions(false)
1543                .with_tracer(tracer.clone()),
1544        );
1545
1546        let err = TestDynError::new("base error")
1547            .with_parent("intermediate error")
1548            .with_parent("user error");
1549
1550        tracing::subscriber::with_default(subscriber, || {
1551            tracing::debug_span!(
1552                "request",
1553                error = &err as &(dyn std::error::Error + 'static)
1554            );
1555        });
1556
1557        let attributes = tracer
1558            .0
1559            .lock()
1560            .unwrap()
1561            .as_ref()
1562            .unwrap()
1563            .builder
1564            .attributes
1565            .as_ref()
1566            .unwrap()
1567            .clone();
1568
1569        let key_values = attributes
1570            .into_iter()
1571            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1572            .collect::<HashMap<_, _>>();
1573
1574        assert_eq!(key_values["error"].as_str(), "user error");
1575        assert_eq!(
1576            key_values["error.chain"],
1577            Value::Array(
1578                vec![
1579                    StringValue::from("intermediate error"),
1580                    StringValue::from("base error")
1581                ]
1582                .into()
1583            )
1584        );
1585
1586        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1587        assert_eq!(
1588            key_values[FIELD_EXCEPTION_STACKTRACE],
1589            Value::Array(
1590                vec![
1591                    StringValue::from("intermediate error"),
1592                    StringValue::from("base error")
1593                ]
1594                .into()
1595            )
1596        );
1597    }
1598
1599    #[test]
1600    fn includes_span_location() {
1601        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1602        let subscriber = tracing_subscriber::registry()
1603            .with(layer().with_tracer(tracer.clone()).with_location(true));
1604
1605        tracing::subscriber::with_default(subscriber, || {
1606            tracing::debug_span!("request");
1607        });
1608
1609        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1610        let keys = attributes
1611            .iter()
1612            .map(|kv| kv.key.as_str())
1613            .collect::<Vec<&str>>();
1614        assert!(keys.contains(&"code.filepath"));
1615        assert!(keys.contains(&"code.namespace"));
1616        assert!(keys.contains(&"code.lineno"));
1617    }
1618
1619    #[test]
1620    fn excludes_span_location() {
1621        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1622        let subscriber = tracing_subscriber::registry()
1623            .with(layer().with_tracer(tracer.clone()).with_location(false));
1624
1625        tracing::subscriber::with_default(subscriber, || {
1626            tracing::debug_span!("request");
1627        });
1628
1629        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1630        let keys = attributes
1631            .iter()
1632            .map(|kv| kv.key.as_str())
1633            .collect::<Vec<&str>>();
1634        assert!(!keys.contains(&"code.filepath"));
1635        assert!(!keys.contains(&"code.namespace"));
1636        assert!(!keys.contains(&"code.lineno"));
1637    }
1638
1639    #[test]
1640    fn includes_thread() {
1641        let thread = thread::current();
1642        let expected_name = thread
1643            .name()
1644            .map(|name| Value::String(name.to_owned().into()));
1645        let expected_id = Value::I64(thread_id_integer(thread.id()) as i64);
1646
1647        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1648        let subscriber = tracing_subscriber::registry()
1649            .with(layer().with_tracer(tracer.clone()).with_threads(true));
1650
1651        tracing::subscriber::with_default(subscriber, || {
1652            tracing::debug_span!("request");
1653        });
1654
1655        let attributes = tracer
1656            .with_data(|data| data.builder.attributes.as_ref().unwrap().clone())
1657            .drain(..)
1658            .map(|kv| (kv.key.as_str().to_string(), kv.value))
1659            .collect::<HashMap<_, _>>();
1660        assert_eq!(attributes.get("thread.name"), expected_name.as_ref());
1661        assert_eq!(attributes.get("thread.id"), Some(&expected_id));
1662    }
1663
1664    #[test]
1665    fn excludes_thread() {
1666        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1667        let subscriber = tracing_subscriber::registry()
1668            .with(layer().with_tracer(tracer.clone()).with_threads(false));
1669
1670        tracing::subscriber::with_default(subscriber, || {
1671            tracing::debug_span!("request");
1672        });
1673
1674        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1675        let keys = attributes
1676            .iter()
1677            .map(|kv| kv.key.as_str())
1678            .collect::<Vec<&str>>();
1679        assert!(!keys.contains(&"thread.name"));
1680        assert!(!keys.contains(&"thread.id"));
1681    }
1682
1683    #[test]
1684    fn includes_level() {
1685        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1686        let subscriber = tracing_subscriber::registry()
1687            .with(layer().with_tracer(tracer.clone()).with_level(true));
1688
1689        tracing::subscriber::with_default(subscriber, || {
1690            tracing::debug_span!("request");
1691        });
1692
1693        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1694        let keys = attributes
1695            .iter()
1696            .map(|kv| kv.key.as_str())
1697            .collect::<Vec<&str>>();
1698        assert!(keys.contains(&"level"));
1699    }
1700
1701    #[test]
1702    fn excludes_level() {
1703        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1704        let subscriber = tracing_subscriber::registry()
1705            .with(layer().with_tracer(tracer.clone()).with_level(false));
1706
1707        tracing::subscriber::with_default(subscriber, || {
1708            tracing::debug_span!("request");
1709        });
1710
1711        let attributes = tracer.with_data(|data| data.builder.attributes.as_ref().unwrap().clone());
1712        let keys = attributes
1713            .iter()
1714            .map(|kv| kv.key.as_str())
1715            .collect::<Vec<&str>>();
1716        assert!(!keys.contains(&"level"));
1717    }
1718
1719    #[test]
1720    fn propagates_error_fields_from_event_to_span() {
1721        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1722        let subscriber = tracing_subscriber::registry().with(layer().with_tracer(tracer.clone()));
1723
1724        let err = TestDynError::new("base error")
1725            .with_parent("intermediate error")
1726            .with_parent("user error");
1727
1728        tracing::subscriber::with_default(subscriber, || {
1729            let _guard = tracing::debug_span!("request",).entered();
1730
1731            tracing::error!(
1732                error = &err as &(dyn std::error::Error + 'static),
1733                "request error!"
1734            )
1735        });
1736
1737        let attributes = tracer
1738            .0
1739            .lock()
1740            .unwrap()
1741            .as_ref()
1742            .unwrap()
1743            .builder
1744            .attributes
1745            .as_ref()
1746            .unwrap()
1747            .clone();
1748
1749        let key_values = attributes
1750            .into_iter()
1751            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1752            .collect::<HashMap<_, _>>();
1753
1754        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1755        assert_eq!(
1756            key_values[FIELD_EXCEPTION_STACKTRACE],
1757            Value::Array(
1758                vec![
1759                    StringValue::from("intermediate error"),
1760                    StringValue::from("base error")
1761                ]
1762                .into()
1763            )
1764        );
1765    }
1766
1767    #[test]
1768    fn propagates_no_error_fields_from_event_to_span() {
1769        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1770        let subscriber = tracing_subscriber::registry().with(
1771            layer()
1772                .with_error_fields_to_exceptions(false)
1773                .with_tracer(tracer.clone()),
1774        );
1775
1776        let err = TestDynError::new("base error")
1777            .with_parent("intermediate error")
1778            .with_parent("user error");
1779
1780        tracing::subscriber::with_default(subscriber, || {
1781            let _guard = tracing::debug_span!("request",).entered();
1782
1783            tracing::error!(
1784                error = &err as &(dyn std::error::Error + 'static),
1785                "request error!"
1786            )
1787        });
1788
1789        let attributes = tracer
1790            .0
1791            .lock()
1792            .unwrap()
1793            .as_ref()
1794            .unwrap()
1795            .builder
1796            .attributes
1797            .as_ref()
1798            .unwrap()
1799            .clone();
1800
1801        let key_values = attributes
1802            .into_iter()
1803            .map(|kv| (kv.key.as_str().to_owned(), kv.value))
1804            .collect::<HashMap<_, _>>();
1805
1806        assert_eq!(key_values[FIELD_EXCEPTION_MESSAGE].as_str(), "user error");
1807        assert_eq!(
1808            key_values[FIELD_EXCEPTION_STACKTRACE],
1809            Value::Array(
1810                vec![
1811                    StringValue::from("intermediate error"),
1812                    StringValue::from("base error")
1813                ]
1814                .into()
1815            )
1816        );
1817    }
1818
1819    #[test]
1820    fn tracing_error_compatibility() {
1821        let tracer = TestTracer(Arc::new(Mutex::new(None)));
1822        let subscriber = tracing_subscriber::registry()
1823            .with(
1824                layer()
1825                    .with_error_fields_to_exceptions(false)
1826                    .with_tracer(tracer.clone()),
1827            )
1828            .with(tracing_error::ErrorLayer::default());
1829
1830        tracing::subscriber::with_default(subscriber, || {
1831            let span = tracing::info_span!("Blows up!", exception = tracing::field::Empty);
1832            let _entered = span.enter();
1833            let context = tracing_error::SpanTrace::capture();
1834
1835            // This can cause a deadlock if `on_record` locks extensions while attributes are visited
1836            span.record("exception", tracing::field::debug(&context));
1837            // This can cause a deadlock if `on_event` locks extensions while the event is visited
1838            tracing::info!(exception = &tracing::field::debug(&context), "hello");
1839        });
1840
1841        // No need to assert anything, as long as this finished (and did not panic), everything is ok.
1842    }
1843}