Skip to main content

trellis_runner/watchers/
tracing.rs

1use num_traits::float::FloatCore;
2use tracing::Level;
3
4use crate::engine::{EngineSignal, Termination};
5use crate::progress::Progress;
6use crate::state::{StateView, UserState};
7use crate::watchers::Observe;
8
9macro_rules! log_at_level {
10    ($level:expr, $($arg:tt)*) => {{
11        match $level {
12            tracing::Level::ERROR => tracing::error!($($arg)*),
13            tracing::Level::WARN => tracing::warn!($($arg)*),
14            tracing::Level::INFO => tracing::info!($($arg)*),
15            tracing::Level::DEBUG => tracing::debug!($($arg)*),
16            tracing::Level::TRACE => tracing::trace!($($arg)*),
17        }
18    }};
19}
20
21/// Structured tracing observer for engine execution.
22///
23/// This observer emits lifecycle and progress events using the `tracing`
24/// ecosystem. It is designed to remain thin and not interpret numerical
25/// semantics beyond formatting.
26#[derive(Clone)]
27pub struct Tracer {
28    level: Level,
29}
30
31impl Tracer {
32    /// Create a new tracer with a base logging level.
33    ///
34    /// Only INFO, DEBUG, and TRACE are supported. ERROR/WARN are rejected
35    /// because this observer is not intended for failure signaling.
36    pub fn new(level: Level) -> Self {
37        if matches!(level, Level::ERROR | Level::WARN) {
38            panic!("Tracer only supports INFO, DEBUG, TRACE levels");
39        }
40
41        Self { level }
42    }
43
44    fn lifecycle(&self, ident: &str, event_name: &str) {
45        log_at_level!(
46            self.level,
47            target: "engine.lifecycle",
48            ident = ident,
49            event = event_name,
50        );
51    }
52
53    fn termination(&self, ident: &str, termination: Termination) {
54        log_at_level!(
55            self.level,
56            target: "engine.termination",
57            ident = ident,
58            ?termination
59        );
60    }
61
62    fn progress<S>(&self, state: StateView<'_, S>, progress: &Progress<S::Float>)
63    where
64        S: UserState,
65        S::Float: FloatCore + tracing::Value,
66    {
67        match progress {
68            Progress::Measure(value) => {
69                log_at_level!(
70                    self.level,
71                    target: "engine.progress",
72                    kind = "metric",
73                    iteration = state.iteration(),
74                    value = *value
75                );
76            }
77
78            Progress::Report {
79                measure,
80                diagnostics,
81            } => {
82                log_at_level!(
83                    self.level,
84                    target: "engine.progress",
85                    kind = "report",
86                    iteration = state.iteration(),
87                    measure = *measure,
88                    ?diagnostics
89                );
90            }
91
92            Progress::Complete => {
93                log_at_level!(
94                    self.level,
95                    target: "engine.progress",
96                    kind = "complete",
97                    iteration = state.iteration()
98                );
99            }
100        }
101    }
102}
103
104impl<S> Observe<S> for Tracer
105where
106    S: UserState,
107    S::Float: FloatCore + tracing::Value,
108{
109    fn observe(
110        &self,
111        ident: &'static str,
112        state: StateView<'_, S>,
113        event: &EngineSignal<S::Float>,
114    ) {
115        match event {
116            EngineSignal::Initialised => {
117                self.lifecycle(ident, "initialised");
118            }
119
120            EngineSignal::CheckpointSaved => {
121                self.lifecycle(ident, "checkpoint_saved");
122            }
123
124            EngineSignal::CheckpointRequested(_) => {
125                self.lifecycle(ident, "checkpoint_requested");
126            }
127
128            EngineSignal::Termination(reason) => {
129                self.termination(ident, *reason);
130            }
131
132            EngineSignal::Progress(progress) => {
133                self.progress(state, progress);
134            }
135        }
136    }
137}