Skip to main content

opentelemetry_sdk/trace/
provider.rs

1use super::IdGenerator;
2use crate::error::{OTelSdkError, OTelSdkResult};
3/// # Trace Provider SDK
4///
5/// The `TracerProvider` handles the creation and management of [`Tracer`] instances and coordinates
6/// span processing. It serves as the central configuration point for tracing, ensuring consistency
7/// across all [`Tracer`] instances it creates.
8///
9/// ## Tracer Creation
10///
11/// New [`Tracer`] instances are always created through a `TracerProvider`. These `Tracer`s share
12/// a common configuration, which includes the [`Resource`], span processors, sampling strategies,
13/// and span limits. This avoids the need for each `Tracer` to maintain its own version of these
14/// configurations, ensuring uniform behavior across all instances.
15///
16/// ## Cloning and Shutdown
17///
18/// The `TracerProvider` is designed to be clonable. Cloning a `TracerProvider`  creates a
19/// new reference to the same provider, not a new instance. Dropping the last reference
20/// to the `TracerProvider` will automatically trigger its shutdown. During shutdown, the provider
21/// will flush all remaining spans, ensuring they are passed to the configured processors.
22/// Users can also manually trigger shutdown using the [`shutdown`](TracerProvider::shutdown)
23/// method, which will ensure the same behavior.
24///
25/// Once shut down, the `TracerProvider` transitions into a disabled state. In this state, further
26/// operations on its associated `Tracer` instances will result in no-ops, ensuring that no spans
27/// are processed or exported after shutdown.
28///
29/// ## Span Processing and Force Flush
30///
31/// The `TracerProvider` manages the lifecycle of span processors, which are responsible for
32/// collecting, processing, and exporting spans. The [`force_flush`](TracerProvider::force_flush) method
33/// invoked at any time will trigger an immediate flush of all pending spans (if any) to the exporters.
34/// This will block the user thread till all the spans are passed to exporters.
35///
36/// # Examples
37///
38/// ```
39/// use opentelemetry::global;
40/// use opentelemetry_sdk::trace::SdkTracerProvider;
41/// use opentelemetry::trace::Tracer;
42///
43/// fn init_tracing() -> SdkTracerProvider {
44///     let provider = SdkTracerProvider::default();
45///
46///     // Set the provider to be used globally
47///     let _ = global::set_tracer_provider(provider.clone());
48///
49///     provider
50/// }
51///
52/// fn main() {
53///     let provider = init_tracing();
54///
55///     // create tracer..
56///     let tracer = global::tracer("example/client");
57///
58///     // create span...
59///     let span = tracer
60///         .span_builder("test_span")
61///         .start(&tracer);
62///
63///     // Explicitly shut down the provider
64///     provider.shutdown();
65/// }
66/// ```
67use crate::trace::{
68    BatchSpanProcessor, Config, RandomIdGenerator, Sampler, SdkTracer, SimpleSpanProcessor,
69    SpanLimits,
70};
71use crate::Resource;
72use crate::{trace::SpanExporter, trace::SpanProcessor};
73use opentelemetry::otel_debug;
74use opentelemetry::{otel_info, InstrumentationScope};
75use std::borrow::Cow;
76use std::sync::atomic::{AtomicBool, Ordering};
77use std::sync::{Arc, OnceLock};
78use std::time::Duration;
79
80static PROVIDER_RESOURCE: OnceLock<Resource> = OnceLock::new();
81
82// a no nop tracer provider used as placeholder when the provider is shutdown
83// TODO Replace with LazyLock once it is stable
84static NOOP_TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();
85#[inline]
86fn noop_tracer_provider() -> &'static SdkTracerProvider {
87    NOOP_TRACER_PROVIDER.get_or_init(|| {
88        SdkTracerProvider {
89            inner: Arc::new(TracerProviderInner {
90                processors: Vec::new(),
91                config: Config {
92                    // cannot use default here as the default resource is not empty
93                    sampler: Box::new(Sampler::ParentBased(Box::new(Sampler::AlwaysOn))),
94                    id_generator: Box::<RandomIdGenerator>::default(),
95                    span_limits: SpanLimits::default(),
96                    resource: Cow::Owned(Resource::empty()),
97                },
98                is_shutdown: AtomicBool::new(true),
99            }),
100        }
101    })
102}
103
104/// TracerProvider inner type
105#[derive(Debug)]
106pub(crate) struct TracerProviderInner {
107    processors: Vec<Box<dyn SpanProcessor>>,
108    config: crate::trace::Config,
109    is_shutdown: AtomicBool,
110}
111
112impl TracerProviderInner {
113    /// Crate-private shutdown method to be called both from explicit shutdown
114    /// and from Drop when the last reference is released.
115    pub(crate) fn shutdown_with_timeout(&self, timeout: Duration) -> Vec<OTelSdkResult> {
116        let mut results = vec![];
117        for processor in &self.processors {
118            let result = processor.shutdown_with_timeout(timeout);
119            if let Err(err) = &result {
120                // Log at debug level because:
121                //  - The error is also returned to the user for handling (if applicable)
122                //  - Or the error occurs during `TracerProviderInner::Drop` as part of telemetry shutdown,
123                //    which is non-actionable by the user
124                otel_debug!(name: "TracerProvider.Drop.ShutdownError",
125                        error = format!("{err}"));
126            }
127            results.push(result);
128        }
129        results
130    }
131    /// shutdown with default timeout
132    pub(crate) fn shutdown(&self) -> Vec<OTelSdkResult> {
133        self.shutdown_with_timeout(Duration::from_secs(5))
134    }
135}
136
137impl Drop for TracerProviderInner {
138    fn drop(&mut self) {
139        if !self.is_shutdown.load(Ordering::Relaxed) {
140            let _ = self.shutdown(); // errors are handled within shutdown
141        } else {
142            otel_debug!(
143                name: "TracerProvider.Drop.AlreadyShutdown",
144                message = "TracerProvider was already shut down; drop will not attempt shutdown again."
145            );
146        }
147    }
148}
149
150/// Creator and registry of named [`SdkTracer`] instances.
151///
152/// `TracerProvider` is a container holding pointers to `SpanProcessor` and other components.
153/// Cloning a `TracerProvider` instance and dropping it will not stop span processing. To stop span processing, users
154/// must either call the `shutdown` method explicitly or allow the last reference to the `TracerProvider`
155/// to be dropped. When the last reference is dropped, the shutdown process will be automatically triggered
156/// to ensure proper cleanup.
157#[derive(Clone, Debug)]
158pub struct SdkTracerProvider {
159    inner: Arc<TracerProviderInner>,
160}
161
162impl Default for SdkTracerProvider {
163    fn default() -> Self {
164        SdkTracerProvider::builder().build()
165    }
166}
167
168impl SdkTracerProvider {
169    /// Build a new tracer provider
170    pub(crate) fn new(inner: TracerProviderInner) -> Self {
171        SdkTracerProvider {
172            inner: Arc::new(inner),
173        }
174    }
175
176    /// Create a new [`SdkTracerProvider`] builder.
177    pub fn builder() -> TracerProviderBuilder {
178        TracerProviderBuilder::default()
179    }
180
181    /// Span processors associated with this provider
182    pub(crate) fn span_processors(&self) -> &[Box<dyn SpanProcessor>] {
183        &self.inner.processors
184    }
185
186    /// Config associated with this tracer
187    pub(crate) fn config(&self) -> &crate::trace::Config {
188        &self.inner.config
189    }
190
191    /// true if the provider has been shutdown
192    /// Don't start span or export spans when provider is shutdown
193    pub(crate) fn is_shutdown(&self) -> bool {
194        self.inner.is_shutdown.load(Ordering::Relaxed)
195    }
196
197    /// Force flush all remaining spans in span processors and return results.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use opentelemetry::global;
203    /// use opentelemetry_sdk::trace::SdkTracerProvider;
204    ///
205    /// fn init_tracing() -> SdkTracerProvider {
206    ///     let provider = SdkTracerProvider::default();
207    ///
208    ///     // Set provider to be used as global tracer provider
209    ///     let _ = global::set_tracer_provider(provider.clone());
210    ///
211    ///     provider
212    /// }
213    ///
214    /// fn main() {
215    ///     let provider = init_tracing();
216    ///
217    ///     // create spans..
218    ///
219    ///     // force all spans to flush
220    ///     if let Err(err) = provider.force_flush() {
221    ///         // .. handle flush error
222    ///     }
223    ///
224    ///     // create more spans..
225    ///
226    ///     // dropping provider ensures all remaining spans are exported
227    ///     drop(provider);
228    /// }
229    /// ```
230    pub fn force_flush(&self) -> OTelSdkResult {
231        let result: Vec<_> = self
232            .span_processors()
233            .iter()
234            .map(|processor| processor.force_flush())
235            .collect();
236        if result.iter().all(|r| r.is_ok()) {
237            Ok(())
238        } else {
239            Err(OTelSdkError::InternalFailure(format!("errs: {result:?}")))
240        }
241    }
242
243    /// Shuts down the current `TracerProvider`.
244    ///
245    /// Note that shut down doesn't means the TracerProvider has dropped
246    pub fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult {
247        if self
248            .inner
249            .is_shutdown
250            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
251            .is_ok()
252        {
253            // propagate the shutdown signal to processors
254            let results = self.inner.shutdown_with_timeout(timeout);
255
256            if results.iter().all(|res| res.is_ok()) {
257                Ok(())
258            } else {
259                Err(OTelSdkError::InternalFailure(format!(
260                    "Shutdown errors: {:?}",
261                    results
262                        .into_iter()
263                        .filter_map(Result::err)
264                        .collect::<Vec<_>>() // Collect only the errors
265                )))
266            }
267        } else {
268            Err(OTelSdkError::AlreadyShutdown)
269        }
270    }
271
272    /// shutdown with default timeout
273    pub fn shutdown(&self) -> OTelSdkResult {
274        self.shutdown_with_timeout(Duration::from_secs(5))
275    }
276}
277
278impl opentelemetry::trace::TracerProvider for SdkTracerProvider {
279    /// This implementation of `TracerProvider` produces `Tracer` instances.
280    type Tracer = SdkTracer;
281
282    fn tracer(&self, name: impl Into<Cow<'static, str>>) -> Self::Tracer {
283        let scope = InstrumentationScope::builder(name).build();
284        self.tracer_with_scope(scope)
285    }
286
287    fn tracer_with_scope(&self, scope: InstrumentationScope) -> Self::Tracer {
288        if self.inner.is_shutdown.load(Ordering::Relaxed) {
289            return SdkTracer::new(scope, noop_tracer_provider().clone());
290        }
291        if scope.name().is_empty() {
292            otel_info!(name: "TracerNameEmpty",  message = "Tracer name is empty; consider providing a meaningful name. Tracer will function normally and the provided name will be used as-is.");
293        };
294        SdkTracer::new(scope, self.clone())
295    }
296}
297
298/// Builder for provider attributes.
299#[derive(Debug, Default)]
300pub struct TracerProviderBuilder {
301    processors: Vec<Box<dyn SpanProcessor>>,
302    config: crate::trace::Config,
303    resource: Option<Resource>,
304}
305
306impl TracerProviderBuilder {
307    /// Adds a [SimpleSpanProcessor] with the configured exporter to the pipeline.
308    ///
309    /// # Arguments
310    ///
311    /// * `exporter` - The exporter to be used by the SimpleSpanProcessor.
312    ///
313    /// # Returns
314    ///
315    /// A new `Builder` instance with the SimpleSpanProcessor added to the pipeline.
316    ///
317    /// Processors are invoked in the order they are added.
318    pub fn with_simple_exporter<T: SpanExporter + 'static>(self, exporter: T) -> Self {
319        let simple = SimpleSpanProcessor::new(exporter);
320        self.with_span_processor(simple)
321    }
322
323    /// Adds a [BatchSpanProcessor] with the configured exporter to the pipeline.
324    ///
325    /// # Arguments
326    ///
327    /// * `exporter` - The exporter to be used by the BatchSpanProcessor.
328    ///
329    /// # Returns
330    ///
331    /// A new `Builder` instance with the BatchSpanProcessor added to the pipeline.
332    ///
333    /// Processors are invoked in the order they are added.
334    pub fn with_batch_exporter<T: SpanExporter + 'static>(self, exporter: T) -> Self {
335        let batch = BatchSpanProcessor::builder(exporter).build();
336        self.with_span_processor(batch)
337    }
338
339    /// Adds a custom [SpanProcessor] to the pipeline.
340    ///
341    /// # Arguments
342    ///
343    /// * `processor` - The `SpanProcessor` to be added.
344    ///
345    /// # Returns
346    ///
347    /// A new `Builder` instance with the custom `SpanProcessor` added to the pipeline.
348    ///
349    /// Processors are invoked in the order they are added.
350    pub fn with_span_processor<T: SpanProcessor + 'static>(self, processor: T) -> Self {
351        let mut processors = self.processors;
352        processors.push(Box::new(processor));
353
354        TracerProviderBuilder { processors, ..self }
355    }
356
357    /// Specify the sampler to be used.
358    ///
359    /// ## Dynamic sampler selection
360    ///
361    /// ```
362    /// use opentelemetry_sdk::trace::{Sampler, ShouldSample};
363    ///
364    /// fn should_return_dynamic_sampler() -> Box<dyn ShouldSample + 'static> {
365    ///     Box::new(opentelemetry_sdk::trace::Sampler::AlwaysOn)
366    /// }
367    ///
368    /// opentelemetry_sdk::trace::SdkTracerProvider::builder()
369    ///     // You can pass already boxed sampler if you need to configure your sampler in a simple fashion
370    ///     // This can be useful if you create your own sampler that implements `ShouldSample`
371    ///     .with_sampler(should_return_dynamic_sampler())
372    ///     // Or you can pass exact instance if you do not have complex configuration
373    ///     .with_sampler(Sampler::AlwaysOff);
374    /// ```
375    pub fn with_sampler(mut self, sampler: impl Into<Box<dyn crate::trace::ShouldSample>>) -> Self {
376        self.config.sampler = sampler.into();
377        self
378    }
379
380    /// Specify the id generator to be used.
381    pub fn with_id_generator<T: IdGenerator + 'static>(mut self, id_generator: T) -> Self {
382        self.config.id_generator = Box::new(id_generator);
383        self
384    }
385
386    /// Specify the number of events to be recorded per span.
387    pub fn with_max_events_per_span(mut self, max_events: u32) -> Self {
388        self.config.span_limits.max_events_per_span = max_events;
389        self
390    }
391
392    /// Specify the number of attributes to be recorded per span.
393    pub fn with_max_attributes_per_span(mut self, max_attributes: u32) -> Self {
394        self.config.span_limits.max_attributes_per_span = max_attributes;
395        self
396    }
397
398    /// Specify the number of events to be recorded per span.
399    pub fn with_max_links_per_span(mut self, max_links: u32) -> Self {
400        self.config.span_limits.max_links_per_span = max_links;
401        self
402    }
403
404    /// Specify the number of attributes one event can have.
405    pub fn with_max_attributes_per_event(mut self, max_attributes: u32) -> Self {
406        self.config.span_limits.max_attributes_per_event = max_attributes;
407        self
408    }
409
410    /// Specify the number of attributes one link can have.
411    pub fn with_max_attributes_per_link(mut self, max_attributes: u32) -> Self {
412        self.config.span_limits.max_attributes_per_link = max_attributes;
413        self
414    }
415
416    /// Specify all limit via the span_limits
417    pub fn with_span_limits(mut self, span_limits: SpanLimits) -> Self {
418        self.config.span_limits = span_limits;
419        self
420    }
421
422    /// Associates a [Resource] with a [SdkTracerProvider].
423    ///
424    /// This [Resource] represents the entity producing telemetry and is associated
425    /// with all [Tracer]s the [SdkTracerProvider] will create.
426    ///
427    /// By default, if this option is not used, the default [Resource] will be used.
428    ///
429    /// When constructing a [Resource], use [`Resource::builder()`] to preserve
430    /// SDK-provided defaults such as `telemetry.sdk.*` and `service.name`.
431    /// Using [`Resource::builder_empty()`] will **not** include these attributes.
432    ///
433    /// # Example
434    ///
435    /// ```
436    /// use opentelemetry_sdk::{Resource, trace::SdkTracerProvider};
437    /// use opentelemetry::KeyValue;
438    ///
439    /// let provider = SdkTracerProvider::builder()
440    ///     .with_resource(
441    ///         Resource::builder()
442    ///             .with_service_name("my-service")
443    ///             .with_attributes([KeyValue::new("deployment.environment.name", "production")])
444    ///             .build(),
445    ///     )
446    ///     .build();
447    /// ```
448    ///
449    /// *Note*: Calls to this method are additive, each call merges the provided
450    /// resource with the previous one.
451    ///
452    /// [Tracer]: opentelemetry::trace::Tracer
453    /// [`Resource::builder()`]: Resource::builder
454    /// [`Resource::builder_empty()`]: Resource::builder_empty
455    pub fn with_resource(self, resource: Resource) -> Self {
456        let resource = match self.resource {
457            Some(existing) => Some(existing.merge(&resource)),
458            None => Some(resource),
459        };
460
461        TracerProviderBuilder { resource, ..self }
462    }
463
464    /// Create a new provider from this configuration.
465    pub fn build(self) -> SdkTracerProvider {
466        let mut config = self.config;
467
468        // Now, we can update the config with the resource.
469        if let Some(resource) = self.resource {
470            config.resource = Cow::Owned(resource);
471        };
472
473        // Standard config will contain an owned [`Resource`] (either sdk default or use supplied)
474        // we can optimize the common case with a static ref to avoid cloning the underlying
475        // resource data for each span.
476        //
477        // For the uncommon case where there are multiple tracer providers with different resource
478        // configurations, users can optionally provide their own borrowed static resource.
479        if matches!(config.resource, Cow::Owned(_)) {
480            config.resource =
481                match PROVIDER_RESOURCE.get_or_init(|| config.resource.clone().into_owned()) {
482                    static_resource if *static_resource == *config.resource.as_ref() => {
483                        Cow::Borrowed(static_resource)
484                    }
485                    _ => config.resource, // Use the new resource if different
486                };
487        }
488
489        // Create a new vector to hold the modified processors
490        let mut processors = self.processors;
491
492        // Set the resource for each processor
493        for p in &mut processors {
494            p.set_resource(config.resource.as_ref());
495        }
496
497        let is_shutdown = AtomicBool::new(false);
498        SdkTracerProvider::new(TracerProviderInner {
499            processors,
500            config,
501            is_shutdown,
502        })
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use crate::error::{OTelSdkError, OTelSdkResult};
509    use crate::resource::{
510        SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
511    };
512    use crate::trace::provider::TracerProviderInner;
513    use crate::trace::{Config, Span, SpanProcessor};
514    use crate::trace::{SdkTracerProvider, SpanData};
515    use crate::Resource;
516    use opentelemetry::trace::{Tracer, TracerProvider};
517    use opentelemetry::{Context, Key, KeyValue, Value};
518
519    use std::env;
520    use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
521    use std::sync::Arc;
522    use std::time::Duration;
523
524    // fields below is wrapped with Arc so we can assert it
525    #[derive(Default, Debug)]
526    struct AssertInfo {
527        started_span: AtomicU32,
528        is_shutdown: AtomicBool,
529    }
530
531    #[derive(Default, Debug, Clone)]
532    struct SharedAssertInfo(Arc<AssertInfo>);
533
534    impl SharedAssertInfo {
535        fn started_span_count(&self, count: u32) -> bool {
536            self.0.started_span.load(Ordering::SeqCst) == count
537        }
538    }
539
540    #[derive(Debug)]
541    struct TestSpanProcessor {
542        success: bool,
543        assert_info: SharedAssertInfo,
544    }
545
546    impl TestSpanProcessor {
547        fn new(success: bool) -> TestSpanProcessor {
548            TestSpanProcessor {
549                success,
550                assert_info: SharedAssertInfo::default(),
551            }
552        }
553
554        // get handle to assert info
555        fn assert_info(&self) -> SharedAssertInfo {
556            self.assert_info.clone()
557        }
558    }
559
560    impl SpanProcessor for TestSpanProcessor {
561        fn on_start(&self, _span: &mut Span, _cx: &Context) {
562            self.assert_info
563                .0
564                .started_span
565                .fetch_add(1, Ordering::SeqCst);
566        }
567
568        fn on_end(&self, _span: SpanData) {
569            // ignore
570        }
571
572        fn force_flush(&self) -> OTelSdkResult {
573            if self.success {
574                Ok(())
575            } else {
576                Err(OTelSdkError::InternalFailure("cannot export".into()))
577            }
578        }
579
580        fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
581            if self.assert_info.0.is_shutdown.load(Ordering::SeqCst) {
582                Ok(())
583            } else {
584                let _ = self.assert_info.0.is_shutdown.compare_exchange(
585                    false,
586                    true,
587                    Ordering::SeqCst,
588                    Ordering::SeqCst,
589                );
590                self.force_flush()
591            }
592        }
593    }
594
595    #[test]
596    fn test_force_flush() {
597        let tracer_provider = super::SdkTracerProvider::new(TracerProviderInner {
598            processors: vec![
599                Box::from(TestSpanProcessor::new(true)),
600                Box::from(TestSpanProcessor::new(false)),
601            ],
602            config: Default::default(),
603            is_shutdown: AtomicBool::new(false),
604        });
605
606        let results = tracer_provider.force_flush();
607        assert!(results.is_err());
608    }
609
610    #[test]
611    fn test_tracer_provider_default_resource() {
612        let assert_resource = |provider: &super::SdkTracerProvider,
613                               resource_key: &'static str,
614                               expect: Option<&'static str>| {
615            assert_eq!(
616                provider
617                    .config()
618                    .resource
619                    .get(&Key::from_static_str(resource_key))
620                    .map(|v| v.to_string()),
621                expect.map(|s| s.to_string())
622            );
623        };
624        let assert_telemetry_resource = |provider: &super::SdkTracerProvider| {
625            assert_eq!(
626                provider
627                    .config()
628                    .resource
629                    .get(&TELEMETRY_SDK_LANGUAGE.into()),
630                Some(Value::from("rust"))
631            );
632            assert_eq!(
633                provider.config().resource.get(&TELEMETRY_SDK_NAME.into()),
634                Some(Value::from("opentelemetry"))
635            );
636            assert_eq!(
637                provider
638                    .config()
639                    .resource
640                    .get(&TELEMETRY_SDK_VERSION.into()),
641                Some(Value::from(env!("CARGO_PKG_VERSION")))
642            );
643        };
644
645        // If users didn't provide a resource and there isn't a env var set. Use default one.
646        temp_env::with_var_unset("OTEL_RESOURCE_ATTRIBUTES", || {
647            let default_config_provider = super::SdkTracerProvider::builder().build();
648            let service_name = default_config_provider
649                .config()
650                .resource
651                .get(&Key::from_static_str(SERVICE_NAME))
652                .map(|v| v.to_string())
653                .unwrap();
654            assert!(
655                service_name.starts_with("unknown_service:opentelemetry_sdk-"),
656                "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
657                service_name
658            );
659            assert_telemetry_resource(&default_config_provider);
660        });
661
662        // If user provided config, use that.
663        let custom_config_provider = super::SdkTracerProvider::builder()
664            .with_resource(
665                Resource::builder_empty()
666                    .with_service_name("test_service")
667                    .build(),
668            )
669            .build();
670        assert_resource(&custom_config_provider, SERVICE_NAME, Some("test_service"));
671        assert_eq!(custom_config_provider.config().resource.len(), 1);
672
673        // If `OTEL_RESOURCE_ATTRIBUTES` is set, read them automatically
674        temp_env::with_var(
675            "OTEL_RESOURCE_ATTRIBUTES",
676            Some("key1=value1, k2, k3=value2"),
677            || {
678                let env_resource_provider = super::SdkTracerProvider::builder().build();
679                let service_name = env_resource_provider
680                    .config()
681                    .resource
682                    .get(&Key::from_static_str(SERVICE_NAME))
683                    .map(|v| v.to_string())
684                    .unwrap();
685                assert!(
686                    service_name.starts_with("unknown_service:opentelemetry_sdk-"),
687                    "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
688                    service_name
689                );
690                assert_resource(&env_resource_provider, "key1", Some("value1"));
691                assert_resource(&env_resource_provider, "k3", Some("value2"));
692                assert_telemetry_resource(&env_resource_provider);
693                assert_eq!(env_resource_provider.config().resource.len(), 6);
694            },
695        );
696
697        // When `OTEL_RESOURCE_ATTRIBUTES` is set and also user provided config
698        temp_env::with_var(
699            "OTEL_RESOURCE_ATTRIBUTES",
700            Some("my-custom-key=env-val,k2=value2"),
701            || {
702                let user_provided_resource_config_provider = super::SdkTracerProvider::builder()
703                    .with_resource(
704                        Resource::builder()
705                            .with_attributes([
706                                KeyValue::new("my-custom-key", "my-custom-value"),
707                                KeyValue::new("my-custom-key2", "my-custom-value2"),
708                            ])
709                            .build(),
710                    )
711                    .build();
712                let service_name = user_provided_resource_config_provider
713                    .config()
714                    .resource
715                    .get(&Key::from_static_str(SERVICE_NAME))
716                    .map(|v| v.to_string())
717                    .unwrap();
718                assert!(
719                    service_name.starts_with("unknown_service:opentelemetry_sdk-"),
720                    "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
721                    service_name
722                );
723                assert_resource(
724                    &user_provided_resource_config_provider,
725                    "my-custom-key",
726                    Some("my-custom-value"),
727                );
728                assert_resource(
729                    &user_provided_resource_config_provider,
730                    "my-custom-key2",
731                    Some("my-custom-value2"),
732                );
733                assert_resource(
734                    &user_provided_resource_config_provider,
735                    "k2",
736                    Some("value2"),
737                );
738                assert_telemetry_resource(&user_provided_resource_config_provider);
739                assert_eq!(
740                    user_provided_resource_config_provider
741                        .config()
742                        .resource
743                        .len(),
744                    7
745                );
746            },
747        );
748
749        // If user provided a resource, it takes priority during collision.
750        let no_service_name = super::SdkTracerProvider::builder()
751            .with_resource(Resource::empty())
752            .build();
753
754        assert_eq!(no_service_name.config().resource.len(), 0)
755    }
756
757    #[test]
758    fn test_shutdown_noops() {
759        let processor = TestSpanProcessor::new(false);
760        let assert_handle = processor.assert_info();
761        let tracer_provider = super::SdkTracerProvider::new(TracerProviderInner {
762            processors: vec![Box::from(processor)],
763            config: Default::default(),
764            is_shutdown: AtomicBool::new(false),
765        });
766
767        let test_tracer_1 = tracer_provider.tracer("test1");
768        let _ = test_tracer_1.start("test");
769
770        assert!(assert_handle.started_span_count(1));
771
772        let _ = test_tracer_1.start("test");
773
774        assert!(assert_handle.started_span_count(2));
775
776        let shutdown = |tracer_provider: super::SdkTracerProvider| {
777            let _ = tracer_provider.shutdown(); // shutdown once
778        };
779
780        // assert tracer provider can be shutdown using on a cloned version
781        shutdown(tracer_provider.clone());
782
783        // after shutdown we should get noop tracer
784        let noop_tracer = tracer_provider.tracer("noop");
785
786        // noop tracer cannot start anything
787        let _ = noop_tracer.start("test");
788        assert!(assert_handle.started_span_count(2));
789        // noop tracer's tracer provider should be shutdown
790        assert!(noop_tracer.provider().is_shutdown());
791
792        // existing tracer becomes noop after shutdown
793        let _ = test_tracer_1.start("test");
794        assert!(assert_handle.started_span_count(2));
795
796        // also existing tracer's tracer provider are in shutdown state
797        assert!(test_tracer_1.provider().is_shutdown());
798    }
799
800    #[test]
801    fn with_resource_multiple_calls_ensure_additive() {
802        let resource = SdkTracerProvider::builder()
803            .with_resource(
804                Resource::builder_empty()
805                    .with_attributes(vec![KeyValue::new("key1", "value1")])
806                    .build(),
807            )
808            .with_resource(
809                Resource::builder_empty()
810                    .with_attributes(vec![KeyValue::new("key2", "value2")])
811                    .build(),
812            )
813            .with_resource(
814                Resource::builder_empty()
815                    .with_schema_url(vec![], "http://example.com")
816                    .build(),
817            )
818            .with_resource(
819                Resource::builder_empty()
820                    .with_attributes(vec![KeyValue::new("key3", "value3")])
821                    .build(),
822            )
823            .build()
824            .inner
825            .config
826            .resource
827            .clone()
828            .into_owned();
829
830        assert_eq!(
831            resource.get(&Key::from_static_str("key1")),
832            Some(Value::from("value1"))
833        );
834        assert_eq!(
835            resource.get(&Key::from_static_str("key2")),
836            Some(Value::from("value2"))
837        );
838        assert_eq!(
839            resource.get(&Key::from_static_str("key3")),
840            Some(Value::from("value3"))
841        );
842        assert_eq!(resource.schema_url(), Some("http://example.com"));
843    }
844
845    #[derive(Debug)]
846    struct CountingShutdownProcessor {
847        shutdown_count: Arc<AtomicU32>,
848    }
849
850    impl CountingShutdownProcessor {
851        fn new(shutdown_count: Arc<AtomicU32>) -> Self {
852            CountingShutdownProcessor { shutdown_count }
853        }
854    }
855
856    impl SpanProcessor for CountingShutdownProcessor {
857        fn on_start(&self, _span: &mut Span, _cx: &Context) {
858            // No operation needed for this processor
859        }
860
861        fn on_end(&self, _span: SpanData) {
862            // No operation needed for this processor
863        }
864
865        fn force_flush(&self) -> OTelSdkResult {
866            Ok(())
867        }
868
869        fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
870            self.shutdown_count.fetch_add(1, Ordering::SeqCst);
871            Ok(())
872        }
873    }
874
875    #[test]
876    fn drop_test_with_multiple_providers() {
877        let shutdown_count = Arc::new(AtomicU32::new(0));
878
879        {
880            // Create a shared TracerProviderInner and use it across multiple providers
881            let shared_inner = Arc::new(TracerProviderInner {
882                processors: vec![Box::new(CountingShutdownProcessor::new(
883                    shutdown_count.clone(),
884                ))],
885                config: Config::default(),
886                is_shutdown: AtomicBool::new(false),
887            });
888
889            {
890                let tracer_provider1 = super::SdkTracerProvider {
891                    inner: shared_inner.clone(),
892                };
893                let tracer_provider2 = super::SdkTracerProvider {
894                    inner: shared_inner.clone(),
895                };
896
897                let tracer1 = tracer_provider1.tracer("test-tracer1");
898                let tracer2 = tracer_provider2.tracer("test-tracer2");
899
900                let _span1 = tracer1.start("span1");
901                let _span2 = tracer2.start("span2");
902
903                // TracerProviderInner should not be dropped yet, since both providers and `shared_inner`
904                // are still holding a reference.
905            }
906            // At this point, both `tracer_provider1` and `tracer_provider2` are dropped,
907            // but `shared_inner` still holds a reference, so `TracerProviderInner` is NOT dropped yet.
908            assert_eq!(shutdown_count.load(Ordering::SeqCst), 0);
909        }
910        // Verify shutdown was called during the drop of the shared TracerProviderInner
911        assert_eq!(shutdown_count.load(Ordering::SeqCst), 1);
912    }
913
914    #[test]
915    fn drop_after_shutdown_test_with_multiple_providers() {
916        let shutdown_count = Arc::new(AtomicU32::new(0));
917
918        // Create a shared TracerProviderInner and use it across multiple providers
919        let shared_inner = Arc::new(TracerProviderInner {
920            processors: vec![Box::new(CountingShutdownProcessor::new(
921                shutdown_count.clone(),
922            ))],
923            config: Config::default(),
924            is_shutdown: AtomicBool::new(false),
925        });
926
927        // Create a scope to test behavior when providers are dropped
928        {
929            let tracer_provider1 = super::SdkTracerProvider {
930                inner: shared_inner.clone(),
931            };
932            let tracer_provider2 = super::SdkTracerProvider {
933                inner: shared_inner.clone(),
934            };
935
936            // Explicitly shut down the tracer provider
937            let shutdown_result = tracer_provider1.shutdown();
938            assert!(shutdown_result.is_ok());
939
940            // Verify that shutdown was called exactly once
941            assert_eq!(shutdown_count.load(Ordering::SeqCst), 1);
942
943            // TracerProvider2 should observe the shutdown state but not trigger another shutdown
944            let shutdown_result2 = tracer_provider2.shutdown();
945            assert!(shutdown_result2.is_err());
946            assert_eq!(shutdown_count.load(Ordering::SeqCst), 1);
947
948            // Both tracer providers will be dropped at the end of this scope
949        }
950
951        // Verify that shutdown was only called once, even after drop
952        assert_eq!(shutdown_count.load(Ordering::SeqCst), 1);
953    }
954}