1#![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#[macro_export]
64macro_rules! ax_print {
65 ($($arg:tt)*) => {
66 $crate::__print_impl(format_args!($($arg)*));
67 }
68}
69
70#[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#[ax_crate_interface::def_interface]
108pub trait LogIf {
109 fn console_write_str(s: &str);
111
112 fn current_time() -> core::time::Duration;
114
115 fn current_cpu_id() -> Option<usize>;
119
120 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
141const LOG_BUFFER_SIZE: usize = 2048;
142const LOG_TRUNCATED_WITH_NEWLINE: &str = "\u{1B}[m<log truncated>\n";
143
144struct LogBuffer<const N: usize> {
145 buf: [u8; N],
146 len: usize,
147 truncated: bool,
148}
149
150impl<const N: usize> LogBuffer<N> {
151 const fn new() -> Self {
152 Self {
153 buf: [0; N],
154 len: 0,
155 truncated: false,
156 }
157 }
158
159 fn as_str(&self) -> &str {
160 unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
163 }
164
165 fn append_truncation_marker(&mut self) {
166 if !self.truncated {
167 return;
168 }
169
170 let marker = LOG_TRUNCATED_WITH_NEWLINE.as_bytes();
171 if N < marker.len() {
172 self.len = 0;
173 return;
174 }
175
176 if self.len + marker.len() > N {
177 let mut keep = self.len.min(N - marker.len());
178 while !self.is_char_boundary(keep) {
179 keep -= 1;
180 }
181 self.len = keep;
182 }
183
184 self.buf[self.len..self.len + marker.len()].copy_from_slice(marker);
185 self.len += marker.len();
186 }
187
188 fn is_char_boundary(&self, index: usize) -> bool {
189 index == 0 || index == self.len || (self.buf[index] & 0b1100_0000) != 0b1000_0000
190 }
191}
192
193impl<const N: usize> Write for LogBuffer<N> {
194 fn write_str(&mut self, s: &str) -> fmt::Result {
195 let available = N.saturating_sub(self.len);
196 if s.len() <= available {
197 self.buf[self.len..self.len + s.len()].copy_from_slice(s.as_bytes());
198 self.len += s.len();
199 return Ok(());
200 }
201
202 self.truncated = true;
203 let end = s
204 .char_indices()
205 .map(|(index, _)| index)
206 .take_while(|&index| index <= available)
207 .last()
208 .unwrap_or(0);
209 self.buf[self.len..self.len + end].copy_from_slice(&s.as_bytes()[..end]);
210 self.len += end;
211 Ok(())
212 }
213}
214
215impl Log for Logger {
216 #[inline]
217 fn enabled(&self, _metadata: &Metadata) -> bool {
218 true
219 }
220
221 fn log(&self, record: &Record) {
222 if !self.enabled(record.metadata()) {
223 return;
224 }
225
226 let level = record.level();
227 let line = record.line().unwrap_or(0);
228 let path = record.target();
229 let args_color = match level {
230 Level::Error => ColorCode::Red,
231 Level::Warn => ColorCode::Yellow,
232 Level::Info => ColorCode::Green,
233 Level::Debug => ColorCode::Cyan,
234 Level::Trace => ColorCode::BrightBlack,
235 };
236
237 cfg_if::cfg_if! {
238 if #[cfg(feature = "std")] {
239 print_log_fmt(with_color!(
240 ColorCode::White,
241 "[{time} {path}:{line}] {args}\n",
242 time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.6f"),
243 path = path,
244 line = line,
245 args = with_color!(args_color, "{}", record.args()),
246 ));
247 } else {
248 let cpu_id = call_interface!(LogIf::current_cpu_id);
249 let tid = call_interface!(LogIf::current_task_id);
250 let now = call_interface!(LogIf::current_time);
251 if let Some(cpu_id) = cpu_id {
252 if let Some(tid) = tid {
253 print_log_fmt(with_color!(
255 ColorCode::White,
256 "[{:>3}.{:06} {cpu_id}:{tid} {path}:{line}] {args}\n",
257 now.as_secs(),
258 now.subsec_micros(),
259 cpu_id = cpu_id,
260 tid = tid,
261 path = path,
262 line = line,
263 args = with_color!(args_color, "{}", record.args()),
264 ));
265 } else {
266 print_log_fmt(with_color!(
268 ColorCode::White,
269 "[{:>3}.{:06} {cpu_id} {path}:{line}] {args}\n",
270 now.as_secs(),
271 now.subsec_micros(),
272 cpu_id = cpu_id,
273 path = path,
274 line = line,
275 args = with_color!(args_color, "{}", record.args()),
276 ));
277 }
278 } else {
279 print_log_fmt(with_color!(
281 ColorCode::White,
282 "[{:>3}.{:06} {path}:{line}] {args}\n",
283 now.as_secs(),
284 now.subsec_micros(),
285 path = path,
286 line = line,
287 args = with_color!(args_color, "{}", record.args()),
288 ));
289 }
290 }
291 }
292 }
293
294 fn flush(&self) {}
295}
296
297fn write_fmt_locked(args: fmt::Arguments) -> fmt::Result {
298 use ax_kspin::SpinNoIrq; static LOCK: SpinNoIrq<()> = SpinNoIrq::new(());
300
301 if axpanic::oops_in_progress() {
305 return Logger.write_fmt(args);
306 }
307
308 let _guard = LOCK.lock();
309 Logger.write_fmt(args)
310}
311
312fn print_log_fmt(args: fmt::Arguments) {
313 if axpanic::oops_in_progress() {
314 Logger.write_fmt(args).unwrap();
315 return;
316 }
317
318 let mut buf = LogBuffer::<LOG_BUFFER_SIZE>::new();
323 buf.write_fmt(args).unwrap();
324 buf.append_truncation_marker();
325 write_fmt_locked(format_args!("{}", buf.as_str())).unwrap();
326}
327
328pub fn print_fmt(args: fmt::Arguments) -> fmt::Result {
330 write_fmt_locked(args)
334}
335
336#[doc(hidden)]
337pub fn __print_impl(args: fmt::Arguments) {
338 print_fmt(args).unwrap();
339}
340
341pub fn init() {
346 log::set_logger(&Logger).unwrap();
347 log::set_max_level(LevelFilter::Warn);
348}
349
350pub fn set_max_level(level: &str) {
358 let lf = LevelFilter::from_str(level)
359 .ok()
360 .unwrap_or(LevelFilter::Off);
361 log::set_max_level(lf);
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[test]
369 fn log_buffer_truncates_at_utf8_boundary() {
370 let mut buf = LogBuffer::<22>::new();
371 write!(buf, "ab你好xxxxxxxxxxxxxxxx\n").unwrap();
372 buf.append_truncation_marker();
373
374 assert_eq!(buf.as_str(), "ab\u{1B}[m<log truncated>\n");
375 }
376
377 #[test]
378 fn log_buffer_keeps_untruncated_message() {
379 let mut buf = LogBuffer::<32>::new();
380 write!(buf, "short").unwrap();
381 buf.append_truncation_marker();
382
383 assert_eq!(buf.as_str(), "short");
384 }
385}