use std::{
sync::atomic::{AtomicBool, Ordering},
time::Instant,
};
use tracing::{Span, field};
pub(super) const TELEMETRY_TARGET: &str = "heddle_telemetry";
static TRACE_EXPORT_ENABLED: AtomicBool = AtomicBool::new(false);
#[cfg(feature = "telemetry")]
pub(super) fn set_trace_export_enabled(enabled: bool) {
TRACE_EXPORT_ENABLED.store(enabled, Ordering::Release);
}
pub fn trace_export_enabled() -> bool {
TRACE_EXPORT_ENABLED.load(Ordering::Acquire)
}
pub struct CommandTrace {
span: Span,
started: Instant,
finished: bool,
}
impl CommandTrace {
pub fn start(command: &str, started: Instant) -> Option<Self> {
trace_export_enabled().then(|| Self {
span: tracing::info_span!(
target: TELEMETRY_TARGET,
"heddle.command",
command.name = command,
command.status = field::Empty,
command.exit_code = field::Empty,
command.duration_ms = field::Empty,
),
started,
finished: false,
})
}
pub fn span(&self) -> &Span {
&self.span
}
pub fn finish(&mut self, exit_code: i32) {
self.span.record(
"command.status",
if exit_code == 0 { "ok" } else { "error" },
);
self.span.record("command.exit_code", exit_code);
self.span.record(
"command.duration_ms",
u64::try_from(self.started.elapsed().as_millis()).unwrap_or(u64::MAX),
);
self.finished = true;
}
}
impl Drop for CommandTrace {
fn drop(&mut self) {
if !self.finished {
self.finish(1);
}
}
}
pub fn record_phase_span(name: &'static str, duration_ms: u64) {
if !trace_export_enabled() {
return;
}
let span = tracing::info_span!(
target: TELEMETRY_TARGET,
"heddle.phase",
phase.name = name,
phase.duration_ms = duration_ms,
);
let _entered = span.enter();
}