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}