Skip to main content

opentelemetry_sdk/logs/
logger.rs

1#[cfg(feature = "trace")]
2use super::TraceContext;
3use super::{SdkLogRecord, SdkLoggerProvider};
4#[cfg(feature = "trace")]
5use opentelemetry::trace::TraceContextExt;
6use opentelemetry::{Context, InstrumentationScope};
7
8use opentelemetry::logs::Severity;
9use opentelemetry::time::now;
10
11#[derive(Debug, Clone)]
12/// The object for emitting [`LogRecord`]s.
13///
14/// [`LogRecord`]: opentelemetry::logs::LogRecord
15pub struct SdkLogger {
16    scope: InstrumentationScope,
17    provider: SdkLoggerProvider,
18
19    // Bound is not strictly needed (no attributes), but the semconv is still
20    // `development` so the metric must be feature-gated; reuse the same
21    // `experimental_metrics_bound_instruments` flag as the other SDK
22    // self-observability metrics for consistency.
23    #[cfg(feature = "experimental_metrics_bound_instruments")]
24    log_created_counter: opentelemetry::metrics::BoundCounter<u64>,
25}
26
27impl SdkLogger {
28    pub(crate) fn new(scope: InstrumentationScope, provider: SdkLoggerProvider) -> Self {
29        #[cfg(feature = "experimental_metrics_bound_instruments")]
30        let log_created_counter = opentelemetry::global::meter("otel.sdk")
31            .u64_counter("otel.sdk.log.created")
32            .with_description("The number of log records submitted to the SDK.")
33            .with_unit("{log_record}")
34            .build()
35            .bind(&[]);
36        SdkLogger {
37            scope,
38            provider,
39            #[cfg(feature = "experimental_metrics_bound_instruments")]
40            log_created_counter,
41        }
42    }
43}
44
45impl opentelemetry::logs::Logger for SdkLogger {
46    type LogRecord = SdkLogRecord;
47
48    fn create_log_record(&self) -> Self::LogRecord {
49        SdkLogRecord::new()
50    }
51
52    /// Emit a `LogRecord`.
53    fn emit(&self, mut record: Self::LogRecord) {
54        // Records emitted while telemetry is suppressed are the SDK's own
55        // internal-operation logs (suppressed to prevent feedback loops). They
56        // are not application intake, so `otel.sdk.log.created` intentionally
57        // excludes them (counting them would create a phantom drop signal
58        // against downstream metrics).
59        if Context::is_current_telemetry_suppressed() {
60            return;
61        }
62
63        // Count every record submitted to the SDK, before any processing, so
64        // this metric is the top of the delivery funnel: records dropped by
65        // downstream processing show up as a gap against downstream metrics.
66        #[cfg(feature = "experimental_metrics_bound_instruments")]
67        self.log_created_counter.add(1);
68
69        let provider = &self.provider;
70        let processors = provider.log_processors();
71
72        //let mut log_record = record;
73        if record.trace_context.is_none() {
74            #[cfg(feature = "trace")]
75            Context::map_current(|cx| {
76                cx.has_active_span().then(|| {
77                    record.trace_context = Some(TraceContext::from(cx.span().span_context()))
78                })
79            });
80        }
81        if record.observed_timestamp.is_none() {
82            record.observed_timestamp = Some(now());
83        }
84
85        for p in processors {
86            p.emit(&mut record, &self.scope);
87        }
88    }
89
90    #[inline]
91    fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool {
92        if Context::is_current_telemetry_suppressed() {
93            return false;
94        }
95        // Returns false if there are no log processors.
96        // Returns true if at least one processor returns true.
97        self.provider
98            .log_processors()
99            .iter()
100            .any(|processor| processor.event_enabled(level, target, name))
101    }
102}
103
104#[cfg(all(test, feature = "experimental_metrics_bound_instruments"))]
105mod self_obs {
106    use crate::logs::SdkLoggerProvider;
107    use crate::metrics::data::{AggregatedMetrics, MetricData};
108    use crate::metrics::{InMemoryMetricExporter, SdkMeterProvider};
109    use opentelemetry::logs::{Logger, LoggerProvider};
110
111    fn sum_log_created(exporter: &InMemoryMetricExporter) -> u64 {
112        let metrics = exporter.get_finished_metrics().unwrap();
113        let mut total = 0u64;
114        for rm in &metrics {
115            for sm in &rm.scope_metrics {
116                for metric in &sm.metrics {
117                    if metric.name == "otel.sdk.log.created" {
118                        if let AggregatedMetrics::U64(MetricData::Sum(sum)) = &metric.data {
119                            for dp in sum.data_points() {
120                                total += dp.value();
121                            }
122                        }
123                    }
124                }
125            }
126        }
127        total
128    }
129
130    /// Verifies `otel.sdk.log.created` counts every record submitted to the SDK
131    /// even when no processors are registered, proving the metric is a
132    /// pre-processing intake count (the top of the delivery funnel) rather than
133    /// a post-filter count.
134    ///
135    /// `#[ignore]`d because it calls `global::set_meter_provider()`, which
136    /// mutates process-wide state; CI runs it in isolation via `test.sh`.
137    #[test]
138    #[ignore]
139    fn log_created_counts_intake_without_processors() {
140        let metric_exporter = InMemoryMetricExporter::default();
141        let meter_provider = SdkMeterProvider::builder()
142            .with_periodic_exporter(metric_exporter.clone())
143            .build();
144        opentelemetry::global::set_meter_provider(meter_provider.clone());
145
146        // Provider with NO log processors registered.
147        let logger_provider = SdkLoggerProvider::builder().build();
148        let logger = logger_provider.logger("test");
149
150        for _ in 0..10 {
151            logger.emit(logger.create_log_record());
152        }
153
154        meter_provider.force_flush().unwrap();
155
156        assert_eq!(
157            sum_log_created(&metric_exporter),
158            10,
159            "expected 10 records counted at intake regardless of processors"
160        );
161
162        meter_provider.shutdown().unwrap();
163    }
164}