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
32pub 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
57pub fn layer<S>() -> OpenTelemetryLayer<S, noop::NoopTracer>
73where
74 S: Subscriber + for<'span> LookupSpan<'span>,
75{
76 OpenTelemetryLayer::default()
77}
78
79pub(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 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 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 #[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 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 #[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 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 #[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 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 "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 #[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 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 "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 #[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 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 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 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#[derive(Clone, Copy)]
368struct SemConvConfig {
369 error_fields_to_exceptions: bool,
374
375 error_records_to_exceptions: bool,
380
381 error_events_to_status: bool,
390
391 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 fn record_bool(&mut self, field: &field::Field, value: bool) {
420 self.record(KeyValue::new(field.name(), value));
421 }
422
423 fn record_f64(&mut self, field: &field::Field, value: f64) {
427 self.record(KeyValue::new(field.name(), value));
428 }
429
430 fn record_i64(&mut self, field: &field::Field, value: i64) {
434 self.record(KeyValue::new(field.name(), value));
435 }
436
437 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 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 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 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 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 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 }
629 }
630
631 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 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 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 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 pub fn with_location(self, location: bool) -> Self {
726 Self { location, ..self }
727 }
728
729 pub fn with_tracked_inactivity(self, tracked_inactivity: bool) -> Self {
733 Self {
734 tracked_inactivity,
735 ..self
736 }
737 }
738
739 pub fn with_threads(self, threads: bool) -> Self {
747 Self {
748 with_threads: threads,
749 ..self
750 }
751 }
752
753 pub fn with_level(self, level: bool) -> Self {
760 Self {
761 with_level: level,
762 ..self
763 }
764 }
765
766 fn parent_context(&self, attrs: &Attributes<'_>, ctx: &Context<'_, S>) -> OtelContext {
774 if let Some(parent) = attrs.parent() {
775 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 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 } 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 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 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 .with_span_id(self.tracer.new_span_id());
885
886 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 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 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 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 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
1026 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 #[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 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 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 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 builder.start_with_context(&self.tracer, &parent_cx);
1173 }
1174 }
1175
1176 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"); 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"); assert_eq!(iter.next().unwrap().name, "field3"); assert_eq!(iter.next().unwrap().name, "event name 5"); }
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 span.record("exception", tracing::field::debug(&context));
1837 tracing::info!(exception = &tracing::field::debug(&context), "hello");
1839 });
1840
1841 }
1843}