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//! [`crate::compile_time_ceiling`](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 — rclrs expresses
43//! throttling as a modifier on ONE macro
44//! (`log_info!(logger.throttle(Duration::from_millis(500)), "…")` via
45//! `LogParams` / `ToLogParams`), not as a macro per severity. Ours is a
46//! different DECOMPOSITION of the same capability, already ledgered as
47//! `divergence`; see that family's own note for what adopting rclrs's shape
48//! would cost.
49
50/// Internal macro emitting one log record at `$severity`.
51///
52/// Use the named helpers ([`nros_trace!`](crate::nros_trace),
53/// [`log_debug!`](crate::log_debug), etc.) — they
54/// gate on the compile-time ceiling before evaluating this body.
55#[doc(hidden)]
56#[macro_export]
57macro_rules! __nros_log_emit {
58 ($logger:expr, $severity:expr, $($arg:tt)+) => {{
59 // Cheap runtime threshold check first to short-circuit
60 // disabled call sites before formatting.
61 let __logger: &$crate::Logger = $logger;
62 let __sev = $severity;
63 if __logger.is_enabled(__sev) {
64 use ::core::fmt::Write as _;
65 let mut __buf = $crate::FormatBuffer::new();
66 // Ignoring `write!` result — `FormatBuffer::write_str`
67 // never returns `Err`; overflow is signalled via the
68 // `truncated()` accessor (not the `Result`).
69 let _ = ::core::write!(__buf, $($arg)+);
70 let __record = $crate::Record {
71 severity: __sev,
72 logger_name: __logger.name(),
73 message: __buf.as_str(),
74 file: ::core::file!(),
75 line: ::core::line!(),
76 timestamp_ns: $crate::__timestamp_ns(),
77 };
78 __logger.dispatch(&__record);
79 }
80 }};
81}
82
83/// Emit one record at a severity chosen at RUNTIME — rclrs's `log!`.
84///
85/// phase-417 G6, ledger row `rust:log`. The severity-taking emitter existed as
86/// [`__nros_log_emit!`], `#[doc(hidden)]` and named as an implementation
87/// detail, so a ported `log!(logger, severity, …)` had no supported spelling
88/// to land on. This is that spelling.
89///
90/// ```ignore
91/// let severity = if recovered { Severity::Info } else { Severity::Error };
92/// nros_log::log!(logger, severity, "link {}", state);
93/// ```
94///
95/// **It routes through exactly what the five level macros route through**, and
96/// that is the whole risk in a macro this cheap: the compile-time ceiling, the
97/// runtime `is_enabled` threshold and the one `FormatBuffer`. If it grew its
98/// own gate, `log!(logger, Severity::Debug, …)` would obey a different
99/// threshold from [`log_debug!`](crate::log_debug) and the two spellings of one call would
100/// disagree — which is what a second implementation of a gate always ends up
101/// meaning.
102///
103/// MEASURED, and worth knowing before trusting the `is_enabled` half of that
104/// sentence: [`crate::Logger::dispatch`] re-checks the threshold itself, so
105/// removing the check inside [`__nros_log_emit!`] changes nothing observable.
106/// The runtime gate is belt-and-braces; what a second implementation could
107/// actually get wrong here is the COMPILE-TIME ceiling and the severity the
108/// record carries, and those are what the test asserts.
109///
110/// **One difference from the five, forced by the runtime severity.** Those
111/// consult [`crate::severity_enabled_at_compile_time`] with a CONSTANT, so a
112/// call below the ceiling compiles to `()` and the formatting is
113/// dead-code-eliminated. Here the severity is a value, so the same check
114/// happens — at run time, on a `Severity` the compiler does not know. A call
115/// site that wants the record and its arguments gone from the image entirely
116/// names the level macro; this one keeps the `write!` in the binary and
117/// decides per call.
118///
119/// A THROTTLED severity-taking form is deliberately absent: our throttling is
120/// a macro per severity (`nros_*_throttle!`), a DECOMPOSITION already ledgered
121/// as `divergence` against rclrs's `logger.throttle(…)` modifier, and adding a
122/// severity-taking member to that family would commit to the modifier shape in
123/// one place only.
124#[macro_export]
125macro_rules! log {
126 ($logger:expr, $severity:expr, $($arg:tt)+) => {{
127 let __sev = $severity;
128 if $crate::severity_enabled_at_compile_time(__sev) {
129 $crate::__nros_log_emit!($logger, __sev, $($arg)+);
130 }
131 }};
132}
133
134/// Emit at [`crate::Severity::Trace`].
135///
136/// Disabled at compile time unless the `max-level-trace` feature is
137/// the active ceiling (default).
138#[macro_export]
139macro_rules! nros_trace {
140 ($logger:expr, $($arg:tt)+) => {
141 if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
142 $crate::__nros_log_emit!($logger, $crate::Severity::Trace, $($arg)+);
143 }
144 };
145}
146
147/// Emit at [`crate::Severity::Debug`].
148///
149/// `rclrs::log_debug!`'s spelling — see the module note on RFC-0089's
150/// "each language follows its own upstream" rule. The pre-rename
151/// `nros_debug!` forwarder was removed in phase-417 W-B5.
152#[macro_export]
153macro_rules! log_debug {
154 ($logger:expr, $($arg:tt)+) => {
155 if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
156 $crate::__nros_log_emit!($logger, $crate::Severity::Debug, $($arg)+);
157 }
158 };
159}
160
161/// Emit at [`crate::Severity::Info`].
162///
163/// `rclrs::log_info!`'s spelling — see the module note on RFC-0089's
164/// "each language follows its own upstream" rule. The pre-rename
165/// `nros_info!` forwarder was removed in phase-417 W-B5.
166#[macro_export]
167macro_rules! log_info {
168 ($logger:expr, $($arg:tt)+) => {
169 if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
170 $crate::__nros_log_emit!($logger, $crate::Severity::Info, $($arg)+);
171 }
172 };
173}
174
175/// Emit at [`crate::Severity::Warn`].
176///
177/// `rclrs::log_warn!`'s spelling — see the module note on RFC-0089's
178/// "each language follows its own upstream" rule. The pre-rename
179/// `nros_warn!` forwarder was removed in phase-417 W-B5.
180#[macro_export]
181macro_rules! log_warn {
182 ($logger:expr, $($arg:tt)+) => {
183 if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
184 $crate::__nros_log_emit!($logger, $crate::Severity::Warn, $($arg)+);
185 }
186 };
187}
188
189/// Emit at [`crate::Severity::Error`].
190///
191/// `rclrs::log_error!`'s spelling — see the module note on RFC-0089's
192/// "each language follows its own upstream" rule. The pre-rename
193/// `nros_error!` forwarder was removed in phase-417 W-B5.
194#[macro_export]
195macro_rules! log_error {
196 ($logger:expr, $($arg:tt)+) => {
197 if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
198 $crate::__nros_log_emit!($logger, $crate::Severity::Error, $($arg)+);
199 }
200 };
201}
202
203/// Emit at [`crate::Severity::Fatal`].
204///
205/// `rclrs::log_fatal!`'s spelling — see the module note on RFC-0089's
206/// "each language follows its own upstream" rule. The pre-rename
207/// `nros_fatal!` forwarder was removed in phase-417 W-B5.
208#[macro_export]
209macro_rules! log_fatal {
210 ($logger:expr, $($arg:tt)+) => {
211 if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
212 $crate::__nros_log_emit!($logger, $crate::Severity::Fatal, $($arg)+);
213 }
214 };
215}
216
217// -----------------------------------------------------------------------------
218// phase-417 W4.d — throttled emission.
219//
220// There WAS a throttle in this tree before: `nros_core::logger::Logger::
221// *_throttle`. It sat on a DIFFERENT `Logger` — the one that forwards to the
222// `log` crate — so it never reached `nros_platform_log_write`, and the
223// `Logger` the FFI hands to C and C++ had no throttle at all. Two types, the
224// capability on the unused one. The rule itself now lives in
225// [`crate::throttle`], which both this macro family and the C shim call.
226//
227// Two spellings, and the difference is WHERE THE TIME COMES FROM:
228//
229// * `nros_*_throttle!(logger, interval_ms, ...)` reads the platform clock.
230// * `nros_*_throttle_at!(logger, now_ns, interval_ms, ...)` takes it, which is
231// `rclcpp`'s shape (`RCLCPP_INFO_THROTTLE(logger, clock, ...)` names the
232// clock) and the only form available with no platform port.
233//
234// The clock-reading form REFUSES TO COMPILE without the platform clock
235// (`cfg(nros_log_clock)`, issue 1037), rather than compiling into a throttle with no time base. Without a
236// clock `__timestamp_ns()` is a constant `0`, and a window measured against a
237// constant never elapses — the site emits its first record and then nothing,
238// forever (issue 1152; before it, the same constant admitted EVERY record),
239// while reading at the call site exactly like a working throttle. RFC-0089:
240// never compile and differ.
241//
242// WHY THIS FAMILY KEEPS THE `nros_` PREFIX WHILE THE FIVE SEVERITY MACROS TOOK
243// rclrs's (phase-417, 2026-09-04). rclrs has no `log_info_throttle!` to follow.
244// Its throttle is a MODIFIER on the one macro — `log_info!(logger.throttle(
245// Duration::from_millis(500)), "…")`, where the first argument is anything
246// implementing `ToLogParams` — so adopting "rclrs's spelling" here means
247// adopting `ToLogParams` and `LogParams`, not renaming a macro. That is a
248// SHAPE change, and three ledger rows have already decided against the pieces
249// it needs, each on a platform ground:
250//
251// * `rust:ToLogParams` — `declined`, an ergonomic overload trait over what
252// is one concrete caller shape here (`&Logger`).
253// * `rust:LogOccurrence` — `divergence`; `once` / `skip_first` take a
254// CALLER-SUPPLIED `&OnceFlag` because riscv32imc has no CAS, so the
255// occurrence state cannot live inside a params value.
256// * `rust:ThrottleClock` — `divergence`; rclrs's `LogParams` names the clock
257// a window is measured against and we have none to name.
258//
259// A `LogParams` missing those members is not rclrs's `LogParams`; it is a new
260// divergence wearing an upstream name, which is the "compiles and differs"
261// RFC-0089 exists to forbid. There is also a live cost: in the modifier form
262// whether a throttle was requested is a RUNTIME field, so the `platform-clock`
263// refusal above stops being expressible at the call site — the exact
264// silently-admits-everything failure the `compile_error!` was added to prevent.
265//
266// So: ours is KEPT, and it keeps the `nros_` prefix that says it is ours. The
267// ledger already records it as a `divergence` with a `provides` arrow at
268// `rust:nros_debug_throttle`; that verdict stands unchanged.
269// -----------------------------------------------------------------------------
270
271/// Resolves the timestamp the `nros_*_throttle!` family measures against.
272///
273/// With the platform clock (`cfg(nros_log_clock)` — the platform's `clock`
274/// capability or the `platform-clock` feature, issue 1037): the platform's
275/// monotonic clock. Without: a compile error — see the family's module note
276/// for why this is not a silent degradation.
277#[cfg(nros_log_clock)]
278#[doc(hidden)]
279#[macro_export]
280macro_rules! __nros_throttle_now {
281 () => {
282 $crate::__timestamp_ns()
283 };
284}
285
286/// See the `nros_log_clock` arm above.
287#[cfg(not(nros_log_clock))]
288#[doc(hidden)]
289#[macro_export]
290macro_rules! __nros_throttle_now {
291 () => {
292 ::core::compile_error!(
293 "nros_*_throttle! needs a monotonic clock: build for a platform whose \
294 nros-platform.toml declares `[capabilities] clock = true` (the lane names it through \
295 NROS_PLATFORM_NAME), enable nros-log's `platform-clock` feature where a port is linked, \
296 or use nros_*_throttle_at!(logger, now_ns, interval_ms, ...) and pass the \
297 time yourself. Without a clock `__timestamp_ns()` is a constant 0, so the window \
298 would admit every record while looking exactly like a working throttle."
299 )
300 };
301}
302
303/// Internal macro emitting one throttled record at `$severity`.
304///
305/// `$state` is a `&'static ThrottleState`; the public macros declare it as a
306/// `static` at the call site, which is what makes the window per-SITE rather
307/// than per-logger.
308#[doc(hidden)]
309#[macro_export]
310macro_rules! __nros_log_emit_throttled {
311 ($logger:expr, $severity:expr, $state:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {{
312 let __logger: &$crate::Logger = $logger;
313 let __sev = $severity;
314 let __now: u64 = $now_ns;
315 let __interval = $crate::interval_ms_to_ns($interval_ms as u64);
316 // Severity first, window second — see `Logger::is_enabled_throttled`.
317 // Also short-circuits the format call, like the unthrottled macros.
318 if __logger.is_enabled_throttled(__sev, $state, __now, __interval) {
319 use ::core::fmt::Write as _;
320 let mut __buf = $crate::FormatBuffer::new();
321 let _ = ::core::write!(__buf, $($arg)+);
322 let __record = $crate::Record {
323 severity: __sev,
324 logger_name: __logger.name(),
325 message: __buf.as_str(),
326 file: ::core::file!(),
327 line: ::core::line!(),
328 // The timestamp the window was decided against, not a second
329 // reading — two clock reads for one record is how a record can
330 // be stamped before the window it opened.
331 timestamp_ns: __now,
332 };
333 __logger.dispatch(&__record);
334 }
335 }};
336}
337
338/// Declares the per-call-site window and forwards. Internal.
339#[doc(hidden)]
340#[macro_export]
341macro_rules! __nros_throttle_site {
342 ($logger:expr, $severity:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {{
343 static __NROS_THROTTLE_SITE: $crate::ThrottleState = $crate::ThrottleState::new();
344 $crate::__nros_log_emit_throttled!(
345 $logger, $severity, &__NROS_THROTTLE_SITE, $now_ns, $interval_ms, $($arg)+
346 );
347 }};
348}
349
350/// Emit at [`crate::Severity::Trace`], at most once per `interval_ms` at this
351/// call site.
352///
353/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
354/// [`nros_trace_throttle_at!`](crate::nros_trace_throttle_at) for the form that takes the time).
355#[macro_export]
356macro_rules! nros_trace_throttle {
357 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
358 if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
359 $crate::__nros_throttle_site!(
360 $logger,
361 $crate::Severity::Trace,
362 $crate::__nros_throttle_now!(),
363 $interval_ms,
364 $($arg)+
365 );
366 }
367 };
368}
369
370/// Emit at [`crate::Severity::Trace`], at most once per `interval_ms` at this
371/// call site, measured against the `now_ns` the CALLER supplies.
372///
373/// `rclcpp`'s shape — `RCLCPP_TRACE_THROTTLE(logger, clock, …)` names its clock
374/// too — and the only throttled form available with no platform clock.
375#[macro_export]
376macro_rules! nros_trace_throttle_at {
377 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
378 if $crate::severity_enabled_at_compile_time($crate::Severity::Trace) {
379 $crate::__nros_throttle_site!(
380 $logger, $crate::Severity::Trace, $now_ns, $interval_ms, $($arg)+
381 );
382 }
383 };
384}
385
386/// Emit at [`crate::Severity::Debug`], at most once per `interval_ms` at this
387/// call site.
388///
389/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
390/// [`nros_debug_throttle_at!`](crate::nros_debug_throttle_at) for the form that takes the time).
391#[macro_export]
392macro_rules! nros_debug_throttle {
393 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
394 if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
395 $crate::__nros_throttle_site!(
396 $logger,
397 $crate::Severity::Debug,
398 $crate::__nros_throttle_now!(),
399 $interval_ms,
400 $($arg)+
401 );
402 }
403 };
404}
405
406/// Emit at [`crate::Severity::Debug`], at most once per `interval_ms` at this
407/// call site, measured against the `now_ns` the CALLER supplies.
408///
409/// `rclcpp`'s shape — `RCLCPP_DEBUG_THROTTLE(logger, clock, …)` names its clock
410/// too — and the only throttled form available with no platform clock.
411#[macro_export]
412macro_rules! nros_debug_throttle_at {
413 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
414 if $crate::severity_enabled_at_compile_time($crate::Severity::Debug) {
415 $crate::__nros_throttle_site!(
416 $logger, $crate::Severity::Debug, $now_ns, $interval_ms, $($arg)+
417 );
418 }
419 };
420}
421
422/// Emit at [`crate::Severity::Info`], at most once per `interval_ms` at this
423/// call site.
424///
425/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
426/// [`nros_info_throttle_at!`](crate::nros_info_throttle_at) for the form that takes the time).
427#[macro_export]
428macro_rules! nros_info_throttle {
429 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
430 if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
431 $crate::__nros_throttle_site!(
432 $logger,
433 $crate::Severity::Info,
434 $crate::__nros_throttle_now!(),
435 $interval_ms,
436 $($arg)+
437 );
438 }
439 };
440}
441
442/// Emit at [`crate::Severity::Info`], at most once per `interval_ms` at this
443/// call site, measured against the `now_ns` the CALLER supplies.
444///
445/// `rclcpp`'s shape — `RCLCPP_INFO_THROTTLE(logger, clock, …)` names its clock
446/// too — and the only throttled form available with no platform clock.
447#[macro_export]
448macro_rules! nros_info_throttle_at {
449 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
450 if $crate::severity_enabled_at_compile_time($crate::Severity::Info) {
451 $crate::__nros_throttle_site!(
452 $logger, $crate::Severity::Info, $now_ns, $interval_ms, $($arg)+
453 );
454 }
455 };
456}
457
458/// Emit at [`crate::Severity::Warn`], at most once per `interval_ms` at this
459/// call site.
460///
461/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
462/// [`nros_warn_throttle_at!`](crate::nros_warn_throttle_at) for the form that takes the time).
463#[macro_export]
464macro_rules! nros_warn_throttle {
465 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
466 if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
467 $crate::__nros_throttle_site!(
468 $logger,
469 $crate::Severity::Warn,
470 $crate::__nros_throttle_now!(),
471 $interval_ms,
472 $($arg)+
473 );
474 }
475 };
476}
477
478/// Emit at [`crate::Severity::Warn`], at most once per `interval_ms` at this
479/// call site, measured against the `now_ns` the CALLER supplies.
480///
481/// `rclcpp`'s shape — `RCLCPP_WARN_THROTTLE(logger, clock, …)` names its clock
482/// too — and the only throttled form available with no platform clock.
483#[macro_export]
484macro_rules! nros_warn_throttle_at {
485 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
486 if $crate::severity_enabled_at_compile_time($crate::Severity::Warn) {
487 $crate::__nros_throttle_site!(
488 $logger, $crate::Severity::Warn, $now_ns, $interval_ms, $($arg)+
489 );
490 }
491 };
492}
493
494/// Emit at [`crate::Severity::Error`], at most once per `interval_ms` at this
495/// call site.
496///
497/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
498/// [`nros_error_throttle_at!`](crate::nros_error_throttle_at) for the form that takes the time).
499#[macro_export]
500macro_rules! nros_error_throttle {
501 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
502 if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
503 $crate::__nros_throttle_site!(
504 $logger,
505 $crate::Severity::Error,
506 $crate::__nros_throttle_now!(),
507 $interval_ms,
508 $($arg)+
509 );
510 }
511 };
512}
513
514/// Emit at [`crate::Severity::Error`], at most once per `interval_ms` at this
515/// call site, measured against the `now_ns` the CALLER supplies.
516///
517/// `rclcpp`'s shape — `RCLCPP_ERROR_THROTTLE(logger, clock, …)` names its clock
518/// too — and the only throttled form available with no platform clock.
519#[macro_export]
520macro_rules! nros_error_throttle_at {
521 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
522 if $crate::severity_enabled_at_compile_time($crate::Severity::Error) {
523 $crate::__nros_throttle_site!(
524 $logger, $crate::Severity::Error, $now_ns, $interval_ms, $($arg)+
525 );
526 }
527 };
528}
529
530/// Emit at [`crate::Severity::Fatal`], at most once per `interval_ms` at this
531/// call site.
532///
533/// Reads the platform clock; needs `cfg(nros_log_clock)` (see
534/// [`nros_fatal_throttle_at!`](crate::nros_fatal_throttle_at) for the form that takes the time).
535#[macro_export]
536macro_rules! nros_fatal_throttle {
537 ($logger:expr, $interval_ms:expr, $($arg:tt)+) => {
538 if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
539 $crate::__nros_throttle_site!(
540 $logger,
541 $crate::Severity::Fatal,
542 $crate::__nros_throttle_now!(),
543 $interval_ms,
544 $($arg)+
545 );
546 }
547 };
548}
549
550/// Emit at [`crate::Severity::Fatal`], at most once per `interval_ms` at this
551/// call site, measured against the `now_ns` the CALLER supplies.
552///
553/// `rclcpp`'s shape — `RCLCPP_FATAL_THROTTLE(logger, clock, …)` names its clock
554/// too — and the only throttled form available with no platform clock.
555#[macro_export]
556macro_rules! nros_fatal_throttle_at {
557 ($logger:expr, $now_ns:expr, $interval_ms:expr, $($arg:tt)+) => {
558 if $crate::severity_enabled_at_compile_time($crate::Severity::Fatal) {
559 $crate::__nros_throttle_site!(
560 $logger, $crate::Severity::Fatal, $now_ns, $interval_ms, $($arg)+
561 );
562 }
563 };
564}