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