1
- //! # Trace Provider SDK
2
- //!
3
- //! ## Tracer Creation
4
- //!
5
- //! New [`Tracer`] instances are always created through a [`TracerProvider`].
6
- //!
7
- //! All configuration objects and extension points (span processors,
8
- //! propagators) are provided by the [`TracerProvider`]. [`Tracer`] instances do
9
- //! not duplicate this data to avoid that different [`Tracer`] instances
10
- //! of the [`TracerProvider`] have different versions of these data.
1
+ /// # Trace Provider SDK
2
+ ///
3
+ /// The `TracerProvider` handles the creation and management of [`Tracer`] instances and coordinates
4
+ /// span processing. It serves as the central configuration point for tracing, ensuring consistency
5
+ /// across all [`Tracer`] instances it creates.
6
+ ///
7
+ /// ## Tracer Creation
8
+ ///
9
+ /// New [`Tracer`] instances are always created through a `TracerProvider`. These `Tracer`s share
10
+ /// a common configuration, which includes the [`Resource`], span processors, sampling strategies,
11
+ /// and span limits. This avoids the need for each `Tracer` to maintain its own version of these
12
+ /// configurations, ensuring uniform behavior across all instances.
13
+ ///
14
+ /// ## Cloning and Shutdown
15
+ ///
16
+ /// The `TracerProvider` is designed to be clonable. Cloning a `TracerProvider` creates a
17
+ /// new reference to the same provider, not a new instance. Dropping the last reference
18
+ /// to the `TracerProvider` will automatically trigger its shutdown. During shutdown, the provider
19
+ /// will flush all remaining spans, ensuring they are passed to the configured processors.
20
+ /// Users can also manually trigger shutdown using the [`shutdown`](TracerProvider::shutdown)
21
+ /// method, which will ensure the same behavior.
22
+ ///
23
+ /// Once shut down, the `TracerProvider` transitions into a disabled state. In this state, further
24
+ /// operations on its associated `Tracer` instances will result in no-ops, ensuring that no spans
25
+ /// are processed or exported after shutdown.
26
+ ///
27
+ /// ## Span Processing and Force Flush
28
+ ///
29
+ /// The `TracerProvider` manages the lifecycle of span processors, which are responsible for
30
+ /// collecting, processing, and exporting spans. The [`force_flush`](TracerProvider::force_flush) method
31
+ /// invoked at any time will trigger an immediate flush of all pending spans (if any) to the exporters.
32
+ /// This will block the user thread till all the spans are passed to exporters.
33
+ ///
34
+ /// # Examples
35
+ ///
36
+ /// ```
37
+ /// use opentelemetry::global;
38
+ /// use opentelemetry_sdk::trace::TracerProvider;
39
+ /// use opentelemetry::trace::Tracer;
40
+ ///
41
+ /// fn init_tracing() -> TracerProvider {
42
+ /// let provider = TracerProvider::default();
43
+ ///
44
+ /// // Set the provider to be used globally
45
+ /// let _ = global::set_tracer_provider(provider.clone());
46
+ ///
47
+ /// provider
48
+ /// }
49
+ ///
50
+ /// fn main() {
51
+ /// let provider = init_tracing();
52
+ ///
53
+ /// // create tracer..
54
+ /// let tracer = global::tracer("example/client");
55
+ ///
56
+ /// // create span...
57
+ /// let span = tracer
58
+ /// .span_builder("test_span")
59
+ /// .start(&tracer);
60
+ ///
61
+ /// // Explicitly shut down the provider
62
+ /// provider.shutdown();
63
+ /// }
64
+ /// ```
11
65
use crate :: runtime:: RuntimeChannel ;
12
66
use crate :: trace:: {
13
67
BatchSpanProcessor , Config , RandomIdGenerator , Sampler , SimpleSpanProcessor , SpanLimits , Tracer ,
@@ -16,7 +70,7 @@ use crate::{export::trace::SpanExporter, trace::SpanProcessor};
16
70
use crate :: { InstrumentationLibrary , Resource } ;
17
71
use once_cell:: sync:: { Lazy , OnceCell } ;
18
72
use opentelemetry:: trace:: TraceError ;
19
- use opentelemetry:: { global , trace:: TraceResult } ;
73
+ use opentelemetry:: { otel_debug , trace:: TraceResult } ;
20
74
use std:: borrow:: Cow ;
21
75
use std:: sync:: atomic:: { AtomicBool , Ordering } ;
22
76
use std:: sync:: Arc ;
@@ -36,36 +90,60 @@ static NOOP_TRACER_PROVIDER: Lazy<TracerProvider> = Lazy::new(|| TracerProvider
36
90
span_limits : SpanLimits :: default ( ) ,
37
91
resource : Cow :: Owned ( Resource :: empty ( ) ) ,
38
92
} ,
93
+ is_shutdown : AtomicBool :: new ( true ) ,
39
94
} ) ,
40
- is_shutdown : Arc :: new ( AtomicBool :: new ( true ) ) ,
41
95
} ) ;
42
96
43
97
/// TracerProvider inner type
44
98
#[ derive( Debug ) ]
45
99
pub ( crate ) struct TracerProviderInner {
46
100
processors : Vec < Box < dyn SpanProcessor > > ,
47
101
config : crate :: trace:: Config ,
102
+ is_shutdown : AtomicBool ,
48
103
}
49
104
50
- impl Drop for TracerProviderInner {
51
- fn drop ( & mut self ) {
52
- for processor in & mut self . processors {
105
+ impl TracerProviderInner {
106
+ /// Crate-private shutdown method to be called both from explicit shutdown
107
+ /// and from Drop when the last reference is released.
108
+ pub ( crate ) fn shutdown ( & self ) -> Vec < TraceError > {
109
+ let mut errs = vec ! [ ] ;
110
+ for processor in & self . processors {
53
111
if let Err ( err) = processor. shutdown ( ) {
54
- global:: handle_error ( err) ;
112
+ // Log at debug level because:
113
+ // - The error is also returned to the user for handling (if applicable)
114
+ // - Or the error occurs during `TracerProviderInner::Drop` as part of telemetry shutdown,
115
+ // which is non-actionable by the user
116
+ otel_debug ! ( name: "TracerProvider.Drop.ShutdownError" ,
117
+ error = format!( "{err}" ) ) ;
118
+ errs. push ( err) ;
55
119
}
56
120
}
121
+ errs
122
+ }
123
+ }
124
+
125
+ impl Drop for TracerProviderInner {
126
+ fn drop ( & mut self ) {
127
+ if !self . is_shutdown . load ( Ordering :: Relaxed ) {
128
+ let _ = self . shutdown ( ) ; // errors are handled within shutdown
129
+ } else {
130
+ otel_debug ! (
131
+ name: "TracerProvider.Drop.AlreadyShutdown"
132
+ ) ;
133
+ }
57
134
}
58
135
}
59
136
60
137
/// Creator and registry of named [`Tracer`] instances.
61
138
///
62
- /// `TracerProvider` is lightweight container holding pointers to `SpanProcessor` and other components.
63
- /// Cloning and dropping them will not stop the span processing. To stop span processing, users
64
- /// must either call `shutdown` method explicitly, or drop every clone of `TracerProvider`.
139
+ /// `TracerProvider` is a container holding pointers to `SpanProcessor` and other components.
140
+ /// Cloning a `TracerProvider` instance and dropping it will not stop span processing. To stop span processing, users
141
+ /// must either call the `shutdown` method explicitly or allow the last reference to the `TracerProvider`
142
+ /// to be dropped. When the last reference is dropped, the shutdown process will be automatically triggered
143
+ /// to ensure proper cleanup.
65
144
#[ derive( Clone , Debug ) ]
66
145
pub struct TracerProvider {
67
146
inner : Arc < TracerProviderInner > ,
68
- is_shutdown : Arc < AtomicBool > ,
69
147
}
70
148
71
149
impl Default for TracerProvider {
@@ -79,7 +157,6 @@ impl TracerProvider {
79
157
pub ( crate ) fn new ( inner : TracerProviderInner ) -> Self {
80
158
TracerProvider {
81
159
inner : Arc :: new ( inner) ,
82
- is_shutdown : Arc :: new ( AtomicBool :: new ( false ) ) ,
83
160
}
84
161
}
85
162
@@ -101,7 +178,7 @@ impl TracerProvider {
101
178
/// true if the provider has been shutdown
102
179
/// Don't start span or export spans when provider is shutdown
103
180
pub ( crate ) fn is_shutdown ( & self ) -> bool {
104
- self . is_shutdown . load ( Ordering :: Relaxed )
181
+ self . inner . is_shutdown . load ( Ordering :: Relaxed )
105
182
}
106
183
107
184
/// Force flush all remaining spans in span processors and return results.
@@ -153,28 +230,20 @@ impl TracerProvider {
153
230
/// Note that shut down doesn't means the TracerProvider has dropped
154
231
pub fn shutdown ( & self ) -> TraceResult < ( ) > {
155
232
if self
233
+ . inner
156
234
. is_shutdown
157
235
. compare_exchange ( false , true , Ordering :: SeqCst , Ordering :: SeqCst )
158
236
. is_ok ( )
159
237
{
160
238
// propagate the shutdown signal to processors
161
- // it's up to the processor to properly block new spans after shutdown
162
- let mut errs = vec ! [ ] ;
163
- for processor in & self . inner . processors {
164
- if let Err ( err) = processor. shutdown ( ) {
165
- errs. push ( err) ;
166
- }
167
- }
168
-
239
+ let errs = self . inner . shutdown ( ) ;
169
240
if errs. is_empty ( ) {
170
241
Ok ( ( ) )
171
242
} else {
172
243
Err ( TraceError :: Other ( format ! ( "{errs:?}" ) . into ( ) ) )
173
244
}
174
245
} else {
175
- Err ( TraceError :: Other (
176
- "tracer provider already shut down" . into ( ) ,
177
- ) )
246
+ Err ( TraceError :: TracerProviderAlreadyShutdown )
178
247
}
179
248
}
180
249
}
@@ -215,7 +284,7 @@ impl opentelemetry::trace::TracerProvider for TracerProvider {
215
284
}
216
285
217
286
fn library_tracer ( & self , library : Arc < InstrumentationLibrary > ) -> Self :: Tracer {
218
- if self . is_shutdown . load ( Ordering :: Relaxed ) {
287
+ if self . inner . is_shutdown . load ( Ordering :: Relaxed ) {
219
288
return Tracer :: new ( library, NOOP_TRACER_PROVIDER . clone ( ) ) ;
220
289
}
221
290
Tracer :: new ( library, self . clone ( ) )
@@ -292,7 +361,12 @@ impl Builder {
292
361
p. set_resource ( config. resource . as_ref ( ) ) ;
293
362
}
294
363
295
- TracerProvider :: new ( TracerProviderInner { processors, config } )
364
+ let is_shutdown = AtomicBool :: new ( false ) ;
365
+ TracerProvider :: new ( TracerProviderInner {
366
+ processors,
367
+ config,
368
+ is_shutdown,
369
+ } )
296
370
}
297
371
}
298
372
@@ -391,6 +465,7 @@ mod tests {
391
465
Box :: from( TestSpanProcessor :: new( false ) ) ,
392
466
] ,
393
467
config : Default :: default ( ) ,
468
+ is_shutdown : AtomicBool :: new ( false ) ,
394
469
} ) ;
395
470
396
471
let results = tracer_provider. force_flush ( ) ;
@@ -534,6 +609,7 @@ mod tests {
534
609
let tracer_provider = super :: TracerProvider :: new ( TracerProviderInner {
535
610
processors : vec ! [ Box :: from( processor) ] ,
536
611
config : Default :: default ( ) ,
612
+ is_shutdown : AtomicBool :: new ( false ) ,
537
613
} ) ;
538
614
539
615
let test_tracer_1 = tracer_provider. tracer ( "test1" ) ;
@@ -554,14 +630,128 @@ mod tests {
554
630
555
631
// after shutdown we should get noop tracer
556
632
let noop_tracer = tracer_provider. tracer ( "noop" ) ;
633
+
557
634
// noop tracer cannot start anything
558
635
let _ = noop_tracer. start ( "test" ) ;
559
636
assert ! ( assert_handle. started_span_count( 2 ) ) ;
560
637
// noop tracer's tracer provider should be shutdown
561
- assert ! ( noop_tracer. provider( ) . is_shutdown. load ( Ordering :: SeqCst ) ) ;
638
+ assert ! ( noop_tracer. provider( ) . is_shutdown( ) ) ;
562
639
563
640
// existing tracer becomes noops after shutdown
564
641
let _ = test_tracer_1. start ( "test" ) ;
565
642
assert ! ( assert_handle. started_span_count( 2 ) ) ;
643
+
644
+ // also existing tracer's tracer provider are in shutdown state
645
+ assert ! ( test_tracer_1. provider( ) . is_shutdown( ) ) ;
646
+ }
647
+
648
+ #[ derive( Debug ) ]
649
+ struct CountingShutdownProcessor {
650
+ shutdown_count : Arc < AtomicU32 > ,
651
+ }
652
+
653
+ impl CountingShutdownProcessor {
654
+ fn new ( shutdown_count : Arc < AtomicU32 > ) -> Self {
655
+ CountingShutdownProcessor { shutdown_count }
656
+ }
657
+ }
658
+
659
+ impl SpanProcessor for CountingShutdownProcessor {
660
+ fn on_start ( & self , _span : & mut Span , _cx : & Context ) {
661
+ // No operation needed for this processor
662
+ }
663
+
664
+ fn on_end ( & self , _span : SpanData ) {
665
+ // No operation needed for this processor
666
+ }
667
+
668
+ fn force_flush ( & self ) -> TraceResult < ( ) > {
669
+ Ok ( ( ) )
670
+ }
671
+
672
+ fn shutdown ( & self ) -> TraceResult < ( ) > {
673
+ self . shutdown_count . fetch_add ( 1 , Ordering :: SeqCst ) ;
674
+ Ok ( ( ) )
675
+ }
676
+ }
677
+
678
+ #[ test]
679
+ fn drop_test_with_multiple_providers ( ) {
680
+ let shutdown_count = Arc :: new ( AtomicU32 :: new ( 0 ) ) ;
681
+
682
+ {
683
+ // Create a shared TracerProviderInner and use it across multiple providers
684
+ let shared_inner = Arc :: new ( TracerProviderInner {
685
+ processors : vec ! [ Box :: new( CountingShutdownProcessor :: new(
686
+ shutdown_count. clone( ) ,
687
+ ) ) ] ,
688
+ config : Config :: default ( ) ,
689
+ is_shutdown : AtomicBool :: new ( false ) ,
690
+ } ) ;
691
+
692
+ {
693
+ let tracer_provider1 = super :: TracerProvider {
694
+ inner : shared_inner. clone ( ) ,
695
+ } ;
696
+ let tracer_provider2 = super :: TracerProvider {
697
+ inner : shared_inner. clone ( ) ,
698
+ } ;
699
+
700
+ let tracer1 = tracer_provider1. tracer ( "test-tracer1" ) ;
701
+ let tracer2 = tracer_provider2. tracer ( "test-tracer2" ) ;
702
+
703
+ let _span1 = tracer1. start ( "span1" ) ;
704
+ let _span2 = tracer2. start ( "span2" ) ;
705
+
706
+ // TracerProviderInner should not be dropped yet, since both providers and `shared_inner`
707
+ // are still holding a reference.
708
+ }
709
+ // At this point, both `tracer_provider1` and `tracer_provider2` are dropped,
710
+ // but `shared_inner` still holds a reference, so `TracerProviderInner` is NOT dropped yet.
711
+ assert_eq ! ( shutdown_count. load( Ordering :: SeqCst ) , 0 ) ;
712
+ }
713
+ // Verify shutdown was called during the drop of the shared TracerProviderInner
714
+ assert_eq ! ( shutdown_count. load( Ordering :: SeqCst ) , 1 ) ;
715
+ }
716
+
717
+ #[ test]
718
+ fn drop_after_shutdown_test_with_multiple_providers ( ) {
719
+ let shutdown_count = Arc :: new ( AtomicU32 :: new ( 0 ) ) ;
720
+
721
+ // Create a shared TracerProviderInner and use it across multiple providers
722
+ let shared_inner = Arc :: new ( TracerProviderInner {
723
+ processors : vec ! [ Box :: new( CountingShutdownProcessor :: new(
724
+ shutdown_count. clone( ) ,
725
+ ) ) ] ,
726
+ config : Config :: default ( ) ,
727
+ is_shutdown : AtomicBool :: new ( false ) ,
728
+ } ) ;
729
+
730
+ // Create a scope to test behavior when providers are dropped
731
+ {
732
+ let tracer_provider1 = super :: TracerProvider {
733
+ inner : shared_inner. clone ( ) ,
734
+ } ;
735
+ let tracer_provider2 = super :: TracerProvider {
736
+ inner : shared_inner. clone ( ) ,
737
+ } ;
738
+
739
+ // Explicitly shut down the tracer provider
740
+ let shutdown_result = tracer_provider1. shutdown ( ) ;
741
+ assert ! ( shutdown_result. is_ok( ) ) ;
742
+
743
+ // Verify that shutdown was called exactly once
744
+ assert_eq ! ( shutdown_count. load( Ordering :: SeqCst ) , 1 ) ;
745
+
746
+ // TracerProvider2 should observe the shutdown state but not trigger another shutdown
747
+ let shutdown_result2 = tracer_provider2. shutdown ( ) ;
748
+ assert ! ( shutdown_result2. is_err( ) ) ;
749
+ assert_eq ! ( shutdown_count. load( Ordering :: SeqCst ) , 1 ) ;
750
+
751
+ // Both tracer providers will be dropped at the end of this scope
752
+ }
753
+
754
+ // Verify that shutdown was only called once, even after drop
755
+ assert_eq ! ( shutdown_count. load( Ordering :: SeqCst ) , 1 ) ;
566
756
}
567
757
}
0 commit comments