You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

464 lines
11 KiB

//! 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::*;
pub 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<Arena<RwLock<Box<dyn custom::Hook>>>>,
}
static INSTANCE: OnceCell<Logger> = OnceCell::new();
#[derive(Debug)]
/// Dynamic date wrapper
enum Date {
/// Local time
Local(chrono::DateTime<chrono::offset::Local>),
/// Utc time
Utc(chrono::DateTime<chrono::offset::Utc>),
}
impl Date
{
/// Timestamp for now (local time)
pub fn now_local() -> Self
{
chrono::offset::Local::now().into()
}
/// Timestamp for now (UTC time)
pub fn now_utc() -> Self
{
chrono::offset::Utc::now().into()
}
}
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<chrono::DateTime<chrono::offset::Local>> for Date
{
#[inline]
fn from(from: chrono::DateTime<chrono::offset::Local>) -> Self
{
Self::Local(from)
}
}
impl From<chrono::DateTime<chrono::offset::Utc>> for Date
{
#[inline]
fn from(from: chrono::DateTime<chrono::offset::Utc>) -> Self
{
Self::Utc(from)
}
}
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<L,W,T>(&self, level: &L, what: &W, trace: &T)
where L: AsLevel,
W: Display,
T: Borrow<Trace>
{
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<H: custom::Hook + 'static>(&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<Index>) -> Option<Box<dyn custom::Hook>>
{
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<H: custom::Hook, I: Into<Index>>(&self, hook: I) -> Option<H>
{
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<H: custom::Hook, I: Into<Index>>(&self, hook: I) -> Option<H>
{
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<H: custom::Hook, I: Into<Index>>(&self, hook: I) -> Result<H, bool>
{
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<W,L,D,T>(&self, mut to: W, level: L, what: D, trace: T) -> io::Result<()>
where W: Write,
L: AsLevel,
D: Display,
T: Borrow<Trace>
{
self.dispatch(&level, &what, &trace);
//lol
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
}