use std::io::Write; use std::sync::{LazyLock, Mutex}; use std::time::SystemTime; use tokio::sync::watch; use tokio::task::JoinSet; pub static LOG: LazyLock = LazyLock::new(Log::new); pub fn init() { log::set_logger(&*LOG).expect("set_logger should not fail"); log::set_max_level(log::LevelFilter::Info); } pub struct Log { start: SystemTime, log: Mutex>, tx: Mutex>>, rx: watch::Receiver, tasks: Mutex>>, } impl Log { pub fn new() -> Self { let (tx, rx) = watch::channel(0); Self { start: SystemTime::now(), log: Mutex::new(Vec::new()), tx: Mutex::new(Some(tx)), rx, tasks: Mutex::new(Some(JoinSet::new())), } } pub fn spawn(&self, task: impl Future + Send + 'static) { if let Some(tasks) = self.tasks.lock().unwrap().as_mut() { tasks.spawn(task); } } pub fn subscribe(&self) -> LogWatch { LogWatch { log: self, pos: 0, rx: self.rx.clone(), } } pub async fn copy_to(&self, mut out: W) { let mut log = self.subscribe(); use tokio::io::AsyncWriteExt; while let Some(chunk) = log.next().await { let _ = out.write_all(&chunk).await; let _ = out.flush().await; } } pub async fn close(&self) { self.tx.lock().unwrap().take(); if let Some(tasks) = self.tasks.lock().unwrap().take() { tasks.join_all().await; } } } impl log::Log for Log { fn enabled(&self, metadata: &log::Metadata) -> bool { log::max_level().to_level().unwrap_or(log::Level::Info) >= metadata.level() } fn log(&self, record: &log::Record) { if !self.enabled(record.metadata()) { return; } let ts = self.start.elapsed().unwrap_or_default(); use log::Level::*; let level = match record.level() { Trace => "\x1b[2mTRC\x1b[0m", Debug => "\x1b[34mDBG\x1b[0m", Info => "\x1b[32mINF\x1b[0m", Warn => "\x1b[93mWRN\x1b[0m", Error => "\x1b[91mERR\x1b[0m", }; let Ok(mut log) = self.log.lock() else { return; }; writeln!( log, "{:3}.{:03} {level} {}", ts.as_secs(), ts.subsec_millis(), record.args() ) .expect("write to buf should not fail"); if let Some(tx) = self.tx.lock().unwrap().as_mut() { tx.send_replace(log.len()); } } fn flush(&self) { // always flushed } } pub struct LogWatch<'t> { log: &'t Log, pos: usize, rx: watch::Receiver, } impl<'t> LogWatch<'t> { pub async fn next(&mut self) -> Option> { loop { let new_pos = self.rx.borrow_and_update().clone(); if new_pos <= self.pos { if self.rx.changed().await.is_err() { return None; // finished } continue; } let mut chunk = Vec::new(); chunk.extend(&self.log.log.lock().unwrap()[self.pos..new_pos]); self.pos = new_pos; return Some(chunk); } } }