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}