1use crate::error::{OTelSdkError, OTelSdkResult};
20use crate::logs::log_processor::LogProcessor;
21use crate::{
22 logs::{LogBatch, LogExporter, SdkLogRecord},
23 Resource,
24};
25
26use opentelemetry::{otel_debug, otel_error, otel_warn, Context, InstrumentationScope};
27
28use std::fmt::Debug;
29use std::sync::atomic::AtomicBool;
30use std::sync::Mutex;
31use std::time::Duration;
32
33#[derive(Debug)]
65pub struct SimpleLogProcessor<T: LogExporter> {
66 exporter: Mutex<T>,
67 is_shutdown: AtomicBool,
68}
69
70impl<T: LogExporter> SimpleLogProcessor<T> {
71 pub fn new(exporter: T) -> Self {
73 SimpleLogProcessor {
74 exporter: Mutex::new(exporter),
75 is_shutdown: AtomicBool::new(false),
76 }
77 }
78}
79
80impl<T: LogExporter> LogProcessor for SimpleLogProcessor<T> {
81 fn emit(&self, record: &mut SdkLogRecord, instrumentation: &InstrumentationScope) {
82 let _suppress_guard = Context::enter_telemetry_suppressed_scope();
83 if self.is_shutdown.load(std::sync::atomic::Ordering::Relaxed) {
85 otel_warn!(
87 name: "SimpleLogProcessor.Emit.ProcessorShutdown",
88 );
89 return;
90 }
91
92 let result = self
93 .exporter
94 .lock()
95 .map_err(|_| OTelSdkError::InternalFailure("SimpleLogProcessor mutex poison".into()))
96 .and_then(|exporter| {
97 let log_tuple = &[(record as &SdkLogRecord, instrumentation)];
98 futures_executor::block_on(exporter.export(LogBatch::new(log_tuple)))
99 });
100 match result {
102 Err(OTelSdkError::InternalFailure(_)) => {
103 otel_debug!(
105 name: "SimpleLogProcessor.Emit.MutexPoisoning",
106 );
107 }
108 Err(err) => {
109 otel_error!(
110 name: "SimpleLogProcessor.Emit.ExportError",
111 error = format!("{}",err)
112 );
113 }
114 _ => {}
115 }
116 }
117
118 fn force_flush(&self) -> OTelSdkResult {
119 Ok(())
120 }
121
122 fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
123 self.is_shutdown
124 .store(true, std::sync::atomic::Ordering::Relaxed);
125 if let Ok(exporter) = self.exporter.lock() {
126 exporter.shutdown_with_timeout(timeout)
127 } else {
128 Err(OTelSdkError::InternalFailure(
129 "SimpleLogProcessor mutex poison at shutdown".into(),
130 ))
131 }
132 }
133
134 fn set_resource(&mut self, resource: &Resource) {
135 if let Ok(mut exporter) = self.exporter.lock() {
136 exporter.set_resource(resource);
137 }
138 }
139
140 #[inline]
141 fn event_enabled(
142 &self,
143 level: opentelemetry::logs::Severity,
144 target: &str,
145 name: Option<&str>,
146 ) -> bool {
147 if let Ok(exporter) = self.exporter.lock() {
148 exporter.event_enabled(level, target, name)
149 } else {
150 true
151 }
152 }
153}
154
155#[cfg(all(test, feature = "testing", feature = "logs"))]
156mod tests {
157 use crate::logs::log_processor::tests::MockLogExporter;
158 use crate::logs::{LogBatch, LogExporter, SdkLogRecord, SdkLogger};
159 use crate::{
160 error::OTelSdkResult,
161 logs::{InMemoryLogExporterBuilder, LogProcessor, SdkLoggerProvider, SimpleLogProcessor},
162 Resource,
163 };
164 use opentelemetry::logs::{LogRecord, Logger, LoggerProvider};
165 use opentelemetry::InstrumentationScope;
166 use opentelemetry::KeyValue;
167 use std::sync::atomic::{AtomicUsize, Ordering};
168 use std::sync::{Arc, Mutex};
169 use std::time;
170 use std::time::Duration;
171
172 #[derive(Debug, Clone)]
173 struct LogExporterThatRequiresTokio {
174 export_count: Arc<AtomicUsize>,
175 }
176
177 impl LogExporterThatRequiresTokio {
178 fn new() -> Self {
180 LogExporterThatRequiresTokio {
181 export_count: Arc::new(AtomicUsize::new(0)),
182 }
183 }
184
185 fn len(&self) -> usize {
187 self.export_count.load(Ordering::Acquire)
188 }
189 }
190
191 impl LogExporter for LogExporterThatRequiresTokio {
192 async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult {
193 tokio::time::sleep(Duration::from_millis(50)).await;
195
196 for _ in batch.iter() {
197 self.export_count.fetch_add(1, Ordering::Acquire);
198 }
199 Ok(())
200 }
201 fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult {
202 Ok(())
203 }
204 }
205
206 #[test]
207 fn test_set_resource_simple_processor() {
208 let exporter = MockLogExporter {
209 resource: Arc::new(Mutex::new(None)),
210 };
211 let processor = SimpleLogProcessor::new(exporter.clone());
212 let _ = SdkLoggerProvider::builder()
213 .with_log_processor(processor)
214 .with_resource(
215 Resource::builder_empty()
216 .with_attributes([
217 KeyValue::new("k1", "v1"),
218 KeyValue::new("k2", "v3"),
219 KeyValue::new("k3", "v3"),
220 KeyValue::new("k4", "v4"),
221 KeyValue::new("k5", "v5"),
222 ])
223 .build(),
224 )
225 .build();
226 assert_eq!(exporter.get_resource().unwrap().into_iter().count(), 5);
227 }
228
229 #[test]
230 fn test_simple_shutdown() {
231 let exporter = InMemoryLogExporterBuilder::default()
232 .keep_records_on_shutdown()
233 .build();
234 let processor = SimpleLogProcessor::new(exporter.clone());
235
236 let mut record: SdkLogRecord = SdkLogRecord::new();
237 let instrumentation: InstrumentationScope = Default::default();
238
239 processor.emit(&mut record, &instrumentation);
240
241 processor.shutdown().unwrap();
242
243 let is_shutdown = processor
244 .is_shutdown
245 .load(std::sync::atomic::Ordering::Relaxed);
246 assert!(is_shutdown);
247
248 processor.emit(&mut record, &instrumentation);
249
250 assert_eq!(1, exporter.get_emitted_logs().unwrap().len());
251 assert!(exporter.is_shutdown_called());
252 }
253
254 #[test]
255 fn test_simple_processor_sync_exporter_without_runtime() {
256 let exporter = InMemoryLogExporterBuilder::default().build();
257 let processor = SimpleLogProcessor::new(exporter.clone());
258
259 let mut record: SdkLogRecord = SdkLogRecord::new();
260 let instrumentation: InstrumentationScope = Default::default();
261
262 processor.emit(&mut record, &instrumentation);
263
264 assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1);
265 }
266
267 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
268 async fn test_simple_processor_sync_exporter_with_runtime() {
269 let exporter = InMemoryLogExporterBuilder::default().build();
270 let processor = SimpleLogProcessor::new(exporter.clone());
271
272 let mut record: SdkLogRecord = SdkLogRecord::new();
273 let instrumentation: InstrumentationScope = Default::default();
274
275 processor.emit(&mut record, &instrumentation);
276
277 assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1);
278 }
279
280 #[tokio::test(flavor = "multi_thread")]
281 async fn test_simple_processor_sync_exporter_with_multi_thread_runtime() {
282 let exporter = InMemoryLogExporterBuilder::default().build();
283 let processor = Arc::new(SimpleLogProcessor::new(exporter.clone()));
284
285 let mut handles = vec![];
286 for _ in 0..10 {
287 let processor_clone = Arc::clone(&processor);
288 let handle = tokio::spawn(async move {
289 let mut record: SdkLogRecord = SdkLogRecord::new();
290 let instrumentation: InstrumentationScope = Default::default();
291 processor_clone.emit(&mut record, &instrumentation);
292 });
293 handles.push(handle);
294 }
295
296 for handle in handles {
297 handle.await.unwrap();
298 }
299
300 assert_eq!(exporter.get_emitted_logs().unwrap().len(), 10);
301 }
302
303 #[tokio::test(flavor = "current_thread")]
304 async fn test_simple_processor_sync_exporter_with_current_thread_runtime() {
305 let exporter = InMemoryLogExporterBuilder::default().build();
306 let processor = SimpleLogProcessor::new(exporter.clone());
307
308 let mut record: SdkLogRecord = SdkLogRecord::new();
309 let instrumentation: InstrumentationScope = Default::default();
310
311 processor.emit(&mut record, &instrumentation);
312
313 assert_eq!(exporter.get_emitted_logs().unwrap().len(), 1);
314 }
315
316 #[test]
317 fn test_simple_processor_async_exporter_without_runtime() {
318 let result = std::panic::catch_unwind(|| {
320 let exporter = LogExporterThatRequiresTokio::new();
321 let processor = SimpleLogProcessor::new(exporter.clone());
322
323 let mut record: SdkLogRecord = SdkLogRecord::new();
324 let instrumentation: InstrumentationScope = Default::default();
325
326 processor.emit(&mut record, &instrumentation);
328 });
329
330 assert!(
332 result.is_err(),
333 "The test should fail due to missing Tokio runtime, but it did not."
334 );
335 let panic_payload = result.unwrap_err();
336 let panic_message = panic_payload
337 .downcast_ref::<String>()
338 .map(|s| s.as_str())
339 .or_else(|| panic_payload.downcast_ref::<&str>().copied())
340 .unwrap_or("No panic message");
341
342 assert!(
343 panic_message.contains("no reactor running")
344 || panic_message.contains("must be called from the context of a Tokio 1.x runtime"),
345 "Expected panic message about missing Tokio runtime, but got: {panic_message}"
346 );
347 }
348
349 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
350 #[ignore]
351 async fn test_simple_processor_async_exporter_with_all_runtime_worker_threads_blocked() {
368 let exporter = LogExporterThatRequiresTokio::new();
369 let processor = Arc::new(SimpleLogProcessor::new(exporter.clone()));
370
371 let concurrent_emit = 4; let mut handles = vec![];
374 for _ in 0..concurrent_emit {
376 let processor_clone = Arc::clone(&processor);
377 let handle = tokio::spawn(async move {
378 let mut record: SdkLogRecord = SdkLogRecord::new();
379 let instrumentation: InstrumentationScope = Default::default();
380 processor_clone.emit(&mut record, &instrumentation);
381 });
382 handles.push(handle);
383 }
384
385 for handle in handles {
387 handle.await.unwrap();
388 }
389 assert_eq!(exporter.len(), concurrent_emit);
390 }
391
392 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
393 async fn test_simple_processor_async_exporter_with_runtime() {
399 let exporter = LogExporterThatRequiresTokio::new();
400 let processor = SimpleLogProcessor::new(exporter.clone());
401
402 let mut record: SdkLogRecord = SdkLogRecord::new();
403 let instrumentation: InstrumentationScope = Default::default();
404
405 processor.emit(&mut record, &instrumentation);
406
407 assert_eq!(exporter.len(), 1);
408 }
409
410 #[tokio::test(flavor = "multi_thread")]
411 async fn test_simple_processor_async_exporter_with_multi_thread_runtime() {
417 let exporter = LogExporterThatRequiresTokio::new();
418
419 let processor = SimpleLogProcessor::new(exporter.clone());
420
421 let mut record: SdkLogRecord = SdkLogRecord::new();
422 let instrumentation: InstrumentationScope = Default::default();
423
424 processor.emit(&mut record, &instrumentation);
425
426 assert_eq!(exporter.len(), 1);
427 }
428
429 #[tokio::test(flavor = "current_thread")]
430 #[ignore]
431 async fn test_simple_processor_async_exporter_with_current_thread_runtime() {
437 let exporter = LogExporterThatRequiresTokio::new();
438
439 let processor = SimpleLogProcessor::new(exporter.clone());
440
441 let mut record: SdkLogRecord = SdkLogRecord::new();
442 let instrumentation: InstrumentationScope = Default::default();
443
444 processor.emit(&mut record, &instrumentation);
445
446 assert_eq!(exporter.len(), 1);
447 }
448
449 #[derive(Debug, Clone)]
450 struct ReentrantLogExporter {
451 logger: Arc<Mutex<Option<SdkLogger>>>,
452 }
453
454 impl ReentrantLogExporter {
455 fn new() -> Self {
456 Self {
457 logger: Arc::new(Mutex::new(None)),
458 }
459 }
460
461 fn set_logger(&self, logger: SdkLogger) {
462 let mut guard = self.logger.lock().unwrap();
463 *guard = Some(logger);
464 }
465 }
466
467 impl LogExporter for ReentrantLogExporter {
468 async fn export(&self, _batch: LogBatch<'_>) -> OTelSdkResult {
469 let logger = self.logger.lock().unwrap();
470 if let Some(logger) = logger.as_ref() {
471 let mut log_record = logger.create_log_record();
472 log_record.set_severity_number(opentelemetry::logs::Severity::Error);
473 logger.emit(log_record);
474 }
475
476 Ok(())
477 }
478 }
479
480 #[test]
481 fn exporter_internal_log_does_not_deadlock_with_simple_processor() {
482 let exporter: ReentrantLogExporter = ReentrantLogExporter::new();
486 let logger_provider = SdkLoggerProvider::builder()
487 .with_simple_exporter(exporter.clone())
488 .build();
489 exporter.set_logger(logger_provider.logger("processor-logger"));
490
491 let logger = logger_provider.logger("test-logger");
492 let mut log_record = logger.create_log_record();
493 log_record.set_severity_number(opentelemetry::logs::Severity::Error);
494 logger.emit(log_record);
495 }
496}