log4r/src/lib.rs

205 lines
5.4 KiB
Rust

//! # log4r - A log4j-like logging framework for Rust
//!
//! This crate provides a simple, extensible logging framework similar to log4j.
//!
//! ## Example
//!
//! ```
//! use log4r::{LogLevel, Logger};
//!
//! Logger::init(LogLevel::Debug);
//!
//! log4r::trace!("This is a trace message");
//! log4r::debug!("This is a debug message");
//! log4r::info!("This is an info message");
//! log4r::warn!("This is a warning message");
//! log4r::error!("This is an error message");
//! ```
use std::{sync::{Mutex, OnceLock}, time};
/// Log levels compatible with log4j
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
/// The "trace" level.
Trace,
/// The "debug" level.
Debug,
/// The "info" level.
Info,
/// The "warn" level.
Warn,
/// The "error" level.
Error,
}
impl LogLevel {
/// Returns the string representation of the log level
pub fn as_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warn => "WARN",
LogLevel::Error => "ERROR",
}
}
}
/// Log event containing all information about a log message
pub struct LogEvent<'a> {
/// The level of the log event
pub level: LogLevel,
/// The message to log
pub message: &'a str,
/// The timestamp when the event occurred
pub timestamp: std::time::SystemTime,
}
/// Trait for formatting log events into strings
pub trait Layout: Send + Sync {
/// Format a log event into a string
fn format(&self, event: &LogEvent) -> String;
}
/// Simple layout that formats log events as "[LEVEL] message"
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)
}
}
/// Trait for output targets (console, file, etc.)
pub trait Appender: Send + Sync {
/// Append a log event to the output target
fn append(&self, event: &LogEvent);
}
/// Console appender that writes to stdout
pub struct ConsoleAppender {
layout: Box<dyn Layout>,
}
impl ConsoleAppender {
/// Create a new console appender with a specific layout
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: OnceLock<Mutex<Option<Logger>>> = OnceLock::new();
/// The logger struct that holds the current log level
pub struct Logger {
level: LogLevel,
appenders: Vec<Box<dyn Appender>>,
}
fn get_now_str(sys_time: time::SystemTime) -> String {
use chrono::{Local, DateTime};
let dt = DateTime::<Local>::from(sys_time);
return dt.format("%Y-%m-%d %H:%M:%S").to_string();
}
impl Logger {
/// Initialize the logger with a specific log level
pub fn init(level: LogLevel) {
let console_appender = Box::new(ConsoleAppender::new(Box::new(SimpleLayout)));
let logger = Logger {
level,
appenders: vec![console_appender],
};
LOGGER.set(Mutex::new(Some(logger))).ok();
}
/// Get the current logger instance
pub fn get() -> Option<std::sync::MutexGuard<'static, Option<Logger>>> {
LOGGER.get().map(|logger| logger.lock().unwrap())
}
/// Check if a message at the specified level should be logged
fn should_log(&self, level: LogLevel) -> bool {
level >= self.level
}
/// Log a message at the specified 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 for logging trace messages
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Trace, &format!($($arg)*));
}
}
};
}
/// Macro for logging debug messages
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Debug, &format!($($arg)*));
}
}
};
}
/// Macro for logging info messages
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Info, &format!($($arg)*));
}
}
};
}
/// Macro for logging warning messages
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Warn, &format!($($arg)*));
}
}
};
}
/// Macro for logging error messages
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
if let Some(guard) = $crate::Logger::get() {
if let Some(logger) = guard.as_ref() {
logger.log($crate::LogLevel::Error, &format!($($arg)*));
}
}
};
}