1use crate::error::{OTelSdkError, OTelSdkResult};
38use crate::resource::Resource;
39use crate::trace::Span;
40use crate::trace::{SpanData, SpanExporter};
41use opentelemetry::Context;
42use opentelemetry::{otel_debug, otel_error, otel_warn};
43use std::cmp::min;
44use std::sync::atomic::{AtomicUsize, Ordering};
45use std::sync::{Arc, Mutex};
46use std::{env, str::FromStr, time::Duration};
47
48use std::sync::atomic::AtomicBool;
49use std::thread;
50use std::time::Instant;
51
52pub(crate) const OTEL_BSP_SCHEDULE_DELAY: &str = "OTEL_BSP_SCHEDULE_DELAY";
54pub(crate) const OTEL_BSP_SCHEDULE_DELAY_DEFAULT: Duration = Duration::from_millis(5_000);
56pub(crate) const OTEL_BSP_MAX_QUEUE_SIZE: &str = "OTEL_BSP_MAX_QUEUE_SIZE";
58pub(crate) const OTEL_BSP_MAX_QUEUE_SIZE_DEFAULT: usize = 2_048;
60pub(crate) const OTEL_BSP_MAX_EXPORT_BATCH_SIZE: &str = "OTEL_BSP_MAX_EXPORT_BATCH_SIZE";
62pub(crate) const OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT: usize = 512;
64pub(crate) const OTEL_BSP_EXPORT_TIMEOUT: &str = "OTEL_BSP_EXPORT_TIMEOUT";
66pub(crate) const OTEL_BSP_EXPORT_TIMEOUT_DEFAULT: Duration = Duration::from_millis(30_000);
68pub(crate) const OTEL_BSP_MAX_CONCURRENT_EXPORTS: &str = "OTEL_BSP_MAX_CONCURRENT_EXPORTS";
69pub(crate) const OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT: usize = 1;
71
72pub trait SpanProcessor: Send + Sync + std::fmt::Debug {
76 fn on_start(&self, span: &mut Span, cx: &Context);
80
81 fn on_end(&self, span: SpanData);
121 fn force_flush(&self) -> OTelSdkResult;
123 fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult;
128 fn shutdown(&self) -> OTelSdkResult {
130 self.shutdown_with_timeout(Duration::from_secs(5))
131 }
132 fn set_resource(&mut self, _resource: &Resource) {}
134}
135
136#[derive(Debug)]
157pub struct SimpleSpanProcessor<T: SpanExporter> {
158 exporter: Mutex<T>,
159}
160
161impl<T: SpanExporter> SimpleSpanProcessor<T> {
162 pub fn new(exporter: T) -> Self {
164 Self {
165 exporter: Mutex::new(exporter),
166 }
167 }
168}
169
170impl<T: SpanExporter> SpanProcessor for SimpleSpanProcessor<T> {
171 fn on_start(&self, _span: &mut Span, _cx: &Context) {
172 }
174
175 fn on_end(&self, span: SpanData) {
176 if !span.span_context.is_sampled() {
177 return;
178 }
179
180 let result = self
181 .exporter
182 .lock()
183 .map_err(|_| OTelSdkError::InternalFailure("SimpleSpanProcessor mutex poison".into()))
184 .and_then(|exporter| futures_executor::block_on(exporter.export(vec![span])));
185
186 if let Err(err) = result {
187 otel_debug!(
189 name: "SimpleProcessor.OnEnd.Error",
190 reason = format!("{:?}", err)
191 );
192 }
193 }
194
195 fn force_flush(&self) -> OTelSdkResult {
196 Ok(())
198 }
199
200 fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
201 if let Ok(exporter) = self.exporter.lock() {
202 exporter.shutdown_with_timeout(timeout)
203 } else {
204 Err(OTelSdkError::InternalFailure(
205 "SimpleSpanProcessor mutex poison at shutdown".into(),
206 ))
207 }
208 }
209
210 fn set_resource(&mut self, resource: &Resource) {
211 if let Ok(mut exporter) = self.exporter.lock() {
212 exporter.set_resource(resource);
213 }
214 }
215}
216
217use std::sync::mpsc::sync_channel;
283use std::sync::mpsc::Receiver;
284use std::sync::mpsc::RecvTimeoutError;
285use std::sync::mpsc::SyncSender;
286
287#[allow(clippy::large_enum_variant)]
289#[derive(Debug)]
290enum BatchMessage {
291 ExportSpan(Arc<AtomicBool>),
293 ForceFlush(SyncSender<OTelSdkResult>),
294 Shutdown(SyncSender<OTelSdkResult>),
295 SetResource(Arc<Resource>),
296}
297
298#[derive(Debug)]
334pub struct BatchSpanProcessor {
335 span_sender: SyncSender<SpanData>, message_sender: SyncSender<BatchMessage>, handle: Mutex<Option<thread::JoinHandle<()>>>,
338 forceflush_timeout: Duration,
339 export_span_message_sent: Arc<AtomicBool>,
340 current_batch_size: Arc<AtomicUsize>,
341 max_export_batch_size: usize,
342 dropped_spans_count: AtomicUsize,
343 max_queue_size: usize,
344}
345
346impl BatchSpanProcessor {
347 pub fn new<E>(
349 mut exporter: E,
350 config: BatchConfig,
351 ) -> Self
355 where
356 E: SpanExporter + Send + 'static,
357 {
358 let (span_sender, span_receiver) = sync_channel::<SpanData>(config.max_queue_size);
359 let (message_sender, message_receiver) = sync_channel::<BatchMessage>(64); let max_queue_size = config.max_queue_size;
361 let max_export_batch_size = config.max_export_batch_size;
362 let current_batch_size = Arc::new(AtomicUsize::new(0));
363 let current_batch_size_for_thread = current_batch_size.clone();
364
365 let handle = thread::Builder::new()
366 .name("OpenTelemetry.Traces.BatchProcessor".to_string())
367 .spawn(move || {
368 let _suppress_guard = Context::enter_telemetry_suppressed_scope();
369 otel_debug!(
370 name: "BatchSpanProcessor.ThreadStarted",
371 interval_in_millisecs = config.scheduled_delay.as_millis(),
372 max_export_batch_size = config.max_export_batch_size,
373 max_queue_size = config.max_queue_size,
374 );
375 let mut spans = Vec::with_capacity(config.max_export_batch_size);
376 let mut last_export_time = Instant::now();
377 let current_batch_size = current_batch_size_for_thread;
378 loop {
379 let remaining_time_option = config
380 .scheduled_delay
381 .checked_sub(last_export_time.elapsed());
382 let remaining_time = match remaining_time_option {
383 Some(remaining_time) => remaining_time,
384 None => config.scheduled_delay,
385 };
386 match message_receiver.recv_timeout(remaining_time) {
387 Ok(message) => match message {
388 BatchMessage::ExportSpan(export_span_message_sent) => {
389 export_span_message_sent.store(false, Ordering::Relaxed);
391 otel_debug!(
392 name: "BatchSpanProcessor.ExportingDueToBatchSize",
393 );
394 let _ = Self::get_spans_and_export(
395 &span_receiver,
396 &exporter,
397 &mut spans,
398 &mut last_export_time,
399 ¤t_batch_size,
400 &config,
401 );
402 }
403 BatchMessage::ForceFlush(sender) => {
404 otel_debug!(name: "BatchSpanProcessor.ExportingDueToForceFlush");
405 let result = Self::get_spans_and_export(
406 &span_receiver,
407 &exporter,
408 &mut spans,
409 &mut last_export_time,
410 ¤t_batch_size,
411 &config,
412 );
413 let _ = sender.send(result);
414 }
415 BatchMessage::Shutdown(sender) => {
416 otel_debug!(name: "BatchSpanProcessor.ExportingDueToShutdown");
417 let result = Self::get_spans_and_export(
418 &span_receiver,
419 &exporter,
420 &mut spans,
421 &mut last_export_time,
422 ¤t_batch_size,
423 &config,
424 );
425 let _ = exporter.shutdown();
426 let _ = sender.send(result);
427
428 otel_debug!(
429 name: "BatchSpanProcessor.ThreadExiting",
430 reason = "ShutdownRequested"
431 );
432 break;
436 }
437 BatchMessage::SetResource(resource) => {
438 exporter.set_resource(&resource);
439 }
440 },
441 Err(RecvTimeoutError::Timeout) => {
442 otel_debug!(
443 name: "BatchSpanProcessor.ExportingDueToTimer",
444 );
445
446 let _ = Self::get_spans_and_export(
447 &span_receiver,
448 &exporter,
449 &mut spans,
450 &mut last_export_time,
451 ¤t_batch_size,
452 &config,
453 );
454 }
455 Err(RecvTimeoutError::Disconnected) => {
456 otel_debug!(
459 name: "BatchSpanProcessor.ThreadExiting",
460 reason = "MessageSenderDisconnected"
461 );
462 break;
463 }
464 }
465 }
466 otel_debug!(
467 name: "BatchSpanProcessor.ThreadStopped"
468 );
469 })
470 .expect("Failed to spawn thread"); Self {
473 span_sender,
474 message_sender,
475 handle: Mutex::new(Some(handle)),
476 forceflush_timeout: Duration::from_secs(5), dropped_spans_count: AtomicUsize::new(0),
478 max_queue_size,
479 export_span_message_sent: Arc::new(AtomicBool::new(false)),
480 current_batch_size,
481 max_export_batch_size,
482 }
483 }
484
485 pub fn builder<E>(exporter: E) -> BatchSpanProcessorBuilder<E>
487 where
488 E: SpanExporter + Send + 'static,
489 {
490 BatchSpanProcessorBuilder {
491 exporter,
492 config: BatchConfig::default(),
493 }
494 }
495
496 #[inline]
500 fn get_spans_and_export<E>(
501 spans_receiver: &Receiver<SpanData>,
502 exporter: &E,
503 spans: &mut Vec<SpanData>,
504 last_export_time: &mut Instant,
505 current_batch_size: &AtomicUsize,
506 config: &BatchConfig,
507 ) -> OTelSdkResult
508 where
509 E: SpanExporter + Send + Sync + 'static,
510 {
511 let target = current_batch_size.load(Ordering::Acquire); let mut result = OTelSdkResult::Ok(());
513 let mut total_exported_spans: usize = 0;
514
515 while target > 0 && total_exported_spans < target {
516 let batch_limit = config
517 .max_export_batch_size
518 .min(target - total_exported_spans);
519
520 while let Ok(span) = spans_receiver.try_recv() {
522 spans.push(span);
523 if spans.len() == batch_limit {
524 break;
525 }
526 }
527
528 let count_of_spans = spans.len(); if count_of_spans == 0 {
530 break;
531 }
532 total_exported_spans += count_of_spans;
533
534 result = Self::export_batch_sync(exporter, spans, last_export_time); current_batch_size.fetch_sub(count_of_spans, Ordering::AcqRel);
537 }
538 result
539 }
540
541 #[allow(clippy::vec_box)]
542 fn export_batch_sync<E>(
543 exporter: &E,
544 batch: &mut Vec<SpanData>,
545 last_export_time: &mut Instant,
546 ) -> OTelSdkResult
547 where
548 E: SpanExporter + ?Sized,
549 {
550 *last_export_time = Instant::now();
551
552 if batch.is_empty() {
553 return OTelSdkResult::Ok(());
554 }
555
556 let export = exporter.export(batch.split_off(0));
563 let export_result = futures_executor::block_on(export);
564
565 match export_result {
566 Ok(_) => OTelSdkResult::Ok(()),
567 Err(err) => {
568 otel_error!(
569 name: "BatchSpanProcessor.ExportError",
570 error = format!("{}", err)
571 );
572 OTelSdkResult::Err(err)
573 }
574 }
575 }
576}
577
578impl SpanProcessor for BatchSpanProcessor {
579 fn on_start(&self, _span: &mut Span, _cx: &Context) {
581 }
583
584 fn on_end(&self, span: SpanData) {
586 let result = self.span_sender.try_send(span);
587
588 match result {
590 Ok(_) => {
591 if self.current_batch_size.fetch_add(1, Ordering::AcqRel) + 1
595 >= self.max_export_batch_size
596 {
597 if !self.export_span_message_sent.load(Ordering::Relaxed) {
604 if !self.export_span_message_sent.swap(true, Ordering::Relaxed) {
614 match self.message_sender.try_send(BatchMessage::ExportSpan(
615 self.export_span_message_sent.clone(),
616 )) {
617 Ok(_) => {
618 }
620 Err(_err) => {
621 self.export_span_message_sent
625 .store(false, Ordering::Relaxed);
626 }
627 }
628 }
629 }
630 }
631 }
632 Err(std::sync::mpsc::TrySendError::Full(_)) => {
633 if self.dropped_spans_count.fetch_add(1, Ordering::Relaxed) == 0 {
636 otel_warn!(name: "BatchSpanProcessor.SpanDroppingStarted",
637 message = "BatchSpanProcessor dropped a Span due to queue full. No further log will be emitted for further drops until Shutdown. During Shutdown time, a log will be emitted with exact count of total spans dropped.");
638 }
639 }
640 Err(std::sync::mpsc::TrySendError::Disconnected(_)) => {
641 otel_warn!(
644 name: "BatchSpanProcessor.OnEnd.AfterShutdown",
645 message = "Spans are being emitted even after Shutdown. This indicates incorrect lifecycle management of TracerProvider in application. Spans will not be exported."
646 );
647 }
648 }
649 }
650
651 fn force_flush(&self) -> OTelSdkResult {
653 let (sender, receiver) = std::sync::mpsc::sync_channel(1);
654 match self
655 .message_sender
656 .try_send(BatchMessage::ForceFlush(sender))
657 {
658 Ok(_) => receiver
659 .recv_timeout(self.forceflush_timeout)
660 .map_err(|err| {
661 if err == std::sync::mpsc::RecvTimeoutError::Timeout {
662 OTelSdkError::Timeout(self.forceflush_timeout)
663 } else {
664 OTelSdkError::InternalFailure(format!("{err}"))
665 }
666 })?,
667 Err(std::sync::mpsc::TrySendError::Full(_)) => {
668 otel_debug!(
670 name: "BatchSpanProcessor.ForceFlush.ControlChannelFull",
671 message = "Control message to flush the worker thread could not be sent as the control channel is full. This can occur if user repeatedly calls force_flush/shutdown without finishing the previous call."
672 );
673 Err(OTelSdkError::InternalFailure("ForceFlush cannot be performed as Control channel is full. This can occur if user repeatedly calls force_flush/shutdown without finishing the previous call.".into()))
674 }
675 Err(std::sync::mpsc::TrySendError::Disconnected(_)) => {
676 otel_debug!(
679 name: "BatchSpanProcessor.ForceFlush.AlreadyShutdown",
680 message = "ForceFlush invoked after Shutdown. This will not perform Flush and indicates a incorrect lifecycle management in Application."
681 );
682
683 Err(OTelSdkError::AlreadyShutdown)
684 }
685 }
686 }
687
688 fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
690 let dropped_spans = self.dropped_spans_count.load(Ordering::Relaxed);
691 let max_queue_size = self.max_queue_size;
692 if dropped_spans > 0 {
693 otel_warn!(
694 name: "BatchSpanProcessor.SpansDropped",
695 dropped_span_count = dropped_spans,
696 max_queue_size = max_queue_size,
697 message = "Spans were dropped due to a queue being full. The count represents the total count of spans dropped in the lifetime of this BatchSpanProcessor. Consider increasing the queue size and/or decrease delay between intervals."
698 );
699 }
700
701 let (sender, receiver) = std::sync::mpsc::sync_channel(1);
702 match self.message_sender.try_send(BatchMessage::Shutdown(sender)) {
703 Ok(_) => {
704 receiver
705 .recv_timeout(timeout)
706 .map(|_| {
707 if let Some(handle) = self.handle.lock().unwrap().take() {
710 handle.join().unwrap();
711 }
712 OTelSdkResult::Ok(())
713 })
714 .map_err(|err| match err {
715 std::sync::mpsc::RecvTimeoutError::Timeout => {
716 otel_error!(
717 name: "BatchSpanProcessor.Shutdown.Timeout",
718 message = "BatchSpanProcessor shutdown timing out."
719 );
720 OTelSdkError::Timeout(timeout)
721 }
722 _ => {
723 otel_error!(
724 name: "BatchSpanProcessor.Shutdown.Error",
725 error = format!("{}", err)
726 );
727 OTelSdkError::InternalFailure(format!("{err}"))
728 }
729 })?
730 }
731 Err(std::sync::mpsc::TrySendError::Full(_)) => {
732 otel_debug!(
734 name: "BatchSpanProcessor.Shutdown.ControlChannelFull",
735 message = "Control message to shutdown the worker thread could not be sent as the control channel is full. This can occur if user repeatedly calls force_flush/shutdown without finishing the previous call."
736 );
737 Err(OTelSdkError::InternalFailure("Shutdown cannot be performed as Control channel is full. This can occur if user repeatedly calls force_flush/shutdown without finishing the previous call.".into()))
738 }
739 Err(std::sync::mpsc::TrySendError::Disconnected(_)) => {
740 otel_debug!(
743 name: "BatchSpanProcessor.Shutdown.AlreadyShutdown",
744 message = "Shutdown is being invoked more than once. This is noop, but indicates a potential issue in the application's lifecycle management."
745 );
746
747 Err(OTelSdkError::AlreadyShutdown)
748 }
749 }
750 }
751
752 fn set_resource(&mut self, resource: &Resource) {
754 let resource = Arc::new(resource.clone());
755 let _ = self
756 .message_sender
757 .try_send(BatchMessage::SetResource(resource));
758 }
759}
760
761#[derive(Debug, Default)]
763pub struct BatchSpanProcessorBuilder<E>
764where
765 E: SpanExporter + Send + 'static,
766{
767 exporter: E,
768 config: BatchConfig,
769}
770
771impl<E> BatchSpanProcessorBuilder<E>
772where
773 E: SpanExporter + Send + 'static,
774{
775 pub fn with_batch_config(self, config: BatchConfig) -> Self {
777 BatchSpanProcessorBuilder { config, ..self }
778 }
779
780 pub fn build(self) -> BatchSpanProcessor {
782 BatchSpanProcessor::new(self.exporter, self.config)
783 }
784}
785
786#[derive(Debug)]
789pub struct BatchConfig {
790 pub(crate) max_queue_size: usize,
793
794 pub(crate) scheduled_delay: Duration,
797
798 #[allow(dead_code)]
799 pub(crate) max_export_batch_size: usize,
804
805 #[allow(dead_code)]
806 pub(crate) max_export_timeout: Duration,
808
809 #[allow(dead_code)]
810 pub(crate) max_concurrent_exports: usize,
811}
812
813impl Default for BatchConfig {
814 fn default() -> Self {
815 BatchConfigBuilder::default().build()
816 }
817}
818
819#[derive(Debug)]
821pub struct BatchConfigBuilder {
822 max_queue_size: usize,
823 scheduled_delay: Duration,
824 max_export_batch_size: usize,
825 max_export_timeout: Duration,
826 max_concurrent_exports: usize,
827}
828
829impl Default for BatchConfigBuilder {
830 fn default() -> Self {
841 BatchConfigBuilder {
842 max_queue_size: OTEL_BSP_MAX_QUEUE_SIZE_DEFAULT,
843 scheduled_delay: OTEL_BSP_SCHEDULE_DELAY_DEFAULT,
844 max_export_batch_size: OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT,
845 max_export_timeout: OTEL_BSP_EXPORT_TIMEOUT_DEFAULT,
846 max_concurrent_exports: OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT,
847 }
848 .init_from_env_vars()
849 }
850}
851
852impl BatchConfigBuilder {
853 pub fn with_max_queue_size(mut self, max_queue_size: usize) -> Self {
862 self.max_queue_size = max_queue_size;
863 self
864 }
865
866 pub fn with_max_export_batch_size(mut self, max_export_batch_size: usize) -> Self {
876 self.max_export_batch_size = max_export_batch_size;
877 self
878 }
879
880 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
881 pub fn with_max_concurrent_exports(mut self, max_concurrent_exports: usize) -> Self {
898 self.max_concurrent_exports = max_concurrent_exports;
899 self
900 }
901
902 pub fn with_scheduled_delay(mut self, scheduled_delay: Duration) -> Self {
910 self.scheduled_delay = scheduled_delay;
911 self
912 }
913
914 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
922 pub fn with_max_export_timeout(mut self, max_export_timeout: Duration) -> Self {
923 self.max_export_timeout = max_export_timeout;
924 self
925 }
926
927 pub fn build(self) -> BatchConfig {
930 let max_export_batch_size = min(self.max_export_batch_size, self.max_queue_size);
933
934 BatchConfig {
935 max_queue_size: self.max_queue_size,
936 scheduled_delay: self.scheduled_delay,
937 max_export_timeout: self.max_export_timeout,
938 max_concurrent_exports: self.max_concurrent_exports,
939 max_export_batch_size,
940 }
941 }
942
943 fn init_from_env_vars(mut self) -> Self {
944 if let Some(max_concurrent_exports) = env::var(OTEL_BSP_MAX_CONCURRENT_EXPORTS)
945 .ok()
946 .and_then(|max_concurrent_exports| usize::from_str(&max_concurrent_exports).ok())
947 {
948 self.max_concurrent_exports = max_concurrent_exports;
949 }
950
951 if let Some(max_queue_size) = env::var(OTEL_BSP_MAX_QUEUE_SIZE)
952 .ok()
953 .and_then(|queue_size| usize::from_str(&queue_size).ok())
954 {
955 self.max_queue_size = max_queue_size;
956 }
957
958 if let Some(scheduled_delay) = env::var(OTEL_BSP_SCHEDULE_DELAY)
959 .ok()
960 .and_then(|delay| u64::from_str(&delay).ok())
961 {
962 self.scheduled_delay = Duration::from_millis(scheduled_delay);
963 }
964
965 if let Some(max_export_batch_size) = env::var(OTEL_BSP_MAX_EXPORT_BATCH_SIZE)
966 .ok()
967 .and_then(|batch_size| usize::from_str(&batch_size).ok())
968 {
969 self.max_export_batch_size = max_export_batch_size;
970 }
971
972 if self.max_export_batch_size > self.max_queue_size {
975 self.max_export_batch_size = self.max_queue_size;
976 }
977
978 if let Some(max_export_timeout) = env::var(OTEL_BSP_EXPORT_TIMEOUT)
979 .ok()
980 .and_then(|timeout| u64::from_str(&timeout).ok())
981 {
982 self.max_export_timeout = Duration::from_millis(max_export_timeout);
983 }
984
985 self
986 }
987}
988
989#[cfg(all(test, feature = "testing", feature = "trace"))]
990mod tests {
991 use super::{
993 BatchSpanProcessor, SimpleSpanProcessor, SpanProcessor, OTEL_BSP_EXPORT_TIMEOUT,
994 OTEL_BSP_MAX_EXPORT_BATCH_SIZE, OTEL_BSP_MAX_QUEUE_SIZE, OTEL_BSP_MAX_QUEUE_SIZE_DEFAULT,
995 OTEL_BSP_SCHEDULE_DELAY, OTEL_BSP_SCHEDULE_DELAY_DEFAULT,
996 };
997 use crate::error::OTelSdkResult;
998 use crate::testing::trace::new_test_export_span_data;
999 use crate::trace::span_processor::{
1000 OTEL_BSP_EXPORT_TIMEOUT_DEFAULT, OTEL_BSP_MAX_CONCURRENT_EXPORTS,
1001 OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT, OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT,
1002 };
1003 use crate::trace::InMemorySpanExporterBuilder;
1004 use crate::trace::{BatchConfig, BatchConfigBuilder, SpanEvents, SpanLinks};
1005 use crate::trace::{SpanData, SpanExporter};
1006 use opentelemetry::trace::{SpanContext, SpanId, SpanKind, Status};
1007 use std::fmt::Debug;
1008 use std::time::Duration;
1009
1010 #[test]
1011 fn simple_span_processor_on_end_calls_export() {
1012 let exporter = InMemorySpanExporterBuilder::new().build();
1013 let processor = SimpleSpanProcessor::new(exporter.clone());
1014 let span_data = new_test_export_span_data();
1015 processor.on_end(span_data.clone());
1016 assert_eq!(exporter.get_finished_spans().unwrap()[0], span_data);
1017 let _result = processor.shutdown();
1018 }
1019
1020 #[test]
1021 fn simple_span_processor_on_end_skips_export_if_not_sampled() {
1022 let exporter = InMemorySpanExporterBuilder::new().build();
1023 let processor = SimpleSpanProcessor::new(exporter.clone());
1024 let unsampled = SpanData {
1025 span_context: SpanContext::empty_context(),
1026 parent_span_id: SpanId::INVALID,
1027 parent_span_is_remote: false,
1028 span_kind: SpanKind::Internal,
1029 name: "opentelemetry".into(),
1030 start_time: opentelemetry::time::now(),
1031 end_time: opentelemetry::time::now(),
1032 attributes: Vec::new(),
1033 dropped_attributes_count: 0,
1034 events: SpanEvents::default(),
1035 links: SpanLinks::default(),
1036 status: Status::Unset,
1037 instrumentation_scope: Default::default(),
1038 };
1039 processor.on_end(unsampled);
1040 assert!(exporter.get_finished_spans().unwrap().is_empty());
1041 }
1042
1043 #[test]
1044 fn simple_span_processor_shutdown_calls_shutdown() {
1045 let exporter = InMemorySpanExporterBuilder::new().build();
1046 let processor = SimpleSpanProcessor::new(exporter.clone());
1047 let span_data = new_test_export_span_data();
1048 processor.on_end(span_data.clone());
1049 assert!(!exporter.get_finished_spans().unwrap().is_empty());
1050 let _result = processor.shutdown();
1051 assert!(exporter.get_finished_spans().unwrap().is_empty());
1053 }
1054
1055 #[test]
1056 fn test_default_const_values() {
1057 assert_eq!(OTEL_BSP_MAX_QUEUE_SIZE, "OTEL_BSP_MAX_QUEUE_SIZE");
1058 assert_eq!(OTEL_BSP_MAX_QUEUE_SIZE_DEFAULT, 2048);
1059 assert_eq!(OTEL_BSP_SCHEDULE_DELAY, "OTEL_BSP_SCHEDULE_DELAY");
1060 assert_eq!(OTEL_BSP_SCHEDULE_DELAY_DEFAULT.as_millis(), 5000);
1061 assert_eq!(
1062 OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
1063 "OTEL_BSP_MAX_EXPORT_BATCH_SIZE"
1064 );
1065 assert_eq!(OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT, 512);
1066 assert_eq!(OTEL_BSP_EXPORT_TIMEOUT, "OTEL_BSP_EXPORT_TIMEOUT");
1067 assert_eq!(OTEL_BSP_EXPORT_TIMEOUT_DEFAULT.as_millis(), 30000);
1068 }
1069
1070 #[test]
1071 fn test_default_batch_config_adheres_to_specification() {
1072 let env_vars = vec![
1073 OTEL_BSP_SCHEDULE_DELAY,
1074 OTEL_BSP_EXPORT_TIMEOUT,
1075 OTEL_BSP_MAX_QUEUE_SIZE,
1076 OTEL_BSP_MAX_EXPORT_BATCH_SIZE,
1077 OTEL_BSP_MAX_CONCURRENT_EXPORTS,
1078 ];
1079
1080 let config = temp_env::with_vars_unset(env_vars, BatchConfig::default);
1081
1082 assert_eq!(
1083 config.max_concurrent_exports,
1084 OTEL_BSP_MAX_CONCURRENT_EXPORTS_DEFAULT
1085 );
1086 assert_eq!(config.scheduled_delay, OTEL_BSP_SCHEDULE_DELAY_DEFAULT);
1087 assert_eq!(config.max_export_timeout, OTEL_BSP_EXPORT_TIMEOUT_DEFAULT);
1088 assert_eq!(config.max_queue_size, OTEL_BSP_MAX_QUEUE_SIZE_DEFAULT);
1089 assert_eq!(
1090 config.max_export_batch_size,
1091 OTEL_BSP_MAX_EXPORT_BATCH_SIZE_DEFAULT
1092 );
1093 }
1094
1095 #[test]
1096 fn test_code_based_config_overrides_env_vars() {
1097 let env_vars = vec![
1098 (OTEL_BSP_EXPORT_TIMEOUT, Some("60000")),
1099 (OTEL_BSP_MAX_CONCURRENT_EXPORTS, Some("5")),
1100 (OTEL_BSP_MAX_EXPORT_BATCH_SIZE, Some("1024")),
1101 (OTEL_BSP_MAX_QUEUE_SIZE, Some("4096")),
1102 (OTEL_BSP_SCHEDULE_DELAY, Some("2000")),
1103 ];
1104
1105 temp_env::with_vars(env_vars, || {
1106 let config = BatchConfigBuilder::default()
1107 .with_max_export_batch_size(512)
1108 .with_max_queue_size(2048)
1109 .with_scheduled_delay(Duration::from_millis(1000));
1110 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
1111 let config = {
1112 config
1113 .with_max_concurrent_exports(10)
1114 .with_max_export_timeout(Duration::from_millis(2000))
1115 };
1116 let config = config.build();
1117
1118 assert_eq!(config.max_export_batch_size, 512);
1119 assert_eq!(config.max_queue_size, 2048);
1120 assert_eq!(config.scheduled_delay, Duration::from_millis(1000));
1121 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
1122 {
1123 assert_eq!(config.max_concurrent_exports, 10);
1124 assert_eq!(config.max_export_timeout, Duration::from_millis(2000));
1125 }
1126 });
1127 }
1128
1129 #[test]
1130 fn test_batch_config_configurable_by_env_vars() {
1131 let env_vars = vec![
1132 (OTEL_BSP_SCHEDULE_DELAY, Some("2000")),
1133 (OTEL_BSP_EXPORT_TIMEOUT, Some("60000")),
1134 (OTEL_BSP_MAX_QUEUE_SIZE, Some("4096")),
1135 (OTEL_BSP_MAX_EXPORT_BATCH_SIZE, Some("1024")),
1136 ];
1137
1138 let config = temp_env::with_vars(env_vars, BatchConfig::default);
1139
1140 assert_eq!(config.scheduled_delay, Duration::from_millis(2000));
1141 assert_eq!(config.max_export_timeout, Duration::from_millis(60000));
1142 assert_eq!(config.max_queue_size, 4096);
1143 assert_eq!(config.max_export_batch_size, 1024);
1144 }
1145
1146 #[test]
1147 fn test_batch_config_max_export_batch_size_validation() {
1148 let env_vars = vec![
1149 (OTEL_BSP_MAX_QUEUE_SIZE, Some("256")),
1150 (OTEL_BSP_MAX_EXPORT_BATCH_SIZE, Some("1024")),
1151 ];
1152
1153 let config = temp_env::with_vars(env_vars, BatchConfig::default);
1154
1155 assert_eq!(config.max_queue_size, 256);
1156 assert_eq!(config.max_export_batch_size, 256);
1157 assert_eq!(config.scheduled_delay, OTEL_BSP_SCHEDULE_DELAY_DEFAULT);
1158 assert_eq!(config.max_export_timeout, OTEL_BSP_EXPORT_TIMEOUT_DEFAULT);
1159 }
1160
1161 #[test]
1162 fn test_batch_config_with_fields() {
1163 let batch = BatchConfigBuilder::default()
1164 .with_max_export_batch_size(10)
1165 .with_scheduled_delay(Duration::from_millis(10))
1166 .with_max_queue_size(10);
1167 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
1168 let batch = {
1169 batch
1170 .with_max_concurrent_exports(10)
1171 .with_max_export_timeout(Duration::from_millis(10))
1172 };
1173 let batch = batch.build();
1174 assert_eq!(batch.max_export_batch_size, 10);
1175 assert_eq!(batch.scheduled_delay, Duration::from_millis(10));
1176 assert_eq!(batch.max_queue_size, 10);
1177 #[cfg(feature = "experimental_trace_batch_span_processor_with_async_runtime")]
1178 {
1179 assert_eq!(batch.max_concurrent_exports, 10);
1180 assert_eq!(batch.max_export_timeout, Duration::from_millis(10));
1181 }
1182 }
1183
1184 fn create_test_span(name: &str) -> SpanData {
1186 SpanData {
1187 span_context: SpanContext::empty_context(),
1188 parent_span_id: SpanId::INVALID,
1189 parent_span_is_remote: false,
1190 span_kind: SpanKind::Internal,
1191 name: name.to_string().into(),
1192 start_time: opentelemetry::time::now(),
1193 end_time: opentelemetry::time::now(),
1194 attributes: Vec::new(),
1195 dropped_attributes_count: 0,
1196 events: SpanEvents::default(),
1197 links: SpanLinks::default(),
1198 status: Status::Unset,
1199 instrumentation_scope: Default::default(),
1200 }
1201 }
1202
1203 use crate::Resource;
1204 use opentelemetry::{Key, KeyValue, Value};
1205 use std::{
1206 sync::{
1207 atomic::{AtomicUsize, Ordering},
1208 Arc, Mutex,
1209 },
1210 time::Instant,
1211 };
1212
1213 #[derive(Debug)]
1215 struct MockSpanExporter {
1216 exported_spans: Arc<Mutex<Vec<SpanData>>>,
1217 exported_resource: Arc<Mutex<Option<Resource>>>,
1218 }
1219
1220 impl MockSpanExporter {
1221 fn new() -> Self {
1222 Self {
1223 exported_spans: Arc::new(Mutex::new(Vec::new())),
1224 exported_resource: Arc::new(Mutex::new(None)),
1225 }
1226 }
1227 }
1228
1229 impl SpanExporter for MockSpanExporter {
1230 async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
1231 let exported_spans = self.exported_spans.clone();
1232 exported_spans.lock().unwrap().extend(batch);
1233 Ok(())
1234 }
1235
1236 fn shutdown(&self) -> OTelSdkResult {
1237 Ok(())
1238 }
1239 fn set_resource(&mut self, resource: &Resource) {
1240 let mut exported_resource = self.exported_resource.lock().unwrap();
1241 *exported_resource = Some(resource.clone());
1242 }
1243 }
1244
1245 #[test]
1246 fn batchspanprocessor_handles_on_end() {
1247 let exporter = MockSpanExporter::new();
1248 let exporter_shared = exporter.exported_spans.clone();
1249 let config = BatchConfigBuilder::default()
1250 .with_max_queue_size(10)
1251 .with_max_export_batch_size(10)
1252 .with_scheduled_delay(Duration::from_secs(5))
1253 .build();
1254 let processor = BatchSpanProcessor::new(exporter, config);
1255
1256 let test_span = create_test_span("test_span");
1257 processor.on_end(test_span.clone());
1258
1259 std::thread::sleep(Duration::from_secs(6));
1261
1262 let exported_spans = exporter_shared.lock().unwrap();
1263 assert_eq!(exported_spans.len(), 1);
1264 assert_eq!(exported_spans[0].name, "test_span");
1265 }
1266
1267 #[test]
1268 fn batchspanprocessor_force_flush() {
1269 let exporter = MockSpanExporter::new();
1270 let exporter_shared = exporter.exported_spans.clone(); let config = BatchConfigBuilder::default()
1272 .with_max_queue_size(10)
1273 .with_max_export_batch_size(10)
1274 .with_scheduled_delay(Duration::from_secs(5))
1275 .build();
1276 let processor = BatchSpanProcessor::new(exporter, config);
1277
1278 let test_span = create_test_span("force_flush_span");
1280 processor.on_end(test_span.clone());
1281
1282 let flush_result = processor.force_flush();
1284 assert!(flush_result.is_ok(), "Force flush failed unexpectedly");
1285
1286 let exported_spans = exporter_shared.lock().unwrap();
1288 assert_eq!(
1289 exported_spans.len(),
1290 1,
1291 "Unexpected number of exported spans"
1292 );
1293 assert_eq!(exported_spans[0].name, "force_flush_span");
1294 }
1295
1296 #[test]
1297 fn batchspanprocessor_does_not_overdrain_unaccounted_spans() {
1298 let exporter = MockSpanExporter::new();
1299 let exported_spans = exporter.exported_spans.clone();
1300 let (sender, receiver) = std::sync::mpsc::sync_channel(4);
1301 let current_batch_size = AtomicUsize::new(1);
1302 let config = BatchConfigBuilder::default()
1303 .with_max_queue_size(4)
1304 .with_max_export_batch_size(4)
1305 .build();
1306 let mut spans = Vec::with_capacity(config.max_export_batch_size);
1307 let mut last_export_time = Instant::now();
1308
1309 sender.send(create_test_span("counted")).unwrap();
1310 sender.send(create_test_span("unaccounted")).unwrap();
1311
1312 let result = BatchSpanProcessor::get_spans_and_export(
1313 &receiver,
1314 &exporter,
1315 &mut spans,
1316 &mut last_export_time,
1317 ¤t_batch_size,
1318 &config,
1319 );
1320
1321 assert!(result.is_ok(), "export should succeed");
1322 assert_eq!(
1323 current_batch_size.load(Ordering::Relaxed),
1324 0,
1325 "helper should only subtract the counted span"
1326 );
1327 assert_eq!(
1328 exported_spans.lock().unwrap().len(),
1329 1,
1330 "helper should export at most the target batch size snapshot"
1331 );
1332 assert!(
1333 receiver.try_recv().is_ok(),
1334 "one span should remain queued for a later export cycle"
1335 );
1336 }
1337
1338 #[test]
1339 fn batchspanprocessor_shutdown() {
1340 let exporter = InMemorySpanExporterBuilder::new()
1342 .keep_records_on_shutdown()
1343 .build();
1344 let processor = BatchSpanProcessor::new(exporter.clone(), BatchConfig::default());
1345
1346 let record = create_test_span("test_span");
1347
1348 processor.on_end(record);
1349 processor.force_flush().unwrap();
1350 processor.shutdown().unwrap();
1351
1352 processor.on_end(create_test_span("after_shutdown_span"));
1354
1355 assert_eq!(1, exporter.get_finished_spans().unwrap().len());
1356 assert!(exporter.is_shutdown_called());
1357 }
1358
1359 #[test]
1360 fn batchspanprocessor_handles_dropped_spans() {
1361 #[derive(Debug)]
1364 struct SlowExporter {
1365 exported_count: Arc<std::sync::atomic::AtomicUsize>,
1366 }
1367
1368 impl SpanExporter for SlowExporter {
1369 async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
1370 std::thread::sleep(Duration::from_millis(50));
1372 self.exported_count
1373 .fetch_add(batch.len(), Ordering::Relaxed);
1374 Ok(())
1375 }
1376
1377 fn shutdown(&self) -> OTelSdkResult {
1378 Ok(())
1379 }
1380
1381 fn set_resource(&mut self, _resource: &Resource) {}
1382 }
1383
1384 let exported_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
1385 let exporter = SlowExporter {
1386 exported_count: exported_count.clone(),
1387 };
1388
1389 let max_queue_size = 10;
1390 let config = BatchConfigBuilder::default()
1391 .with_max_queue_size(max_queue_size)
1392 .with_max_export_batch_size(5)
1393 .with_scheduled_delay(Duration::from_millis(10))
1394 .build();
1395 let processor = BatchSpanProcessor::new(exporter, config);
1396
1397 let total_spans_to_send = 100;
1399 for i in 0..total_spans_to_send {
1400 let span = create_test_span(&format!("span_{}", i));
1401 processor.on_end(span);
1402 }
1403
1404 let _ = processor.force_flush();
1406
1407 let dropped = processor.dropped_spans_count.load(Ordering::Relaxed);
1408 let exported = exported_count.load(Ordering::Relaxed);
1409
1410 assert_eq!(
1412 dropped + exported,
1413 total_spans_to_send,
1414 "dropped ({}) + exported ({}) should equal total sent ({})",
1415 dropped,
1416 exported,
1417 total_spans_to_send
1418 );
1419
1420 assert!(
1422 dropped > 0,
1423 "Expected some spans to be dropped due to full queue. Exported: {}",
1424 exported
1425 );
1426 }
1427
1428 #[test]
1429 fn batchspanprocessor_sync_ignores_max_concurrent_exports() {
1430 #[derive(Debug)]
1431 struct TrackingExporter {
1432 active: Arc<AtomicUsize>,
1433 max_inflight: Arc<AtomicUsize>,
1434 export_calls: Arc<AtomicUsize>,
1435 delay: Duration,
1436 }
1437
1438 impl SpanExporter for TrackingExporter {
1439 async fn export(&self, _batch: Vec<SpanData>) -> OTelSdkResult {
1440 self.export_calls.fetch_add(1, Ordering::SeqCst);
1441 let inflight = self.active.fetch_add(1, Ordering::SeqCst) + 1;
1442 self.max_inflight.fetch_max(inflight, Ordering::SeqCst);
1443
1444 std::thread::sleep(self.delay);
1445 self.active.fetch_sub(1, Ordering::SeqCst);
1446 Ok(())
1447 }
1448 }
1449
1450 let active = Arc::new(AtomicUsize::new(0));
1451 let max_inflight = Arc::new(AtomicUsize::new(0));
1452 let export_calls = Arc::new(AtomicUsize::new(0));
1453 let exporter = TrackingExporter {
1454 active: active.clone(),
1455 max_inflight: max_inflight.clone(),
1456 export_calls: export_calls.clone(),
1457 delay: Duration::from_millis(50),
1458 };
1459
1460 let config = BatchConfig {
1461 max_export_batch_size: 1,
1462 max_queue_size: 16,
1463 scheduled_delay: Duration::from_secs(3600),
1464 max_export_timeout: Duration::from_secs(5),
1465 max_concurrent_exports: 4,
1466 };
1467
1468 let processor = BatchSpanProcessor::new(exporter, config);
1469
1470 processor.on_end(new_test_export_span_data());
1471 processor.on_end(new_test_export_span_data());
1472 processor.on_end(new_test_export_span_data());
1473
1474 processor.force_flush().expect("force flush failed");
1475 processor.shutdown().expect("shutdown failed");
1476
1477 assert_eq!(
1478 export_calls.load(Ordering::SeqCst),
1479 3,
1480 "expected three exports for three spans with max_export_batch_size=1"
1481 );
1482 assert_eq!(
1483 max_inflight.load(Ordering::SeqCst),
1484 1,
1485 "sync BatchSpanProcessor should export serially regardless of max_concurrent_exports"
1486 );
1487 }
1488
1489 #[test]
1490 fn validate_span_attributes_exported_correctly() {
1491 let exporter = MockSpanExporter::new();
1492 let exporter_shared = exporter.exported_spans.clone();
1493 let config = BatchConfigBuilder::default().build();
1494 let processor = BatchSpanProcessor::new(exporter, config);
1495
1496 let mut span_data = create_test_span("attribute_validation");
1498 span_data.attributes = vec![
1499 KeyValue::new("key1", "value1"),
1500 KeyValue::new("key2", "value2"),
1501 ];
1502 processor.on_end(span_data.clone());
1503
1504 let _ = processor.force_flush();
1506
1507 let exported_spans = exporter_shared.lock().unwrap();
1509 assert_eq!(exported_spans.len(), 1);
1510 let exported_span = &exported_spans[0];
1511 assert!(exported_span
1512 .attributes
1513 .contains(&KeyValue::new("key1", "value1")));
1514 assert!(exported_span
1515 .attributes
1516 .contains(&KeyValue::new("key2", "value2")));
1517 }
1518
1519 #[test]
1520 fn batchspanprocessor_sets_and_exports_with_resource() {
1521 let exporter = MockSpanExporter::new();
1522 let exporter_shared = exporter.exported_spans.clone();
1523 let resource_shared = exporter.exported_resource.clone();
1524 let config = BatchConfigBuilder::default().build();
1525 let mut processor = BatchSpanProcessor::new(exporter, config);
1526
1527 let resource = Resource::builder_empty()
1529 .with_attributes(vec![KeyValue::new("service.name", "test_service")])
1530 .build();
1531 processor.set_resource(&resource);
1532
1533 let test_span = create_test_span("resource_test");
1535 processor.on_end(test_span.clone());
1536
1537 let _ = processor.force_flush();
1539
1540 let exported_spans = exporter_shared.lock().unwrap();
1542 assert_eq!(exported_spans.len(), 1);
1543
1544 let exported_resource = resource_shared.lock().unwrap();
1546 assert!(exported_resource.is_some());
1547 assert_eq!(
1548 exported_resource
1549 .as_ref()
1550 .unwrap()
1551 .get(&Key::new("service.name")),
1552 Some(Value::from("test_service"))
1553 );
1554 }
1555
1556 #[tokio::test(flavor = "current_thread")]
1557 async fn test_batch_processor_current_thread_runtime() {
1558 let exporter = MockSpanExporter::new();
1559 let exporter_shared = exporter.exported_spans.clone();
1560
1561 let config = BatchConfigBuilder::default()
1562 .with_max_queue_size(5)
1563 .with_max_export_batch_size(3)
1564 .build();
1565
1566 let processor = BatchSpanProcessor::new(exporter, config);
1567
1568 for _ in 0..4 {
1569 let span = new_test_export_span_data();
1570 processor.on_end(span);
1571 }
1572
1573 processor.force_flush().unwrap();
1574
1575 let exported_spans = exporter_shared.lock().unwrap();
1576 assert_eq!(exported_spans.len(), 4);
1577 }
1578
1579 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1580 async fn test_batch_processor_multi_thread_count_1_runtime() {
1581 let exporter = MockSpanExporter::new();
1582 let exporter_shared = exporter.exported_spans.clone();
1583
1584 let config = BatchConfigBuilder::default()
1585 .with_max_queue_size(5)
1586 .with_max_export_batch_size(3)
1587 .build();
1588
1589 let processor = BatchSpanProcessor::new(exporter, config);
1590
1591 for _ in 0..4 {
1592 let span = new_test_export_span_data();
1593 processor.on_end(span);
1594 }
1595
1596 processor.force_flush().unwrap();
1597
1598 let exported_spans = exporter_shared.lock().unwrap();
1599 assert_eq!(exported_spans.len(), 4);
1600 }
1601
1602 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1603 async fn test_batch_processor_multi_thread() {
1604 let exporter = MockSpanExporter::new();
1605 let exporter_shared = exporter.exported_spans.clone();
1606
1607 let config = BatchConfigBuilder::default()
1608 .with_max_queue_size(20)
1609 .with_max_export_batch_size(5)
1610 .build();
1611
1612 let processor = Arc::new(BatchSpanProcessor::new(exporter, config));
1614
1615 let mut handles = vec![];
1616 for _ in 0..10 {
1617 let processor_clone = Arc::clone(&processor);
1618 let handle = tokio::spawn(async move {
1619 let span = new_test_export_span_data();
1620 processor_clone.on_end(span);
1621 });
1622 handles.push(handle);
1623 }
1624
1625 for handle in handles {
1626 handle.await.unwrap();
1627 }
1628
1629 processor.force_flush().unwrap();
1630
1631 let exported_spans = exporter_shared.lock().unwrap();
1633 assert_eq!(exported_spans.len(), 10);
1634 }
1635}