Skip to main content

opentelemetry_sdk/logs/
mod.rs

1//! # OpenTelemetry Log SDK
2mod batch_log_processor;
3mod export;
4mod log_processor;
5mod logger;
6mod logger_provider;
7pub(crate) mod record;
8mod simple_log_processor;
9
10/// In-Memory log exporter for testing purpose.
11#[cfg(any(feature = "testing", test))]
12#[cfg_attr(docsrs, doc(cfg(any(feature = "testing", test))))]
13pub mod in_memory_exporter;
14#[cfg(any(feature = "testing", test))]
15#[cfg_attr(docsrs, doc(cfg(any(feature = "testing", test))))]
16pub use in_memory_exporter::{InMemoryLogExporter, InMemoryLogExporterBuilder};
17
18pub use batch_log_processor::{
19    BatchConfig, BatchConfigBuilder, BatchLogProcessor, BatchLogProcessorBuilder,
20};
21pub use export::{LogBatch, LogExporter};
22pub use log_processor::LogProcessor;
23pub use logger::SdkLogger;
24pub use logger_provider::{LoggerProviderBuilder, SdkLoggerProvider};
25pub use record::{SdkLogRecord, TraceContext};
26pub use simple_log_processor::SimpleLogProcessor;
27
28#[cfg(feature = "experimental_logs_batch_log_processor_with_async_runtime")]
29/// Module for BatchLogProcessor with async runtime.
30pub mod log_processor_with_async_runtime;
31
32#[cfg(all(test, feature = "testing"))]
33mod tests {
34    use super::*;
35    use crate::error::OTelSdkResult;
36    use crate::Resource;
37    use opentelemetry::baggage::BaggageExt;
38    use opentelemetry::logs::LogRecord;
39    use opentelemetry::logs::{Logger, LoggerProvider, Severity};
40    use opentelemetry::{logs::AnyValue, Key, KeyValue};
41    use opentelemetry::{Context, InstrumentationScope};
42    use std::borrow::Borrow;
43    use std::collections::HashMap;
44    use std::sync::{Arc, Mutex};
45
46    #[test]
47    fn logging_sdk_test() {
48        // Arrange
49        let resource = Resource::builder_empty()
50            .with_attributes([
51                KeyValue::new("k1", "v1"),
52                KeyValue::new("k2", "v2"),
53                KeyValue::new("k3", "v3"),
54                KeyValue::new("k4", "v4"),
55            ])
56            .build();
57        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
58        let logger_provider = SdkLoggerProvider::builder()
59            .with_resource(resource.clone())
60            .with_log_processor(SimpleLogProcessor::new(exporter.clone()))
61            .build();
62
63        // Act
64        let logger = logger_provider.logger("test-logger");
65        let mut log_record = logger.create_log_record();
66        log_record.set_severity_number(Severity::Error);
67        log_record.set_severity_text("Error");
68
69        // Adding attributes using a vector with explicitly constructed Key and AnyValue objects.
70        log_record.add_attributes(vec![
71            (Key::new("key1"), AnyValue::from("value1")),
72            (Key::new("key2"), AnyValue::from("value2")),
73        ]);
74
75        // Adding attributes using an array with explicitly constructed Key and AnyValue objects.
76        log_record.add_attributes([
77            (Key::new("key3"), AnyValue::from("value3")),
78            (Key::new("key4"), AnyValue::from("value4")),
79        ]);
80
81        // Adding attributes using a vector with tuple auto-conversion to Key and AnyValue.
82        log_record.add_attributes(vec![("key5", "value5"), ("key6", "value6")]);
83
84        // Adding attributes using an array with tuple auto-conversion to Key and AnyValue.
85        log_record.add_attributes([("key7", "value7"), ("key8", "value8")]);
86
87        // Adding Attributes from a HashMap
88        let mut attributes_map = HashMap::new();
89        attributes_map.insert("key9", "value9");
90        attributes_map.insert("key10", "value10");
91
92        log_record.add_attributes(attributes_map);
93
94        logger.emit(log_record);
95
96        // Assert
97        let exported_logs = exporter
98            .get_emitted_logs()
99            .expect("Logs are expected to be exported.");
100        assert_eq!(exported_logs.len(), 1);
101        let log = exported_logs
102            .first()
103            .expect("Atleast one log is expected to be present.");
104        assert_eq!(log.instrumentation.name(), "test-logger");
105        assert_eq!(log.record.severity_number, Some(Severity::Error));
106        assert_eq!(log.record.attributes_len(), 10);
107        for i in 1..=10 {
108            assert!(log.record.attributes_contains(
109                &Key::new(format!("key{i}")),
110                &AnyValue::String(format!("value{i}").into())
111            ));
112        }
113
114        // validate Resource
115        assert_eq!(&resource, log.resource.borrow());
116    }
117
118    #[test]
119    fn logger_attributes() {
120        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
121        let provider = SdkLoggerProvider::builder()
122            .with_log_processor(SimpleLogProcessor::new(exporter.clone()))
123            .build();
124
125        let scope = InstrumentationScope::builder("test_logger")
126            .with_schema_url("https://opentelemetry.io/schemas/1.0.0")
127            .with_attributes(vec![(KeyValue::new("test_k", "test_v"))])
128            .build();
129
130        let logger = provider.logger_with_scope(scope);
131
132        let mut log_record = logger.create_log_record();
133        log_record.set_severity_number(Severity::Error);
134
135        logger.emit(log_record);
136
137        let mut exported_logs = exporter
138            .get_emitted_logs()
139            .expect("Logs are expected to be exported.");
140        assert_eq!(exported_logs.len(), 1);
141        let log = exported_logs.remove(0);
142        assert_eq!(log.record.severity_number, Some(Severity::Error));
143
144        let instrumentation_scope = log.instrumentation;
145        assert_eq!(instrumentation_scope.name(), "test_logger");
146        assert_eq!(
147            instrumentation_scope.schema_url(),
148            Some("https://opentelemetry.io/schemas/1.0.0")
149        );
150        assert!(instrumentation_scope
151            .attributes()
152            .eq(&[KeyValue::new("test_k", "test_v")]));
153    }
154
155    #[derive(Debug)]
156    struct EnrichWithBaggageProcessor;
157    impl LogProcessor for EnrichWithBaggageProcessor {
158        fn emit(&self, data: &mut SdkLogRecord, _instrumentation: &InstrumentationScope) {
159            Context::map_current(|cx| {
160                for (kk, vv) in cx.baggage().iter() {
161                    data.add_attribute(kk.clone(), vv.0.clone());
162                }
163            });
164        }
165
166        fn force_flush(&self) -> crate::error::OTelSdkResult {
167            Ok(())
168        }
169
170        fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
171            Ok(())
172        }
173    }
174    #[test]
175    fn log_and_baggage() {
176        // Arrange
177        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
178        let logger_provider = SdkLoggerProvider::builder()
179            .with_log_processor(EnrichWithBaggageProcessor)
180            .with_log_processor(SimpleLogProcessor::new(exporter.clone()))
181            .build();
182
183        // Act
184        let logger = logger_provider.logger("test-logger");
185        let context_with_baggage =
186            Context::current_with_baggage(vec![KeyValue::new("key-from-bag", "value-from-bag")]);
187        let _cx_guard = context_with_baggage.attach();
188        let mut log_record = logger.create_log_record();
189        log_record.add_attribute("key", "value");
190        logger.emit(log_record);
191
192        // Assert
193        let exported_logs = exporter
194            .get_emitted_logs()
195            .expect("Logs are expected to be exported.");
196        assert_eq!(exported_logs.len(), 1);
197        let log = exported_logs
198            .first()
199            .expect("Atleast one log is expected to be present.");
200        assert_eq!(log.instrumentation.name(), "test-logger");
201        assert_eq!(log.record.attributes_len(), 2);
202
203        // Assert that the log record contains the baggage attribute
204        // and the attribute added to the log record.
205        assert!(log
206            .record
207            .attributes_contains(&Key::new("key"), &AnyValue::String("value".into())));
208        assert!(log.record.attributes_contains(
209            &Key::new("key-from-bag"),
210            &AnyValue::String("value-from-bag".into())
211        ));
212    }
213
214    #[test]
215    fn log_suppression() {
216        // Arrange
217        let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
218        let logger_provider = SdkLoggerProvider::builder()
219            .with_simple_exporter(exporter.clone())
220            .build();
221
222        // Act
223        let logger = logger_provider.logger("test-logger");
224        let log_record = logger.create_log_record();
225        {
226            let _suppressed_context = Context::enter_telemetry_suppressed_scope();
227            // This log emission should be suppressed and not exported.
228            logger.emit(log_record);
229        }
230
231        // Assert
232        let exported_logs = exporter.get_emitted_logs().expect("this should not fail.");
233        assert_eq!(
234            exported_logs.len(),
235            0,
236            "There should be a no logs as log emission is done inside a suppressed context"
237        );
238    }
239
240    #[derive(Debug, Clone)]
241    struct ReentrantLogProcessor {
242        logger: Arc<Mutex<Option<SdkLogger>>>,
243    }
244
245    impl ReentrantLogProcessor {
246        fn new() -> Self {
247            Self {
248                logger: Arc::new(Mutex::new(None)),
249            }
250        }
251
252        fn set_logger(&self, logger: SdkLogger) {
253            let mut guard = self.logger.lock().unwrap();
254            *guard = Some(logger);
255        }
256    }
257
258    impl LogProcessor for ReentrantLogProcessor {
259        fn emit(&self, _data: &mut SdkLogRecord, _instrumentation: &InstrumentationScope) {
260            let _suppress = Context::enter_telemetry_suppressed_scope();
261            // Without the suppression above, the logger.emit(log_record) below will cause a deadlock,
262            // as it emits another log, which will attempt to acquire the same lock that is
263            // already held by itself!
264            let logger = self.logger.lock().unwrap();
265            if let Some(logger) = logger.as_ref() {
266                let mut log_record = logger.create_log_record();
267                log_record.set_severity_number(Severity::Error);
268                logger.emit(log_record);
269            }
270        }
271
272        fn force_flush(&self) -> OTelSdkResult {
273            Ok(())
274        }
275
276        fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
277            Ok(())
278        }
279    }
280
281    #[test]
282    fn processor_internal_log_does_not_deadlock_with_suppression_enabled() {
283        let processor: ReentrantLogProcessor = ReentrantLogProcessor::new();
284        let logger_provider = SdkLoggerProvider::builder()
285            .with_log_processor(processor.clone())
286            .build();
287        processor.set_logger(logger_provider.logger("processor-logger"));
288
289        let logger = logger_provider.logger("test-logger");
290        let mut log_record = logger.create_log_record();
291        log_record.set_severity_number(Severity::Error);
292        logger.emit(log_record);
293    }
294}