cli_shared/logging/
trace.rs1use 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
20pub fn trace_export_enabled() -> bool {
22 TRACE_EXPORT_ENABLED.load(Ordering::Acquire)
23}
24
25pub struct CommandTrace {
27 span: Span,
28 started: Instant,
29 finished: bool,
30}
31
32impl CommandTrace {
33 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
75pub 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}