use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use opentelemetry_sdk::trace::TracerProvider as SdkTracerProvider;
#[must_use = "OtelGuard drops immediately if not bound to a named local; use `let _otel = install(...)?;` (NOT `let _ = install(...)?;`) to keep the guard alive for the process lifetime — see install's doc-comment"]
pub struct OtelGuard {
provider: Option<SdkTracerProvider>,
shutdown_marker: Arc<AtomicBool>,
}
impl OtelGuard {
pub(crate) fn new(provider: SdkTracerProvider) -> Self {
Self {
provider: Some(provider),
shutdown_marker: Arc::new(AtomicBool::new(false)),
}
}
#[doc(hidden)]
#[must_use]
pub fn shutdown_marker(&self) -> Arc<AtomicBool> {
Arc::clone(&self.shutdown_marker)
}
}
impl Drop for OtelGuard {
fn drop(&mut self) {
if let Some(provider) = self.provider.take() {
for result in provider.force_flush() {
if let Err(err) = result {
tracing::warn!(error = %err, "OTel force_flush returned an error");
}
}
if let Err(err) = provider.shutdown() {
tracing::warn!(error = %err, "OTel provider shutdown returned an error");
}
}
self.shutdown_marker.store(true, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{build_provider, config::OtelConfig};
use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn drop_marks_shutdown_complete() {
let cfg = OtelConfig::new("svc")
.endpoint("http://127.0.0.1:1")
.timeout(Duration::from_millis(50));
let provider = build_provider(&cfg, &cfg.endpoint).expect("build provider");
let guard = OtelGuard::new(provider);
let marker = guard.shutdown_marker();
assert!(!marker.load(Ordering::SeqCst));
tokio::task::spawn_blocking(move || drop(guard))
.await
.expect("drop must not panic");
assert!(marker.load(Ordering::SeqCst));
}
}