baichun-framework-logger 0.1.0

Logger module for Baichun-Rust framework
Documentation
// ruoyi-framework-logger/src/lib.rs
//! Logging module for RuoYi-Rust framework

pub mod appender;
pub mod config;
pub mod error;

#[cfg(test)]
mod tests;

use std::sync::Arc;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

use crate::{
    appender::LogAppender,
    config::{LogFormat, LogLevel, RollingPolicy},
    error::Result,
};

use self::appender::rolling::{RollingFileAppender, RollingStrategy};

pub use config::{
    ConsoleConfig, JsonFormatConfig, LoggerConfig, SizeRollingConfig, TextFormatConfig, TimePeriod,
    TimeRollingConfig,
};
pub use error::LoggerError;

/// 日志管理器
#[derive(Clone, Debug)]
pub struct Logger {
    /// 配置
    config: config::LoggerConfig,
    /// 追加器
    appender: Arc<dyn LogAppender>,
}

impl Logger {
    /// 创建日志管理器
    pub fn new(config: config::LoggerConfig) -> Result<Self> {
        // 确保日志目录存在
        std::fs::create_dir_all(&config.dir)?;

        let appender: Arc<dyn LogAppender> = if config.split_by_level {
            // 创建多个追加器,每个级别一个
            let mut appenders = Vec::new();
            for level in [
                LogLevel::Trace,
                LogLevel::Debug,
                LogLevel::Info,
                LogLevel::Warn,
                LogLevel::Error,
            ] {
                let filename = format!("{}.{:?}", config.filename, level).to_lowercase();
                let path = config.dir.join(&filename);

                let appender: Arc<dyn LogAppender> = match config.rolling_policy {
                    RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
                        path,
                        RollingStrategy::Time(time_config.clone()),
                    )?),
                    RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
                        path,
                        RollingStrategy::Size(size_config.clone()),
                    )?),
                    RollingPolicy::Compound { ref time, ref size } => {
                        let time_appender = RollingFileAppender::new(
                            path.clone(),
                            RollingStrategy::Time(time.clone()),
                        )?;
                        let size_appender =
                            RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
                        Arc::new(CompoundAppender::new(time_appender, size_appender)?)
                    }
                };
                appenders.push((level, appender));
            }
            Arc::new(LevelSplitAppender::new(appenders))
        } else {
            let path = config.dir.join(&config.filename);
            match config.rolling_policy {
                RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
                    path,
                    RollingStrategy::Time(time_config.clone()),
                )?),
                RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
                    path,
                    RollingStrategy::Size(size_config.clone()),
                )?),
                RollingPolicy::Compound { ref time, ref size } => {
                    let time_appender = RollingFileAppender::new(
                        path.clone(),
                        RollingStrategy::Time(time.clone()),
                    )?;
                    let size_appender =
                        RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
                    Arc::new(CompoundAppender::new(time_appender, size_appender)?)
                }
            }
        };

        Ok(Self { config, appender })
    }

    /// 写入日志
    pub fn write(&self, level: LogLevel, message: &str) -> Result<()> {
        // 检查日志级别
        if level as u8 >= self.config.level as u8 {
            let formatted_message = self.format_message(level, message)?;
            self.appender.write(level, &formatted_message)?;
        }
        Ok(())
    }

    /// 刷新日志
    pub fn flush(&self) -> Result<()> {
        self.appender.flush()
    }

    /// 格式化日志消息
    fn format_message(&self, level: LogLevel, message: &str) -> Result<String> {
        match &self.config.format {
            LogFormat::Text(config) => {
                let mut parts = Vec::new();

                // 添加时间
                let now = chrono::Local::now();
                parts.push(now.format(&config.time_format).to_string());

                // 添加日志级别
                if config.show_level {
                    parts.push(format!("[{}]", level));
                }

                // 添加线程ID
                if config.show_thread_id {
                    let thread = std::thread::current();
                    parts.push(format!("[Thread-{:?}]", thread.id()));
                }

                // 添加目标
                if config.show_target {
                    parts.push("[ruoyi-logger]".to_string());
                }

                // 添加文件和行号
                if config.show_file || config.show_line {
                    let location = std::panic::Location::caller();
                    let mut file_info = String::new();
                    if config.show_file {
                        file_info.push_str(&format!("[{}]", location.file()));
                    }
                    if config.show_line {
                        file_info.push_str(&format!(":{}", location.line()));
                    }
                    parts.push(file_info);
                }

                // 添加消息
                parts.push(message.to_string());

                Ok(parts.join(" "))
            }
            LogFormat::Json(config) => {
                use serde_json::json;
                // 尝试解析消息为JSON
                let message_json = if message.trim().starts_with('{') {
                    match serde_json::from_str(message) {
                        Ok(json) => json,
                        Err(_) => json!(message),
                    }
                } else {
                    json!(message)
                };

                let mut log_entry = json!({
                    "timestamp": chrono::Local::now().format(&config.time_format).to_string(),
                    "level": format!("{}", level),
                });

                // 合并消息JSON
                if let Some(obj) = message_json.as_object() {
                    for (key, value) in obj {
                        log_entry[key] = value.clone();
                    }
                } else {
                    log_entry["message"] = message_json;
                }

                if config.include_thread {
                    let thread = std::thread::current();
                    log_entry["thread_id"] = json!(format!("{:?}", thread.id()));
                }

                if config.include_caller {
                    let location = std::panic::Location::caller();
                    log_entry["caller"] = json!({
                        "file": location.file(),
                        "line": location.line(),
                    });
                }

                if config.pretty {
                    Ok(serde_json::to_string_pretty(&log_entry)?)
                } else {
                    Ok(serde_json::to_string(&log_entry)?)
                }
            }
        }
    }
}

/// 复合追加器
#[derive(Debug)]
struct CompoundAppender {
    time_appender: RollingFileAppender,
    size_appender: RollingFileAppender,
}

impl CompoundAppender {
    fn new(time_appender: RollingFileAppender, size_appender: RollingFileAppender) -> Result<Self> {
        Ok(Self {
            time_appender,
            size_appender,
        })
    }
}

impl LogAppender for CompoundAppender {
    fn write(&self, level: LogLevel, message: &str) -> Result<()> {
        self.time_appender.write(level, message)?;
        self.size_appender.write(level, message)
    }

    fn flush(&self) -> Result<()> {
        self.time_appender.flush()?;
        self.size_appender.flush()
    }
}

impl Clone for CompoundAppender {
    fn clone(&self) -> Self {
        Self {
            time_appender: self.time_appender.clone(),
            size_appender: self.size_appender.clone(),
        }
    }
}

/// 按级别分离的追加器
#[derive(Debug)]
struct LevelSplitAppender {
    appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>,
}

impl LevelSplitAppender {
    fn new(appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>) -> Self {
        Self { appenders }
    }
}

impl LogAppender for LevelSplitAppender {
    fn write(&self, level: LogLevel, message: &str) -> Result<()> {
        // 找到对应级别的追加器
        if let Some((_, appender)) = self.appenders.iter().find(|(l, _)| *l == level) {
            appender.write(level, message)?;
        }
        Ok(())
    }

    fn flush(&self) -> Result<()> {
        for (_, appender) in &self.appenders {
            appender.flush()?;
        }
        Ok(())
    }
}

impl Clone for LevelSplitAppender {
    fn clone(&self) -> Self {
        Self {
            appenders: self.appenders.clone(),
        }
    }
}

/// 初始化日志系统
pub fn init(config: LoggerConfig) -> Result<()> {
    // 创建日志管理器
    let logger = Logger::new(config.clone())?;

    // 创建自定义层
    let layer = LogLayer::new(logger);

    // 设置全局默认订阅者
    tracing_subscriber::registry()
        .with(layer)
        .try_init()
        .map_err(|e| error::LoggerError::Other(e.to_string()))?;

    Ok(())
}

/// 自定义tracing层
#[derive(Debug)]
struct LogLayer {
    logger: Logger,
}

impl LogLayer {
    fn new(logger: Logger) -> Self {
        Self { logger }
    }
}

impl<S> tracing_subscriber::Layer<S> for LogLayer
where
    S: tracing::Subscriber,
{
    fn on_event(
        &self,
        event: &tracing::Event<'_>,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        // 获取日志级别
        let level = event.metadata().level();
        let level = match *level {
            tracing::Level::ERROR => LogLevel::Error,
            tracing::Level::WARN => LogLevel::Warn,
            tracing::Level::INFO => LogLevel::Info,
            tracing::Level::DEBUG => LogLevel::Debug,
            tracing::Level::TRACE => LogLevel::Trace,
        };

        // 获取日志消息
        let mut message = String::new();
        let mut visitor = MessageVisitor(&mut message);
        event.record(&mut visitor);

        // 写入日志
        if let Err(e) = self.logger.write(level, &message) {
            eprintln!("Failed to write log: {}", e);
        }
    }
}

/// 消息访问器
struct MessageVisitor<'a>(&'a mut String);

impl<'a> tracing::field::Visit for MessageVisitor<'a> {
    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if field.name() == "message" {
            self.0.push_str(&format!("{:?}", value));
        }
    }

    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if field.name() == "message" {
            self.0.push_str(value);
        }
    }
}