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