Skip to main content

reovim_kernel/printk/
logger.rs

1//! Logger trait and global logger storage.
2//!
3//! Linux equivalent: `struct console` and `register_console()` in `kernel/printk/`
4//!
5//! This module defines the `Logger` trait that drivers implement to provide
6//! actual log output. The kernel only defines the interface (mechanism),
7//! while drivers implement the policy (where and how to log).
8
9use std::{error::Error, fmt, sync::OnceLock};
10
11use super::{level::Level, record::Record};
12
13/// Logger trait - the kernel mechanism for logging.
14///
15/// Drivers implement this trait to provide actual log output. The kernel
16/// only defines the interface, not the policy (where logs go, formatting, etc.).
17///
18/// # Thread Safety
19///
20/// Implementations must be `Send + Sync` as the logger is accessed from
21/// multiple threads concurrently. Implementations should be lock-free
22/// or use minimal locking to avoid blocking the caller.
23///
24/// # Example
25///
26/// ```
27/// use reovim_kernel::api::v1::*;
28///
29/// struct StderrLogger;
30///
31/// impl Logger for StderrLogger {
32///     fn log(&self, record: &Record) {
33///         eprintln!("[{}] {}: {}",
34///             record.level(),
35///             record.file(),
36///             record.message());
37///     }
38///
39///     fn flush(&self) {
40///         // stderr is unbuffered by default
41///     }
42///
43///     fn enabled(&self, level: Level) -> bool {
44///         level <= Level::Debug  // Log everything except Trace
45///     }
46/// }
47/// ```
48pub trait Logger: Send + Sync {
49    /// Log a record.
50    ///
51    /// This method should be fast and non-blocking. If the logger
52    /// buffers output, it should do so efficiently.
53    fn log(&self, record: &Record);
54
55    /// Flush any buffered output.
56    ///
57    /// Called to ensure all pending logs are written. May be called
58    /// before program exit or when immediate output is needed.
59    fn flush(&self);
60
61    /// Check if a level is enabled for logging.
62    ///
63    /// This method is called before formatting the log message,
64    /// allowing early exit if the level is not enabled. This avoids
65    /// the cost of string formatting when logging is disabled.
66    fn enabled(&self, level: Level) -> bool;
67}
68
69/// No-op logger used when no logger is set.
70///
71/// All operations are no-ops. `enabled()` returns `false` for all levels,
72/// causing the logging macros to skip message formatting entirely.
73///
74/// This is the default logger if `set_logger()` is never called.
75#[derive(Debug, Clone, Copy, Default)]
76pub struct NopLogger;
77
78impl Logger for NopLogger {
79    #[inline]
80    fn log(&self, _record: &Record) {
81        // No-op
82    }
83
84    #[inline]
85    fn flush(&self) {
86        // No-op
87    }
88
89    #[inline]
90    fn enabled(&self, _level: Level) -> bool {
91        false
92    }
93}
94
95/// Error returned when attempting to set the logger more than once.
96///
97/// The global logger can only be set once. This error is returned
98/// if `set_logger()` is called after a logger has already been set.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct SetLoggerError;
101
102// LLVM coverage artifact: unit struct Display impl closing brace marked DA:0
103// despite being exercised by test_set_logger_error_display.
104#[cfg_attr(coverage_nightly, coverage(off))]
105impl fmt::Display for SetLoggerError {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(f, "logger already set")
108    }
109}
110
111impl Error for SetLoggerError {}
112
113// =============================================================================
114// Global Logger State
115// =============================================================================
116
117/// Global logger storage.
118///
119/// Uses `OnceLock` for thread-safe one-time initialization.
120/// This is the only global state in the printk module.
121static LOGGER: OnceLock<&'static dyn Logger> = OnceLock::new();
122
123/// Static no-op logger instance used as default.
124static NOP_LOGGER: NopLogger = NopLogger;
125
126/// Sets the global logger.
127///
128/// This function can only be called once. Subsequent calls will
129/// return `Err(SetLoggerError)`.
130///
131/// # Errors
132///
133/// Returns `Err(SetLoggerError)` if a logger has already been set.
134/// The global logger can only be set once.
135///
136/// # Thread Safety
137///
138/// This function is thread-safe. If multiple threads call `set_logger()`
139/// concurrently, only one will succeed (and return `Ok`), while the
140/// others will return `Err(SetLoggerError)`.
141///
142/// # Example
143///
144/// ```
145/// use reovim_kernel::api::v1::*;
146///
147/// struct MyLogger;
148///
149/// impl Logger for MyLogger {
150///     fn log(&self, record: &Record) {
151///         eprintln!("{}", record.message());
152///     }
153///     fn flush(&self) {}
154///     fn enabled(&self, _level: Level) -> bool { true }
155/// }
156///
157/// static MY_LOGGER: MyLogger = MyLogger;
158///
159/// // First call succeeds
160/// // Note: This would succeed, but we can't actually run it in doctests
161/// // because other tests might have already set the logger.
162/// // assert!(set_logger(&MY_LOGGER).is_ok());
163/// ```
164#[cfg_attr(coverage_nightly, coverage(off))]
165pub fn set_logger(logger: &'static dyn Logger) -> Result<(), SetLoggerError> {
166    LOGGER.set(logger).map_err(|_| SetLoggerError)
167}
168
169/// Returns the global logger.
170///
171/// If no logger has been set via `set_logger()`, returns the no-op logger
172/// which silently discards all log messages.
173///
174/// # Example
175///
176/// ```
177/// use reovim_kernel::api::v1::*;
178///
179/// // Before set_logger() is called, returns NopLogger
180/// assert!(!logger().enabled(Level::Error));
181/// ```
182#[must_use]
183pub fn logger() -> &'static dyn Logger {
184    LOGGER.get().copied().unwrap_or(&NOP_LOGGER)
185}
186
187/// Internal helper for logging macros.
188///
189/// This function is called by the `pr_*` macros after the level check passes.
190/// It formats the message and passes it to the logger.
191///
192/// # Note
193///
194/// This function is marked `#[doc(hidden)]` because it's an implementation
195/// detail of the logging macros. Users should use the macros instead.
196#[doc(hidden)]
197pub fn __log(
198    level: Level,
199    module_path: &'static str,
200    file: &'static str,
201    line: u32,
202    args: fmt::Arguments,
203) {
204    // Format the message (this is the allocation we want to avoid
205    // when logging is disabled - hence the level check in macros)
206    let message = args.to_string();
207
208    let record = Record::builder(level)
209        .message(&message)
210        .module_path(module_path)
211        .file(file)
212        .line(line)
213        .build();
214
215    logger().log(&record);
216}
217
218/// Flushes the global logger.
219///
220/// Ensures all buffered log messages are written out. Useful before
221/// program exit or when immediate output is required.
222pub fn flush() {
223    logger().flush();
224}