// Copyright (c) 2021 Weird Constructor // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. use lazy_static::lazy_static; use ringbuf::{Consumer, Producer, RingBuffer}; use std::cell::RefCell; use std::sync::{Arc, Mutex}; lazy_static! { static ref LOG_RECV: Arc> = Arc::new(Mutex::new(LogReceiver::new())); } thread_local! { pub static LOG: RefCell> = RefCell::new(None); } pub fn retrieve_log_messages(f: F) { if let Ok(mut lr) = LOG_RECV.lock() { lr.retrieve_messages(f); } } #[inline] pub fn init_thread_logger(name: &'static str) -> bool { if !LOG.with(|l| l.borrow().is_some()) { if let Ok(mut lr) = LOG_RECV.lock() { lr.spawn_global_logger(name); return true; } } false } pub fn log)>(f: F) { use std::borrow::BorrowMut; LOG.with(|l| { let mut lh = l.borrow_mut(); if let Some(lh) = (*(*lh.borrow_mut())).as_mut() { lh.log(f); } }); } const MAX_LOG_BUFFER: usize = 4096; pub struct LogReceiver { consumers: Vec<(&'static str, Consumer)>, } impl LogReceiver { pub fn new() -> Self { Self { consumers: vec![] } } pub fn retrieve_messages(&mut self, mut f: F) { for (name, consumer) in self.consumers.iter_mut() { let mut buf = [0; 1024]; let mut oi = 0; while let Some(byte) = consumer.pop() { if oi >= buf.len() || byte == 0xFF { f(name, std::str::from_utf8(&buf[0..oi]).unwrap()); oi = 0; } else { buf[oi] = byte; oi += 1; } } } } pub fn spawn_logger(&mut self, name: &'static str) -> Log { let rb = RingBuffer::new(MAX_LOG_BUFFER); let (producer, con) = rb.split(); self.consumers.push((name, con)); Log { producer, buf: [0; 512] } } #[inline] pub fn spawn_global_logger(&mut self, name: &'static str) { let hdl = self.spawn_logger(name); LOG.with(move |f| { *f.borrow_mut() = Some(hdl); }); } } pub struct Log { producer: Producer, buf: [u8; 512], } impl Log { pub fn log_buf(&mut self, data: &[u8]) { self.producer.push_slice(data); let _ = self.producer.push(0xFF); } pub fn log)>(&mut self, f: F) { self.buf.fill(0xFF); let len = { let mut bw = std::io::BufWriter::new(&mut self.buf[..]); f(&mut bw); bw.buffer().len() }; if len < (self.buf.len() - 1) { // include one 0xFF! self.producer.push_slice(&self.buf[0..len + 1]); } else { self.producer.push_slice(&self.buf[0..len]); } } } #[cfg(test)] mod tests { use super::*; #[test] fn check_threaded_logger() { std::thread::spawn(|| { use std::io::Write; assert!(init_thread_logger("tstlog")); log(|w| write!(w, "Test Log{}!", 1).unwrap()); log(|w| write!(w, "Test Log{}!", 2).unwrap()); }); let mut msgs = vec![]; for _ in 0..100 { std::thread::sleep(std::time::Duration::from_millis(100)); retrieve_log_messages(|name, s| msgs.push(name.to_string() + "/" + s)); if msgs.len() > 1 { assert_eq!(msgs[0], "tstlog/Test Log1!"); assert_eq!(msgs[1], "tstlog/Test Log2!"); break; } } } }