1use super::{BatchLogProcessor, LogProcessor, SdkLogger, SimpleLogProcessor};
2use crate::error::{OTelSdkError, OTelSdkResult};
3use crate::logs::LogExporter;
4use crate::Resource;
5use opentelemetry::{otel_debug, otel_info, InstrumentationScope};
6use std::time::Duration;
7use std::{
8 borrow::Cow,
9 sync::{
10 atomic::{AtomicBool, Ordering},
11 Arc, OnceLock,
12 },
13};
14
15static NOOP_LOGGER_PROVIDER: OnceLock<SdkLoggerProvider> = OnceLock::new();
18
19#[inline]
20fn noop_logger_provider() -> &'static SdkLoggerProvider {
21 NOOP_LOGGER_PROVIDER.get_or_init(|| SdkLoggerProvider {
22 inner: Arc::new(LoggerProviderInner {
23 processors: Vec::new(),
24 is_shutdown: AtomicBool::new(true),
25 }),
26 })
27}
28
29#[derive(Debug, Clone)]
30pub struct SdkLoggerProvider {
44 inner: Arc<LoggerProviderInner>,
45}
46
47impl opentelemetry::logs::LoggerProvider for SdkLoggerProvider {
48 type Logger = SdkLogger;
49
50 fn logger(&self, name: impl Into<Cow<'static, str>>) -> Self::Logger {
51 let scope = InstrumentationScope::builder(name).build();
52 self.logger_with_scope(scope)
53 }
54
55 fn logger_with_scope(&self, scope: InstrumentationScope) -> Self::Logger {
56 if self.inner.is_shutdown.load(Ordering::Relaxed) {
58 otel_debug!(
59 name: "LoggerProvider.NoOpLoggerReturned",
60 logger_name = scope.name(),
61 );
62 return SdkLogger::new(scope, noop_logger_provider().clone());
63 }
64 if scope.name().is_empty() {
65 otel_info!(name: "LoggerNameEmpty", message = "Logger name is empty; consider providing a meaningful name. Logger will function normally and the provided name will be used as-is.");
66 };
67 otel_debug!(
68 name: "LoggerProvider.NewLoggerReturned",
69 logger_name = scope.name(),
70 );
71 SdkLogger::new(scope, self.clone())
72 }
73}
74
75impl SdkLoggerProvider {
76 pub fn builder() -> LoggerProviderBuilder {
78 LoggerProviderBuilder::default()
79 }
80
81 pub(crate) fn log_processors(&self) -> &[Box<dyn LogProcessor>] {
82 &self.inner.processors
83 }
84
85 pub fn force_flush(&self) -> OTelSdkResult {
87 let result: Vec<_> = self
88 .log_processors()
89 .iter()
90 .map(|processor| processor.force_flush())
91 .collect();
92 if result.iter().all(|r| r.is_ok()) {
93 Ok(())
94 } else {
95 Err(OTelSdkError::InternalFailure(format!("errs: {result:?}")))
96 }
97 }
98
99 pub fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
101 otel_debug!(
102 name: "LoggerProvider.ShutdownInvokedByUser",
103 );
104 if self
105 .inner
106 .is_shutdown
107 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
108 .is_ok()
109 {
110 let result = self.inner.shutdown_with_timeout(timeout);
112 if result.iter().all(|res| res.is_ok()) {
113 Ok(())
114 } else {
115 Err(OTelSdkError::InternalFailure(format!(
116 "Shutdown errors: {:?}",
117 result
118 .into_iter()
119 .filter_map(Result::err)
120 .collect::<Vec<_>>()
121 )))
122 }
123 } else {
124 Err(OTelSdkError::AlreadyShutdown)
125 }
126 }
127
128 pub fn shutdown(&self) -> OTelSdkResult {
130 self.shutdown_with_timeout(Duration::from_secs(5))
131 }
132}
133
134#[derive(Debug)]
135struct LoggerProviderInner {
136 processors: Vec<Box<dyn LogProcessor>>,
137 is_shutdown: AtomicBool,
138}
139
140impl LoggerProviderInner {
141 pub(crate) fn shutdown_with_timeout(&self, timeout: Duration) -> Vec<OTelSdkResult> {
143 let mut results = vec![];
144 for processor in &self.processors {
145 let result = processor.shutdown_with_timeout(timeout);
146 if let Err(err) = &result {
147 otel_debug!(name: "LoggerProvider.ShutdownError",
152 error = format!("{err}"));
153 }
154 results.push(result);
155 }
156 results
157 }
158
159 pub(crate) fn shutdown(&self) -> Vec<OTelSdkResult> {
161 self.shutdown_with_timeout(Duration::from_secs(5))
162 }
163}
164
165impl Drop for LoggerProviderInner {
166 fn drop(&mut self) {
167 if !self.is_shutdown.load(Ordering::Relaxed) {
168 otel_info!(
169 name: "LoggerProvider.Drop",
170 message = "Last reference of LoggerProvider dropped, initiating shutdown."
171 );
172 let _ = self.shutdown(); } else {
174 otel_debug!(
175 name: "LoggerProvider.Drop.AlreadyShutdown",
176 message = "LoggerProvider was already shut down; drop will not attempt shutdown again."
177 );
178 }
179 }
180}
181
182#[derive(Debug, Default)]
183pub struct LoggerProviderBuilder {
185 processors: Vec<Box<dyn LogProcessor>>,
186 resource: Option<Resource>,
187}
188
189impl LoggerProviderBuilder {
190 pub fn with_simple_exporter<T: LogExporter + 'static>(self, exporter: T) -> Self {
202 let mut processors = self.processors;
203 processors.push(Box::new(SimpleLogProcessor::new(exporter)));
204
205 LoggerProviderBuilder { processors, ..self }
206 }
207
208 pub fn with_batch_exporter<T: LogExporter + 'static>(self, exporter: T) -> Self {
227 let batch = BatchLogProcessor::builder(exporter).build();
228 self.with_log_processor(batch)
229 }
230
231 pub fn with_log_processor<T: LogProcessor + 'static>(self, processor: T) -> Self {
243 let mut processors = self.processors;
244 processors.push(Box::new(processor));
245
246 LoggerProviderBuilder { processors, ..self }
247 }
248
249 pub fn with_resource(self, resource: Resource) -> Self {
254 let resource = match self.resource {
255 Some(existing) => Some(existing.merge(&resource)),
256 None => Some(resource),
257 };
258
259 LoggerProviderBuilder { resource, ..self }
260 }
261
262 pub fn build(self) -> SdkLoggerProvider {
264 let resource = self.resource.unwrap_or(Resource::builder().build());
265 let mut processors = self.processors;
266 for processor in &mut processors {
267 processor.set_resource(&resource);
268 }
269
270 let logger_provider = SdkLoggerProvider {
271 inner: Arc::new(LoggerProviderInner {
272 processors,
273 is_shutdown: AtomicBool::new(false),
274 }),
275 };
276
277 otel_debug!(
278 name: "LoggerProvider.Built",
279 );
280 logger_provider
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 #[cfg(feature = "trace")]
287 use crate::logs::TraceContext;
288 #[cfg(feature = "trace")]
289 use crate::trace::SdkTracerProvider;
290 use crate::{
291 logs::{InMemoryLogExporter, LogBatch, SdkLogRecord},
292 resource::{
293 SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
294 },
295 Resource,
296 };
297
298 use super::*;
299 use opentelemetry::logs::{AnyValue, LogRecord as _, Logger, LoggerProvider};
300 #[cfg(feature = "trace")]
301 use opentelemetry::trace::TraceContextExt;
302 #[cfg(feature = "trace")]
303 use opentelemetry::trace::{SpanId, TraceId, Tracer as _, TracerProvider};
304 use opentelemetry::{Key, KeyValue, Value};
305 use std::fmt::{Debug, Formatter};
306 use std::sync::atomic::AtomicU64;
307 use std::sync::Mutex;
308 use std::{thread, time};
309
310 struct ShutdownTestLogProcessor {
311 is_shutdown: Arc<Mutex<bool>>,
312 counter: Arc<AtomicU64>,
313 }
314
315 impl Debug for ShutdownTestLogProcessor {
316 fn fmt(&self, _f: &mut Formatter<'_>) -> std::fmt::Result {
317 todo!()
318 }
319 }
320
321 impl ShutdownTestLogProcessor {
322 pub(crate) fn new(counter: Arc<AtomicU64>) -> Self {
323 ShutdownTestLogProcessor {
324 is_shutdown: Arc::new(Mutex::new(false)),
325 counter,
326 }
327 }
328 }
329
330 impl LogProcessor for ShutdownTestLogProcessor {
331 fn emit(&self, _data: &mut SdkLogRecord, _scope: &InstrumentationScope) {
332 self.is_shutdown
333 .lock()
334 .map(|is_shutdown| {
335 if !*is_shutdown {
336 self.counter
337 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
338 }
339 })
340 .expect("lock poisoned");
341 }
342
343 fn force_flush(&self) -> OTelSdkResult {
344 Ok(())
345 }
346
347 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
348 self.is_shutdown
349 .lock()
350 .map(|mut is_shutdown| *is_shutdown = true)
351 .expect("lock poisoned");
352 Ok(())
353 }
354 }
355
356 #[derive(Debug, Clone)]
357 struct TestExporterForResource {
358 resource: Arc<Mutex<Resource>>,
359 }
360 impl TestExporterForResource {
361 fn new() -> Self {
362 TestExporterForResource {
363 resource: Arc::new(Mutex::new(Resource::empty())),
364 }
365 }
366
367 fn resource(&self) -> Resource {
368 self.resource.lock().unwrap().clone()
369 }
370 }
371 impl LogExporter for TestExporterForResource {
372 async fn export(&self, _: LogBatch<'_>) -> OTelSdkResult {
373 Ok(())
374 }
375
376 fn set_resource(&mut self, resource: &Resource) {
377 let mut res = self.resource.lock().unwrap();
378 *res = resource.clone();
379 }
380
381 fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult {
382 Ok(())
383 }
384 }
385
386 #[derive(Debug, Clone)]
387 struct TestProcessorForResource {
388 resource: Arc<Mutex<Resource>>,
389 exporter: TestExporterForResource,
390 }
391 impl LogProcessor for TestProcessorForResource {
392 fn emit(&self, _data: &mut SdkLogRecord, _scope: &InstrumentationScope) {
393 }
395
396 fn force_flush(&self) -> OTelSdkResult {
397 Ok(())
398 }
399
400 fn set_resource(&mut self, resource: &Resource) {
401 let mut res = self.resource.lock().unwrap();
402 *res = resource.clone();
403 self.exporter.set_resource(resource);
404 }
405 }
406 impl TestProcessorForResource {
407 fn new(exporter: TestExporterForResource) -> Self {
408 TestProcessorForResource {
409 resource: Arc::new(Mutex::new(Resource::empty())),
410 exporter,
411 }
412 }
413 fn resource(&self) -> Resource {
414 self.resource.lock().unwrap().clone()
415 }
416 }
417
418 #[test]
419 fn test_resource_handling_provider_processor_exporter() {
420 let assert_resource = |processor: &TestProcessorForResource,
421 exporter: &TestExporterForResource,
422 resource_key: &'static str,
423 expect: Option<&'static str>| {
424 assert_eq!(
425 processor
426 .resource()
427 .get(&Key::from_static_str(resource_key))
428 .map(|v| v.to_string()),
429 expect.map(|s| s.to_string())
430 );
431
432 assert_eq!(
433 exporter
434 .resource()
435 .get(&Key::from_static_str(resource_key))
436 .map(|v| v.to_string()),
437 expect.map(|s| s.to_string())
438 );
439 };
440 let assert_telemetry_resource =
441 |processor: &TestProcessorForResource, exporter: &TestExporterForResource| {
442 assert_eq!(
443 processor.resource().get(&TELEMETRY_SDK_LANGUAGE.into()),
444 Some(Value::from("rust"))
445 );
446 assert_eq!(
447 processor.resource().get(&TELEMETRY_SDK_NAME.into()),
448 Some(Value::from("opentelemetry"))
449 );
450 assert_eq!(
451 processor.resource().get(&TELEMETRY_SDK_VERSION.into()),
452 Some(Value::from(env!("CARGO_PKG_VERSION")))
453 );
454 assert_eq!(
455 exporter.resource().get(&TELEMETRY_SDK_LANGUAGE.into()),
456 Some(Value::from("rust"))
457 );
458 assert_eq!(
459 exporter.resource().get(&TELEMETRY_SDK_NAME.into()),
460 Some(Value::from("opentelemetry"))
461 );
462 assert_eq!(
463 exporter.resource().get(&TELEMETRY_SDK_VERSION.into()),
464 Some(Value::from(env!("CARGO_PKG_VERSION")))
465 );
466 };
467
468 temp_env::with_var_unset("OTEL_RESOURCE_ATTRIBUTES", || {
470 let exporter_with_resource = TestExporterForResource::new();
471 let processor_with_resource =
472 TestProcessorForResource::new(exporter_with_resource.clone());
473 let _ = super::SdkLoggerProvider::builder()
474 .with_log_processor(processor_with_resource.clone())
475 .build();
476 assert_resource(
477 &processor_with_resource,
478 &exporter_with_resource,
479 SERVICE_NAME,
480 Some("unknown_service"),
481 );
482 assert_telemetry_resource(&processor_with_resource, &exporter_with_resource);
483 });
484
485 let exporter_with_resource = TestExporterForResource::new();
487 let processor_with_resource = TestProcessorForResource::new(exporter_with_resource.clone());
488 let _ = super::SdkLoggerProvider::builder()
489 .with_resource(
490 Resource::builder_empty()
491 .with_service_name("test_service")
492 .build(),
493 )
494 .with_log_processor(processor_with_resource.clone())
495 .build();
496 assert_resource(
497 &processor_with_resource,
498 &exporter_with_resource,
499 SERVICE_NAME,
500 Some("test_service"),
501 );
502 assert_eq!(processor_with_resource.resource().len(), 1);
503
504 temp_env::with_var(
506 "OTEL_RESOURCE_ATTRIBUTES",
507 Some("key1=value1, k2, k3=value2"),
508 || {
509 let exporter_with_resource = TestExporterForResource::new();
510 let processor_with_resource =
511 TestProcessorForResource::new(exporter_with_resource.clone());
512 let _ = super::SdkLoggerProvider::builder()
513 .with_log_processor(processor_with_resource.clone())
514 .build();
515 assert_resource(
516 &processor_with_resource,
517 &exporter_with_resource,
518 SERVICE_NAME,
519 Some("unknown_service"),
520 );
521 assert_resource(
522 &processor_with_resource,
523 &exporter_with_resource,
524 "key1",
525 Some("value1"),
526 );
527 assert_resource(
528 &processor_with_resource,
529 &exporter_with_resource,
530 "k3",
531 Some("value2"),
532 );
533 assert_telemetry_resource(&processor_with_resource, &exporter_with_resource);
534 assert_eq!(processor_with_resource.resource().len(), 6);
535 },
536 );
537
538 temp_env::with_var(
540 "OTEL_RESOURCE_ATTRIBUTES",
541 Some("my-custom-key=env-val,k2=value2"),
542 || {
543 let exporter_with_resource = TestExporterForResource::new();
544 let processor_with_resource =
545 TestProcessorForResource::new(exporter_with_resource.clone());
546 let _ = super::SdkLoggerProvider::builder()
547 .with_resource(
548 Resource::builder()
549 .with_attributes([
550 KeyValue::new("my-custom-key", "my-custom-value"),
551 KeyValue::new("my-custom-key2", "my-custom-value2"),
552 ])
553 .build(),
554 )
555 .with_log_processor(processor_with_resource.clone())
556 .build();
557 assert_resource(
558 &processor_with_resource,
559 &exporter_with_resource,
560 SERVICE_NAME,
561 Some("unknown_service"),
562 );
563 assert_resource(
564 &processor_with_resource,
565 &exporter_with_resource,
566 "my-custom-key",
567 Some("my-custom-value"),
568 );
569 assert_resource(
570 &processor_with_resource,
571 &exporter_with_resource,
572 "my-custom-key2",
573 Some("my-custom-value2"),
574 );
575 assert_resource(
576 &processor_with_resource,
577 &exporter_with_resource,
578 "k2",
579 Some("value2"),
580 );
581 assert_telemetry_resource(&processor_with_resource, &exporter_with_resource);
582 assert_eq!(processor_with_resource.resource().len(), 7);
583 },
584 );
585
586 let exporter_with_resource = TestExporterForResource::new();
588 let processor_with_resource = TestProcessorForResource::new(exporter_with_resource);
589 let _ = super::SdkLoggerProvider::builder()
590 .with_resource(Resource::empty())
591 .with_log_processor(processor_with_resource.clone())
592 .build();
593 assert_eq!(processor_with_resource.resource().len(), 0);
594 }
595
596 #[test]
597 #[cfg(feature = "trace")]
598 fn trace_context_test() {
599 let exporter = InMemoryLogExporter::default();
600
601 let logger_provider = SdkLoggerProvider::builder()
602 .with_simple_exporter(exporter.clone())
603 .build();
604
605 let logger = logger_provider.logger("test-logger");
606
607 let tracer_provider = SdkTracerProvider::builder().build();
608
609 let tracer = tracer_provider.tracer("test-tracer");
610
611 tracer.in_span("test-span", |cx| {
612 let ambient_ctxt = cx.span().span_context().clone();
613 let explicit_ctxt = TraceContext {
614 trace_id: TraceId::from(13),
615 span_id: SpanId::from(14),
616 trace_flags: None,
617 };
618
619 let mut ambient_ctxt_record = logger.create_log_record();
620 ambient_ctxt_record.set_body(AnyValue::String("ambient".into()));
621
622 let mut explicit_ctxt_record = logger.create_log_record();
623 explicit_ctxt_record.set_body(AnyValue::String("explicit".into()));
624 explicit_ctxt_record.set_trace_context(
625 explicit_ctxt.trace_id,
626 explicit_ctxt.span_id,
627 explicit_ctxt.trace_flags,
628 );
629
630 logger.emit(ambient_ctxt_record);
631 logger.emit(explicit_ctxt_record);
632
633 let emitted = exporter.get_emitted_logs().unwrap();
634
635 assert_eq!(
636 Some(AnyValue::String("ambient".into())),
637 emitted[0].record.body
638 );
639 assert_eq!(
640 ambient_ctxt.trace_id(),
641 emitted[0].record.trace_context.as_ref().unwrap().trace_id
642 );
643 assert_eq!(
644 ambient_ctxt.span_id(),
645 emitted[0].record.trace_context.as_ref().unwrap().span_id
646 );
647
648 assert_eq!(
649 Some(AnyValue::String("explicit".into())),
650 emitted[1].record.body
651 );
652 assert_eq!(
653 explicit_ctxt.trace_id,
654 emitted[1].record.trace_context.as_ref().unwrap().trace_id
655 );
656 assert_eq!(
657 explicit_ctxt.span_id,
658 emitted[1].record.trace_context.as_ref().unwrap().span_id
659 );
660 });
661 }
662
663 #[test]
664 fn shutdown_test() {
665 let counter = Arc::new(AtomicU64::new(0));
666 let logger_provider = SdkLoggerProvider::builder()
667 .with_log_processor(ShutdownTestLogProcessor::new(counter.clone()))
668 .build();
669
670 let logger1 = logger_provider.logger("test-logger1");
671 let logger2 = logger_provider.logger("test-logger2");
672 logger1.emit(logger1.create_log_record());
673 logger2.emit(logger1.create_log_record());
674
675 let logger3 = logger_provider.logger("test-logger3");
676 let handle = thread::spawn(move || {
677 logger3.emit(logger3.create_log_record());
678 });
679 handle.join().expect("thread panicked");
680
681 let _ = logger_provider.shutdown();
682 logger1.emit(logger1.create_log_record());
683
684 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 3);
685 }
686
687 #[test]
688 fn shutdown_idempotent_test() {
689 let counter = Arc::new(AtomicU64::new(0));
690 let logger_provider = SdkLoggerProvider::builder()
691 .with_log_processor(ShutdownTestLogProcessor::new(counter.clone()))
692 .build();
693
694 let shutdown_res = logger_provider.shutdown();
695 assert!(shutdown_res.is_ok());
696
697 let shutdown_res = logger_provider.shutdown();
699 assert!(shutdown_res.is_err());
700
701 let shutdown_res = logger_provider.shutdown();
703 assert!(shutdown_res.is_err());
704 }
705
706 #[test]
707 fn global_shutdown_test() {
708 let shutdown_called = Arc::new(Mutex::new(false));
712 let flush_called = Arc::new(Mutex::new(false));
713 let logger_provider = SdkLoggerProvider::builder()
714 .with_log_processor(LazyLogProcessor::new(
715 shutdown_called.clone(),
716 flush_called.clone(),
717 ))
718 .build();
719 let logger1 = logger_provider.logger("test-logger1");
721 let logger2 = logger_provider.logger("test-logger2");
722
723 logger1.emit(logger1.create_log_record());
725 logger2.emit(logger1.create_log_record());
726
727 let _ = logger_provider.shutdown();
730
731 assert!(*shutdown_called.lock().unwrap());
735
736 assert!(!*flush_called.lock().unwrap());
738 }
739
740 #[test]
741 fn drop_test_with_multiple_providers() {
742 let shutdown_called = Arc::new(Mutex::new(false));
743 let flush_called = Arc::new(Mutex::new(false));
744 {
745 let shared_inner = Arc::new(LoggerProviderInner {
747 processors: vec![Box::new(LazyLogProcessor::new(
748 shutdown_called.clone(),
749 flush_called.clone(),
750 ))],
751 is_shutdown: AtomicBool::new(false),
752 });
753
754 {
755 let logger_provider1 = SdkLoggerProvider {
756 inner: shared_inner.clone(),
757 };
758 let logger_provider2 = SdkLoggerProvider {
759 inner: shared_inner.clone(),
760 };
761
762 let logger1 = logger_provider1.logger("test-logger1");
763 let logger2 = logger_provider2.logger("test-logger2");
764
765 logger1.emit(logger1.create_log_record());
766 logger2.emit(logger1.create_log_record());
767
768 }
771 }
774 assert!(*shutdown_called.lock().unwrap());
776 assert!(!*flush_called.lock().unwrap());
778 }
779
780 #[test]
781 fn drop_after_shutdown_test_with_multiple_providers() {
782 let shutdown_called = Arc::new(Mutex::new(0)); let flush_called = Arc::new(Mutex::new(false));
784
785 let shared_inner = Arc::new(LoggerProviderInner {
787 processors: vec![Box::new(CountingShutdownProcessor::new(
788 shutdown_called.clone(),
789 flush_called.clone(),
790 ))],
791 is_shutdown: AtomicBool::new(false),
792 });
793
794 {
796 let logger_provider1 = SdkLoggerProvider {
797 inner: shared_inner.clone(),
798 };
799 let logger_provider2 = SdkLoggerProvider {
800 inner: shared_inner.clone(),
801 };
802
803 let shutdown_result = logger_provider1.shutdown();
805 println!("---->Result: {shutdown_result:?}");
806 assert!(shutdown_result.is_ok());
807
808 assert_eq!(*shutdown_called.lock().unwrap(), 1);
810
811 let shutdown_result2 = logger_provider2.shutdown();
813 assert!(shutdown_result2.is_err());
814
815 }
817
818 assert_eq!(*shutdown_called.lock().unwrap(), 1);
820 }
821
822 #[test]
823 fn test_empty_logger_name() {
824 let exporter = InMemoryLogExporter::default();
825 let logger_provider = SdkLoggerProvider::builder()
826 .with_simple_exporter(exporter.clone())
827 .build();
828 let logger = logger_provider.logger("");
829 let mut record = logger.create_log_record();
830 record.set_body("Testing empty logger name".into());
831 logger.emit(record);
832
833 let scope = InstrumentationScope::builder("").build();
835 let scoped_logger = logger_provider.logger_with_scope(scope);
836 let mut scoped_record = scoped_logger.create_log_record();
837 scoped_record.set_body("Testing empty logger scope name".into());
838 scoped_logger.emit(scoped_record);
839
840 let mut emitted_logs = exporter.get_emitted_logs().unwrap();
842 assert_eq!(emitted_logs.len(), 2);
843 let log1 = emitted_logs.remove(0);
844 assert_eq!(
846 log1.record.body,
847 Some(AnyValue::String("Testing empty logger name".into()))
848 );
849 assert_eq!(log1.instrumentation.name(), "");
850
851 let log2 = emitted_logs.remove(0);
853 assert_eq!(
854 log2.record.body,
855 Some(AnyValue::String("Testing empty logger scope name".into()))
856 );
857 assert_eq!(log1.instrumentation.name(), "");
858 }
859
860 #[test]
861 fn with_resource_multiple_calls_ensure_additive() {
862 let builder = SdkLoggerProvider::builder()
863 .with_resource(Resource::new(vec![KeyValue::new("key1", "value1")]))
864 .with_resource(Resource::new(vec![KeyValue::new("key2", "value2")]))
865 .with_resource(
866 Resource::builder_empty()
867 .with_schema_url(vec![], "http://example.com")
868 .build(),
869 )
870 .with_resource(Resource::new(vec![KeyValue::new("key3", "value3")]));
871
872 let resource = builder.resource.unwrap();
873
874 assert_eq!(
875 resource.get(&Key::from_static_str("key1")),
876 Some(Value::from("value1"))
877 );
878 assert_eq!(
879 resource.get(&Key::from_static_str("key2")),
880 Some(Value::from("value2"))
881 );
882 assert_eq!(
883 resource.get(&Key::from_static_str("key3")),
884 Some(Value::from("value3"))
885 );
886 assert_eq!(resource.schema_url(), Some("http://example.com"));
887 }
888
889 #[derive(Debug)]
890 pub(crate) struct LazyLogProcessor {
891 shutdown_called: Arc<Mutex<bool>>,
892 flush_called: Arc<Mutex<bool>>,
893 }
894
895 impl LazyLogProcessor {
896 pub(crate) fn new(
897 shutdown_called: Arc<Mutex<bool>>,
898 flush_called: Arc<Mutex<bool>>,
899 ) -> Self {
900 LazyLogProcessor {
901 shutdown_called,
902 flush_called,
903 }
904 }
905 }
906
907 impl LogProcessor for LazyLogProcessor {
908 fn emit(&self, _data: &mut SdkLogRecord, _scope: &InstrumentationScope) {
909 }
911
912 fn force_flush(&self) -> OTelSdkResult {
913 *self.flush_called.lock().unwrap() = true;
914 Ok(())
915 }
916
917 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
918 *self.shutdown_called.lock().unwrap() = true;
919 Ok(())
920 }
921 }
922
923 #[derive(Debug)]
924 struct CountingShutdownProcessor {
925 shutdown_count: Arc<Mutex<i32>>,
926 flush_called: Arc<Mutex<bool>>,
927 }
928
929 impl CountingShutdownProcessor {
930 fn new(shutdown_count: Arc<Mutex<i32>>, flush_called: Arc<Mutex<bool>>) -> Self {
931 CountingShutdownProcessor {
932 shutdown_count,
933 flush_called,
934 }
935 }
936 }
937
938 impl LogProcessor for CountingShutdownProcessor {
939 fn emit(&self, _data: &mut SdkLogRecord, _scope: &InstrumentationScope) {
940 }
942
943 fn force_flush(&self) -> OTelSdkResult {
944 *self.flush_called.lock().unwrap() = true;
945 Ok(())
946 }
947
948 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
949 let mut count = self.shutdown_count.lock().unwrap();
950 *count += 1;
951 Ok(())
952 }
953 }
954}