use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use log::{Level, LevelFilter, Metadata, Record};
use simplelog::{Config, SharedLogger};
use tokio::sync::broadcast;
#[derive(Clone, Debug)]
pub struct LogEntry {
pub time: SystemTime,
pub level: Level,
pub target: String,
pub message: String,
}
pub struct LogBuffer {
capacity: usize,
entries: VecDeque<LogEntry>,
}
impl LogBuffer {
fn new(capacity: usize) -> Self {
Self {
capacity,
entries: VecDeque::with_capacity(capacity),
}
}
fn push(&mut self, entry: LogEntry) {
if self.entries.len() == self.capacity {
self.entries.pop_front();
}
self.entries.push_back(entry);
}
pub fn snapshot(&self) -> Vec<LogEntry> {
self.entries.iter().cloned().collect()
}
}
pub struct LogState {
pub buffer: Mutex<LogBuffer>,
pub tx: broadcast::Sender<LogEntry>,
}
impl LogState {
fn new(capacity: usize) -> Arc<Self> {
let (tx, _) = broadcast::channel(4096);
Arc::new(Self {
buffer: Mutex::new(LogBuffer::new(capacity)),
tx,
})
}
pub fn subscribe(&self) -> broadcast::Receiver<LogEntry> {
self.tx.subscribe()
}
}
pub struct InMemoryLogger {
state: Arc<LogState>,
level: LevelFilter,
}
impl log::Log for InMemoryLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.level
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
let entry = LogEntry {
time: SystemTime::now(),
level: record.level(),
target: record.target().to_owned(),
message: record.args().to_string(),
};
if let Ok(mut buf) = self.state.buffer.lock() {
buf.push(entry.clone());
}
let _ = self.state.tx.send(entry);
}
fn flush(&self) {}
}
impl SharedLogger for InMemoryLogger {
fn level(&self) -> LevelFilter {
self.level
}
fn config(&self) -> Option<&Config> {
None
}
fn as_log(self: Box<Self>) -> Box<dyn log::Log> {
self
}
}
pub fn init_combined_logger(log_file: std::fs::File, level: LevelFilter) -> Arc<LogState> {
let state = LogState::new(10_000);
let file_logger = simplelog::WriteLogger::new(level, Config::default(), log_file);
let mem_logger = Box::new(InMemoryLogger {
state: state.clone(),
level,
});
simplelog::CombinedLogger::init(vec![file_logger, mem_logger])
.expect("logger already initialised");
state
}