Skip to main content

os_dev_toolkit/
log.rs

1//! Minimal logging primitives for `no_std` kernels.
2//!
3//! This module is intentionally small:
4//!
5//! - You provide a [`crate::log::LogSink`] that decides *where bytes go* (serial/VGA/hypervisor console/etc.).
6//! - This crate provides a [`crate::log::Logger`] that decides *how bytes are formatted* and implements
7//!   level filtering.
8//! - All APIs are allocation-free and usable in early boot.
9//!
10//! ## Typical usage
11//!
12//! ```rust
13//! use os_dev_toolkit::log::{Level, LogSink, Logger};
14//!
15//! struct Sink;
16//! impl LogSink for Sink {
17//!     fn write_str(&mut self, s: &str) {
18//!         let _ = s;
19//!     }
20//! }
21//!
22//! fn demo() {
23//!     let mut sink = Sink;
24//!     let mut logger = Logger::new(&mut sink, Level::Info);
25//!     os_dev_toolkit::kinfo!(logger, "boot ok");
26//!     os_dev_toolkit::kdebug!(logger, "this will be filtered out");
27//! }
28//! ```
29
30use core::fmt;
31use core::fmt::Write;
32
33use crate::buffer::RingBuffer;
34
35#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
36/// Log level used by [`Logger`] for filtering.
37///
38/// Ordering is from most important to least important:
39///
40/// - [`Level::Error`] is the highest priority.
41/// - [`Level::Trace`] is the lowest priority.
42///
43/// A [`Logger`] configured with a given level will accept any message whose level is
44/// `<=` its configured level.
45pub enum Level {
46    /// Something is wrong and likely requires immediate attention.
47    Error,
48    /// Something unexpected happened, but execution may continue.
49    Warn,
50    /// High-level informational messages (boot progress, state transitions).
51    Info,
52    /// Debug-level messages (may be verbose).
53    Debug,
54    /// Extremely verbose tracing.
55    Trace,
56}
57
58/// A minimal sink for kernel/OS logging.
59///
60/// The sink decides where bytes go (serial, VGA, hypervisor console, ring buffer, etc.).
61///
62/// ## Contract
63///
64/// - `write_str` is expected to be *best-effort* and should not panic.
65/// - Implementations should handle being called many times with small fragments.
66/// - The default [`LogSink::flush`] is a no-op; override it if your device benefits from it.
67pub trait LogSink {
68    /// Writes a string fragment to the output device.
69    fn write_str(&mut self, s: &str);
70    /// Flushes buffered output if applicable.
71    fn flush(&mut self) {}
72}
73
74/// A fixed-capacity in-memory log sink.
75///
76/// This is useful when you don't have a device early during boot, or when you want to keep the
77/// last `N` bytes of logs for later inspection.
78///
79/// Internally it uses [`RingBuffer`], so it has **overwrite-on-full** semantics.
80pub struct RingLog<const N: usize> {
81    buf: RingBuffer<N>,
82}
83
84impl<const N: usize> Default for RingLog<N> {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90impl<const N: usize> RingLog<N> {
91    /// Creates an empty ring log.
92    ///
93    /// If `N == 0`, all writes become no-ops and `as_slices()` returns empty slices.
94    pub const fn new() -> Self {
95        Self {
96            buf: RingBuffer::new(),
97        }
98    }
99
100    /// Clears the stored bytes.
101    pub fn clear(&mut self) {
102        self.buf.clear();
103    }
104
105    /// Returns the current content as two slices.
106    ///
107    /// Because this is a ring buffer, the data may wrap. The returned slices represent the
108    /// content in logical FIFO order (`slice0` then `slice1`).
109    pub fn as_slices(&self) -> (&[u8], &[u8]) {
110        self.buf.as_slices()
111    }
112
113    /// Pushes a single byte into the ring.
114    ///
115    /// This can be used by device drivers that already stream bytes.
116    pub fn push_byte(&mut self, b: u8) {
117        self.buf.push(b);
118    }
119}
120
121impl<const N: usize> LogSink for RingLog<N> {
122    fn write_str(&mut self, s: &str) {
123        for &b in s.as_bytes() {
124            self.buf.push(b);
125        }
126    }
127}
128
129/// A tiny logger with level filtering.
130///
131/// `Logger` formats each line as:
132///
133/// ```text
134/// [Level] message\n
135/// ```
136///
137/// It is intentionally synchronous and allocation-free.
138pub struct Logger<'a, S: LogSink> {
139    sink: &'a mut S,
140    level: Level,
141}
142
143struct SinkWriter<'a, S: LogSink>(&'a mut S);
144
145impl<S: LogSink> fmt::Write for SinkWriter<'_, S> {
146    fn write_str(&mut self, s: &str) -> fmt::Result {
147        self.0.write_str(s);
148        Ok(())
149    }
150}
151
152impl<'a, S: LogSink> Logger<'a, S> {
153    /// Creates a new logger writing to `sink` and filtering at `level`.
154    pub fn new(sink: &'a mut S, level: Level) -> Self {
155        Self { sink, level }
156    }
157
158    /// Returns the current configured level.
159    pub fn level(&self) -> Level {
160        self.level
161    }
162
163    /// Returns a mutable reference to the underlying sink.
164    ///
165    /// This is useful when you need to flush or reconfigure the device.
166    pub fn sink_mut(&mut self) -> &mut S {
167        self.sink
168    }
169
170    /// Returns `true` if messages of `level` would be emitted.
171    pub fn enabled(&self, level: Level) -> bool {
172        level <= self.level
173    }
174
175    /// Writes a log line with the given level and pre-formatted arguments.
176    ///
177    /// This is the core routine used by the `k*` macros.
178    pub fn log(&mut self, level: Level, args: fmt::Arguments<'_>) {
179        if !self.enabled(level) {
180            return;
181        }
182
183        let mut w = SinkWriter(self.sink);
184        let _ = write!(w, "[{:?}] ", level);
185        let _ = w.write_fmt(args);
186        let _ = w.write_str("\n");
187    }
188}
189
190#[macro_export]
191/// Emits an error-level log line.
192macro_rules! kerror {
193    ($logger:expr, $($arg:tt)*) => {{
194        $logger.log($crate::log::Level::Error, core::format_args!($($arg)*));
195    }};
196}
197
198#[macro_export]
199/// Emits a warning-level log line.
200macro_rules! kwarn {
201    ($logger:expr, $($arg:tt)*) => {{
202        $logger.log($crate::log::Level::Warn, core::format_args!($($arg)*));
203    }};
204}
205
206#[macro_export]
207/// Emits an info-level log line.
208macro_rules! kinfo {
209    ($logger:expr, $($arg:tt)*) => {{
210        $logger.log($crate::log::Level::Info, core::format_args!($($arg)*));
211    }};
212}
213
214#[macro_export]
215/// Emits a debug-level log line.
216macro_rules! kdebug {
217    ($logger:expr, $($arg:tt)*) => {{
218        $logger.log($crate::log::Level::Debug, core::format_args!($($arg)*));
219    }};
220}
221
222#[macro_export]
223/// Emits a trace-level log line.
224macro_rules! ktrace {
225    ($logger:expr, $($arg:tt)*) => {{
226        $logger.log($crate::log::Level::Trace, core::format_args!($($arg)*));
227    }};
228}