#![allow(static_mut_refs)] use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex}; use esp_hal::time::Instant; use esp_println::println; use log::{LevelFilter, Metadata, Record}; // Provides a threadsafe serial logger pub struct RenderbugLogger { lock: Mutex, current_level: LevelFilter, default_level: LevelFilter } impl Default for RenderbugLogger { fn default() -> Self { Self { lock: Mutex::new(()), current_level: LevelFilter::Info, default_level: LevelFilter::Info } } } static mut LOGGER: Option = None; impl RenderbugLogger { pub fn init_logger() { #[cfg(feature = "rtt")] rtt_target::rtt_init_print!(rtt_target::ChannelMode::BlockIfFull); let default_level = match option_env!("LOG_LEVEL").unwrap_or("info").to_lowercase().as_str() { "debug" => LevelFilter::Debug, "warn" => LevelFilter::Warn, "trace" => LevelFilter::Trace, "error" => LevelFilter::Error, _ => LevelFilter::Info }; unsafe { LOGGER.replace(RenderbugLogger { default_level, current_level: default_level, ..Default::default() }) }; let logger = unsafe { LOGGER.as_mut().unwrap() }; unsafe { critical_section::with(|_| { log::set_logger_racy(logger).ok(); log::set_max_level_racy(default_level); }); }; } pub fn set_level(level: LevelFilter) { unsafe { critical_section::with(|_| { log::set_max_level_racy(level); LOGGER.as_mut().unwrap().current_level = level; }) } } pub fn reset_level() { unsafe { critical_section::with(|_| { let logger = LOGGER.as_mut().unwrap(); log::set_max_level_racy(logger.default_level); logger.current_level = logger.default_level; }) } } } impl log::Log for RenderbugLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() >= self.default_level } fn flush(&self) {} fn log(&self, record: &Record) { const RESET: &str = "\u{001B}[0m"; const RED: &str = "\u{001B}[31m"; const GREEN: &str = "\u{001B}[32m"; const YELLOW: &str = "\u{001B}[33m"; const BLUE: &str = "\u{001B}[34m"; const CYAN: &str = "\u{001B}[35m"; const GREY: &str = "\u{001B}[38;5;240m"; let color = match record.level() { log::Level::Error => RED, log::Level::Warn => YELLOW, log::Level::Info => GREEN, log::Level::Debug => BLUE, log::Level::Trace => CYAN, }; let filename = record.file().map_or("???", |f| {f}); let crate_name = record.module_path_static().unwrap(); let timestamp = Instant::now().duration_since_epoch().as_micros(); for enabled in option_env!("LOG_CRATES").unwrap_or("").split(",") { if crate_name.starts_with(enabled) { self.lock.lock(|_| { #[cfg(feature = "rtt")] rtt_target::rprintln!("{}{}\t{}{}:{}{}\t{}{}", color, record.level(), GREY, filename, record.line().map_or(0, |f| {f}), RESET, record.args(), RESET); println!("{color}{}\t{GREY}{timestamp}\t{} {filename}:{}{RESET}\t{}{RESET}", record.level(), record.module_path_static().unwrap(), record.line().map_or(0, |f| {f}), record.args()); }); return; } } } }