//! Global logging state use super::util::*; use std::{ fmt::{ self, Display, }, io::{ self, Write, }, borrow::{Cow,Borrow,}, sync::{RwLock,}, }; use once_cell::sync::OnceCell; use generational_arena::{Arena,Index}; /// Print local time by default const DEFAULT_USE_LOCAL_TIME: bool = crate::config::build::log::DEFAULT_LOCAL_TIME; const DATE_FORMAT: Cow<'static, str> = crate::config::build::log::DATE_FORMAT; mod level; pub use level::*; mod trace; pub use trace::*; mod custom; /// A logger that prints to stdout and stderr idk. #[derive(Debug)] pub struct Logger { level: Level, title: String, use_local_time: bool, hooks: RwLock>>>, } static INSTANCE: OnceCell = OnceCell::new(); impl Logger { /// Create a new logger pub fn new(level: Level) -> Self { Self { level, title: String::new(), use_local_time: DEFAULT_USE_LOCAL_TIME, hooks: RwLock::new(Arena::new()), } } /// Get a reference to the global logger instance. #[inline] pub fn global() -> &'static Logger { INSTANCE.get().expect("[logger] uninitialised") } fn initialise(level: Level) -> &'static Logger { INSTANCE.set(Logger::new(level)).expect("[logger] already initialised"); Logger::global() } /// Dispatch on hooks fn dispatch(&self, level: &L, what: &W, trace: &T) where L: AsLevel, W: Display, T: Borrow { let trace = trace.borrow(); let reader = self.hooks.read().expect("Poisoned"); for (idx, dispatch_mtx) in reader.iter() { if let Ok(mut reader) = dispatch_mtx.try_write() { // Don't try to print in the same log twice if let Err(e) = reader.println(level, what, trace) { crate::warn!("Logger `{:?}:{}` failed to print: {}", idx, reader.name(), e); } } } } /// Add a logging hook pub fn add_hook(&self, mut hook: H) -> Index { let mut writer = self.hooks.write().expect("Poisoned"); let idx = writer.insert_with(move |idx| { hook.initialise(&idx); RwLock::new(Box::new(hook)) }); idx } /// Try to remove and return the removed logging hook. /// /// # Returns /// `None` if `hook` is not present or its `RwLock` was poisoned, otherwise, returns boxed trait object of the hook. pub fn remove_hook(&self, hook: impl Into) -> Option> { let mut writer = self.hooks.write().expect("Poisoned"); let hook =hook.into(); writer.remove(hook.clone()) .map(|rw| rw.into_inner().ok() .map(|mut x| { x.finalise(hook); x})) .flatten() } /// Try to remove, downcast, and return the removed logging hook. Assume the downcasting will not fail. /// /// # Returns /// `None` if [remove_hook]`remove_hook` returns `None`, `Some(H)` if it is removed successfully **and also** downcasted successfully. /// /// Function will also return `None` if removal was successful but downcasting was not. /// /// If this it not desireable, check [remove_hook_try_downcasted]`remove_hook_try_downcasted`. pub fn remove_hook_assume_downcasted>(&self, hook: I) -> Option { if let Some(bx) = self.remove_hook(hook) { bx.downcast().ok().map(|x| *x) } else { None } } /// Remove, downcast, and return the removed logging hook. /// /// # Returns /// `None` if [remove_hook]`remove_hook` returns `None`, `Some(H)` if it is removed successfully **and also** downcasted successfully. /// /// # Panics /// /// If downcasting fails pub fn remove_hook_downcasted>(&self, hook: I) -> Option { if let Some(bx) = self.remove_hook(hook) { Some(*bx.downcast().expect("Downcast failed on removed hook")) } else { None } } /// Try to remove, downcast, and return the removed logging hook. /// /// # Returns /// `Err(false)` if [remove_hook]`remove_hook` returns `None`, `Ok(H)` if it is removed successfully **and also** downcasted successfully. `Err(true)` if removal was successful but not downcasting. pub fn remove_hook_try_downcasted>(&self, hook: I) -> Result { if let Some(bx) = self.remove_hook(hook) { bx.downcast().map_err(|_| true).map(|x| *x) } else { Err(false) } } /// Print a line to this logger. /// You should usually print using the macros defined here instead of calling this directly. pub fn println(&self, mut to: W, level: L, what: D, trace: T) -> io::Result<()> where W: Write, L: AsLevel, D: Display, T: Borrow { self.dispatch(&level, &what, &trace); //lol enum Date { Local(chrono::DateTime), Utc(chrono::DateTime), } impl std::fmt::Display for Date { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Local(l) => write!(f, "{}", l.format(DATE_FORMAT.as_ref())), Self::Utc(l) => write!(f, "{}", l.format(DATE_FORMAT.as_ref())), } } } impl From> for Date { #[inline] fn from(from: chrono::DateTime) -> Self { Self::Local(from) } } impl From> for Date { #[inline] fn from(from: chrono::DateTime) -> Self { Self::Utc(from) } } if &self.level >= level.borrow() { let now: Date = if self.use_local_time { chrono::offset::Local::now().into() } else { chrono::offset::Utc::now().into() }; write!(to, "{} ", now)?; if self.title.len() > 0 { write!(to, " <{}>\t", self.title)?; } else { write!(to, " <{}>\t", trace.borrow())?; } write!(to," [")?; struct Prefix<'a,L: AsLevel>(&'a L); impl<'a,L: AsLevel> std::fmt::Display for Prefix<'a, L> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.prefix(f) } } write!(to, "{}", Prefix(&level))?; //what a mess... write!(to, "{}", level)?; write!(to, "]: ")?; writeln!(to, "{}", what)?; } Ok(()) } } #[cfg(any(debug_assertions,feature="debug_logger"))] #[macro_export] macro_rules! debug { ($obj:expr) => { { let stdout = std::io::stdout(); let stdout = stdout.lock(); $crate::log::Logger::global().println(stdout, $crate::log::Level::Debug, $obj, get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { debug!(format!($fmt, $($args,)*)); }; } #[cfg(not(any(debug_assertions,feature="debug_logger")))] #[macro_export] macro_rules! debug { ($obj:expr) => {{}}; ($fmt:literal, $($args:expr),*) => {{}}; } #[cfg(any(debug_assertions,feature="debug_logger"))] #[macro_export] macro_rules! status { ($obj:expr) => { { struct Status; use std::{ borrow::Borrow, fmt, }; impl Borrow<$crate::log::Level> for Status { fn borrow(&self) -> &$crate::log::Level { &$crate::log::Level::Debug } } impl fmt::Display for Status { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use recolored::Colorize; write!(f, "{}", "Status".blue()) } } impl $crate::log::AsLevel for Status{} let stdout = std::io::stdout(); let stdout = stdout.lock(); $crate::log::Logger::global().println(stdout, Status, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { status!(format!($fmt, $($args,)*)); }; } #[cfg(not(any(debug_assertions,feature="debug_logger")))] #[macro_export] macro_rules! status { ($obj:expr) => {{}}; ($fmt:literal, $($args:expr),*) => {{}}; } #[macro_export] macro_rules! info { ($obj:expr) => { { let stdout = std::io::stdout(); let stdout = stdout.lock(); $crate::log::Logger::global().println(stdout, $crate::log::Level::Info, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { $crate::info!(format!($fmt, $($args,)*)); }; } #[macro_export] macro_rules! important { ($obj:expr) => { { struct Important; use std::{ borrow::Borrow, fmt, }; impl Borrow<$crate::log::Level> for Important { fn borrow(&self) -> &$crate::log::Level { &$crate::log::Level::Info } } impl fmt::Display for Important { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use recolored::Colorize; write!(f, "{}", "Important".bright_blue()) } } impl $crate::log::AsLevel for Important{} let stdout = std::io::stdout(); let stdout = stdout.lock(); $crate::log::Logger::global().println(stdout, Important, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { $crate::important!(format!($fmt, $($args,)*)); }; } #[macro_export] macro_rules! warn { ($obj:expr) => { { let stderr = std::io::stderr(); let stderr = stderr.lock(); $crate::log::Logger::global().println(stderr, $crate::log::Level::Warn, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { $crate::warn!(format!($fmt, $($args,)*)); }; } #[macro_export] macro_rules! dangerous { ($obj:expr) => { { struct Dangerous; use std::{ borrow::Borrow, fmt, }; impl Borrow<$crate::log::Level> for Dangerous { fn borrow(&self) -> &$crate::log::Level { &$crate::log::Level::Warn } } impl fmt::Display for Dangerous { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use recolored::Colorize; write!(f, "{}", "Dangerous".bright_yellow()) } } impl $crate::log::AsLevel for Dangerous{} let stderr = std::io::stderr(); let stderr = stderr.lock(); $crate::log::Logger::global().println(stderr, Dangerous, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { $crate::dangerous!(format!($fmt, $($args,)*)); }; } #[macro_export] macro_rules! error { ($obj:expr) => { { let stderr = std::io::stderr(); let stderr = stderr.lock(); $crate::log::Logger::global().println(stderr, $crate::log::Level::Error, $obj, $crate::get_trace!()).expect("i/o error"); } }; ($fmt:literal, $($args:expr),*) => { $crate::error!(format!($fmt, $($args,)*)); }; } #[macro_export] macro_rules! fatal { ($obj:expr) => { { let stdout = std::io::stdout(); let stdout = stdout.lock(); $crate::log::Logger::global().println(stdout, $crate::log::Level::Silent, $obj, $crate::get_trace!()).expect("i/o error"); std::process::exit(-1) } }; ($fmt:literal, $($args:expr),*) => { $crate::fatal!(format!($fmt, $($args,)*)); }; } /// Initialise the global logger instance. If logging macros are called before this, they will panic. #[inline] pub fn init(level: Level) { Logger::initialise(level); } /// The global logger's level #[inline] pub fn level() -> &'static Level { &Logger::global().level }