baichun_framework_logger/
lib.rs

1// ruoyi-framework-logger/src/lib.rs
2//! Logging module for RuoYi-Rust framework
3
4pub mod appender;
5pub mod config;
6pub mod error;
7
8#[cfg(test)]
9mod tests;
10
11use std::sync::Arc;
12use tracing_subscriber::layer::SubscriberExt;
13use tracing_subscriber::util::SubscriberInitExt;
14
15use crate::{
16    appender::LogAppender,
17    config::{LogFormat, LogLevel, RollingPolicy},
18    error::Result,
19};
20
21use self::appender::rolling::{RollingFileAppender, RollingStrategy};
22
23pub use config::{
24    ConsoleConfig, JsonFormatConfig, LoggerConfig, SizeRollingConfig, TextFormatConfig, TimePeriod,
25    TimeRollingConfig,
26};
27pub use error::LoggerError;
28
29/// 日志管理器
30#[derive(Clone, Debug)]
31pub struct Logger {
32    /// 配置
33    config: config::LoggerConfig,
34    /// 追加器
35    appender: Arc<dyn LogAppender>,
36}
37
38impl Logger {
39    /// 创建日志管理器
40    pub fn new(config: config::LoggerConfig) -> Result<Self> {
41        // 确保日志目录存在
42        std::fs::create_dir_all(&config.dir)?;
43
44        let appender: Arc<dyn LogAppender> = if config.split_by_level {
45            // 创建多个追加器,每个级别一个
46            let mut appenders = Vec::new();
47            for level in [
48                LogLevel::Trace,
49                LogLevel::Debug,
50                LogLevel::Info,
51                LogLevel::Warn,
52                LogLevel::Error,
53            ] {
54                let filename = format!("{}.{:?}", config.filename, level).to_lowercase();
55                let path = config.dir.join(&filename);
56
57                let appender: Arc<dyn LogAppender> = match config.rolling_policy {
58                    RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
59                        path,
60                        RollingStrategy::Time(time_config.clone()),
61                    )?),
62                    RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
63                        path,
64                        RollingStrategy::Size(size_config.clone()),
65                    )?),
66                    RollingPolicy::Compound { ref time, ref size } => {
67                        let time_appender = RollingFileAppender::new(
68                            path.clone(),
69                            RollingStrategy::Time(time.clone()),
70                        )?;
71                        let size_appender =
72                            RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
73                        Arc::new(CompoundAppender::new(time_appender, size_appender)?)
74                    }
75                };
76                appenders.push((level, appender));
77            }
78            Arc::new(LevelSplitAppender::new(appenders))
79        } else {
80            let path = config.dir.join(&config.filename);
81            match config.rolling_policy {
82                RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
83                    path,
84                    RollingStrategy::Time(time_config.clone()),
85                )?),
86                RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
87                    path,
88                    RollingStrategy::Size(size_config.clone()),
89                )?),
90                RollingPolicy::Compound { ref time, ref size } => {
91                    let time_appender = RollingFileAppender::new(
92                        path.clone(),
93                        RollingStrategy::Time(time.clone()),
94                    )?;
95                    let size_appender =
96                        RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
97                    Arc::new(CompoundAppender::new(time_appender, size_appender)?)
98                }
99            }
100        };
101
102        Ok(Self { config, appender })
103    }
104
105    /// 写入日志
106    pub fn write(&self, level: LogLevel, message: &str) -> Result<()> {
107        // 检查日志级别
108        if level as u8 >= self.config.level as u8 {
109            let formatted_message = self.format_message(level, message)?;
110            self.appender.write(level, &formatted_message)?;
111        }
112        Ok(())
113    }
114
115    /// 刷新日志
116    pub fn flush(&self) -> Result<()> {
117        self.appender.flush()
118    }
119
120    /// 格式化日志消息
121    fn format_message(&self, level: LogLevel, message: &str) -> Result<String> {
122        match &self.config.format {
123            LogFormat::Text(config) => {
124                let mut parts = Vec::new();
125
126                // 添加时间
127                let now = chrono::Local::now();
128                parts.push(now.format(&config.time_format).to_string());
129
130                // 添加日志级别
131                if config.show_level {
132                    parts.push(format!("[{}]", level));
133                }
134
135                // 添加线程ID
136                if config.show_thread_id {
137                    let thread = std::thread::current();
138                    parts.push(format!("[Thread-{:?}]", thread.id()));
139                }
140
141                // 添加目标
142                if config.show_target {
143                    parts.push("[ruoyi-logger]".to_string());
144                }
145
146                // 添加文件和行号
147                if config.show_file || config.show_line {
148                    let location = std::panic::Location::caller();
149                    let mut file_info = String::new();
150                    if config.show_file {
151                        file_info.push_str(&format!("[{}]", location.file()));
152                    }
153                    if config.show_line {
154                        file_info.push_str(&format!(":{}", location.line()));
155                    }
156                    parts.push(file_info);
157                }
158
159                // 添加消息
160                parts.push(message.to_string());
161
162                Ok(parts.join(" "))
163            }
164            LogFormat::Json(config) => {
165                use serde_json::json;
166                // 尝试解析消息为JSON
167                let message_json = if message.trim().starts_with('{') {
168                    match serde_json::from_str(message) {
169                        Ok(json) => json,
170                        Err(_) => json!(message),
171                    }
172                } else {
173                    json!(message)
174                };
175
176                let mut log_entry = json!({
177                    "timestamp": chrono::Local::now().format(&config.time_format).to_string(),
178                    "level": format!("{}", level),
179                });
180
181                // 合并消息JSON
182                if let Some(obj) = message_json.as_object() {
183                    for (key, value) in obj {
184                        log_entry[key] = value.clone();
185                    }
186                } else {
187                    log_entry["message"] = message_json;
188                }
189
190                if config.include_thread {
191                    let thread = std::thread::current();
192                    log_entry["thread_id"] = json!(format!("{:?}", thread.id()));
193                }
194
195                if config.include_caller {
196                    let location = std::panic::Location::caller();
197                    log_entry["caller"] = json!({
198                        "file": location.file(),
199                        "line": location.line(),
200                    });
201                }
202
203                if config.pretty {
204                    Ok(serde_json::to_string_pretty(&log_entry)?)
205                } else {
206                    Ok(serde_json::to_string(&log_entry)?)
207                }
208            }
209        }
210    }
211}
212
213/// 复合追加器
214#[derive(Debug)]
215struct CompoundAppender {
216    time_appender: RollingFileAppender,
217    size_appender: RollingFileAppender,
218}
219
220impl CompoundAppender {
221    fn new(time_appender: RollingFileAppender, size_appender: RollingFileAppender) -> Result<Self> {
222        Ok(Self {
223            time_appender,
224            size_appender,
225        })
226    }
227}
228
229impl LogAppender for CompoundAppender {
230    fn write(&self, level: LogLevel, message: &str) -> Result<()> {
231        self.time_appender.write(level, message)?;
232        self.size_appender.write(level, message)
233    }
234
235    fn flush(&self) -> Result<()> {
236        self.time_appender.flush()?;
237        self.size_appender.flush()
238    }
239}
240
241impl Clone for CompoundAppender {
242    fn clone(&self) -> Self {
243        Self {
244            time_appender: self.time_appender.clone(),
245            size_appender: self.size_appender.clone(),
246        }
247    }
248}
249
250/// 按级别分离的追加器
251#[derive(Debug)]
252struct LevelSplitAppender {
253    appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>,
254}
255
256impl LevelSplitAppender {
257    fn new(appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>) -> Self {
258        Self { appenders }
259    }
260}
261
262impl LogAppender for LevelSplitAppender {
263    fn write(&self, level: LogLevel, message: &str) -> Result<()> {
264        // 找到对应级别的追加器
265        if let Some((_, appender)) = self.appenders.iter().find(|(l, _)| *l == level) {
266            appender.write(level, message)?;
267        }
268        Ok(())
269    }
270
271    fn flush(&self) -> Result<()> {
272        for (_, appender) in &self.appenders {
273            appender.flush()?;
274        }
275        Ok(())
276    }
277}
278
279impl Clone for LevelSplitAppender {
280    fn clone(&self) -> Self {
281        Self {
282            appenders: self.appenders.clone(),
283        }
284    }
285}
286
287/// 初始化日志系统
288pub fn init(config: LoggerConfig) -> Result<()> {
289    // 创建日志管理器
290    let logger = Logger::new(config.clone())?;
291
292    // 创建自定义层
293    let layer = LogLayer::new(logger);
294
295    // 设置全局默认订阅者
296    tracing_subscriber::registry()
297        .with(layer)
298        .try_init()
299        .map_err(|e| error::LoggerError::Other(e.to_string()))?;
300
301    Ok(())
302}
303
304/// 自定义tracing层
305#[derive(Debug)]
306struct LogLayer {
307    logger: Logger,
308}
309
310impl LogLayer {
311    fn new(logger: Logger) -> Self {
312        Self { logger }
313    }
314}
315
316impl<S> tracing_subscriber::Layer<S> for LogLayer
317where
318    S: tracing::Subscriber,
319{
320    fn on_event(
321        &self,
322        event: &tracing::Event<'_>,
323        _ctx: tracing_subscriber::layer::Context<'_, S>,
324    ) {
325        // 获取日志级别
326        let level = event.metadata().level();
327        let level = match *level {
328            tracing::Level::ERROR => LogLevel::Error,
329            tracing::Level::WARN => LogLevel::Warn,
330            tracing::Level::INFO => LogLevel::Info,
331            tracing::Level::DEBUG => LogLevel::Debug,
332            tracing::Level::TRACE => LogLevel::Trace,
333        };
334
335        // 获取日志消息
336        let mut message = String::new();
337        let mut visitor = MessageVisitor(&mut message);
338        event.record(&mut visitor);
339
340        // 写入日志
341        if let Err(e) = self.logger.write(level, &message) {
342            eprintln!("Failed to write log: {}", e);
343        }
344    }
345}
346
347/// 消息访问器
348struct MessageVisitor<'a>(&'a mut String);
349
350impl<'a> tracing::field::Visit for MessageVisitor<'a> {
351    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
352        if field.name() == "message" {
353            self.0.push_str(&format!("{:?}", value));
354        }
355    }
356
357    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
358        if field.name() == "message" {
359            self.0.push_str(value);
360        }
361    }
362}