provide_telemetry/otel/
adopt.rs1use std::sync::atomic::{AtomicBool, Ordering};
26
27static TRACES_ADOPTED: AtomicBool = AtomicBool::new(false);
28static METRICS_ADOPTED: AtomicBool = AtomicBool::new(false);
29
30#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
32pub struct AdoptedProviders {
33 pub traces: bool,
35 pub metrics: bool,
37}
38
39impl AdoptedProviders {
40 #[must_use]
42 pub fn all() -> Self {
43 Self {
44 traces: true,
45 metrics: true,
46 }
47 }
48}
49
50pub fn adopt_global_providers(adopted: AdoptedProviders) {
57 TRACES_ADOPTED.store(adopted.traces, Ordering::Release);
58 METRICS_ADOPTED.store(adopted.metrics, Ordering::Release);
59}
60
61#[must_use]
63pub fn adopted_global_providers() -> AdoptedProviders {
64 AdoptedProviders {
65 traces: TRACES_ADOPTED.load(Ordering::Acquire),
66 metrics: METRICS_ADOPTED.load(Ordering::Acquire),
67 }
68}
69
70pub(crate) fn release_adopted_providers() {
73 adopt_global_providers(AdoptedProviders::default());
74}
75
76#[cfg(feature = "otel")]
78pub(crate) fn traces_adopted() -> bool {
79 TRACES_ADOPTED.load(Ordering::Acquire)
80}
81
82#[cfg(feature = "otel")]
84pub(crate) fn metrics_adopted() -> bool {
85 METRICS_ADOPTED.load(Ordering::Acquire)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::testing::acquire_test_state_lock;
92
93 #[test]
94 fn nothing_is_adopted_by_default() {
95 let _guard = acquire_test_state_lock();
96 release_adopted_providers();
97 assert_eq!(adopted_global_providers(), AdoptedProviders::default());
98 assert!(!adopted_global_providers().traces);
99 assert!(!adopted_global_providers().metrics);
100 }
101
102 #[test]
103 fn adoption_is_per_signal() {
104 let _guard = acquire_test_state_lock();
105 adopt_global_providers(AdoptedProviders {
106 traces: true,
107 metrics: false,
108 });
109 assert!(adopted_global_providers().traces);
110 assert!(!adopted_global_providers().metrics);
111 release_adopted_providers();
112 }
113
114 #[test]
115 fn all_asserts_both_signals() {
116 let _guard = acquire_test_state_lock();
117 adopt_global_providers(AdoptedProviders::all());
118 assert!(adopted_global_providers().traces);
119 assert!(adopted_global_providers().metrics);
120 release_adopted_providers();
121 }
122
123 #[cfg(feature = "otel")]
126 #[test]
127 fn adoption_makes_the_traces_provider_effective() {
128 let _guard = acquire_test_state_lock();
129 crate::testing::reset_telemetry_state();
134 release_adopted_providers();
135 assert!(!crate::otel::traces_provider_effective());
136
137 adopt_global_providers(AdoptedProviders::all());
138 assert!(crate::otel::traces_provider_effective());
139 assert!(crate::otel::metrics_provider_effective());
140
141 release_adopted_providers();
142 }
143
144 #[cfg(feature = "otel")]
153 #[test]
154 fn an_adopted_provider_actually_receives_the_span() {
155 use opentelemetry_sdk::error::OTelSdkResult;
156 use opentelemetry_sdk::trace::{SdkTracerProvider, SpanData, SpanExporter};
157 use std::sync::{Arc, Mutex};
158
159 #[derive(Debug, Clone)]
160 struct RecordingExporter {
161 names: Arc<Mutex<Vec<String>>>,
162 }
163
164 impl SpanExporter for RecordingExporter {
165 async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
166 let mut names = self.names.lock().expect("exporter mutex");
167 names.extend(batch.iter().map(|span| span.name.to_string()));
168 Ok(())
169 }
170 }
171
172 let _guard = acquire_test_state_lock();
173 crate::testing::reset_telemetry_state();
174
175 let names = Arc::new(Mutex::new(Vec::new()));
176 let host_provider = SdkTracerProvider::builder()
177 .with_simple_exporter(RecordingExporter {
178 names: Arc::clone(&names),
179 })
180 .build();
181 opentelemetry::global::set_tracer_provider(host_provider.clone());
182 adopt_global_providers(AdoptedProviders::all());
183
184 crate::trace("adopted.export.span", || {});
185
186 host_provider.force_flush().expect("flush host provider");
187 let recorded = names.lock().expect("exporter mutex").clone();
188 assert!(
189 recorded.iter().any(|name| name == "adopted.export.span"),
190 "facade reported the host provider as in play but no span reached its exporter; got {recorded:?}"
191 );
192
193 release_adopted_providers();
194 crate::testing::reset_telemetry_state();
195 }
196
197 #[test]
198 fn shutdown_releases_the_assertion_without_owning_the_providers() {
199 let _guard = acquire_test_state_lock();
200 adopt_global_providers(AdoptedProviders::all());
201
202 crate::shutdown_telemetry(None).expect("shutdown should succeed");
203
204 assert_eq!(adopted_global_providers(), AdoptedProviders::default());
206 }
207
208 #[cfg(feature = "otel")]
211 #[test]
212 fn adopted_traces_bypass_facade_sampling() {
213 use crate::sampling::{set_sampling_policy, SamplingPolicy, Signal};
214
215 let _guard = acquire_test_state_lock();
216 crate::shutdown_telemetry(None).expect("pre-test shutdown should succeed");
217 crate::health::_reset_health_for_tests();
218 set_sampling_policy(
219 Signal::Traces,
220 SamplingPolicy {
221 default_rate: 0.0,
222 overrides: Default::default(),
223 },
224 )
225 .expect("policy should apply");
226
227 crate::trace("adopt.unsampled", || {});
229 assert_eq!(crate::health::get_health_snapshot().emitted_traces, 0);
230
231 adopt_global_providers(AdoptedProviders::all());
233 crate::trace("adopt.sampled", || {});
234 assert_eq!(crate::health::get_health_snapshot().emitted_traces, 1);
235
236 release_adopted_providers();
237 crate::sampling::_reset_sampling_for_tests();
238 }
239}