Add live log buffering support and endpoint; enhance log display functionality.
This commit is contained in:
@@ -2,42 +2,84 @@ use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_sync::blocking_mutex::Mutex as BlockingMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use log::{error, LevelFilter, Log, Metadata, Record};
|
||||
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<u64>) -> (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 {
|
||||
// Async mutex for start/stop capture from async context
|
||||
async_capture: Mutex<CriticalSectionRawMutex, ()>,
|
||||
// Blocking mutex for the actual data to be used in sync log()
|
||||
sync_capture: BlockingMutex<CriticalSectionRawMutex, core::cell::RefCell<Option<Vec<String>>>>,
|
||||
live_log: BlockingMutex<CriticalSectionRawMutex, core::cell::RefCell<LiveLogBuffer>>,
|
||||
}
|
||||
|
||||
impl InterceptorLogger {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
async_capture: Mutex::new(()),
|
||||
sync_capture: BlockingMutex::new(core::cell::RefCell::new(None)),
|
||||
live_log: BlockingMutex::new(core::cell::RefCell::new(LiveLogBuffer::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start_capture(&self) {
|
||||
let _guard = self.async_capture.lock().await;
|
||||
self.sync_capture.lock(|capture| {
|
||||
*capture.borrow_mut() = Some(Vec::new());
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn stop_capture(&self) -> Option<Vec<String>> {
|
||||
let _guard = self.async_capture.lock().await;
|
||||
self.sync_capture
|
||||
.lock(|capture| capture.borrow_mut().take())
|
||||
/// 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<u64>) -> (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) => {
|
||||
error!("Logger already set: {}", e);
|
||||
Err(_e) => {
|
||||
esp_println::println!("ERROR: Logger already set");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,16 +92,14 @@ impl Log for InterceptorLogger {
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let message = alloc::format!("{}", record.args());
|
||||
let message = alloc::format!("{}: {}", record.level(), record.args());
|
||||
|
||||
// Print to serial using esp_println
|
||||
esp_println::println!("{}: {}", record.level(), message);
|
||||
// Print to serial
|
||||
esp_println::println!("{}", message);
|
||||
|
||||
// Capture if active
|
||||
self.sync_capture.lock(|capture| {
|
||||
if let Some(ref mut buffer) = *capture.borrow_mut() {
|
||||
buffer.push(alloc::format!("{}: {}", record.level(), message));
|
||||
}
|
||||
// Store in live log ring buffer
|
||||
self.live_log.lock(|buf| {
|
||||
buf.borrow_mut().push(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user