//! 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 < Arena < RwLock < Box < dyn custom ::Hook > > > > ,
}
static INSTANCE : OnceCell < Logger > = 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 < 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
enum Date {
Local ( chrono ::DateTime < chrono ::offset ::Local > ) ,
Utc ( chrono ::DateTime < chrono ::offset ::Utc > ) ,
}
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 )
}
}
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
}