use chrono::Local;
use std::path::PathBuf;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt;
/// Error log level
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ErrorLogLevel {
Debug,
Info,
Notice,
Warn,
Error,
Crit,
Alert,
Emerg,
}
impl ErrorLogLevel {
pub fn as_str(&self) -> &'static str {
match self {
ErrorLogLevel::Debug => "debug",
ErrorLogLevel::Info => "info",
ErrorLogLevel::Notice => "notice",
ErrorLogLevel::Warn => "warn",
ErrorLogLevel::Error => "error",
ErrorLogLevel::Crit => "crit",
ErrorLogLevel::Alert => "alert",
ErrorLogLevel::Emerg => "emerg",
}
}
#[allow(clippy::should_implement_trait)] // infallible parser with a fixed fallback, not std::str::FromStr
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"debug" => ErrorLogLevel::Debug,
"info" => ErrorLogLevel::Info,
"notice" => ErrorLogLevel::Notice,
"warn" | "warning" => ErrorLogLevel::Warn,
"error" => ErrorLogLevel::Error,
"crit" => ErrorLogLevel::Crit,
"alert" => ErrorLogLevel::Alert,
"emerg" | "emergency" => ErrorLogLevel::Emerg,
_ => ErrorLogLevel::Error,
}
}
}
/// Error logger
pub struct ErrorLog {
path: PathBuf,
level: ErrorLogLevel,
buffer: Vec<u8>,
}
impl ErrorLog {
pub fn new(path: PathBuf, level: ErrorLogLevel) -> Self {
Self {
path,
level,
buffer: Vec::with_capacity(2048),
}
}
/// Log a message at the given level
pub async fn log(&mut self, level: ErrorLogLevel, message: &str) {
if level < self.level {
return;
}
let timestamp = Local::now().format("%Y/%m/%d %H:%M:%S");
let pid = std::process::id();
let line = format!("{} [{}] {}: {}", timestamp, level.as_str(), pid, message);
self.buffer.extend_from_slice(line.as_bytes());
self.buffer.push(b'\n');
if self.buffer.len() >= 2048 {
self.flush().await.ok();
}
}
/// Convenience methods
pub async fn debug(&mut self, msg: &str) {
self.log(ErrorLogLevel::Debug, msg).await;
}
pub async fn info(&mut self, msg: &str) {
self.log(ErrorLogLevel::Info, msg).await;
}
pub async fn warn(&mut self, msg: &str) {
self.log(ErrorLogLevel::Warn, msg).await;
}
pub async fn error(&mut self, msg: &str) {
self.log(ErrorLogLevel::Error, msg).await;
}
/// Flush buffer to disk
pub async fn flush(&mut self) -> std::io::Result<()> {
if self.buffer.is_empty() {
return Ok(());
}
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)
.await?;
file.write_all(&self.buffer).await?;
self.buffer.clear();
Ok(())
}
/// Reopen log file (for log rotation)
pub async fn reopen(&mut self) -> std::io::Result<()> {
self.flush().await?;
Ok(())
}
}