Ferrit Explore
中文·繁體·EN·日本語 Sign in Register
cielxl / veld / src / log / error.rs
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(())
    }
}