opentelemetry_sdk/logs/
mod.rs1mod batch_log_processor;
3mod export;
4mod log_processor;
5mod logger;
6mod logger_provider;
7pub(crate) mod record;
8mod simple_log_processor;
9
10#[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")]
29pub 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 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 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 log_record.add_attributes(vec![
71 (Key::new("key1"), AnyValue::from("value1")),
72 (Key::new("key2"), AnyValue::from("value2")),
73 ]);
74
75 log_record.add_attributes([
77 (Key::new("key3"), AnyValue::from("value3")),
78 (Key::new("key4"), AnyValue::from("value4")),
79 ]);
80
81 log_record.add_attributes(vec![("key5", "value5"), ("key6", "value6")]);
83
84 log_record.add_attributes([("key7", "value7"), ("key8", "value8")]);
86
87 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 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 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 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 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 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!(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 let exporter: InMemoryLogExporter = InMemoryLogExporter::default();
218 let logger_provider = SdkLoggerProvider::builder()
219 .with_simple_exporter(exporter.clone())
220 .build();
221
222 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 logger.emit(log_record);
229 }
230
231 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 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}