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}