opentelemetry_appender_tracing/
layer.rs

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
15/// Visitor to record the fields from the event record.
16struct EventVisitor<'a, LR: LogRecord> {
17    log_record: &'a mut LR,
18}
19
20/// Logs from the log crate have duplicated attributes that we removed here.
21#[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        //TODO: Consider special casing "message" to populate body and document
92        // to users to use message field for log message, to avoid going to the
93        // record_debug, which has dyn dispatch, string allocation and
94        // formatting cost.
95
96        //TODO: Fix heap allocation. Check if lifetime of &str can be used
97        // to optimize sync exporter scenario.
98        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    // TODO: Remaining field types from AnyValue : Bytes, ListAny, Boolean
122}
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>, // P is not used.
131}
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        // TODO: Fix heap allocation
173        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        // Visit fields.
181        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        //emit record
204        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; // Capture WARN and ERROR levels
257        let layer =
258            layer::OpenTelemetryTracingBridge::new(logger_provider).with_filter(level_filter); // No filter based on target, only based on log level
259
260        tracing_subscriber::registry().with(layer)
261    }
262
263    // cargo test --features=testing
264
265    #[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                // This will cause a deadlock as the export itself creates a log
276                // while still within the lock of the SimpleLogProcessor.
277                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        // Setting subscriber as global as that is the only way to test this scenario.
294        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        // This filter will prevent the deadlock as the reentrant log will be
309        // ignored.
310        let filter = EnvFilter::new("debug").add_directive("reentrant=error".parse().unwrap());
311        // Setting subscriber as global as that is the only way to test this scenario.
312        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        // Arrange
336        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        // avoiding setting tracing subscriber as global as that does not
344        // play well with unit tests.
345        let _guard = tracing::subscriber::set_default(subscriber);
346
347        // Act
348        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        // Assert TODO: move to helper methods
352        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        // Validate common fields
361        assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
362        assert_eq!(log.record.severity_number(), Some(Severity::Error));
363
364        // Validate trace context is none.
365        assert!(log.record.trace_context().is_none());
366
367        // Validate attributes
368        #[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            // The other 3 experimental_metadata_attributes are too unstable to check their value.
400            // Ex.: The path will be different on a Windows and Linux machine.
401            // Ex.: The line can change easily if someone makes changes in this source file.
402            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        // Arrange
416        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        // avoiding setting tracing subscriber as global as that does not
424        // play well with unit tests.
425        let _guard = tracing::subscriber::set_default(subscriber);
426
427        // setup tracing as well.
428        let tracer_provider = SdkTracerProvider::builder()
429            .with_sampler(Sampler::AlwaysOn)
430            .build();
431        let tracer = tracer_provider.tracer("test-tracer");
432
433        // Act
434        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            // logging is done inside span context.
439            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        // Assert TODO: move to helper methods
446        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        // validate common fields.
455        assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
456        assert_eq!(log.record.severity_number(), Some(Severity::Error));
457
458        // validate trace context.
459        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        // validate attributes.
474        #[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            // The other 3 experimental_metadata_attributes are too unstable to check their value.
506            // Ex.: The path will be different on a Windows and Linux machine.
507            // Ex.: The line can change easily if someone makes changes in this source file.
508            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        // Arrange
525        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
526        let logger_provider = SdkLoggerProvider::builder()
527            .with_simple_exporter(exporter.clone())
528            .build();
529
530        // setup tracing layer to compare trace/span IDs against
531        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        // Avoiding global subscriber.init() as that does not play well with unit tests.
546        let _guard = tracing::subscriber::set_default(subscriber);
547
548        // Act
549        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        // Arrange
583        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        // avoiding setting tracing subscriber as global as that does not
591        // play well with unit tests.
592        let _guard = tracing::subscriber::set_default(subscriber);
593        drop(tracing_log::LogTracer::init());
594
595        // Act
596        log::error!(target: "my-system", "log from log crate");
597        assert!(logger_provider.force_flush().is_ok());
598
599        // Assert TODO: move to helper methods
600        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        // Validate common fields
609        assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
610        assert_eq!(log.record.severity_number(), Some(Severity::Error));
611
612        // Validate trace context is none.
613        assert!(log.record.trace_context().is_none());
614
615        // Attributes can be polluted when we don't use this feature.
616        #[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            // The other 3 experimental_metadata_attributes are too unstable to check their value.
632            // Ex.: The path will be different on a Windows and Linux machine.
633            // Ex.: The line can change easily if someone makes changes in this source file.
634            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        // Arrange
648        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        // avoiding setting tracing subscriber as global as that does not
656        // play well with unit tests.
657        let _guard = tracing::subscriber::set_default(subscriber);
658        drop(tracing_log::LogTracer::init());
659
660        // setup tracing as well.
661        let tracer_provider = SdkTracerProvider::builder()
662            .with_sampler(Sampler::AlwaysOn)
663            .build();
664        let tracer = tracer_provider.tracer("test-tracer");
665
666        // Act
667        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            // logging is done inside span context.
672            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        // Assert TODO: move to helper methods
679        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        // validate common fields.
688        assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
689        assert_eq!(log.record.severity_number(), Some(Severity::Error));
690
691        // validate trace context.
692        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        // Attributes can be polluted when we don't use this feature.
707        #[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            // The other 3 experimental_metadata_attributes are too unstable to check their value.
723            // Ex.: The path will be different on a Windows and Linux machine.
724            // Ex.: The line can change easily if someone makes changes in this source file.
725            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}