use crate::handler::Handler;
use crate::types::Level;
use chrono::Local;
use std::collections::HashMap;
use std::error::Error;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
type Handlers = RwLock<Vec<Arc<dyn Handler>>>;
static LOGGERS: OnceLock<Mutex<HashMap<String, Arc<Logger>>>> = OnceLock::new();
fn loggers() -> &'static Mutex<HashMap<String, Arc<Logger>>> {
LOGGERS.get_or_init(|| Mutex::new(HashMap::new()))
}
pub struct Logger {
name: &'static str,
handlers: Handlers,
level: AtomicU8,
}
static ROOT_LOGGER_NAME: &str = "root";
static ROOT_LOGGER: OnceLock<Arc<Logger>> = OnceLock::new();
impl Logger {
pub fn new(name: &'static str) -> Self {
if name == ROOT_LOGGER_NAME {
Self {
name: ROOT_LOGGER_NAME,
handlers: RwLock::new(Vec::new()),
level: AtomicU8::new(Level::Info as u8),
}
} else {
Self {
name,
handlers: RwLock::new(Self::root().handlers()),
level: AtomicU8::new(Self::root().level() as u8),
}
}
}
pub fn get(name: &'static str) -> Arc<Self> {
let mut loggers = loggers().lock().unwrap();
loggers
.entry(name.to_string())
.or_insert_with(|| Arc::new(Logger::new(name)))
.clone()
}
pub fn root() -> Arc<Self> {
ROOT_LOGGER
.get_or_init(|| Arc::new(Self::new(ROOT_LOGGER_NAME)))
.clone()
}
pub fn handlers(&self) -> Vec<Arc<dyn Handler>> {
self.handlers.read().unwrap().clone()
}
pub fn set_level(&self, level: Level) {
self.level.store(level as u8, Ordering::Relaxed);
}
pub fn level(&self) -> Level {
Level::from_u8(self.level.load(Ordering::Relaxed))
}
pub fn add_handler(&self, handler: impl Handler + 'static) -> Result<(), Box<dyn Error>> {
self.handlers
.write()
.map_err(|_| "Handlers mutex was poisoned")?
.push(Arc::new(handler));
Ok(())
}
pub fn log(&self, level: Level, message: &str) {
if level.to_u8() < self.level.load(Ordering::Relaxed) {
return;
}
let handlers = self
.handlers
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner());
for handler in handlers.iter() {
let formatter = handler.formatter();
handler.handle(&HashMap::from([
("message", message),
("level", level.to_str()),
(
"timestamp",
Local::now()
.format(formatter.time_format)
.to_string()
.as_str(),
),
("name", self.name),
("thread", thread::current().name().unwrap_or("unknown")),
]));
}
}
pub fn debug(&self, message: &str) {
self.log(Level::Debug, message)
}
pub fn info(&self, message: &str) {
self.log(Level::Info, message)
}
pub fn warning(&self, message: &str) {
self.log(Level::Warning, message)
}
pub fn error(&self, message: &str) {
self.log(Level::Error, message)
}
pub fn critical(&self, message: &str) {
self.log(Level::Critical, message)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::handler::StreamHandler;
use std::io::Write;
use std::sync::Arc;
#[derive(Clone)]
struct SharedBuf(Arc<Mutex<Vec<u8>>>);
impl Write for SharedBuf {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
static REGISTRY_LOCK: Mutex<()> = Mutex::new(());
struct RegistryGuard {
_lock: std::sync::MutexGuard<'static, ()>,
saved_map: HashMap<String, Arc<Logger>>,
saved_root_level: Level,
saved_root_handlers: Vec<Arc<dyn Handler>>,
}
impl RegistryGuard {
fn acquire() -> Self {
let lock = REGISTRY_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let saved_map = loggers().lock().unwrap().clone();
let root = Logger::root();
let guard = Self {
_lock: lock,
saved_map,
saved_root_level: root.level(),
saved_root_handlers: root.handlers(),
};
loggers().lock().unwrap().clear();
root.set_level(Level::Info);
*root.handlers.write().unwrap() = Vec::new();
guard
}
}
impl Drop for RegistryGuard {
fn drop(&mut self) {
*loggers().lock().unwrap() = std::mem::take(&mut self.saved_map);
let root = Logger::root();
root.set_level(self.saved_root_level);
*root.handlers.write().unwrap() = std::mem::take(&mut self.saved_root_handlers);
}
}
#[test]
fn get_returns_the_same_cached_instance() {
let _guard = RegistryGuard::acquire();
let first = Logger::get("cached");
let second = Logger::get("cached");
assert!(Arc::ptr_eq(&first, &second));
}
#[test]
fn child_inherits_root_level_at_creation() {
let _guard = RegistryGuard::acquire();
Logger::root().set_level(Level::Error);
let child = Logger::new("child_level");
assert_eq!(child.level(), Level::Error);
}
#[test]
fn child_inherits_root_handlers_at_creation() {
let _guard = RegistryGuard::acquire();
let buf = Arc::new(Mutex::new(Vec::new()));
Logger::root()
.add_handler(StreamHandler::with_pattern(
SharedBuf(buf.clone()),
"%(message)",
))
.unwrap();
let child = Logger::new("child_handlers");
child.set_level(Level::Debug);
child.info("via inherited handler");
let written = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
assert_eq!(written, "via inherited handler\n");
}
#[test]
fn level_get_set_round_trips() {
let _guard = RegistryGuard::acquire();
let logger = Logger::new("test_level");
logger.set_level(Level::Error);
assert_eq!(logger.level(), Level::Error);
}
#[test]
fn messages_below_level_are_dropped() {
let _guard = RegistryGuard::acquire();
let buf = Arc::new(Mutex::new(Vec::new()));
let logger = Logger::new("test_filter");
logger.set_level(Level::Warning);
logger
.add_handler(StreamHandler::with_pattern(
SharedBuf(buf.clone()),
"%(message)",
))
.unwrap();
logger.info("dropped"); logger.warning("kept");
let written = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
assert_eq!(written, "kept\n");
}
#[test]
fn record_includes_level_and_message() {
let _guard = RegistryGuard::acquire();
let buf = Arc::new(Mutex::new(Vec::new()));
let logger = Logger::new("test_record");
logger.set_level(Level::Debug);
logger
.add_handler(StreamHandler::with_pattern(
SharedBuf(buf.clone()),
"%(level) %(name) %(message)",
))
.unwrap();
logger.error("boom");
let written = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
assert_eq!(written, "ERROR test_record boom\n");
}
}