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}