Skip to main content

cli_shared/logging/
trace.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Privacy-bounded command and phase tracing for the foreground CLI.
3
4use std::{
5    sync::atomic::{AtomicBool, Ordering},
6    time::Instant,
7};
8
9use tracing::{Span, field};
10
11pub(super) const TELEMETRY_TARGET: &str = "heddle_telemetry";
12
13static TRACE_EXPORT_ENABLED: AtomicBool = AtomicBool::new(false);
14
15#[cfg(feature = "telemetry")]
16pub(super) fn set_trace_export_enabled(enabled: bool) {
17    TRACE_EXPORT_ENABLED.store(enabled, Ordering::Release);
18}
19
20/// Whether this process has an endpoint-backed trace provider.
21pub fn trace_export_enabled() -> bool {
22    TRACE_EXPORT_ENABLED.load(Ordering::Acquire)
23}
24
25/// One root span for a foreground CLI command.
26pub struct CommandTrace {
27    span: Span,
28    started: Instant,
29    finished: bool,
30}
31
32impl CommandTrace {
33    /// Start a command span only when trace export was explicitly enabled.
34    pub fn start(command: &str, started: Instant) -> Option<Self> {
35        trace_export_enabled().then(|| Self {
36            span: tracing::info_span!(
37                target: TELEMETRY_TARGET,
38                "heddle.command",
39                command.name = command,
40                command.status = field::Empty,
41                command.exit_code = field::Empty,
42                command.duration_ms = field::Empty,
43            ),
44            started,
45            finished: false,
46        })
47    }
48
49    pub fn span(&self) -> &Span {
50        &self.span
51    }
52
53    pub fn finish(&mut self, exit_code: i32) {
54        self.span.record(
55            "command.status",
56            if exit_code == 0 { "ok" } else { "error" },
57        );
58        self.span.record("command.exit_code", exit_code);
59        self.span.record(
60            "command.duration_ms",
61            u64::try_from(self.started.elapsed().as_millis()).unwrap_or(u64::MAX),
62        );
63        self.finished = true;
64    }
65}
66
67impl Drop for CommandTrace {
68    fn drop(&mut self) {
69        if !self.finished {
70            self.finish(1);
71        }
72    }
73}
74
75/// Record an already-measured profile phase as a child of the command span.
76pub fn record_phase_span(name: &'static str, duration_ms: u64) {
77    if !trace_export_enabled() {
78        return;
79    }
80    let span = tracing::info_span!(
81        target: TELEMETRY_TARGET,
82        "heddle.phase",
83        phase.name = name,
84        phase.duration_ms = duration_ms,
85    );
86    let _entered = span.enter();
87}