Skip to main content

ax_log/
lib.rs

1//! Macros for multi-level formatted logging used by
2//! [ArceOS](https://github.com/arceos-org/arceos).
3//!
4//! The log macros, in descending order of level, are: [`error!`], [`warn!`],
5//! [`info!`], [`debug!`], and [`trace!`].
6//!
7//! If it is used in `no_std` environment, the users need to implement the
8//! [`LogIf`] to provide external functions such as console output.
9//!
10//! To use in the `std` environment, please enable the `std` feature:
11//!
12//! ```toml
13//! [dependencies]
14//! ax-log = { version = "0.1", features = ["std"] }
15//! ```
16//!
17//! # Cargo features:
18//!
19//! - `std`: Use in the `std` environment. If it is enabled, you can use console
20//!   output without implementing the [`LogIf`] trait. This is disabled by default.
21//!
22//! # Examples
23//!
24//! ```
25//! # #[cfg(feature = "std")]
26//! # {
27//! use ax_log::{debug, error, info, trace, warn};
28//!
29//! // Initialize the logger.
30//! ax_log::init();
31//! // Set the maximum log level to `info`.
32//! ax_log::set_max_level("info");
33//!
34//! // The following logs will be printed.
35//! error!("error");
36//! warn!("warn");
37//! info!("info");
38//!
39//! // The following logs will not be printed.
40//! debug!("debug");
41//! trace!("trace");
42//! # }
43//! ```
44
45#![cfg_attr(not(feature = "std"), no_std)]
46
47extern crate log;
48
49use core::{
50    fmt::{self, Write},
51    str::FromStr,
52};
53
54#[cfg(not(feature = "std"))]
55use ax_crate_interface::call_interface;
56use log::{Level, LevelFilter, Log, Metadata, Record};
57pub use log::{debug, error, info, trace, warn};
58
59/// Prints to the console.
60///
61/// Equivalent to the [`ax_println!`] macro except that a newline is not printed at
62/// the end of the message.
63#[macro_export]
64macro_rules! ax_print {
65    ($($arg:tt)*) => {
66        $crate::__print_impl(format_args!($($arg)*));
67    }
68}
69
70/// Prints to the console, with a newline.
71#[macro_export]
72macro_rules! ax_println {
73    () => { $crate::ax_print!("\n") };
74    ($($arg:tt)*) => {
75        $crate::__print_impl(format_args!("{}\n", format_args!($($arg)*)));
76    }
77}
78
79macro_rules! with_color {
80    ($color_code:expr, $($arg:tt)*) => {
81        format_args!("\u{1B}[{}m{}\u{1B}[m", $color_code as u8, format_args!($($arg)*))
82    };
83}
84
85#[repr(u8)]
86#[allow(dead_code)]
87enum ColorCode {
88    Black         = 30,
89    Red           = 31,
90    Green         = 32,
91    Yellow        = 33,
92    Blue          = 34,
93    Magenta       = 35,
94    Cyan          = 36,
95    White         = 37,
96    BrightBlack   = 90,
97    BrightRed     = 91,
98    BrightGreen   = 92,
99    BrightYellow  = 93,
100    BrightBlue    = 94,
101    BrightMagenta = 95,
102    BrightCyan    = 96,
103    BrightWhite   = 97,
104}
105
106/// Extern interfaces that must be implemented in other crates.
107#[ax_crate_interface::def_interface]
108pub trait LogIf {
109    /// Writes a string to the console.
110    fn console_write_str(s: &str);
111
112    /// Gets current clock time.
113    fn current_time() -> core::time::Duration;
114
115    /// Gets current CPU ID.
116    ///
117    /// Returns [`None`] if you don't want to show the CPU ID in the log.
118    fn current_cpu_id() -> Option<usize>;
119
120    /// Gets current task ID.
121    ///
122    /// Returns [`None`] if you don't want to show the task ID in the log.
123    fn current_task_id() -> Option<u64>;
124}
125
126struct Logger;
127
128impl Write for Logger {
129    fn write_str(&mut self, s: &str) -> fmt::Result {
130        cfg_if::cfg_if! {
131            if #[cfg(feature = "std")] {
132                std::print!("{s}");
133            } else {
134                call_interface!(LogIf::console_write_str, s);
135            }
136        }
137        Ok(())
138    }
139}
140
141impl Log for Logger {
142    #[inline]
143    fn enabled(&self, _metadata: &Metadata) -> bool {
144        true
145    }
146
147    fn log(&self, record: &Record) {
148        if !self.enabled(record.metadata()) {
149            return;
150        }
151
152        let level = record.level();
153        let line = record.line().unwrap_or(0);
154        let path = record.target();
155        let args_color = match level {
156            Level::Error => ColorCode::Red,
157            Level::Warn => ColorCode::Yellow,
158            Level::Info => ColorCode::Green,
159            Level::Debug => ColorCode::Cyan,
160            Level::Trace => ColorCode::BrightBlack,
161        };
162
163        cfg_if::cfg_if! {
164            if #[cfg(feature = "std")] {
165                __print_impl(with_color!(
166                    ColorCode::White,
167                    "[{time} {path}:{line}] {args}\n",
168                    time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.6f"),
169                    path = path,
170                    line = line,
171                    args = with_color!(args_color, "{}", record.args()),
172                ));
173            } else {
174                let cpu_id = call_interface!(LogIf::current_cpu_id);
175                let tid = call_interface!(LogIf::current_task_id);
176                let now = call_interface!(LogIf::current_time);
177                if let Some(cpu_id) = cpu_id {
178                    if let Some(tid) = tid {
179                        // show CPU ID and task ID
180                        __print_impl(with_color!(
181                            ColorCode::White,
182                            "[{:>3}.{:06} {cpu_id}:{tid} {path}:{line}] {args}\n",
183                            now.as_secs(),
184                            now.subsec_micros(),
185                            cpu_id = cpu_id,
186                            tid = tid,
187                            path = path,
188                            line = line,
189                            args = with_color!(args_color, "{}", record.args()),
190                        ));
191                    } else {
192                        // show CPU ID only
193                        __print_impl(with_color!(
194                            ColorCode::White,
195                            "[{:>3}.{:06} {cpu_id} {path}:{line}] {args}\n",
196                            now.as_secs(),
197                            now.subsec_micros(),
198                            cpu_id = cpu_id,
199                            path = path,
200                            line = line,
201                            args = with_color!(args_color, "{}", record.args()),
202                        ));
203                    }
204                } else {
205                    // neither CPU ID nor task ID is shown
206                    __print_impl(with_color!(
207                        ColorCode::White,
208                        "[{:>3}.{:06} {path}:{line}] {args}\n",
209                        now.as_secs(),
210                        now.subsec_micros(),
211                        path = path,
212                        line = line,
213                        args = with_color!(args_color, "{}", record.args()),
214                    ));
215                }
216            }
217        }
218    }
219
220    fn flush(&self) {}
221}
222
223/// Prints the formatted string to the console.
224pub fn print_fmt(args: fmt::Arguments) -> fmt::Result {
225    use ax_kspin::SpinNoIrq; // TODO: more efficient
226    static LOCK: SpinNoIrq<()> = SpinNoIrq::new(());
227
228    // Panic and oops paths must not re-enter the normal print lock because its
229    // unlock path may restore preemption/IRQs and trigger more complex control
230    // flow while the kernel is already failing.
231    if axpanic::oops_in_progress() {
232        return Logger.write_fmt(args);
233    }
234
235    let _guard = LOCK.lock();
236    Logger.write_fmt(args)
237}
238
239#[doc(hidden)]
240pub fn __print_impl(args: fmt::Arguments) {
241    print_fmt(args).unwrap();
242}
243
244/// Initializes the logger.
245///
246/// This function should be called before any log macros are used, otherwise
247/// nothing will be printed.
248pub fn init() {
249    log::set_logger(&Logger).unwrap();
250    log::set_max_level(LevelFilter::Warn);
251}
252
253/// Set the maximum log level.
254///
255/// Unlike the features such as `log-level-error`, setting the logging level in
256/// this way incurs runtime overhead. In addition, this function is no effect
257/// when those features are enabled.
258///
259/// `level` should be one of `off`, `error`, `warn`, `info`, `debug`, `trace`.
260pub fn set_max_level(level: &str) {
261    let lf = LevelFilter::from_str(level)
262        .ok()
263        .unwrap_or(LevelFilter::Off);
264    log::set_max_level(lf);
265}