Skip to main content

Logger

Struct Logger 

Source
pub struct Logger { /* private fields */ }
Expand description

A named logger with a runtime severity threshold.

A logger constructed with Logger::new has NO level of its own and follows Logger::default_level (itself Severity::Info until someone calls Logger::set_default_level). Logger::with_level and Logger::set_level give it one; Logger::unset_level takes it away again. Use register_logger to publish a 'static Logger so multiple call sites with the same name share the same threshold.

Implementations§

Source§

impl Logger

Source

pub const fn new(name: &'static str) -> Self

const-construct with NO level of its own — the logger follows Logger::default_level, which is Severity::Info until something moves it.

phase-467: this stored Severity::Info outright until the process default existed, and “the author wrote new” then looked exactly like “the author wrote with_level(.., Info)”. They are different statements — one is a choice and the other is the absence of one — and telling them apart is what lets Logger::set_default_level move the first without overriding the second.

Source

pub const fn with_level(name: &'static str, level: Severity) -> Self

const-construct with an explicit threshold.

Source

pub const fn name(&self) -> &'static str

Logger name (used as Record::logger_name).

Source

pub fn level(&self) -> Severity

The threshold this logger actually filters on — its own level if it has one, otherwise its nearest ancestor’s (RFC-0102 D2), otherwise Logger::default_level.

This is rcutils_logging_get_logger_effective_level’s answer, not rcutils_logging_get_logger_level’s: it is the number that decides whether a record is emitted, which is the question a call site asks. There is deliberately no accessor for “is a level set on this logger”, because nothing in the tree has a use for the difference and an accessor per language is three more names for one fact.

Source

pub fn parent(&self) -> Option<&'static Logger>

The nearest ancestor this logger resolves an unset level through, if any (RFC-0102 D2). None for a root, and for every logger not yet published to the intern table.

Source

pub fn is_node_logger(&self) -> bool

Whether a node’s accessor resolved this logger (RFC-0102 D4).

Source

pub fn set_level(&self, level: Severity)

Give this logger a level of its own. It stops following Logger::default_level until Logger::unset_level.

Source

pub fn unset_level(&self)

Take this logger’s own level away, so it follows Logger::default_level again.

rcutils_logging_set_logger_level(name, RCUTILS_LOG_SEVERITY_UNSET)’s effect, and what rclc_logger_set_level(logger, RCLC_LOG_SEVERITY_UNSET) reaches. phase-467.

rclrs spells this logger.set_level(LogSeverity::Unset). Here it is a verb, because Severity has no Unset: its values are the sink ABI.

Source

pub fn default_level() -> Severity

The threshold every logger with no level of its own filters on.

Severity::Info until Logger::set_default_level moves it, which is RCUTILS_DEFAULT_LOGGER_DEFAULT_LEVEL’s value and upstream’s starting point too.

Source

pub fn set_default_level(level: Severity)

Move the threshold that every logger WITHOUT a level of its own filters on — rclrs::Logger::set_default_level.

Takes effect immediately and retroactively: a static LOGGER: Logger = Logger::new("x") that was compiled into the image long before this call follows it, because “no level of its own” is a stored state and not a value copied in at construction. That is the whole reason this is a capability rather than a re-export of DEFAULT_LOGGER.set_level, which moves the threshold of the ONE logger named "nros" (ledger row rust:Logger::set_default_level).

It does NOT override a logger that states its own level: after with_level(.., Warn) or set_level(Warn), Warn wins.

Takes Severity, not rclrs’s LogSeverity: it has a Trace level rclrs lacks and no Unset. Where rclrs passes Unset to restore the default default, pass Severity::Info, which is what Unset restores upstream.

Source

pub fn is_enabled(&self, severity: Severity) -> bool

Whether a record at severity would be emitted by this logger AT RUNTIME.

THE HOT PATH of every log macro, on every target, so the shape here is measured rather than chosen (phase-467; the figures below are for the two-level own-or-default form that preceded RFC-0102’s ancestor walk). Resolving the unset level SELECTS the byte to compare against and compares once, instead of comparing twice and short-circuiting: measured at an inlined call site with rustc -O, the select form is 13 instructions on thumbv7m-none-eabi and branch-free (LLVM predicates the whole fallback into one ittt eq block) against 16 with a branch for the short-circuit form, and 10 against 12 on x86_64. The cost over the single-load original (8 thumb / 4 x86_64 instructions) is one compare against an immediate, plus a byte load of one process-global that a logger with its own level never performs.

LEVEL_UNSET being ABOVE Fatal rather than below Trace is what keeps this correct if the select is ever reordered: severity >= u8::MAX is false for every Severity, so an unresolved sentinel can only ever suppress a record, never emit one at the most verbose level — which is the failure 0 had.

RFC-0102 D2 changed the unset arm: it now walks the parent chain before the process default. A logger WITH its own level still costs one load and one compare; an unset root pays one extra pointer load; each ancestor without a level of its own costs one more. No string work, on any arm.

The image’s compile-time CEILING (NROS_LOG_MAX_LEVEL, issue 1037) is applied here too, first: a runtime level below it is unreachable. That is what brings the C and C++ surfaces under the same bound — their macros format on the C side and reach the facade through this check (rclc_log_emit_at, nros_log_is_enabled), so the ceiling the Rust macros fold away at compile time filters theirs at the FFI boundary.

Source

pub fn is_enabled_throttled( &self, severity: Severity, state: &ThrottleState, now_ns: u64, interval_ns: u64, ) -> bool

Whether a record at severity would be emitted, given both this logger’s runtime threshold AND a per-call-site throttle window.

phase-417 W4.d. The order is load-bearing and is the same order rclcpp uses: the SEVERITY test runs first, so a record the level already filters does not consume the throttle window. Reversing them makes a raised-then-lowered log level lose its first record for a whole interval, which is the shape of bug nobody reports because it looks like the throttle working.

Consumes the window when it returns true, so call it exactly once per candidate record — the nros_*_throttle! macros do.

now_ns is the caller’s monotonic clock; the macros pass [__timestamp_ns]. Taking it as an argument rather than reading the clock here is what lets the whole rule be tested without a platform port, and is the same shape rclcpp::RCLCPP_*_THROTTLE uses when it asks for a Clock.

Source

pub fn dispatch(&self, record: &Record<'_>)

Hand record to every registered sink, after the runtime threshold check.

Macros call this; user code should not.

Source§

impl Logger

Source

pub fn create_child( &'static self, child_name: impl Borrow<str>, ) -> Result<&'static Logger, ChildError>

A logger that is a descendant of this one — rclrs’s create_child, rclcpp’s get_child (RFC-0102 D1).

Named "{self}.{child_name}", except that the catch-all (DEFAULT_LOGGER) has top-level children: its child is named child_name, as rclrs does for its empty-named default logger.

A child with no level of its own filters at its nearest ancestor’s (rcutils’s rule, RFC-0102 D2), so parent.set_level(Debug) reaches it. Idempotent: the same name returns the same logger, so a call inside a log statement costs one intern-table lookup.

Takes &'static self because the child links to its parent by reference. self is published to the intern table first if it was not, so a static parent nobody registered still passes its level on.

§Errors

ChildError when the full name is over MAX_LOGGER_NAME_LEN or empty, or no slot is left. The name is built in a stack buffer; nothing is allocated.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.