//! Global logging state use super::util::*; use std::{ fmt::{ self, Display, }, io::{ self, Write, }, borrow::Borrow, sync::{RwLock}, }; use once_cell::sync::OnceCell; /// Print local time by default const DEFAULT_USE_LOCAL_TIME: bool = crate::config::build::log::DEFAULT_LOCAL_TIME; /// Logging level #[derive(PartialEq,Copy,Eq,Debug,Clone,Hash,Ord,PartialOrd)] pub enum Level { Silent, Error, Warn, Info, Debug, } /// Contains backtrace info from where the log macro was invoked #[derive(Debug)] pub struct Trace { line: Option, file: Option<&'static str>, column: Option, display: Opaque>>, } impl Clone for Trace { #[inline] fn clone(&self) -> Self { Self { line: self.line.clone(), file: self.file.clone(), column: self.column.clone(), display: Opaque::new(RwLock::new(None)), } } } impl std::hash::Hash for Trace { fn hash(&self, state: &mut H) { self.line.hash(state); self.file.hash(state); self.column.hash(state); } } impl std::cmp::PartialEq for Trace { fn eq(&self, other: &Self)->bool { self.line == other.line && self.file == other.file && self.column == other.column } } impl Trace { /// Create a new `Trace` manually. Generally not desireable, use `get_trace!()` instead. pub fn new(file: Option<&'static str>, line: Option, column: Option) -> Self { use std::convert::TryInto; let this = Self{ file, line: line.and_then(|x| x.try_into().ok()), column: column.and_then(|x| x.try_into().ok()), display: Opaque::new(RwLock::new(None)), }; this } fn genstring(&self) -> Result { let mut out = String::new(); use std::fmt::Write as FmtWrite; write!(out, "{}", self.file.unwrap_or("(unbound)"))?; if let Some(line) = self.line { write!(&mut out, ":{}", line)?; } else { write!(&mut out, ":?")?; } if let Some(column) = self.column { write!(&mut out, ":{}", column)?; } else { write!(&mut out, ":?")?; } Ok(out) } } impl std::fmt::Display for Trace { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { { let read = self.display.read().expect("Poisoned"); if let Some(opt) = read.as_ref() { write!(f, "{}", opt)?; return Ok(()); } } let out = self.genstring()?; write!(f, "{}", out)?; let mut write = self.display.write().expect("Poisoned"); *write = Some(out); Ok(()) } } impl Default for Trace { #[inline] fn default() -> Self { Self{ line:None, file:None, column:None, display: Opaque::new(RwLock::new(None)), } } } /// Get a source code trace of the current file, line and column. #[macro_export] macro_rules! get_trace { () => { $crate::log::Trace::new(Some(file!()), Some(line!()), Some(column!())) } } /// Append this trait to allow you to pub trait AsLevel: fmt::Display + Borrow{ /// Used for derived levels fn prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/", Borrow::::borrow(self)) } } impl AsLevel for Level{ #[inline] fn prefix(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result{Ok(())} } impl AsLevel for &Level{ #[inline] fn prefix(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result{Ok(())} } impl Default for Level { #[inline] fn default() -> Level { #[cfg(debug_assertions)] return Level::Debug; #[cfg(not(debug_assertions))] return Level::Warn; } } impl std::fmt::Display for Level { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use recolored::Colorize; write!(f, "{}", match &self { Self::Silent => "Fatal".bright_red().bold(), Self::Error => "Error".red(), Self::Warn => "Warning".yellow(), Self::Info => "Info".normal(), Self::Debug => "Debug".dimmed(), }) } } /// A logger that prints to stdout and stderr idk. /// # TODO /// Make this a trait and have variants that print to files and such #[derive(Debug)] pub struct Logger { level: Level, title: String, use_local_time: bool, } static INSTANCE: OnceCell = OnceCell::new(); impl Logger { pub fn new(level: Level) -> Self { Self { level, title: String::new(), use_local_time: DEFAULT_USE_LOCAL_TIME, } } #[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() } pub fn println(&self, mut to: W, level: L, what: D, trace: T) -> io::Result<()> where W: Write, L: AsLevel, D: Display, T: Borrow { //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("%Y-%m-%d %H:%M:%S %Z")), Self::Utc(l) => write!(f, "{}", l.format("%Y-%m-%d %H:%M:%S %Z")), } } } 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 }