use chrono::Utc;
use colored::*;
use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
pub struct LoggingService {
pub min_log_level: LevelFilter,
}
impl log::Log for LoggingService {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level().to_level_filter() <= self.min_log_level
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let level = match record.level() {
Level::Error => format!("{:<5}", "ERROR").red(),
Level::Warn => format!("{:<5}", "WARN").yellow(),
Level::Info => format!("{:<5}", "INFO").green(),
Level::Debug => format!("{:<5}", "DEBUG").bright_cyan(),
Level::Trace => format!("{:<5}", "TRACE").blue(),
};
let timestamp = Utc::now().format("%Y-%m-%d %H:%M:%S");
let module = record
.module_path()
.and_then(|m| m.split("::").last())
.unwrap_or("unknown");
println!("[{timestamp}] {level} {module:<20} - {}", record.args());
}
}
fn flush(&self) {}
}
pub fn initialise_logger(log_level: LevelFilter) -> Result<(), SetLoggerError> {
let logger = LoggingService {
min_log_level: log_level,
};
log::set_max_level(logger.min_log_level);
log::set_boxed_logger(Box::new(logger))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use log::{Level, Log};
fn make_service(level: LevelFilter) -> LoggingService {
LoggingService {
min_log_level: level,
}
}
#[test]
fn enabled_passes_at_or_above_min_level() {
let svc = make_service(LevelFilter::Info);
assert!(
svc.enabled(
&log::Metadata::builder()
.level(Level::Error)
.target("t")
.build()
)
);
assert!(
svc.enabled(
&log::Metadata::builder()
.level(Level::Warn)
.target("t")
.build()
)
);
assert!(
svc.enabled(
&log::Metadata::builder()
.level(Level::Info)
.target("t")
.build()
)
);
}
#[test]
fn enabled_filters_below_min_level() {
let svc = make_service(LevelFilter::Info);
assert!(
!svc.enabled(
&log::Metadata::builder()
.level(Level::Debug)
.target("t")
.build()
)
);
assert!(
!svc.enabled(
&log::Metadata::builder()
.level(Level::Trace)
.target("t")
.build()
)
);
}
#[test]
fn log_does_not_panic_for_all_levels() {
let svc = make_service(LevelFilter::Trace);
svc.log(
&log::Record::builder()
.level(Level::Error)
.target("t")
.module_path(Some("test::module"))
.args(format_args!("error message"))
.build(),
);
svc.log(
&log::Record::builder()
.level(Level::Warn)
.target("t")
.module_path(Some("test::module"))
.args(format_args!("warn message"))
.build(),
);
svc.log(
&log::Record::builder()
.level(Level::Info)
.target("t")
.module_path(Some("test::module"))
.args(format_args!("info message"))
.build(),
);
svc.log(
&log::Record::builder()
.level(Level::Debug)
.target("t")
.module_path(Some("test::module"))
.args(format_args!("debug message"))
.build(),
);
svc.log(
&log::Record::builder()
.level(Level::Trace)
.target("t")
.module_path(Some("test::module"))
.args(format_args!("trace message"))
.build(),
);
}
#[test]
fn log_silently_skips_filtered_levels() {
let svc = make_service(LevelFilter::Error);
svc.log(
&log::Record::builder()
.level(Level::Trace)
.target("t")
.args(format_args!("should be skipped"))
.build(),
); }
#[test]
fn log_handles_missing_module_path() {
let svc = make_service(LevelFilter::Trace);
svc.log(
&log::Record::builder()
.level(Level::Info)
.target("t")
.module_path(None)
.args(format_args!("no module path"))
.build(),
); }
}