ostd/log/
logger.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Logger backend trait and global state.
4
5use core::{
6    fmt,
7    sync::atomic::{AtomicU8, Ordering},
8};
9
10use spin::Once;
11
12use super::{Level, LevelFilter, bridge::sync_log_crate_max_level};
13
14static LOGGER: Once<&'static dyn Log> = Once::new();
15
16/// Registers the global logger backend.
17///
18/// The function may be called only once; subsequent calls take no effect.
19pub fn inject_logger(logger: &'static dyn Log) {
20    LOGGER.call_once(|| logger);
21}
22
23/// Returns the registered logger, if any.
24#[inline]
25pub(super) fn __logger() -> Option<&'static dyn Log> {
26    LOGGER.get().copied()
27}
28
29/// Writes a log record to the registered logger,
30/// or falls back to early console output
31/// if no logger has been registered yet.
32///
33/// Called by the `log!` macro. Not intended for direct use.
34#[doc(hidden)]
35pub fn __write_log_record(record: &Record) {
36    if let Some(logger) = __logger() {
37        logger.log(record);
38    } else {
39        crate::console::early_print(format_args!(
40            "{}: {}{}\n",
41            record.level(),
42            record.prefix(),
43            record.args()
44        ));
45    }
46}
47
48/// The logger backend trait.
49///
50/// Implement this trait and register it with [`inject_logger()`] to receive log
51/// records from the OSTD logging macros.
52///
53/// # Implementation guidelines
54///
55/// The logging macros can be called from **any context**: interrupt handlers,
56/// early boot, OOM handlers, or panic handlers. An implementation should
57/// be designed to work correctly in all of these contexts. In practice:
58///
59/// - **The ring buffer write must be heapless and lock-free (or IRQ-safe).**
60///   The part of `log()` that records the message must not allocate from the
61///   heap and must use either a lock-free data structure or an IRQ-disabled
62///   spinlock, so that it is safe from any context.
63///
64/// - **Console flushing is best-effort.** After recording the message,
65///   the implementation may attempt to flush pending messages to console
66///   devices synchronously. In contended or non-blockable contexts
67///   (e.g., scheduler code), the implementation should skip or defer
68///   flushing rather than blocking.
69///
70/// - **The implementation should be short.** Long-running work can stall the
71///   calling CPU. Implementations should bound the work per `log()` call.
72pub trait Log: Sync + Send {
73    /// Logs a record.
74    ///
75    /// The caller (the `log!` macro) has already verified that the record's
76    /// level passes both the compile-time and runtime level filters. The
77    /// backend does not need to re-check the level.
78    fn log(&self, record: &Record);
79}
80
81/// A single log record carrying level, message, and source location.
82///
83/// Records are created by the logging macros
84/// and passed to the [`Log`] backend.
85/// They are transient —
86/// the backend must consume all data during the `log()` call.
87pub struct Record<'a> {
88    level: Level,
89    prefix: &'static str,
90    args: fmt::Arguments<'a>,
91    module_path: &'static str,
92    file: &'static str,
93    line: u32,
94}
95
96impl<'a> Record<'a> {
97    /// Creates a new record. Called by the logging macros.
98    #[doc(hidden)]
99    #[inline]
100    pub fn new(
101        level: Level,
102        prefix: &'static str,
103        args: fmt::Arguments<'a>,
104        module_path: &'static str,
105        file: &'static str,
106        line: u32,
107    ) -> Self {
108        Self {
109            level,
110            prefix,
111            args,
112            module_path,
113            file,
114            line,
115        }
116    }
117
118    /// Returns the log level.
119    pub fn level(&self) -> Level {
120        self.level
121    }
122
123    /// Returns the per-module log prefix (may be empty).
124    pub fn prefix(&self) -> &'static str {
125        self.prefix
126    }
127
128    /// Returns the formatted message arguments.
129    pub fn args(&self) -> &fmt::Arguments<'a> {
130        &self.args
131    }
132
133    /// Returns the full module path where the log call originated.
134    pub fn module_path(&self) -> &'static str {
135        self.module_path
136    }
137
138    /// Returns the source file path.
139    pub fn file(&self) -> &'static str {
140        self.file
141    }
142
143    /// Returns the source line number.
144    pub fn line(&self) -> u32 {
145        self.line
146    }
147}
148
149// -- Maximum log level --
150
151/// Compile-time maximum log level.
152// TODO: Add cargo features (e.g., `log_max_level_info`) to
153// set `STATIC_MAX_LEVEL` at compile time
154// so that log calls above the chosen level are eliminated entirely.
155// The same feature should activate the corresponding `log` crate feature
156// (e.g., `log/max_level_info`)
157// so that both OSTD macros and third-party `log::info!()` calls are filtered uniformly.
158pub const STATIC_MAX_LEVEL: LevelFilter = LevelFilter::Debug;
159
160/// Run-time maximum log level.
161static DYNAMIC_MAX_LEVEL: AtomicU8 = {
162    // By default, the run-runtime max log level is off,
163    // which can be overridden
164    AtomicU8::new(LevelFilter::Off as u8)
165};
166
167/// Sets the runtime maximum log level.
168///
169/// If the given `filter` argument is greater than [`STATIC_MAX_LEVEL`],
170/// then the runtime maximum log level is set to `STATIC_MAX_LEVEL`.
171///
172/// This function also updates the `log` crate's max level
173/// so that third-party crates using `log::info!()` etc. are filtered consistently.
174///
175/// # Requirements
176///
177/// This function is intended to be called sequentially:
178/// concurrent calls to this function may cause the maximum levels
179/// kept by OSTD and `log` to diverge.
180pub fn set_max_level(mut filter: LevelFilter) {
181    if filter > STATIC_MAX_LEVEL {
182        filter = STATIC_MAX_LEVEL;
183    }
184
185    DYNAMIC_MAX_LEVEL.store(filter as u8, Ordering::Relaxed);
186    sync_log_crate_max_level(filter);
187}
188
189/// Returns the current runtime maximum log level.
190#[inline]
191pub fn max_level() -> LevelFilter {
192    LevelFilter::from_u8(DYNAMIC_MAX_LEVEL.load(Ordering::Relaxed))
193}