diff --git a/src/log/custom.rs b/src/log/custom.rs new file mode 100644 index 0000000..266ffa5 --- /dev/null +++ b/src/log/custom.rs @@ -0,0 +1,26 @@ +//! Extra logging types for log files and such +use super::*; +use generational_arena::Index; + +use std::marker::{Sync, Send}; + +/// A logging hook +pub trait Hook: Sync + Send +{ + /// The name of this hook object + fn name(&self) -> &str {"(unnamed)"} + /// Fired when hook is added + fn initialise(&mut self, _idx: &Index) {} + /// Fired when `println` is called + fn println(&mut self, level: &dyn AsLevel, what: &dyn Display, trace: &Trace) -> io::Result<()>; + /// Fired when hook is removed + fn finalise(&mut self, _idx: Index) {} +} + +impl std::fmt::Debug for dyn Hook +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "") + } +} diff --git a/src/log/level.rs b/src/log/level.rs new file mode 100644 index 0000000..afb56f4 --- /dev/null +++ b/src/log/level.rs @@ -0,0 +1,57 @@ +//! Logging level for filtering and such +use super::*; + +/// Logging level +#[derive(PartialEq,Copy,Eq,Debug,Clone,Hash,Ord,PartialOrd)] +pub enum Level +{ + Silent, + Error, + Warn, + Info, + Debug, +} + +/// 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(), + }) + } +} + diff --git a/src/log/mod.rs b/src/log/mod.rs new file mode 100644 index 0000000..b91064a --- /dev/null +++ b/src/log/mod.rs @@ -0,0 +1,397 @@ +//! 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 a 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() + } + + /// 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 +} diff --git a/src/log/trace.rs b/src/log/trace.rs new file mode 100644 index 0000000..987cbad --- /dev/null +++ b/src/log/trace.rs @@ -0,0 +1,132 @@ +//! Source traces +use super::*; + +/// 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!())) + } +} +