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}