Skip to main content

Module log

Module log 

Source
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§

LogFrame

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/Debug payload (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 false if the ring was empty. Call this in a loop from a low-priority task to flush the log (see drain_forever for 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).