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}