Skip to main content

nros_log/
macros.rs

1//! Phase 88.2 — the emission macros.
2//!
3//! Each macro formats its arguments into a stack-resident
4//! [`crate::FormatBuffer`] (capacity picked at compile time by the
5//! `buffer-size-<N>` feature), wraps the result in a [`crate::Record`],
6//! and hands it to the [`crate::Logger`]'s dispatcher.
7//!
8//! Compile-time ceiling: macros below
9//! [`severity_enabled_at_compile_time`](super::severity_enabled_at_compile_time)
10//! expand to `()` — the format call is dead-code-eliminated.
11//!
12//! ## Naming (RFC-0089, settled 2026-09-04)
13//!
14//! The three ROS 2 client libraries disagree with each other about how to
15//! spell a log line:
16//!
17//! ```text
18//! rclrs    log_info!(logger, "…")        <- Rust follows THIS
19//! rclcpp   RCLCPP_INFO(logger, "…")
20//! rcutils  RCUTILS_LOG_INFO_NAMED(…)
21//! ```
22//!
23//! There is no single "ROS 2 spelling", so each of our languages follows ITS
24//! OWN upstream. For Rust that is rclrs, and rclrs's five severity macros are
25//! [`log_debug!`](crate::log_debug), [`log_info!`](crate::log_info),
26//! [`log_warn!`](crate::log_warn), [`log_error!`](crate::log_error) and
27//! [`log_fatal!`](crate::log_fatal) — which is what this module now exports.
28//! (They carry an explicit target because `#[macro_export]` puts a macro at
29//! the CRATE ROOT, not in the module whose file declares it.) The pre-rename
30//! ~~`nros_debug!`~~ .. ~~`nros_fatal!`~~ forwarders, and the
31//! `deprecate-legacy-names` feature that armed their `#[deprecated]`, were
32//! removed in phase-417 W-B5 once every in-tree call site had moved. There is
33//! no log_trace!: see below.
34//!
35//! **A name takes rclrs's spelling exactly when rclrs HAS that name.** Two
36//! families deliberately keep the `nros_` prefix, because inventing a name
37//! inside rclrs's namespace-alike would claim an upstream twin that does not
38//! exist:
39//!
40//! * [`nros_trace!`](crate::nros_trace) — rclrs stops at `debug`. TRACE is ours (ledger
41//!   `rust:nros_trace`, verdict `extension`), so it keeps a name that says so.
42//! * the `nros_*_throttle!` / `nros_*_throttle_at!` family — a macro per
43//!   severity, which predates the modifier form below. rclrs has no such
44//!   names.
45//!
46//! **The first argument is rclrs's [`ToLogParams`](crate::ToLogParams)**
47//! (phase-515 R3), so rclrs's modifiers chain off the logger:
48//! `log_info!(logger.once(), …)`, `.skip_first()`, `.only_if(b)`, and — with
49//! the platform clock — `.throttle(d)`. A plain `&Logger` is still the common
50//! case. See [`crate::log_params`] for the bounds.
51
52/// Internal macro emitting one log record at `$severity`.
53///
54/// Use the named helpers ([`nros_trace!`](crate::nros_trace),
55/// [`log_debug!`](crate::log_debug), etc.) — they
56/// gate on the compile-time ceiling before evaluating this body.
57#[doc(hidden)]
58#[macro_export]
59macro_rules! __nros_log_emit {
60    ($logger:expr, $severity:expr, $($arg:tt)+) => {{
61        // Cheap runtime threshold check first to short-circuit
62        // disabled call sites before formatting.
63        let __logger: &$crate::Logger = $logger;
64        let __sev = $severity;
65        if __logger.is_enabled(__sev) {
66            use ::core::fmt::Write as _;
67            let mut __buf = $crate::FormatBuffer::new();
68            // Ignoring `write!` result — `FormatBuffer::write_str`
69            // never returns `Err`; overflow is signalled via the
70            // `truncated()` accessor (not the `Result`).
71            let _ = ::core::write!(__buf, $($arg)+);
72            let __record = $crate::Record {
73                severity:     __sev,
74                logger_name:  __logger.name(),
75                message:      __buf.as_str(),
76                file:         ::core::file!(),
77                line:         ::core::line!(),
78                timestamp_ns: $crate::__timestamp_ns(),
79            };
80            __logger.dispatch(&__record);
81        }
82    }};
83}
84
85/// Internal macro emitting one record described by a
86/// [`LogParams`](crate::LogParams) — the five level macros, [`log!`] and
87/// [`nros_trace!`](crate::nros_trace) all expand to this (phase-515 R3).
88///
89/// The occurrence and throttle state live HERE, as statics at the call site,
90/// which is rclrs's arrangement too: a params value has no call-site identity
91/// to hang them off. A site whose params need neither — a plain
92/// `log_info!(logger, …)` — leaves both branches dead once the params are
93/// constant-folded, and the statics go with them.
94///
95/// Order, the same as the `nros_*_throttle!` family's and for the same reason:
96/// filter and severity first, so a record the level drops consumes neither
97/// the `once` flag nor the throttle window; then occurrence; then the window.
98#[doc(hidden)]
99#[macro_export]
100macro_rules! __nros_log_params_emit {
101    ($params:expr, $($arg:tt)+) => {{
102        let __p: $crate::LogParams<'_> = $params;
103        let __logger: &$crate::Logger = __p.logger();
104        let __sev = __p.get_severity();
105        if __p.get_user_filter() && __logger.is_enabled(__sev) {
106            static __NROS_LOG_OCCURS: $crate::log_params::OccurrenceState =
107                $crate::log_params::OccurrenceState::new();
108            static __NROS_LOG_THROTTLE: $crate::ThrottleState = $crate::ThrottleState::new();
109            let __now = $crate::__timestamp_ns();
110            let __throttle = __p.get_throttle();
111            if __NROS_LOG_OCCURS.admit(__p.get_occurence())
112                && (__throttle.is_zero()
113                    || __NROS_LOG_THROTTLE
114                        .should_log(__now, $crate::log_params::__duration_ns(__throttle)))
115            {
116                use ::core::fmt::Write as _;
117                let mut __buf = $crate::FormatBuffer::new();
118                let _ = ::core::write!(__buf, $($arg)+);
119                let __record = $crate::Record {
120                    severity:     __sev,
121                    logger_name:  __logger.name(),
122                    message:      __buf.as_str(),
123                    file:         ::core::file!(),
124                    line:         ::core::line!(),
125                    // The reading the window was decided against — see
126                    // `__nros_log_emit_throttled!`.
127                    timestamp_ns: __now,
128                };
129                __logger.dispatch(&__record);
130            }
131        }
132    }};
133}
134
135/// Emit one record at a severity chosen at RUNTIME — rclrs's `log!`.
136///
137/// phase-417 G6, ledger row `rust:log`. The severity-taking emitter existed as
138/// [`__nros_log_emit!`], `#[doc(hidden)]` and named as an implementation
139/// detail, so a ported `log!(logger, severity, …)` had no supported spelling
140/// to land on. This is that spelling.
141///
142/// ```ignore
143/// let severity = if recovered { Severity::Info } else { Severity::Error };
144/// nros_log::log!(logger, severity, "link {}", state);
145/// ```
146///
147/// **It routes through exactly what the five level macros route through**, and
148/// that is the whole risk in a macro this cheap: the compile-time ceiling, the
149/// runtime `is_enabled` threshold and the one `FormatBuffer`. If it grew its
150/// own gate, `log!(logger, Severity::Debug, …)` would obey a different
151/// threshold from [`log_debug!`](crate::log_debug) and the two spellings of one call would
152/// disagree — which is what a second implementation of a gate always ends up
153/// meaning.
154///
155/// MEASURED, and worth knowing before trusting the `is_enabled` half of that
156/// sentence: [`crate::Logger::dispatch`] re-checks the threshold itself, so
157/// removing the check inside [`__nros_log_emit!`] changes nothing observable.
158/// The runtime gate is belt-and-braces; what a second implementation could
159/// actually get wrong here is the COMPILE-TIME ceiling and the severity the
160/// record carries, and those are what the test asserts.
161///
162/// **One difference from the five, forced by the runtime severity.** Those
163/// consult [`crate::severity_enabled_at_compile_time`] with a CONSTANT, so a
164/// call below the ceiling compiles to `()` and the formatting is
165/// dead-code-eliminated. Here the severity is a value, so the same check
166/// happens — at run time, on a `Severity` the compiler does not know. A call
167/// site that wants the record and its arguments gone from the image entirely
168/// names the level macro; this one keeps the `write!` in the binary and
169/// decides per call.
170///
171/// The first argument is a [`ToLogParams`](crate::ToLogParams), like the
172/// level macros', so `log!(logger.throttle(d), severity, …)` throttles; the
173/// severity argument wins over one set in the chain.
174#[macro_export]
175macro_rules! log {
176    ($logger:expr, $severity:expr, $($arg:tt)+) => {{
177        let __sev = $severity;
178        if $crate::severity_enabled_at_compile_time(__sev) {
179            $crate::__nros_log_params_emit!(
180                $crate::ToLogParams::severity($logger, __sev),
181                $($arg)+
182            );
183        }
184    }};
185}
186
187/// Emit at [`crate::Severity::Trace`].
188///
189/// Disabled at compile time unless the `max-level-trace` feature is
190/// the active ceiling (default).
191#[macro_export]
192macro_rules! nros_trace {
193    ($logger:expr, $($arg:tt)+) => {
194        if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
195            $crate::__nros_log_params_emit!(
196                $crate::ToLogParams::severity($logger, $crate::Severity::Trace),
197                $($arg)+
198            );
199        }
200    };
201}
202
203/// Emit at [`crate::Severity::Debug`].
204///
205/// `rclrs::log_debug!`'s spelling — see the module note on RFC-0089's
206/// "each language follows its own upstream" rule. The pre-rename
207/// ~~`nros_debug!`~~ forwarder was removed in phase-417 W-B5.
208#[macro_export]
209macro_rules! log_debug {
210    ($logger:expr, $($arg:tt)+) => {
211        if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
212            $crate::__nros_log_params_emit!($crate::ToLogParams::debug($logger), $($arg)+);
213        }
214    };
215}
216
217/// Emit at [`crate::Severity::Info`].
218///
219/// `rclrs::log_info!`'s spelling — see the module note on RFC-0089's
220/// "each language follows its own upstream" rule. The pre-rename
221/// ~~`nros_info!`~~ forwarder was removed in phase-417 W-B5.
222#[macro_export]
223macro_rules! log_info {
224    ($logger:expr, $($arg:tt)+) => {
225        if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
226            $crate::__nros_log_params_emit!($crate::ToLogParams::info($logger), $($arg)+);
227        }
228    };
229}
230
231/// Emit at [`crate::Severity::Warn`].
232///
233/// `rclrs::log_warn!`'s spelling — see the module note on RFC-0089's
234/// "each language follows its own upstream" rule. The pre-rename
235/// ~~`nros_warn!`~~ forwarder was removed in phase-417 W-B5.
236#[macro_export]
237macro_rules! log_warn {
238    ($logger:expr, $($arg:tt)+) => {
239        if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
240            $crate::__nros_log_params_emit!($crate::ToLogParams::warn($logger), $($arg)+);
241        }
242    };
243}
244
245/// Emit at [`crate::Severity::Error`].
246///
247/// `rclrs::log_error!`'s spelling — see the module note on RFC-0089's
248/// "each language follows its own upstream" rule. The pre-rename
249/// ~~`nros_error!`~~ forwarder was removed in phase-417 W-B5.
250#[macro_export]
251macro_rules! log_error {
252    ($logger:expr, $($arg:tt)+) => {
253        if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
254            $crate::__nros_log_params_emit!($crate::ToLogParams::error($logger), $($arg)+);
255        }
256    };
257}
258
259/// Emit at [`crate::Severity::Fatal`].
260///
261/// `rclrs::log_fatal!`'s spelling — see the module note on RFC-0089's
262/// "each language follows its own upstream" rule. The pre-rename
263/// ~~`nros_fatal!`~~ forwarder was removed in phase-417 W-B5.
264#[macro_export]
265macro_rules! log_fatal {
266    ($logger:expr, $($arg:tt)+) => {
267        if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
268            $crate::__nros_log_params_emit!($crate::ToLogParams::fatal($logger), $($arg)+);
269        }
270    };
271}
272
273// -----------------------------------------------------------------------------
274// phase-417 W4.d — throttled emission.
275//
276// There WAS a throttle in this tree before: `nros_core::logger::Logger::
277// *_throttle`. It sat on a DIFFERENT `Logger` — the one that forwards to the
278// `log` crate — so it never reached `nros_platform_log_write`, and the
279// `Logger` the FFI hands to C and C++ had no throttle at all. Two types, the
280// capability on the unused one. The rule itself now lives in
281// [`crate::throttle`], which both this macro family and the C shim call.
282//
283// Two spellings, and the difference is WHERE THE TIME COMES FROM:
284//
285// * `nros_*_throttle!(logger, interval_ms, ...)` reads the platform clock.
286// * `nros_*_throttle_at!(logger, now_ns, interval_ms, ...)` takes it, which is
287//   `rclcpp`'s shape (`RCLCPP_INFO_THROTTLE(logger, clock, ...)` names the
288//   clock) and the only form available with no platform port.
289//
290// The clock-reading form REFUSES TO COMPILE without the platform clock
291// (`cfg(nros_log_clock)`, issue 1037), rather than compiling into a throttle with no time base. Without a
292// clock `__timestamp_ns()` is a constant `0`, and a window measured against a
293// constant never elapses — the site emits its first record and then nothing,
294// forever (issue 1152; before it, the same constant admitted EVERY record),
295// while reading at the call site exactly like a working throttle. RFC-0089:
296// never compile and differ.
297//
298// WHY THIS FAMILY KEEPS THE `nros_` PREFIX. rclrs has no log_info_throttle!
299// to follow: its throttle is a MODIFIER on the one macro, and since phase-515
300// R3 ours is too — `log_info!(logger.throttle(d), …)` through `ToLogParams`
301// (`crate::log_params`). That reversed the three ledger rows this note used to
302// cite against it (`rust:ToLogParams`, `rust:LogOccurrence`,
303// `rust:ThrottleClock`); their objections each had an answer:
304//
305//   * the occurrence and throttle state live in statics the LEVEL MACRO
306//     declares at the call site, as rclrs's do, not in the params value;
307//   * `ThrottleClock` is bounded to `SteadyTime`, the one clock we can read;
308//   * the clockless refusal survives: `ToLogParams::throttle` exists only
309//     under `cfg(nros_log_clock)`, so a clockless `.throttle(d)` is a compile
310//     error rather than a runtime field that silently admits everything.
311//
312// This family stays as our own spelling of the same capability, and the
313// `_at` half stays the ONLY throttle on a clockless target, which the
314// modifier form cannot express.
315// -----------------------------------------------------------------------------
316
317/// Resolves the timestamp the `nros_*_throttle!` family measures against.
318///
319/// With the platform clock (`cfg(nros_log_clock)` — the platform's `clock`
320/// capability or the `platform-clock` feature, issue 1037): the platform's
321/// monotonic clock. Without: a compile error — see the family's module note
322/// for why this is not a silent degradation.
323#[cfg(nros_log_clock)]
324#[doc(hidden)]
325#[macro_export]
326macro_rules! __nros_throttle_now {
327    () => {
328        $crate::__timestamp_ns()
329    };
330}
331
332/// See the `nros_log_clock` arm above.
333#[cfg(not(nros_log_clock))]
334#[doc(hidden)]
335#[macro_export]
336macro_rules! __nros_throttle_now {
337    () => {
338        ::core::compile_error!(
339            "nros_*_throttle! needs a monotonic clock: build for a platform whose \
340             nros-platform.toml declares `[capabilities] clock = true` (the lane names it through \
341             NROS_PLATFORM_NAME), enable nros-log's `platform-clock` feature where a port is linked, \
342             or use nros_*_throttle_at!(logger, now_ns, interval_ms, ...) and pass the \
343             time yourself. Without a clock `__timestamp_ns()` is a constant 0, so the window \
344             would admit every record while looking exactly like a working throttle."
345        )
346    };
347}
348
349/// Internal macro emitting one throttled record at `$severity`.
350///
351/// `$state` is a `&'static ThrottleState`; the public macros declare it as a
352/// `static` at the call site, which is what makes the window per-SITE rather
353/// than per-logger.
354#[doc(hidden)]
355#[macro_export]
356macro_rules! __nros_log_emit_throttled {
357    ($logger:expr, $severity:expr, $state:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {{
358        let __logger: &$crate::Logger = $logger;
359        let __sev = $severity;
360        let __now: u64 = $now_ns;
361        let __interval = $crate::interval_ms_to_ns($interval_ms as u64);
362        // Severity first, window second — see `Logger::is_enabled_throttled`.
363        // Also short-circuits the format call, like the unthrottled macros.
364        if __logger.is_enabled_throttled(__sev, $state, __now, __interval) {
365            use ::core::fmt::Write as _;
366            let mut __buf = $crate::FormatBuffer::new();
367            let _ = ::core::write!(__buf, $($arg)+);
368            let __record = $crate::Record {
369                severity:     __sev,
370                logger_name:  __logger.name(),
371                message:      __buf.as_str(),
372                file:         ::core::file!(),
373                line:         ::core::line!(),
374                // The timestamp the window was decided against, not a second
375                // reading — two clock reads for one record is how a record can
376                // be stamped before the window it opened.
377                timestamp_ns: __now,
378            };
379            __logger.dispatch(&__record);
380        }
381    }};
382}
383
384/// Declares the per-call-site window and forwards. Internal.
385#[doc(hidden)]
386#[macro_export]
387macro_rules! __nros_throttle_site {
388    ($logger:expr, $severity:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {{
389        static __NROS_THROTTLE_SITE: $crate::ThrottleState = $crate::ThrottleState::new();
390        $crate::__nros_log_emit_throttled!(
391            $logger, $severity, &__NROS_THROTTLE_SITE, $now_ns, $interval_ms, $($arg)+
392        );
393    }};
394}
395
396/// Emit at [`crate::Severity::Trace`], at most once per `interval_ms` at this
397/// call site.
398///
399/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
400/// [`nros_trace_throttle_at!`](crate::nros_trace_throttle_at) for the form that takes the time).
401#[macro_export]
402macro_rules! nros_trace_throttle {
403    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
404        if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
405            $crate::__nros_throttle_site!(
406                $logger,
407                $crate::Severity::Trace,
408                $crate::__nros_throttle_now!(),
409                $interval_ms,
410                $($arg)+
411            );
412        }
413    };
414}
415
416/// Emit at [`crate::Severity::Trace`], at most once per `interval_ms` at this
417/// call site, measured against the `now_ns` the CALLER supplies.
418///
419/// `rclcpp`'s shape — `RCLCPP_TRACE_THROTTLE(logger, clock, …)` names its clock
420/// too — and the only throttled form available with no platform clock.
421#[macro_export]
422macro_rules! nros_trace_throttle_at {
423    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
424        if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
425            $crate::__nros_throttle_site!(
426                $logger, $crate::Severity::Trace, $now_ns, $interval_ms, $($arg)+
427            );
428        }
429    };
430}
431
432/// Emit at [`crate::Severity::Debug`], at most once per `interval_ms` at this
433/// call site.
434///
435/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
436/// [`nros_debug_throttle_at!`](crate::nros_debug_throttle_at) for the form that takes the time).
437#[macro_export]
438macro_rules! nros_debug_throttle {
439    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
440        if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
441            $crate::__nros_throttle_site!(
442                $logger,
443                $crate::Severity::Debug,
444                $crate::__nros_throttle_now!(),
445                $interval_ms,
446                $($arg)+
447            );
448        }
449    };
450}
451
452/// Emit at [`crate::Severity::Debug`], at most once per `interval_ms` at this
453/// call site, measured against the `now_ns` the CALLER supplies.
454///
455/// `rclcpp`'s shape — `RCLCPP_DEBUG_THROTTLE(logger, clock, …)` names its clock
456/// too — and the only throttled form available with no platform clock.
457#[macro_export]
458macro_rules! nros_debug_throttle_at {
459    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
460        if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
461            $crate::__nros_throttle_site!(
462                $logger, $crate::Severity::Debug, $now_ns, $interval_ms, $($arg)+
463            );
464        }
465    };
466}
467
468/// Emit at [`crate::Severity::Info`], at most once per `interval_ms` at this
469/// call site.
470///
471/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
472/// [`nros_info_throttle_at!`](crate::nros_info_throttle_at) for the form that takes the time).
473#[macro_export]
474macro_rules! nros_info_throttle {
475    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
476        if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
477            $crate::__nros_throttle_site!(
478                $logger,
479                $crate::Severity::Info,
480                $crate::__nros_throttle_now!(),
481                $interval_ms,
482                $($arg)+
483            );
484        }
485    };
486}
487
488/// Emit at [`crate::Severity::Info`], at most once per `interval_ms` at this
489/// call site, measured against the `now_ns` the CALLER supplies.
490///
491/// `rclcpp`'s shape — `RCLCPP_INFO_THROTTLE(logger, clock, …)` names its clock
492/// too — and the only throttled form available with no platform clock.
493#[macro_export]
494macro_rules! nros_info_throttle_at {
495    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
496        if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
497            $crate::__nros_throttle_site!(
498                $logger, $crate::Severity::Info, $now_ns, $interval_ms, $($arg)+
499            );
500        }
501    };
502}
503
504/// Emit at [`crate::Severity::Warn`], at most once per `interval_ms` at this
505/// call site.
506///
507/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
508/// [`nros_warn_throttle_at!`](crate::nros_warn_throttle_at) for the form that takes the time).
509#[macro_export]
510macro_rules! nros_warn_throttle {
511    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
512        if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
513            $crate::__nros_throttle_site!(
514                $logger,
515                $crate::Severity::Warn,
516                $crate::__nros_throttle_now!(),
517                $interval_ms,
518                $($arg)+
519            );
520        }
521    };
522}
523
524/// Emit at [`crate::Severity::Warn`], at most once per `interval_ms` at this
525/// call site, measured against the `now_ns` the CALLER supplies.
526///
527/// `rclcpp`'s shape — `RCLCPP_WARN_THROTTLE(logger, clock, …)` names its clock
528/// too — and the only throttled form available with no platform clock.
529#[macro_export]
530macro_rules! nros_warn_throttle_at {
531    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
532        if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
533            $crate::__nros_throttle_site!(
534                $logger, $crate::Severity::Warn, $now_ns, $interval_ms, $($arg)+
535            );
536        }
537    };
538}
539
540/// Emit at [`crate::Severity::Error`], at most once per `interval_ms` at this
541/// call site.
542///
543/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
544/// [`nros_error_throttle_at!`](crate::nros_error_throttle_at) for the form that takes the time).
545#[macro_export]
546macro_rules! nros_error_throttle {
547    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
548        if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
549            $crate::__nros_throttle_site!(
550                $logger,
551                $crate::Severity::Error,
552                $crate::__nros_throttle_now!(),
553                $interval_ms,
554                $($arg)+
555            );
556        }
557    };
558}
559
560/// Emit at [`crate::Severity::Error`], at most once per `interval_ms` at this
561/// call site, measured against the `now_ns` the CALLER supplies.
562///
563/// `rclcpp`'s shape — `RCLCPP_ERROR_THROTTLE(logger, clock, …)` names its clock
564/// too — and the only throttled form available with no platform clock.
565#[macro_export]
566macro_rules! nros_error_throttle_at {
567    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
568        if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
569            $crate::__nros_throttle_site!(
570                $logger, $crate::Severity::Error, $now_ns, $interval_ms, $($arg)+
571            );
572        }
573    };
574}
575
576/// Emit at [`crate::Severity::Fatal`], at most once per `interval_ms` at this
577/// call site.
578///
579/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
580/// [`nros_fatal_throttle_at!`](crate::nros_fatal_throttle_at) for the form that takes the time).
581#[macro_export]
582macro_rules! nros_fatal_throttle {
583    ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
584        if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
585            $crate::__nros_throttle_site!(
586                $logger,
587                $crate::Severity::Fatal,
588                $crate::__nros_throttle_now!(),
589                $interval_ms,
590                $($arg)+
591            );
592        }
593    };
594}
595
596/// Emit at [`crate::Severity::Fatal`], at most once per `interval_ms` at this
597/// call site, measured against the `now_ns` the CALLER supplies.
598///
599/// `rclcpp`'s shape — `RCLCPP_FATAL_THROTTLE(logger, clock, …)` names its clock
600/// too — and the only throttled form available with no platform clock.
601#[macro_export]
602macro_rules! nros_fatal_throttle_at {
603    ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
604        if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
605            $crate::__nros_throttle_site!(
606                $logger, $crate::Severity::Fatal, $now_ns, $interval_ms, $($arg)+
607            );
608        }
609    };
610}