use once_cell::sync::OnceCell;
use std::sync::atomic::{AtomicBool, Ordering};
static TELEMETRY: OnceCell<Telemetry> = OnceCell::new();
static TELEMETRY_DISABLED: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "telemetry")]
static OPT_OUT_TRACKED: AtomicBool = AtomicBool::new(false);
pub struct Telemetry {
#[cfg(feature = "telemetry")]
inner: Option<telemetry_kit::TelemetryKit>,
#[cfg(not(feature = "telemetry"))]
_phantom: std::marker::PhantomData<()>,
}
impl Telemetry {
#[must_use]
pub fn is_enabled(&self) -> bool {
#[cfg(feature = "telemetry")]
{
self.inner.is_some() && !TELEMETRY_DISABLED.load(Ordering::Relaxed)
}
#[cfg(not(feature = "telemetry"))]
{
false
}
}
}
#[cfg(feature = "telemetry")]
fn create_telemetry_kit() -> Option<telemetry_kit::TelemetryKit> {
telemetry_kit::TelemetryKit::builder()
.service_name("async-inspect")
.ok()
.and_then(|b| b.service_version(env!("CARGO_PKG_VERSION")).build().ok())
}
pub fn init() {
let _ = TELEMETRY.get_or_init(|| {
let no_telemetry = std::env::var("ASYNC_INSPECT_NO_TELEMETRY")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false);
let do_not_track = std::env::var("DO_NOT_TRACK")
.map(|v| v == "1" || v.to_lowercase() == "true")
.unwrap_or(false);
if no_telemetry || do_not_track {
TELEMETRY_DISABLED.store(true, Ordering::Relaxed);
#[cfg(feature = "telemetry")]
if !OPT_OUT_TRACKED.swap(true, Ordering::Relaxed) {
std::thread::spawn(|| {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
if let Ok(rt) = rt {
rt.block_on(async {
if let Some(tk) = create_telemetry_kit() {
let _ = tk
.track_feature("telemetry_opt_out", |event| event.success(true))
.await;
}
});
}
});
}
return Telemetry {
#[cfg(feature = "telemetry")]
inner: None,
#[cfg(not(feature = "telemetry"))]
_phantom: std::marker::PhantomData,
};
}
#[cfg(feature = "telemetry")]
{
Telemetry {
inner: create_telemetry_kit(),
}
}
#[cfg(not(feature = "telemetry"))]
{
Telemetry {
_phantom: std::marker::PhantomData,
}
}
});
}
#[cfg(feature = "telemetry")]
fn get() -> Option<&'static Telemetry> {
TELEMETRY.get()
}
#[allow(unused_variables)]
pub async fn track_command(command: &str, success: bool, duration_ms: Option<u64>) {
#[cfg(feature = "telemetry")]
{
if let Some(telemetry) = get() {
if let Some(ref tk) = telemetry.inner {
let _ = tk
.track_command(command, |event| {
let event = event.success(success);
if let Some(ms) = duration_ms {
event.duration_ms(ms)
} else {
event
}
})
.await;
}
}
}
}
#[allow(unused_variables)]
pub async fn track_feature(feature: &str, metadata: Option<&str>) {
#[cfg(feature = "telemetry")]
{
if let Some(telemetry) = get() {
if let Some(ref tk) = telemetry.inner {
let _ = tk
.track_feature(feature, |event| {
let event = event.success(true);
if let Some(meta) = metadata {
event.method(meta)
} else {
event
}
})
.await;
}
}
}
}
#[allow(unused_variables)]
pub fn track_feature_sync(feature: &'static str, metadata: Option<&'static str>) {
#[cfg(feature = "telemetry")]
{
if TELEMETRY_DISABLED.load(Ordering::Relaxed) {
return;
}
tokio::spawn(async move {
track_feature(feature, metadata).await;
});
}
}
#[allow(unused_variables)]
pub fn track_command_sync(command: &'static str, success: bool, duration_ms: Option<u64>) {
#[cfg(feature = "telemetry")]
{
if TELEMETRY_DISABLED.load(Ordering::Relaxed) {
return;
}
tokio::spawn(async move {
track_command(command, success, duration_ms).await;
});
}
}
pub fn disable() {
TELEMETRY_DISABLED.store(true, Ordering::Relaxed);
}
#[must_use]
pub fn is_enabled() -> bool {
#[cfg(feature = "telemetry")]
{
!TELEMETRY_DISABLED.load(Ordering::Relaxed) && get().is_some_and(Telemetry::is_enabled)
}
#[cfg(not(feature = "telemetry"))]
{
false
}
}