Expand description
Deferred-formatting logging: log! is safe to call from
ISR context (it does no formatting, no allocation, and never blocks —
it just pushes a {level, task_id, timestamp, message} frame into a
ring buffer), and a drain task formats and writes frames to the
console at its own pace, off the hot path.
§Scope note (plan.md Phase 8, extended by Phase 16)
The old plan (§6.5) called for interning format strings into a
.rivet_log_fmt linker section, storing only a small integer index per
frame, and decoding on the host from the ELF’s debug info (a
rivet-decode crate). This module takes a simpler route that still
delivers the properties that actually matter (ISR-safe, O(1) in the
hot path, deferred formatting, lock-free ring buffer, dropped-frame
accounting): a frame stores the message as a plain &'static str
pointer + length, plus (Phase 16) up to two LogArg values —
not a full format_args!-style template (that needs the
interned-format-string + host-decoder machinery the old plan
described, still not attempted here). log!("x={}", x) covers the
large majority of real call sites, which log one or two values
alongside a fixed message; write_frame substitutes each {} in
msg with the corresponding argument, formatted at drain time (off
the hot path, same as everything else here).
The ring buffer is Rivet’s own SPSC crate::sync::Channel — but
logging is inherently multi-producer (any task or ISR might log,
on any hart), so every producer path goes through
crate::critical::enter to serialize pushes into a single logical
producer. Since plan.md Phase 19, critical::enter is a genuine
cross-hart lock (not just a local interrupt mask), so this holds under
real SMP too, not only the single-hart case.
Structs§
Enums§
- Level
- LogArg
- A single interpolated argument (plan.md Phase 16): a small closed set
covering the large majority of real log call sites, not a general
Display/Debugpayload (which would need real formatting work done eagerly, defeating the point of deferring it to drain time).
Functions§
- drain_
forever - A ready-made drain loop:
.awaits new frames and writes them to the console as they arrive. Spawn this as a low-priority#[rivet::task]if you want logging without writing your own drain loop. - drain_
one - Drain and format one pending frame. Returns
falseif the ring was empty. Call this in a loop from a low-priority task to flush the log (seedrain_foreverfor a ready-made one). - dropped_
frames - Number of frames dropped so far because the ring was full (or logging
hadn’t been initialized yet — i.e. called before
crate::init).