1use opentelemetry::{
2 logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity},
3 InstrumentationScope, Key,
4};
5use std::borrow::Cow;
6use tracing_core::Level;
7#[cfg(feature = "experimental_metadata_attributes")]
8use tracing_core::Metadata;
9#[cfg(feature = "experimental_metadata_attributes")]
10use tracing_log::NormalizeEvent;
11use tracing_subscriber::{registry::LookupSpan, Layer};
12
13const INSTRUMENTATION_LIBRARY_NAME: &str = "opentelemetry-appender-tracing";
14
15struct EventVisitor<'a, LR: LogRecord> {
17 log_record: &'a mut LR,
18}
19
20#[cfg(feature = "experimental_metadata_attributes")]
22fn is_duplicated_metadata(field: &'static str) -> bool {
23 field
24 .strip_prefix("log.")
25 .map(|remainder| matches!(remainder, "file" | "line" | "module_path" | "target"))
26 .unwrap_or(false)
27}
28
29#[cfg(feature = "experimental_metadata_attributes")]
30fn get_filename(filepath: &str) -> &str {
31 if let Some((_, filename)) = filepath.rsplit_once('/') {
32 return filename;
33 }
34 if let Some((_, filename)) = filepath.rsplit_once('\\') {
35 return filename;
36 }
37 filepath
38}
39
40impl<'a, LR: LogRecord> EventVisitor<'a, LR> {
41 fn new(log_record: &'a mut LR) -> Self {
42 EventVisitor { log_record }
43 }
44
45 #[cfg(feature = "experimental_metadata_attributes")]
46 fn visit_experimental_metadata(&mut self, meta: &Metadata) {
47 if let Some(module_path) = meta.module_path() {
48 self.log_record.add_attribute(
49 Key::new("code.namespace"),
50 AnyValue::from(module_path.to_owned()),
51 );
52 }
53
54 if let Some(filepath) = meta.file() {
55 self.log_record.add_attribute(
56 Key::new("code.filepath"),
57 AnyValue::from(filepath.to_owned()),
58 );
59 self.log_record.add_attribute(
60 Key::new("code.filename"),
61 AnyValue::from(get_filename(filepath).to_owned()),
62 );
63 }
64
65 if let Some(line) = meta.line() {
66 self.log_record
67 .add_attribute(Key::new("code.lineno"), AnyValue::from(line));
68 }
69 }
70}
71
72impl<LR: LogRecord> tracing::field::Visit for EventVisitor<'_, LR> {
73 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
74 #[cfg(feature = "experimental_metadata_attributes")]
75 if is_duplicated_metadata(field.name()) {
76 return;
77 }
78 if field.name() == "message" {
79 self.log_record.set_body(format!("{:?}", value).into());
80 } else {
81 self.log_record
82 .add_attribute(Key::new(field.name()), AnyValue::from(format!("{value:?}")));
83 }
84 }
85
86 fn record_str(&mut self, field: &tracing_core::Field, value: &str) {
87 #[cfg(feature = "experimental_metadata_attributes")]
88 if is_duplicated_metadata(field.name()) {
89 return;
90 }
91 self.log_record
99 .add_attribute(Key::new(field.name()), AnyValue::from(value.to_owned()));
100 }
101
102 fn record_bool(&mut self, field: &tracing_core::Field, value: bool) {
103 self.log_record
104 .add_attribute(Key::new(field.name()), AnyValue::from(value));
105 }
106
107 fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
108 self.log_record
109 .add_attribute(Key::new(field.name()), AnyValue::from(value));
110 }
111
112 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
113 #[cfg(feature = "experimental_metadata_attributes")]
114 if is_duplicated_metadata(field.name()) {
115 return;
116 }
117 self.log_record
118 .add_attribute(Key::new(field.name()), AnyValue::from(value));
119 }
120
121 }
123
124pub struct OpenTelemetryTracingBridge<P, L>
125where
126 P: LoggerProvider<Logger = L> + Send + Sync,
127 L: Logger + Send + Sync,
128{
129 logger: L,
130 _phantom: std::marker::PhantomData<P>, }
132
133impl<P, L> OpenTelemetryTracingBridge<P, L>
134where
135 P: LoggerProvider<Logger = L> + Send + Sync,
136 L: Logger + Send + Sync,
137{
138 pub fn new(provider: &P) -> Self {
139 let scope = InstrumentationScope::builder(INSTRUMENTATION_LIBRARY_NAME)
140 .with_version(Cow::Borrowed(env!("CARGO_PKG_VERSION")))
141 .build();
142
143 OpenTelemetryTracingBridge {
144 logger: provider.logger_with_scope(scope),
145 _phantom: Default::default(),
146 }
147 }
148}
149
150impl<S, P, L> Layer<S> for OpenTelemetryTracingBridge<P, L>
151where
152 S: tracing::Subscriber + for<'a> LookupSpan<'a>,
153 P: LoggerProvider<Logger = L> + Send + Sync + 'static,
154 L: Logger + Send + Sync + 'static,
155{
156 fn on_event(
157 &self,
158 event: &tracing::Event<'_>,
159 _ctx: tracing_subscriber::layer::Context<'_, S>,
160 ) {
161 #[cfg(feature = "experimental_metadata_attributes")]
162 let normalized_meta = event.normalized_metadata();
163
164 #[cfg(feature = "experimental_metadata_attributes")]
165 let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
166
167 #[cfg(not(feature = "experimental_metadata_attributes"))]
168 let meta = event.metadata();
169
170 let mut log_record = self.logger.create_log_record();
171
172 log_record.set_target(meta.target().to_string());
174 log_record.set_event_name(meta.name());
175 log_record.set_severity_number(severity_of_level(meta.level()));
176 log_record.set_severity_text(meta.level().as_str());
177 let mut visitor = EventVisitor::new(&mut log_record);
178 #[cfg(feature = "experimental_metadata_attributes")]
179 visitor.visit_experimental_metadata(meta);
180 event.record(&mut visitor);
182
183 #[cfg(feature = "experimental_use_tracing_span_context")]
184 if let Some(span) = _ctx.event_span(event) {
185 use tracing_opentelemetry::OtelData;
186 let opt_span_id = span
187 .extensions()
188 .get::<OtelData>()
189 .and_then(|otd| otd.builder.span_id);
190
191 let opt_trace_id = span.scope().last().and_then(|root_span| {
192 root_span
193 .extensions()
194 .get::<OtelData>()
195 .and_then(|otd| otd.builder.trace_id)
196 });
197
198 if let Some((trace_id, span_id)) = opt_trace_id.zip(opt_span_id) {
199 log_record.set_trace_context(trace_id, span_id, None);
200 }
201 }
202
203 self.logger.emit(log_record);
205 }
206
207 #[cfg(feature = "spec_unstable_logs_enabled")]
208 fn event_enabled(
209 &self,
210 _event: &tracing_core::Event<'_>,
211 _ctx: tracing_subscriber::layer::Context<'_, S>,
212 ) -> bool {
213 let severity = severity_of_level(_event.metadata().level());
214 self.logger
215 .event_enabled(severity, _event.metadata().target())
216 }
217}
218
219const fn severity_of_level(level: &Level) -> Severity {
220 match *level {
221 Level::TRACE => Severity::Trace,
222 Level::DEBUG => Severity::Debug,
223 Level::INFO => Severity::Info,
224 Level::WARN => Severity::Warn,
225 Level::ERROR => Severity::Error,
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use crate::layer;
232 use opentelemetry::logs::Severity;
233 use opentelemetry::trace::TracerProvider;
234 use opentelemetry::trace::{TraceContextExt, TraceFlags, Tracer};
235 use opentelemetry::{logs::AnyValue, Key};
236 use opentelemetry_sdk::error::OTelSdkResult;
237 use opentelemetry_sdk::logs::InMemoryLogExporter;
238 use opentelemetry_sdk::logs::{LogBatch, LogExporter};
239 use opentelemetry_sdk::logs::{SdkLogRecord, SdkLoggerProvider};
240 use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider};
241 use tracing::{error, warn};
242 use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
243 use tracing_subscriber::util::SubscriberInitExt;
244 use tracing_subscriber::{EnvFilter, Layer};
245
246 pub fn attributes_contains(log_record: &SdkLogRecord, key: &Key, value: &AnyValue) -> bool {
247 log_record
248 .attributes_iter()
249 .any(|(k, v)| k == key && v == value)
250 }
251
252 fn create_tracing_subscriber(
253 _exporter: InMemoryLogExporter,
254 logger_provider: &SdkLoggerProvider,
255 ) -> impl tracing::Subscriber {
256 let level_filter = tracing_subscriber::filter::LevelFilter::WARN; let layer =
258 layer::OpenTelemetryTracingBridge::new(logger_provider).with_filter(level_filter); tracing_subscriber::registry().with(layer)
261 }
262
263 #[derive(Clone, Debug, Default)]
266 struct ReentrantLogExporter;
267
268 impl LogExporter for ReentrantLogExporter {
269 #[allow(clippy::manual_async_fn)]
270 fn export(
271 &self,
272 _batch: LogBatch<'_>,
273 ) -> impl std::future::Future<Output = OTelSdkResult> + Send {
274 async {
275 warn!(name: "my-event-name", target: "reentrant", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
278 Ok(())
279 }
280 }
281 }
282
283 #[test]
284 #[ignore = "See issue: https://github.com/open-telemetry/opentelemetry-rust/issues/1745"]
285 fn simple_processor_deadlock() {
286 let exporter: ReentrantLogExporter = ReentrantLogExporter;
287 let logger_provider = SdkLoggerProvider::builder()
288 .with_simple_exporter(exporter.clone())
289 .build();
290
291 let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
292
293 tracing_subscriber::registry().with(layer).init();
295 warn!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
296 }
297
298 #[test]
299 #[ignore = "While this test runs fine, this uses global subscriber and does not play well with other tests."]
300 fn simple_processor_no_deadlock() {
301 let exporter: ReentrantLogExporter = ReentrantLogExporter;
302 let logger_provider = SdkLoggerProvider::builder()
303 .with_simple_exporter(exporter.clone())
304 .build();
305
306 let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
307
308 let filter = EnvFilter::new("debug").add_directive("reentrant=error".parse().unwrap());
311 tracing_subscriber::registry()
313 .with(filter)
314 .with(layer)
315 .init();
316 warn!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
317 }
318
319 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
320 #[ignore = "While this test runs fine, this uses global subscriber and does not play well with other tests."]
321 async fn batch_processor_no_deadlock() {
322 let exporter: ReentrantLogExporter = ReentrantLogExporter;
323 let logger_provider = SdkLoggerProvider::builder()
324 .with_batch_exporter(exporter.clone())
325 .build();
326
327 let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider);
328
329 tracing_subscriber::registry().with(layer).init();
330 warn!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
331 }
332
333 #[test]
334 fn tracing_appender_standalone() {
335 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
337 let logger_provider = SdkLoggerProvider::builder()
338 .with_simple_exporter(exporter.clone())
339 .build();
340
341 let subscriber = create_tracing_subscriber(exporter.clone(), &logger_provider);
342
343 let _guard = tracing::subscriber::set_default(subscriber);
346
347 error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
349 assert!(logger_provider.force_flush().is_ok());
350
351 let exported_logs = exporter
353 .get_emitted_logs()
354 .expect("Logs are expected to be exported.");
355 assert_eq!(exported_logs.len(), 1);
356 let log = exported_logs
357 .first()
358 .expect("Atleast one log is expected to be present.");
359
360 assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
362 assert_eq!(log.record.severity_number(), Some(Severity::Error));
363
364 assert!(log.record.trace_context().is_none());
366
367 #[cfg(not(feature = "experimental_metadata_attributes"))]
369 assert_eq!(log.record.attributes_iter().count(), 3);
370 #[cfg(feature = "experimental_metadata_attributes")]
371 assert_eq!(log.record.attributes_iter().count(), 7);
372 assert!(attributes_contains(
373 &log.record,
374 &Key::new("event_id"),
375 &AnyValue::Int(20)
376 ));
377 assert!(attributes_contains(
378 &log.record,
379 &Key::new("user_name"),
380 &AnyValue::String("otel".into())
381 ));
382 assert!(attributes_contains(
383 &log.record,
384 &Key::new("user_email"),
385 &AnyValue::String("otel@opentelemetry.io".into())
386 ));
387 #[cfg(feature = "experimental_metadata_attributes")]
388 {
389 assert!(attributes_contains(
390 &log.record,
391 &Key::new("code.filename"),
392 &AnyValue::String("layer.rs".into())
393 ));
394 assert!(attributes_contains(
395 &log.record,
396 &Key::new("code.namespace"),
397 &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
398 ));
399 let attributes_key: Vec<Key> = log
403 .record
404 .attributes_iter()
405 .map(|(key, _)| key.clone())
406 .collect();
407 assert!(attributes_key.contains(&Key::new("code.filepath")));
408 assert!(attributes_key.contains(&Key::new("code.lineno")));
409 assert!(!attributes_key.contains(&Key::new("log.target")));
410 }
411 }
412
413 #[test]
414 fn tracing_appender_inside_tracing_context() {
415 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
417 let logger_provider = SdkLoggerProvider::builder()
418 .with_simple_exporter(exporter.clone())
419 .build();
420
421 let subscriber = create_tracing_subscriber(exporter.clone(), &logger_provider);
422
423 let _guard = tracing::subscriber::set_default(subscriber);
426
427 let tracer_provider = SdkTracerProvider::builder()
429 .with_sampler(Sampler::AlwaysOn)
430 .build();
431 let tracer = tracer_provider.tracer("test-tracer");
432
433 let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| {
435 let trace_id = cx.span().span_context().trace_id();
436 let span_id = cx.span().span_context().span_id();
437
438 error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io");
440 (trace_id, span_id)
441 });
442
443 assert!(logger_provider.force_flush().is_ok());
444
445 let exported_logs = exporter
447 .get_emitted_logs()
448 .expect("Logs are expected to be exported.");
449 assert_eq!(exported_logs.len(), 1);
450 let log = exported_logs
451 .first()
452 .expect("Atleast one log is expected to be present.");
453
454 assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
456 assert_eq!(log.record.severity_number(), Some(Severity::Error));
457
458 assert!(log.record.trace_context().is_some());
460 assert_eq!(
461 log.record.trace_context().unwrap().trace_id,
462 trace_id_expected
463 );
464 assert_eq!(
465 log.record.trace_context().unwrap().span_id,
466 span_id_expected
467 );
468 assert_eq!(
469 log.record.trace_context().unwrap().trace_flags.unwrap(),
470 TraceFlags::SAMPLED
471 );
472
473 #[cfg(not(feature = "experimental_metadata_attributes"))]
475 assert_eq!(log.record.attributes_iter().count(), 3);
476 #[cfg(feature = "experimental_metadata_attributes")]
477 assert_eq!(log.record.attributes_iter().count(), 7);
478 assert!(attributes_contains(
479 &log.record,
480 &Key::new("event_id"),
481 &AnyValue::Int(20.into())
482 ));
483 assert!(attributes_contains(
484 &log.record,
485 &Key::new("user_name"),
486 &AnyValue::String("otel".into())
487 ));
488 assert!(attributes_contains(
489 &log.record,
490 &Key::new("user_email"),
491 &AnyValue::String("otel@opentelemetry.io".into())
492 ));
493 #[cfg(feature = "experimental_metadata_attributes")]
494 {
495 assert!(attributes_contains(
496 &log.record,
497 &Key::new("code.filename"),
498 &AnyValue::String("layer.rs".into())
499 ));
500 assert!(attributes_contains(
501 &log.record,
502 &Key::new("code.namespace"),
503 &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
504 ));
505 let attributes_key: Vec<Key> = log
509 .record
510 .attributes_iter()
511 .map(|(key, _)| key.clone())
512 .collect();
513 assert!(attributes_key.contains(&Key::new("code.filepath")));
514 assert!(attributes_key.contains(&Key::new("code.lineno")));
515 assert!(!attributes_key.contains(&Key::new("log.target")));
516 }
517 }
518
519 #[cfg(feature = "experimental_use_tracing_span_context")]
520 #[test]
521 fn tracing_appender_inside_tracing_crate_context() {
522 use opentelemetry_sdk::trace::InMemorySpanExporterBuilder;
523
524 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
526 let logger_provider = SdkLoggerProvider::builder()
527 .with_simple_exporter(exporter.clone())
528 .build();
529
530 let span_exporter = InMemorySpanExporterBuilder::new().build();
532 let tracer_provider = SdkTracerProvider::builder()
533 .with_simple_exporter(span_exporter.clone())
534 .build();
535 let tracer = tracer_provider.tracer("test-tracer");
536
537 let level_filter = tracing_subscriber::filter::LevelFilter::INFO;
538 let log_layer =
539 layer::OpenTelemetryTracingBridge::new(&logger_provider).with_filter(level_filter);
540
541 let subscriber = tracing_subscriber::registry()
542 .with(log_layer)
543 .with(tracing_opentelemetry::layer().with_tracer(tracer));
544
545 let _guard = tracing::subscriber::set_default(subscriber);
547
548 tracing::info_span!("outer-span").in_scope(|| {
550 error!("first-event");
551
552 tracing::info_span!("inner-span").in_scope(|| {
553 error!("second-event");
554 });
555 });
556
557 assert!(logger_provider.force_flush().is_ok());
558
559 let logs = exporter.get_emitted_logs().expect("No emitted logs");
560 assert_eq!(logs.len(), 2);
561
562 let spans = span_exporter.get_finished_spans().unwrap();
563 assert_eq!(spans.len(), 2);
564
565 let trace_id = spans[0].span_context.trace_id();
566 assert_eq!(trace_id, spans[1].span_context.trace_id());
567 let inner_span_id = spans[0].span_context.span_id();
568 let outer_span_id = spans[1].span_context.span_id();
569 assert_eq!(outer_span_id, spans[0].parent_span_id);
570
571 let trace_ctx0 = logs[0].record.trace_context().unwrap();
572 let trace_ctx1 = logs[1].record.trace_context().unwrap();
573
574 assert_eq!(trace_ctx0.trace_id, trace_id);
575 assert_eq!(trace_ctx1.trace_id, trace_id);
576 assert_eq!(trace_ctx0.span_id, outer_span_id);
577 assert_eq!(trace_ctx1.span_id, inner_span_id);
578 }
579
580 #[test]
581 fn tracing_appender_standalone_with_tracing_log() {
582 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
584 let logger_provider = SdkLoggerProvider::builder()
585 .with_simple_exporter(exporter.clone())
586 .build();
587
588 let subscriber = create_tracing_subscriber(exporter.clone(), &logger_provider);
589
590 let _guard = tracing::subscriber::set_default(subscriber);
593 drop(tracing_log::LogTracer::init());
594
595 log::error!(target: "my-system", "log from log crate");
597 assert!(logger_provider.force_flush().is_ok());
598
599 let exported_logs = exporter
601 .get_emitted_logs()
602 .expect("Logs are expected to be exported.");
603 assert_eq!(exported_logs.len(), 1);
604 let log = exported_logs
605 .first()
606 .expect("Atleast one log is expected to be present.");
607
608 assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
610 assert_eq!(log.record.severity_number(), Some(Severity::Error));
611
612 assert!(log.record.trace_context().is_none());
614
615 #[cfg(feature = "experimental_metadata_attributes")]
617 assert_eq!(log.record.attributes_iter().count(), 4);
618
619 #[cfg(feature = "experimental_metadata_attributes")]
620 {
621 assert!(attributes_contains(
622 &log.record,
623 &Key::new("code.filename"),
624 &AnyValue::String("layer.rs".into())
625 ));
626 assert!(attributes_contains(
627 &log.record,
628 &Key::new("code.namespace"),
629 &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
630 ));
631 let attributes_key: Vec<Key> = log
635 .record
636 .attributes_iter()
637 .map(|(key, _)| key.clone())
638 .collect();
639 assert!(attributes_key.contains(&Key::new("code.filepath")));
640 assert!(attributes_key.contains(&Key::new("code.lineno")));
641 assert!(!attributes_key.contains(&Key::new("log.target")));
642 }
643 }
644
645 #[test]
646 fn tracing_appender_inside_tracing_context_with_tracing_log() {
647 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
649 let logger_provider = SdkLoggerProvider::builder()
650 .with_simple_exporter(exporter.clone())
651 .build();
652
653 let subscriber = create_tracing_subscriber(exporter.clone(), &logger_provider);
654
655 let _guard = tracing::subscriber::set_default(subscriber);
658 drop(tracing_log::LogTracer::init());
659
660 let tracer_provider = SdkTracerProvider::builder()
662 .with_sampler(Sampler::AlwaysOn)
663 .build();
664 let tracer = tracer_provider.tracer("test-tracer");
665
666 let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| {
668 let trace_id = cx.span().span_context().trace_id();
669 let span_id = cx.span().span_context().span_id();
670
671 log::error!(target: "my-system", "log from log crate");
673 (trace_id, span_id)
674 });
675
676 assert!(logger_provider.force_flush().is_ok());
677
678 let exported_logs = exporter
680 .get_emitted_logs()
681 .expect("Logs are expected to be exported.");
682 assert_eq!(exported_logs.len(), 1);
683 let log = exported_logs
684 .first()
685 .expect("Atleast one log is expected to be present.");
686
687 assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
689 assert_eq!(log.record.severity_number(), Some(Severity::Error));
690
691 assert!(log.record.trace_context().is_some());
693 assert_eq!(
694 log.record.trace_context().unwrap().trace_id,
695 trace_id_expected
696 );
697 assert_eq!(
698 log.record.trace_context().unwrap().span_id,
699 span_id_expected
700 );
701 assert_eq!(
702 log.record.trace_context().unwrap().trace_flags.unwrap(),
703 TraceFlags::SAMPLED
704 );
705
706 #[cfg(feature = "experimental_metadata_attributes")]
708 assert_eq!(log.record.attributes_iter().count(), 4);
709
710 #[cfg(feature = "experimental_metadata_attributes")]
711 {
712 assert!(attributes_contains(
713 &log.record,
714 &Key::new("code.filename"),
715 &AnyValue::String("layer.rs".into())
716 ));
717 assert!(attributes_contains(
718 &log.record,
719 &Key::new("code.namespace"),
720 &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into())
721 ));
722 let attributes_key: Vec<Key> = log
726 .record
727 .attributes_iter()
728 .map(|(key, _)| key.clone())
729 .collect();
730 assert!(attributes_key.contains(&Key::new("code.filepath")));
731 assert!(attributes_key.contains(&Key::new("code.lineno")));
732 assert!(!attributes_key.contains(&Key::new("log.target")));
733 }
734 }
735}