use std::{sync::Mutex, time};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl LogLevel {
pub fn as_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
}
}
}
pub struct LogEvent<'a> {
pub level: LogLevel,
pub message: &'a str,
pub timestamp: std::time::SystemTime,
}
pub trait Layout: Send + Sync {
fn format(&self, event: &LogEvent) -> String;
}
pub struct SimpleLayout;
impl Layout for SimpleLayout {
fn format(&self, event: &LogEvent) -> String {
format!("[{:<5}] {} {}", event.level.as_str(), get_now_str(event.timestamp), event.message)
}
}
pub trait Appender: Send + Sync {
fn append(&self, event: &LogEvent);
}
pub struct ConsoleAppender {
layout: Box<dyn Layout>,
}
impl ConsoleAppender {
pub fn new(layout: Box<dyn Layout>) -> Self {
Self { layout }
}
}
impl Appender for ConsoleAppender {
fn append(&self, event: &LogEvent) {
println!("{}", self.layout.format(event));
}
}
static LOGGER: Mutex<Option<Logger>> = Mutex::new(None);
pub struct Logger {
level: LogLevel,
appenders: Vec<Box<dyn Appender>>,
}
pub struct LoggerBuilder {
level: LogLevel,
appenders: Vec<Box<dyn Appender>>,
}
impl LoggerBuilder {
pub fn level(mut self, level: LogLevel) -> Self {
self.level = level;
self
}
pub fn appender(mut self, appender: Box<dyn Appender>) -> Self {
self.appenders.push(appender);
self
}
pub fn build(self) -> Logger {
Logger {
level: self.level,
appenders: self.appenders,
}
}
pub fn init(self) {
Logger::set_logger(self.build());
}
}
fn get_now_str(sys_time: time::SystemTime) -> String {
use chrono::{Local, DateTime};
let dt = DateTime::<Local>::from(sys_time);
dt.format("%Y-%m-%d %H:%M:%S").to_string()
}
impl Logger {
pub fn builder(level: LogLevel) -> LoggerBuilder {
LoggerBuilder {
level,
appenders: Vec::new(),
}
}
pub fn set_logger(logger: Logger) {
*LOGGER.lock().unwrap_or_else(|e| e.into_inner()) = Some(logger);
}
pub fn init(level: LogLevel) {
Logger::set_logger(Logger {
level,
appenders: vec![Box::new(ConsoleAppender::new(Box::new(SimpleLayout)))],
});
}
pub fn get() -> std::sync::MutexGuard<'static, Option<Logger>> {
LOGGER.lock().unwrap_or_else(|e| e.into_inner())
}
fn should_log(&self, level: LogLevel) -> bool {
level >= self.level
}
pub fn log(&self, level: LogLevel, message: &str) {
if self.should_log(level) {
let event = LogEvent {
level,
message,
timestamp: std::time::SystemTime::now(),
};
for appender in &self.appenders {
appender.append(&event);
}
}
}
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
if let Some(logger) = $crate::Logger::get().as_ref() {
logger.log($crate::LogLevel::Trace, &format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
if let Some(logger) = $crate::Logger::get().as_ref() {
logger.log($crate::LogLevel::Debug, &format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
if let Some(logger) = $crate::Logger::get().as_ref() {
logger.log($crate::LogLevel::Info, &format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
if let Some(logger) = $crate::Logger::get().as_ref() {
logger.log($crate::LogLevel::Warn, &format!($($arg)*));
}
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
if let Some(logger) = $crate::Logger::get().as_ref() {
logger.log($crate::LogLevel::Error, &format!($($arg)*));
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn init_reconfigures_logger() {
Logger::init(LogLevel::Error);
assert_eq!(Logger::get().as_ref().unwrap().level, LogLevel::Error);
Logger::init(LogLevel::Trace);
assert_eq!(Logger::get().as_ref().unwrap().level, LogLevel::Trace);
}
#[test]
fn should_log_filters_by_level() {
Logger::init(LogLevel::Info);
let guard = Logger::get();
let logger = guard.as_ref().unwrap();
assert!(!logger.should_log(LogLevel::Trace));
assert!(!logger.should_log(LogLevel::Debug));
assert!(logger.should_log(LogLevel::Info));
assert!(logger.should_log(LogLevel::Warn));
assert!(logger.should_log(LogLevel::Error));
}
#[test]
fn custom_appender_receives_events() {
struct CaptureAppender(Arc<Mutex<Vec<String>>>);
impl Appender for CaptureAppender {
fn append(&self, event: &LogEvent) {
self.0.lock().unwrap().push(event.message.to_string());
}
}
let buffer = Arc::new(Mutex::new(Vec::new()));
Logger::builder(LogLevel::Info)
.appender(Box::new(CaptureAppender(buffer.clone())))
.init();
crate::info!("hello {}", 42);
crate::debug!("filtered at Info level");
let messages = buffer.lock().unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0], "hello 42");
}
}