pub mod config; pub mod query; #[cfg(test)] mod tests; use std::{ fmt, fs::OpenOptions, io::{BufWriter, Write}, str::FromStr, sync::{ Arc, RwLock, mpsc::{self, Sender}, }, thread, time::SystemTime, }; use config::LoggerConfig; use query::LogQuery; use tracing::{Event, Level, Subscriber, level_filters::LevelFilter, subscriber::DefaultGuard}; use tracing_subscriber::{ layer::{Context, Layer, SubscriberExt}, registry::LookupSpan, util::SubscriberInitExt, }; #[cfg(feature = "json")] use serde::{Deserialize, Serialize}; #[cfg(feature = "json")] use serde_json::Value; #[cfg(feature = "json")] use chrono::{DateTime, Utc}; #[derive(Debug, Clone, PartialEq, Eq)] enum LogEvent { Log(LogEntry), Shutdown, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct LogEntry { timestamp: SystemTime, level: Level, message: String, #[cfg(feature = "json")] additional_fields: serde_json::Map, } impl PartialOrd for LogEntry { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for LogEntry { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.timestamp .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .cmp( &other .timestamp .duration_since(SystemTime::UNIX_EPOCH) .unwrap(), ) } } struct BufferLayer { log_entries: Arc>>, senders: Vec>, } impl BufferLayer { pub fn new(log_entries: Arc>>, senders: Vec>) -> Self { Self { log_entries, senders, } } } impl Drop for BufferLayer { fn drop(&mut self) { for tx in &self.senders { if let Err(e) = tx.send(LogEvent::Shutdown) { panic!("{e}") } } self.senders.clear(); } } impl Layer for BufferLayer where S: Subscriber + for<'a> LookupSpan<'a>, { fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { let metadata = event.metadata(); let level = *metadata.level(); let timestamp = SystemTime::now(); #[cfg(feature = "json")] let additional_fields = serde_json::Map::new(); let mut message = String::new(); let mut visitor = LogVisitor::new(&mut message); event.record(&mut visitor); let log_entry = LogEvent::Log(LogEntry { timestamp, level, message, #[cfg(feature = "json")] additional_fields }); if let LogEvent::Log(ref entry) = log_entry { if let Ok(mut buf) = self.log_entries.write() { buf.push(entry.clone()); } } for tx in &self.senders { let _ = tx.send(log_entry.clone()); } } } struct LogVisitor<'msg> { message: &'msg mut String, } impl<'msg> LogVisitor<'msg> { fn new(message: &'msg mut String) -> Self { Self { message } } } impl tracing::field::Visit for LogVisitor<'_> { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn fmt::Debug) { use std::fmt::Write; if field.name() == "message" { write!(self.message, "{:?}", value).unwrap(); } else { write!(self.message, "{}={:?} ", field.name(), value).unwrap(); } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum LogLevel { Error, #[default] Info, Warn, Debug, Trace, } impl From for LevelFilter { fn from(level: LogLevel) -> Self { match level { LogLevel::Error => LevelFilter::ERROR, LogLevel::Info => LevelFilter::INFO, LogLevel::Debug => LevelFilter::DEBUG, LogLevel::Trace => LevelFilter::TRACE, LogLevel::Warn => LevelFilter::WARN, } } } impl fmt::Display for LogLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } impl FromStr for LogLevel { type Err = (); fn from_str(s: &str) -> Result { match s { "trace" => Ok(Self::Trace), "debug" => Ok(Self::Debug), "info" => Ok(Self::Info), "error" => Ok(Self::Error), "warn" => Ok(Self::Warn), _ => Err(()), } } } pub struct Logger { subscriber_guard: Option, log_entries: Arc>>, _handles: Vec>, } impl Logger { pub fn new(config: LoggerConfig) -> Self { let log_entries = Arc::new(RwLock::new(Vec::new())); let mut senders = Vec::new(); let mut handles = Vec::new(); if config.log_to_stdout || config.log_use_json { let (tx, rx) = mpsc::channel(); senders.push(tx); let config_clone = config.clone(); let handle = thread::spawn(move || { for msg in rx { match msg { LogEvent::Log(mut entry) => { println!( "{}", format_entry( &mut entry, &config_clone ) ); } LogEvent::Shutdown => break, } } }); handles.push(handle); } if config.log_to_file { let (tx, rx) = mpsc::channel(); senders.push(tx); let config_clone = config.clone(); let path = config.log_file_path.clone(); let handle = thread::spawn(move || { let file = OpenOptions::new() .append(true) .create(true) .open(&path) .expect("Failed to open log file"); let mut writer = BufWriter::new(file); for msg in rx { match msg { LogEvent::Log(mut entry) => { let line = format_entry(&mut entry, &config_clone); writeln!(writer, "{}", line).expect("Failed to write to log file"); writer.flush().expect("Failed to flush log file"); } LogEvent::Shutdown => break, } } }); handles.push(handle); } let buffer_layer = BufferLayer::new(log_entries.clone(), senders); let mk_logger = |guard: DefaultGuard| Logger { subscriber_guard: Some(guard), log_entries, _handles: handles, }; if let Some(level) = config.log_level { let subscriber = tracing_subscriber::registry() .with(buffer_layer) .with(LevelFilter::from(level)); let guard = subscriber.set_default(); mk_logger(guard) } else { let subscriber = tracing_subscriber::registry().with(buffer_layer); let guard = subscriber.set_default(); mk_logger(guard) } } pub fn get_logs(&self, query: LogQuery) -> Vec { let guard = self.log_entries.read().unwrap(); match query { LogQuery::All => guard.clone(), LogQuery::From(t) => guard.iter().filter(|e| e.timestamp >= t).cloned().collect(), } } } impl Drop for Logger { fn drop(&mut self) { let _ = self.subscriber_guard.take(); let handles = std::mem::take(&mut self._handles); for handle in handles { handle.join().unwrap_or_else(|e| { eprintln!("Logger thread panicked: {:?}", e); }); } } } fn format_entry(entry: &mut LogEntry, log_config: &LoggerConfig) -> String { if log_config.log_use_json { return format_entry_json(entry, log_config); } if log_config.log_to_stdout || log_config.log_to_file { return format_entry_string(entry, log_config); } else { return String::new(); } } fn format_entry_string(entry: &LogEntry, log_config: &LoggerConfig) -> String { let lvl = if log_config.stdout_color { match entry.level { Level::ERROR => "\x1b[31mERROR\x1b[0m", Level::WARN => "\x1b[33mWARN\x1b[0m", Level::INFO => "\x1b[32mINFO\x1b[0m", Level::DEBUG => "\x1b[36mDEBUG\x1b[0m", Level::TRACE => "\x1b[34mTRACE\x1b[0m", } } else { entry.level.as_str() }; format!("{} {}", lvl, entry.message) } /// Formats the log entry as a json object ([`serde_json`]) and returns it as a [`String`] fn format_entry_json(entry: &mut LogEntry, log_config: &LoggerConfig) -> String { let mut json_object = serde_json::Map::new(); if log_config.log_json_show_timestamp { json_object.insert("timestamp".to_string(), Value::String(DateTime::::from(entry.timestamp).to_rfc3339())); } if log_config.log_json_show_level { json_object.insert("level".to_string(), Value::String(entry.level.to_string())); } if log_config.log_json_show_message { json_object.insert("message".to_string(), Value::String(entry.message.to_string())); } if log_config.log_json_show_additional_fields { json_object.append(&mut entry.additional_fields); } serde_json::to_string(&json_object).unwrap() }