Skip to main content

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