use alloc::string::String; use alloc::vec::Vec; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex as BlockingMutex; use log::{LevelFilter, Log, Metadata, Record}; const MAX_LIVE_LOG_ENTRIES: usize = 64; struct LiveLogBuffer { entries: Vec<(u64, String)>, next_seq: u64, } impl LiveLogBuffer { const fn new() -> Self { Self { entries: Vec::new(), next_seq: 0, } } fn push(&mut self, text: String) { if self.entries.len() >= MAX_LIVE_LOG_ENTRIES { self.entries.remove(0); } self.entries.push((self.next_seq, text)); self.next_seq += 1; } fn get_after(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) { let next_seq = self.next_seq; match after { None => (self.entries.clone(), false, next_seq), Some(after_seq) => { let result: Vec<_> = self.entries .iter() .filter(|(seq, _)| *seq > after_seq) .cloned() .collect(); // Dropped if there are entries that should exist (seq > after_seq) but // the oldest retained entry has a higher seq than after_seq + 1. let dropped = if next_seq > after_seq.saturating_add(1) { if let Some((oldest_seq, _)) = self.entries.first() { *oldest_seq > after_seq.saturating_add(1) } else { // Buffer empty but entries were written — all dropped true } } else { false }; (result, dropped, next_seq) } } } } pub struct InterceptorLogger { live_log: BlockingMutex>, } impl InterceptorLogger { pub const fn new() -> Self { Self { live_log: BlockingMutex::new(core::cell::RefCell::new(LiveLogBuffer::new())), } } /// Returns (entries_after, dropped, next_seq). /// Pass `after = None` to retrieve the entire current buffer. /// Pass `after = Some(seq)` to retrieve only entries with seq > that value. pub fn get_live_logs(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) { self.live_log.lock(|buf| buf.borrow().get_after(after)) } pub fn init(&'static self) { match log::set_logger(self).map(|()| log::set_max_level(LevelFilter::Info)) { Ok(()) => {} Err(_e) => { esp_println::println!("ERROR: Logger already set"); } } } } impl Log for InterceptorLogger { fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= log::Level::Info } fn log(&self, record: &Record) { if self.enabled(record.metadata()) { let message = alloc::format!("{}: {}", record.level(), record.args()); // Print to serial esp_println::println!("{}", message); // Store in live log ring buffer self.live_log.lock(|buf| { buf.borrow_mut().push(message); }); } } fn flush(&self) {} }