use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[async_trait::async_trait]
pub trait LogHandler: Send + Sync {
async fn handle(&self, record: &LogRecord);
fn level(&self) -> LogLevel;
fn set_level(&mut self, level: LogLevel);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
#[derive(Debug, Clone)]
pub struct LogRecord {
pub level: LogLevel,
pub logger_name: String,
pub message: String,
pub extra: HashMap<String, serde_json::Value>,
}
impl LogRecord {
pub fn new(
level: LogLevel,
logger_name: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
level,
logger_name: logger_name.into(),
message: message.into(),
extra: HashMap::new(),
}
}
pub fn with_extra(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
self.extra.insert(key.into(), value);
self
}
pub fn add_extra(&mut self, key: impl Into<String>, value: serde_json::Value) {
self.extra.insert(key.into(), value);
}
}
pub struct Logger {
name: String,
handlers: Arc<Mutex<Vec<Arc<dyn LogHandler>>>>,
level: Arc<Mutex<LogLevel>>,
}
impl Logger {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
handlers: Arc::new(Mutex::new(Vec::new())),
level: Arc::new(Mutex::new(LogLevel::Debug)),
}
}
pub async fn add_handler(&self, handler: Arc<dyn LogHandler>) {
self.handlers
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(handler);
}
pub async fn set_level(&self, level: LogLevel) {
*self.level.lock().unwrap_or_else(|e| e.into_inner()) = level;
}
pub async fn log_record(&self, record: &LogRecord) {
let handlers: Vec<Arc<dyn LogHandler>> = {
let handlers_guard = self.handlers.lock().unwrap_or_else(|e| e.into_inner());
handlers_guard.clone()
};
for handler in handlers {
handler.handle(record).await;
}
}
async fn log(&self, level: LogLevel, message: impl Into<String>) {
let current_level = *self.level.lock().unwrap_or_else(|e| e.into_inner());
if level < current_level {
return;
}
let record = LogRecord::new(level, &self.name, message);
let handlers: Vec<Arc<dyn LogHandler>> = {
let handlers_guard = self.handlers.lock().unwrap_or_else(|e| e.into_inner());
handlers_guard.clone()
};
for handler in handlers {
handler.handle(&record).await;
}
}
pub async fn debug(&self, message: impl Into<String>) {
self.log(LogLevel::Debug, message).await;
}
pub fn debug_sync(&self, message: impl Into<String>) {
self.log_sync(LogLevel::Debug, message);
}
pub async fn info(&self, message: impl Into<String>) {
self.log(LogLevel::Info, message).await;
}
pub fn info_sync(&self, message: impl Into<String>) {
self.log_sync(LogLevel::Info, message);
}
pub async fn warning(&self, message: impl Into<String>) {
self.log(LogLevel::Warning, message).await;
}
pub fn warning_sync(&self, message: impl Into<String>) {
self.log_sync(LogLevel::Warning, message);
}
fn log_sync(&self, level: LogLevel, message: impl Into<String>) {
let current_level = *self.level.lock().unwrap_or_else(|e| e.into_inner());
if level < current_level {
return;
}
let record = LogRecord::new(level, &self.name, message);
let handlers: Vec<Arc<dyn LogHandler>> = {
let handlers_guard = self.handlers.lock().unwrap_or_else(|e| e.into_inner());
handlers_guard.clone()
};
let rt = tokio::runtime::Handle::try_current().ok().or_else(|| {
tokio::runtime::Runtime::new()
.ok()
.map(|rt| rt.handle().clone())
});
if let Some(handle) = rt {
for handler in handlers.iter() {
handle.block_on(handler.handle(&record));
}
} else {
eprintln!("Warning: No tokio runtime available for synchronous logging");
}
}
pub async fn error(&self, message: impl Into<String>) {
self.log(LogLevel::Error, message).await;
}
pub fn error_sync(&self, message: impl Into<String>) {
self.log_sync(LogLevel::Error, message);
}
}