Skip to main content

nros_log/
log_params.rs

1//! rclrs's log modifiers — `log_info!(logger.once(), …)`,
2//! `.skip_first()`, `.throttle(d)`, `.only_if(b)` (phase-515 R3).
3//!
4//! rclrs's five level macros take anything implementing [`ToLogParams`] as
5//! their first argument, so a modifier chains off the logger:
6//!
7//! ```ignore
8//! log_info!(logger.once(), "link up");
9//! log_warn!(logger.skip_first().throttle(Duration::from_millis(500)), "lag {}", ms);
10//! log_debug!(logger.only_if(verbose), "state {:?}", s);
11//! ```
12//!
13//! ADOPT-BOUNDED. rclrs's names, with three stated differences:
14//!
15//! * [`ToLogParams`] is implemented for `&Logger` and [`LogParams`] only. rclrs
16//!   also takes a node or a logger NAME; here a node's logger is
17//!   `node.logger()`, and a name is [`crate::get_logger`].
18//! * [`ThrottleClock`] has one variant, `SteadyTime`: the platform's monotonic
19//!   clock is the only clock the logging path can read. rclrs's `SystemTime`
20//!   and `Clock(…)` have nothing to name here.
21//! * [`ToLogParams::throttle`] and [`ToLogParams::throttle_clock`] exist only
22//!   when the image has that clock (`cfg(nros_log_clock)`, issue 1037). Without
23//!   one the window would be measured against a constant and the call site
24//!   would read like a working throttle while emitting once and then never
25//!   (issue 1152), so a clockless build refuses `.throttle(d)` at compile time
26//!   — the same refusal the `nros_*_throttle!` family makes, and the
27//!   `nros_*_throttle_at!` family stays the clockless form.
28//!
29//! The state `once`, `skip_first` and `throttle` need is NOT in the params
30//! value. As in rclrs, the level macro declares it as statics at the call site
31//! ([`crate::__nros_log_params_emit!`]), which is what makes it per site. A
32//! call site whose params are known at compile time to need neither — every
33//! plain `log_info!(logger, …)` — references neither static once optimised,
34//! and they are dropped with the dead branch.
35
36use core::time::Duration;
37
38use crate::{Logger, Severity};
39
40/// rclrs's `LogOccurrence`: which records of a call site emit.
41#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub enum LogOccurrence {
43    /// Every record (the default).
44    #[default]
45    All,
46    /// Only the first record that passes the severity and filter checks.
47    Once,
48    /// Every record except the first that passes those checks.
49    SkipFirst,
50}
51
52/// rclrs's `ThrottleClock`: the clock a throttle window is measured on.
53///
54/// ADOPT-BOUNDED: `SteadyTime` only — the platform's monotonic clock is the
55/// one the logging path can read. See the module note.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub enum ThrottleClock {
58    /// The platform's monotonic clock (`nros_platform_clock_ns`).
59    #[default]
60    SteadyTime,
61}
62
63/// rclrs's `LogParams`: the logger plus the modifiers one record carries.
64///
65/// ADOPT-BOUNDED: it borrows the [`Logger`] (`LogParams<'a>`) where rclrs's
66/// owns a logger name. Build one through [`ToLogParams`].
67#[derive(Clone, Copy)]
68pub struct LogParams<'a> {
69    logger: &'a Logger,
70    severity: Severity,
71    occurs: LogOccurrence,
72    throttle: Duration,
73    throttle_clock: ThrottleClock,
74    only_if: bool,
75}
76
77impl<'a> LogParams<'a> {
78    /// rclrs's `LogParams::new`: `logger`, at `Info`, every record, no
79    /// throttle, no filter.
80    #[must_use]
81    pub const fn new(logger: &'a Logger) -> Self {
82        Self {
83            logger,
84            severity: Severity::Info,
85            occurs: LogOccurrence::All,
86            throttle: Duration::ZERO,
87            throttle_clock: ThrottleClock::SteadyTime,
88            only_if: true,
89        }
90    }
91
92    /// The logger the record goes to.
93    #[must_use]
94    pub const fn logger(&self) -> &'a Logger {
95        self.logger
96    }
97
98    /// rclrs's `get_logger_name`: the logger's name.
99    #[must_use]
100    pub const fn get_logger_name(&self) -> &'static str {
101        self.logger.name()
102    }
103
104    /// rclrs's `get_severity`.
105    #[must_use]
106    pub const fn get_severity(&self) -> Severity {
107        self.severity
108    }
109
110    /// rclrs's `get_occurence` (rclrs's spelling, kept).
111    #[must_use]
112    pub const fn get_occurence(&self) -> LogOccurrence {
113        self.occurs
114    }
115
116    /// rclrs's `get_throttle`: `Duration::ZERO` for no throttle.
117    #[must_use]
118    pub const fn get_throttle(&self) -> Duration {
119        self.throttle
120    }
121
122    /// rclrs's `get_throttle_clock`.
123    #[must_use]
124    pub const fn get_throttle_clock(&self) -> ThrottleClock {
125        self.throttle_clock
126    }
127
128    /// rclrs's `get_user_filter`: the `only_if` condition.
129    #[must_use]
130    pub const fn get_user_filter(&self) -> bool {
131        self.only_if
132    }
133}
134
135/// rclrs's `ToLogParams`: anything a level macro takes as its first argument,
136/// and the modifiers that chain off it.
137///
138/// Implemented for `&Logger` and [`LogParams`]; see the module note.
139pub trait ToLogParams<'a>: Sized {
140    /// Convert to params with this value's settings.
141    fn to_log_params(self) -> LogParams<'a>;
142
143    /// Emit only the first record of this call site.
144    fn once(self) -> LogParams<'a> {
145        self.occurs(LogOccurrence::Once)
146    }
147
148    /// Emit every record of this call site except the first.
149    fn skip_first(self) -> LogParams<'a> {
150        self.occurs(LogOccurrence::SkipFirst)
151    }
152
153    /// Set the occurrence rule.
154    fn occurs(self, occurs: LogOccurrence) -> LogParams<'a> {
155        let mut p = self.to_log_params();
156        p.occurs = occurs;
157        p
158    }
159
160    /// Emit at most one record of this call site per `throttle`, measured
161    /// from the last record emitted. `Duration::ZERO` is no throttle.
162    ///
163    /// Present only with the platform clock — see the module note.
164    #[cfg(nros_log_clock)]
165    fn throttle(self, throttle: Duration) -> LogParams<'a> {
166        let mut p = self.to_log_params();
167        p.throttle = throttle;
168        p
169    }
170
171    /// Set the clock a throttle is measured on.
172    ///
173    /// Present only with the platform clock — see the module note.
174    #[cfg(nros_log_clock)]
175    fn throttle_clock(self, clock: ThrottleClock) -> LogParams<'a> {
176        let mut p = self.to_log_params();
177        p.throttle_clock = clock;
178        p
179    }
180
181    /// Emit only while `only_if` is true. Combines with an earlier filter by
182    /// AND, so a chain of conditions all have to hold.
183    fn only_if(self, only_if: bool) -> LogParams<'a> {
184        let mut p = self.to_log_params();
185        p.only_if = p.only_if && only_if;
186        p
187    }
188
189    /// At `Debug`.
190    fn debug(self) -> LogParams<'a> {
191        self.severity(Severity::Debug)
192    }
193
194    /// At `Info`.
195    fn info(self) -> LogParams<'a> {
196        self.severity(Severity::Info)
197    }
198
199    /// At `Warn`.
200    fn warn(self) -> LogParams<'a> {
201        self.severity(Severity::Warn)
202    }
203
204    /// At `Error`.
205    fn error(self) -> LogParams<'a> {
206        self.severity(Severity::Error)
207    }
208
209    /// At `Fatal`.
210    fn fatal(self) -> LogParams<'a> {
211        self.severity(Severity::Fatal)
212    }
213
214    /// At `severity`.
215    fn severity(self, severity: Severity) -> LogParams<'a> {
216        let mut p = self.to_log_params();
217        p.severity = severity;
218        p
219    }
220}
221
222impl<'a> ToLogParams<'a> for &'a Logger {
223    fn to_log_params(self) -> LogParams<'a> {
224        LogParams::new(self)
225    }
226}
227
228/// `log_info!(&logger, …)` where `logger` is already a `&Logger` — the
229/// spelling the level macros accepted by deref coercion before they took
230/// [`ToLogParams`].
231impl<'a, 'b: 'a> ToLogParams<'a> for &'a &'b Logger {
232    fn to_log_params(self) -> LogParams<'a> {
233        LogParams::new(self)
234    }
235}
236
237impl<'a> ToLogParams<'a> for LogParams<'a> {
238    fn to_log_params(self) -> LogParams<'a> {
239        self
240    }
241}
242
243/// Per-call-site state of `once` / `skip_first`. Internal: the level macros
244/// declare one as a `static` at each site.
245#[doc(hidden)]
246pub struct OccurrenceState(portable_atomic::AtomicBool);
247
248impl OccurrenceState {
249    #[doc(hidden)]
250    #[must_use]
251    pub const fn new() -> Self {
252        Self(portable_atomic::AtomicBool::new(false))
253    }
254
255    /// Whether a record that passed every other check emits under `occurs`.
256    /// Marks the site seen on its first such record.
257    #[doc(hidden)]
258    pub fn admit(&self, occurs: LogOccurrence) -> bool {
259        match occurs {
260            LogOccurrence::All => true,
261            LogOccurrence::Once => !self.0.swap(true, portable_atomic::Ordering::AcqRel),
262            LogOccurrence::SkipFirst => self.0.swap(true, portable_atomic::Ordering::AcqRel),
263        }
264    }
265}
266
267impl Default for OccurrenceState {
268    fn default() -> Self {
269        Self::new()
270    }
271}
272
273/// Duration to the nanoseconds a [`crate::ThrottleState`] measures, saturating.
274#[doc(hidden)]
275#[must_use]
276pub const fn __duration_ns(d: Duration) -> u64 {
277    let ns = d.as_nanos();
278    if ns > u64::MAX as u128 {
279        u64::MAX
280    } else {
281        ns as u64
282    }
283}