yan_log/
lib.rs

1pub mod unit;
2
3use crate::unit::{
4    find_log_files, format_u8_as_padded_2_digits, format_u16_as_padded_3_digits,
5    timestamp_ms_to_datetime,
6};
7use proc_tools::concat_vars;
8use proc_tools_core::{concat_str, replace_multiple_patterns};
9use proc_tools_helper::lang_tr;
10use std::cmp::{Ordering, PartialOrd};
11use std::sync::RwLock;
12use std::sync::mpsc::SyncSender;
13use std::thread::sleep;
14use std::time::{Duration, SystemTime, UNIX_EPOCH};
15use std::{
16    fs::{self, OpenOptions},
17    io::Write,
18    path::Path,
19    sync::mpsc::{self},
20    thread::JoinHandle,
21};
22
23/// 日志级别
24#[derive(Clone)]
25pub enum LogLevel {
26    /// 错误
27    Error,
28    /// 警告
29    Warn,
30    /// 信息
31    Info,
32    /// 调试
33    Debug,
34    /// 跟踪
35    Trace,
36}
37
38impl LogLevel {
39    /// 将自定义日志级别转换为 log crate 的 [`log::LevelFilter`]
40    /// - 提供自定义日志级别枚举与标准 log crate 级别过滤器之间的转换。
41    /// - 这用于在设置全局日志级别时与 Rust 生态系统的标准日志库兼容。
42    ///
43    /// # 返回值
44    /// - `log::LevelFilter`: 对应的标准日志级别过滤器
45    ///
46    /// # 转换映射
47    /// - `LogLevel::Error` → `log::LevelFilter::Error`
48    /// - `LogLevel::Warn` → `log::LevelFilter::Warn`
49    /// - `LogLevel::Info` → `log::LevelFilter::Info`
50    /// - `LogLevel::Debug` → `log::LevelFilter::Debug`
51    /// - `LogLevel::Trace` → `log::LevelFilter::Trace`
52    ///
53    /// # 示例
54    /// ```rust,ignore
55    /// let level = yan_log::LogLevel::Info;
56    /// let filter = level.to_level_filter();
57    /// assert_eq!(filter, log::LevelFilter::Info);
58    /// ```
59    ///
60    /// # 命名建议
61    /// 原函数名 `s` 过于简略,建议改为 `to_level_filter`,
62    /// 清晰表达了转换的目标类型和用途。
63    #[inline]
64    pub(crate) fn to_level_filter(&self) -> log::LevelFilter {
65        match self {
66            LogLevel::Error => log::LevelFilter::Error,
67            LogLevel::Warn => log::LevelFilter::Warn,
68            LogLevel::Info => log::LevelFilter::Info,
69            LogLevel::Debug => log::LevelFilter::Debug,
70            LogLevel::Trace => log::LevelFilter::Trace,
71        }
72    }
73}
74
75/// 日志消息结构
76pub(crate) struct LogMessage {
77    /// 格式化后的日志消息内容
78    formatted: String,
79    /// 日志记录的日期时间
80    now: (u32, u8, u8, u8, u8, u8, u16),
81}
82
83impl LogMessage {
84    /// 创建新的日志消息实例
85    /// - 使用当前时间戳和提供的参数初始化日志消息。
86    ///
87    /// # 参数
88    /// - `level`: 日志级别
89    /// - `module_path`: 模块路径标识
90    /// - `message`: 日志消息内容
91    ///
92    /// # 返回值
93    /// - `LogMessage`: 初始化完成的日志消息实例
94    ///
95    /// # 示例
96    /// ```rust,ignore
97    /// let message = yan_log::LogMessage::new(
98    ///     LogLevel::Info,
99    ///     std::sync::Arc::from("my_module"),
100    ///     "这是一条测试消息".to_string()
101    /// );
102    /// ```
103    #[inline]
104    fn new(level: LogLevel, module_path: std::sync::Arc<str>, message: String) -> Self {
105        let mut timestamp = SystemTime::now()
106            .duration_since(UNIX_EPOCH)
107            .unwrap()
108            .as_millis();
109        timestamp += 28_800_000;
110        let (year, month, day, hour, minute, second, millis) = timestamp_ms_to_datetime(timestamp);
111
112        const HYPHEN: &str = "-";
113        const COLON: &str = ":";
114        let mut bytes = [0u8; 3];
115        let month_buf = format_u8_as_padded_2_digits(month, &mut bytes);
116        let mut bytes = [0u8; 3];
117        let day_buf = format_u8_as_padded_2_digits(day, &mut bytes);
118        let mut bytes = [0u8; 3];
119        let hour_buf = format_u8_as_padded_2_digits(hour, &mut bytes);
120        let mut bytes = [0u8; 3];
121        let minute_buf = format_u8_as_padded_2_digits(minute, &mut bytes);
122        let mut bytes = [0u8; 3];
123        let second_buf = format_u8_as_padded_2_digits(second, &mut bytes);
124        let mut bytes = [b'0'; 5];
125        let millis_buf = format_u16_as_padded_3_digits(millis, &mut bytes);
126        let level_str = match level {
127            LogLevel::Error => "ERROR",
128            LogLevel::Warn => " WARN",
129            LogLevel::Info => " INFO",
130            LogLevel::Debug => "DEBUG",
131            LogLevel::Trace => "TRACE",
132        };
133        let formatted = concat_vars!(
134            "[":String,
135            year :u32,
136            HYPHEN : String,
137            month_buf : String,
138            HYPHEN : String,
139            day_buf : String,
140            " " : String,
141            hour_buf : String,
142            COLON : String,
143            minute_buf : String,
144            COLON : String,
145            second_buf : String,
146            "." : String,
147            millis_buf : String,
148            "]_[" : String,
149            level_str : String,
150            "]_[" : String,
151            module_path : String,
152            "] - ": String,
153            message: String,
154            "\n" : String
155        );
156        LogMessage {
157            formatted,
158            now: (year, month, day, hour, minute, second, millis),
159        }
160    }
161}
162
163/// 全局日志消息发送器及其处理线程的实例。
164static LOG_BACKEND: RwLock<Option<(SyncSender<LogMessage>, JoinHandle<()>)>> = RwLock::new(None);
165/// 初始化日志发送器和日志处理线程
166/// - 设置日志文件目录,创建日志文件,并启动后台线程处理日志消息。
167/// - 此函数是日志系统的核心初始化方法。
168///
169/// # 参数
170/// - `logger_format`: 日志格式配置,包含目录路径、文件名、时间分割规则等
171///
172/// # 处理流程
173/// 1. 确保日志目录存在,不存在则创建
174/// 2. 打开或创建日志文件
175/// 3. 创建同步通道用于日志消息传递
176/// 4. 启动后台线程处理日志消息
177/// 5. 将发送器和线程句柄存储到全局静态变量
178///
179/// # 日志分割规则
180/// - 根据配置的时间分割规则(年、月、日等)自动创建新的日志文件
181/// - 根据配置的日志大小分割规则,自动创建新的日志文件
182///
183/// # 错误处理
184/// - 日志写入失败会触发eprintln,打印错误信息
185///
186/// # 注意事项
187/// - 此函数只需在应用程序启动时调用一次
188/// - 重复调用会导致资源泄漏或panic
189/// - 默认情况,在 dev 模式会打印日志到控制台,release模式,不会打印日志到控制台
190/// - 开启 stdout 特性时,release模式,会和 dev 模式相同打印日志到控制台
191#[inline(always)]
192fn init_log_backend(mut logger_format: LoggerFormat) -> Result<(), std::io::Error> {
193    // 确保日志目录存在
194    let log_dir = Path::new(&*logger_format.dir_path);
195    let msg_str = lang_tr!(
196        cn = "创建日志目录失败,错误信息:",
197        en = "Failed to create log directory, error message:",
198    );
199    if !log_dir.exists() {
200        fs::create_dir_all(log_dir).map_err(|err| {
201            std::io::Error::new(err.kind(), concat_str!(msg_str, &err.to_string()))
202        })?;
203    }
204    let file_path: String = concat_str!(&*logger_format.dir_path, "/", &*logger_format.file_name);
205
206    let result = OpenOptions::new()
207        .create(true)
208        .write(true)
209        .truncate(true)
210        .open(file_path);
211    let msg_str = lang_tr!(
212        cn = "打开日志文件失败,错误信息:",
213        en = "Failed to open log file, Exception message:"
214    );
215    let mut log_file =
216        result.map_err(|e| std::io::Error::new(e.kind(), concat_str!(msg_str, &e.to_string())))?;
217    logger_format.file_size = if let Ok(v) = log_file.metadata() {
218        v.len()
219    } else {
220        0
221    };
222    let mut is_create_file = false;
223    let (sender, receiver) = mpsc::sync_channel::<LogMessage>(logger_format.bound as usize);
224    let handle = std::thread::spawn(move || {
225        while let Ok(msg) = receiver.recv() {
226            // 按时间分割日志文件
227            if logger_format.should_split_by_time(&msg.now) {
228                // 创建新的日志文件
229                logger_format = logger_format.update_filename_for_time(msg.now);
230                let file = OpenOptions::new()
231                    .create(true)
232                    .write(true)
233                    .truncate(true)
234                    .open(&*logger_format.file_path);
235                let msg_str = lang_tr!(
236                    cn = "打开日志文件失败,错误信息:",
237                    en = "Failed to open log file, error message:"
238                );
239                match file {
240                    Ok(v) => log_file = v,
241                    Err(e) => eprintln!("{}{}", msg_str, e),
242                }
243                // 更新日期
244                logger_format.datetime = msg.now;
245                logger_format.file_size = if let Ok(v) = log_file.metadata() {
246                    v.len()
247                } else {
248                    0
249                };
250                logger_format.index = 0;
251                is_create_file = true;
252            }
253            // 按文件大小分割日志文件
254            if logger_format.max_file_triggering_policy != 0
255                && logger_format.file_size > logger_format.max_file_triggering_policy
256            {
257                // 创建新的日志文件
258                logger_format = logger_format.update_filename_for_filesize(msg.now);
259                let file = OpenOptions::new()
260                    .create(true)
261                    .write(true)
262                    .truncate(true)
263                    .open(&*logger_format.file_path);
264                let msg_str = lang_tr!(
265                    cn = "打开日志文件失败,错误信息:",
266                    en = "Failed to open log file, error message:"
267                );
268                match file {
269                    Ok(v) => log_file = v,
270                    Err(e) => eprintln!("{}{}", msg_str, e),
271                }
272                logger_format.file_size = if let Ok(v) = log_file.metadata() {
273                    v.len()
274                } else {
275                    0
276                };
277                is_create_file = true;
278            };
279            // 当创建新日志文件时,删除旧文件
280            if is_create_file && logger_format.max_retained_files != 0 {
281                let result = prune_old_logs(&mut logger_format);
282                if let Err((msg, Some(e))) = result {
283                    eprintln!("{}{}", msg, e);
284                } else if let Err((msg, None)) = result {
285                    eprintln!("{}", msg);
286                }
287                sleep(Duration::from_secs(5));
288                is_create_file = false;
289            }
290            // 打印日志到控制台
291            // 检查 stdout 特性
292            #[cfg(feature = "stdout")]
293            print!("{}", msg.formatted);
294            // 未开启 stdout 时,仅在 debug 模式下打印
295            #[cfg(all(not(feature = "stdout"), debug_assertions))]
296            print!("{}", msg.formatted);
297
298            // 写入日志文件
299            match write!(log_file, "{}", msg.formatted) {
300                Ok(_) => logger_format.file_size += msg.formatted.len() as u64,
301                Err(e) => eprintln!(
302                    "{}{}",
303                    lang_tr!(
304                        cn = "写入日志文件失败:",
305                        en = "Writing to log file failed:"
306                    ),
307                    e
308                ),
309            };
310            // 刷新文件确保写入
311            let _ = log_file.flush();
312        }
313    });
314    let new_backend = (sender, handle);
315    LOG_BACKEND.write().unwrap().replace(new_backend);
316    Ok(())
317}
318
319#[inline(always)]
320fn prune_old_logs(logger_format: &mut LoggerFormat) -> Result<(), (&str, Option<std::io::Error>)> {
321    let path_vec_result = find_log_files(
322        logger_format.dir_path.as_ref(),
323        logger_format.file_pattern.as_ref(),
324    );
325    let mut path_vec = match path_vec_result {
326        Ok(v) => v,
327        Err(e) => {
328            let msg = lang_tr!(cn = "删除日志文件失败:", en = "Failed to delete log file:");
329            return Err((msg, Some(e)));
330        }
331    };
332    // 没有超过指定最大日志文件数量时直接返回
333    if path_vec.len() as u64 <= logger_format.max_retained_files {
334        return Ok(());
335    }
336    // 将超出数量的日志文件从旧到新开始删除
337    let n = path_vec.len() as u64 - logger_format.max_retained_files;
338    let mut i = 0;
339    while i < n {
340        match path_vec.pop() {
341            None => {
342                let msg = lang_tr!(
343                    cn = "日志异常,path_vec删除最后一项元素失败",
344                    en = "Log exception, path_cec failed to delete the last element"
345                );
346                return Err((msg, None));
347            }
348            Some(v) => match fs::remove_file(v) {
349                Ok(_) => {}
350                Err(e) => {
351                    let msg =
352                        lang_tr!(cn = "删除日志文件失败:", en = "Failed to delete log file:");
353                    return Err((msg, Some(e)));
354                }
355            },
356        }
357        i += 1;
358    }
359    Ok(())
360}
361
362/// 日志记录器结构体
363pub struct Logger {
364    /// 模块路径标识,用于标识日志来源
365    module_path: &'static str,
366    /// 当前日志记录器的过滤级别
367    log_level: LogLevel,
368}
369
370impl PartialEq for LogLevel {
371    fn eq(&self, other: &Self) -> bool {
372        match (self, other) {
373            (LogLevel::Error, LogLevel::Error)
374            | (LogLevel::Warn, LogLevel::Warn)
375            | (LogLevel::Info, LogLevel::Info)
376            | (LogLevel::Debug, LogLevel::Debug)
377            | (LogLevel::Trace, LogLevel::Trace) => true,
378            _ => false,
379        }
380    }
381}
382
383impl PartialOrd for LogLevel {
384    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
385        // 定义级别优先级(数字越大表示级别越高)
386        let priority = |level: &LogLevel| match level {
387            LogLevel::Error => 4,
388            LogLevel::Warn => 3,
389            LogLevel::Info => 2,
390            LogLevel::Debug => 1,
391            LogLevel::Trace => 0,
392        };
393        priority(self).partial_cmp(&priority(other))
394    }
395}
396
397impl Logger {
398    /// 初始化日志记录器实例
399    /// - 创建一个新的日志记录器实例,指定模块路径和日志级别。
400    /// - 此方法仅创建实例,要启用全局日志系统需要调用 [`Logger::init`] 方法。
401    /// - 可用于设置单独某个模块的日志级别
402    ///
403    /// # 参数
404    /// - `module_path`: 当前模块的路径标识,用于日志记录中的来源标识
405    /// - `log_level`: 日志记录器的过滤级别
406    ///
407    /// # 返回值
408    /// - `Logger`: 初始化完成的日志记录器实例
409    ///
410    /// # 示例
411    /// ```
412    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
413    /// log.debug("Debug information");
414    /// yan_log::Logger::shutdown();
415    /// ```
416    #[inline]
417    pub const fn new(module_path: &'static str, log_level: LogLevel) -> Self {
418        Logger {
419            module_path,
420            log_level,
421        }
422    }
423
424    /// 初始化全局日志系统
425    /// - 搭配 log 日志门面框架使用
426    /// - 设置日志发送器和全局日志级别,使日志系统开始工作
427    /// - 此方法只需在应用程序启动时调用一次
428    ///
429    /// # 参数
430    /// - `dir_path`: 日志文件存储目录路径
431    /// - `level`: 全局日志级别过滤设置
432    ///
433    /// # 注意事项
434    /// - 必须在创建任何日志记录器实例之前调用
435    /// - 如果未调用此方法,日志将无法正常记录
436    /// - 设置全局日志级别会影响所有日志记录器
437    ///
438    /// # 示例
439    /// ```
440    /// yan_log::Logger::init("logs", yan_log::LogLevel::Debug)
441    ///     .start()
442    ///     .unwrap();
443    /// log::info!("Application started");
444    /// yan_log::Logger::shutdown();
445    /// ```
446    #[inline(always)]
447    pub fn init(dir_path: &str, level: LogLevel) -> LoggerFormat {
448        let mut timestamp = SystemTime::now()
449            .duration_since(UNIX_EPOCH)
450            .unwrap()
451            .as_millis();
452        timestamp += 28_800_000;
453        let (year, month, day, hour, minute, second, millis) = timestamp_ms_to_datetime(timestamp);
454        LoggerFormat {
455            dir_path: Box::from(dir_path),
456            file_name: Box::from("application.log"),
457            file_path: Box::from(concat_str!(dir_path, "/", "application.log")),
458            file_pattern: Box::from(""),
459            level,
460            max_file_triggering_policy: 0,
461            bound: 100,
462            file_size: 0,
463            index: 0,
464            time_division_rule: LoggerFormatTimeDivisionRule::None,
465            datetime: (year, month, day, hour, minute, second, millis),
466            max_retained_files: 0,
467        }
468    }
469
470    /// 关闭日志系统并等待日志线程结束
471    /// - 优雅地关闭日志系统,发送关闭信号给日志线程并等待其完成。
472    /// - 建议在应用程序主线程结束前调用此方法以确保所有日志都被处理。
473    ///
474    /// # 注意事项
475    /// - 调用此方法后,日志系统将无法继续使用
476    /// - 会阻塞当前线程直到日志线程完全退出
477    /// - 如果不调用此方法,日志线程可能无法正常退出
478    ///
479    /// # 示例
480    /// ```
481    /// fn main(){
482    ///    // 应用程序运行期间...
483    ///
484    ///    // 在应用程序退出前执行shutdown,确保日志正常记录
485    ///    yan_log::Logger::shutdown();
486    /// }
487    /// ```
488    #[inline(always)]
489    pub fn shutdown() {
490        let log_backend = LOG_BACKEND.write().unwrap().take();
491        match log_backend {
492            None => return,
493            Some((sender, handle)) => {
494                drop(sender); // 关闭通道,触发线程退出
495                handle.join().unwrap(); // 等待线程结束
496            }
497        };
498    }
499
500    /// 记录信息级别日志
501    ///
502    /// # 参数
503    /// - `message`: 日志消息内容,可以是任何能转换为字符串的类型
504    ///
505    /// # 示例
506    /// ```
507    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
508    /// log.info("应用程序启动完成");
509    /// ```
510    pub fn info<T: Into<String>>(&self, message: T) {
511        self.log(LogLevel::Info, message);
512    }
513
514    /// 记录调试级别日志
515    ///
516    /// # 参数
517    /// - `message`: 调试日志消息内容
518    ///
519    /// # 示例
520    /// ```
521    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
522    /// log.debug("进入数据处理函数");
523    /// ```
524    pub fn debug<T: Into<String>>(&self, message: T) {
525        self.log(LogLevel::Debug, message);
526    }
527
528    /// 记录警告级别日志
529    ///
530    /// # 参数
531    /// - `message`: 调试日志消息内容
532    ///
533    /// # 示例
534    /// ```
535    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
536    /// log.warn("磁盘空间不足");
537    /// ```
538    pub fn warn<T: Into<String>>(&self, message: T) {
539        self.log(LogLevel::Warn, message);
540    }
541
542    /// 记录错误级别日志
543    ///
544    /// # 参数
545    /// - `message`: 错误日志消息内容
546    ///
547    /// # 示例
548    /// ```
549    ///
550    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
551    /// log.error("数据库连接失败");
552    /// ```
553    pub fn error<T: Into<String>>(&self, message: T) {
554        self.log(LogLevel::Error, message);
555    }
556
557    /// 记录跟踪级别日志
558    ///
559    /// # 参数
560    /// - `message`: 跟踪日志消息内容
561    ///
562    /// # 示例
563    /// ```
564    /// let log = yan_log::Logger::new(module_path!(), yan_log::LogLevel::Debug);
565    /// log.trace("函数内部变量值: x = 42");
566    /// ```
567    pub fn trace<T: Into<String>>(&self, message: T) {
568        self.log(LogLevel::Trace, message);
569    }
570
571    /// 内部日志记录方法
572    /// - 实际的日志记录实现,将日志消息发送到日志线程处理。
573    ///
574    /// # 参数
575    /// - `level`: 日志级别
576    /// - `message`: 日志消息内容
577    ///
578    /// # 错误处理
579    /// - 如果日志发送失败,会将错误信息打印到标准错误记录
580    /// - 如果日志系统未初始化,会提示初始化异常
581    #[inline]
582    fn log<T: Into<String>>(&self, level: LogLevel, message: T) {
583        if level < self.log_level {
584            return;
585        }
586        let log_backend = LOG_BACKEND.read().unwrap();
587        let option = log_backend.as_ref();
588
589        if let Some((sender, _)) = option {
590            if let Err(e) = sender.send(LogMessage::new(
591                level,
592                std::sync::Arc::from(self.module_path),
593                message.into(),
594            )) {
595                let msg_str = lang_tr!(
596                    cn = "日志记录失败,错误信息:",
597                    en = "Logging failed with error message:"
598                );
599                eprintln!("{}{}", msg_str, e);
600            };
601        } else {
602            let msg_str = lang_tr!(
603                cn = "日志记录失败,日志未初始化或已关闭",
604                en = "Log output failed, Log not initialized or closed"
605            );
606            eprintln!("{}", msg_str);
607        };
608    }
609}
610impl log::Log for Logger {
611    /// 检查是否启用指定级别的日志记录
612    /// - 根据当前日志记录器配置的日志级别,判断是否应该记录给定元数据对应的日志。
613    ///
614    /// # 参数
615    /// - `metadata`: 日志元数据,包含日志级别和目标信息
616    ///
617    /// # 返回值
618    /// - `bool`: 是否启用该级别日志的记录
619    ///   - `true`: 日志级别在当前配置级别范围内,允许记录
620    ///   - `false`: 日志级别低于当前配置级别,不记录
621    #[inline]
622    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
623        metadata.level() <= self.log_level.to_level_filter()
624    }
625
626    /// 记录日志消息
627    /// - 实现 [`log::Log`] trait的核心方法,将标准log记录转换为内部日志格式并发送到日志线程。
628    /// - 此方法由log门面自动调用,通常不应直接调用。
629    ///
630    /// # 参数
631    /// - `record`: 日志记录,包含级别、消息、模块路径等信息
632    ///
633    /// # 处理流程
634    /// 1. 将log::Level转换为内部[`LogLevel`]
635    /// 2. 提取模块路径或使用默认值
636    /// 3. 获取发送器锁并发送日志消息
637    ///
638    /// # 错误处理
639    /// - 如果日志发送失败,会将错误信息打印到标准错误记录
640    /// - 如果日志系统未初始化,消息会被静默丢弃
641    ///
642    /// # 注意事项
643    /// - 此方法会在日志记录时获取全局锁,可能影响性能
644    /// - 在性能敏感的场景中应谨慎使用高频率日志
645    fn log(&self, record: &log::Record<'_>) {
646        let level = match record.level() {
647            log::Level::Error => LogLevel::Error,
648            log::Level::Warn => LogLevel::Warn,
649            log::Level::Info => LogLevel::Info,
650            log::Level::Debug => LogLevel::Debug,
651            log::Level::Trace => LogLevel::Trace,
652        };
653        let module_path: std::sync::Arc<str> =
654            std::sync::Arc::from(record.module_path().unwrap_or_else(|| "None"));
655        let log_backend = LOG_BACKEND.read().unwrap();
656        let option = log_backend.as_ref();
657        if let Some((sender, _)) = option {
658            if let Err(e) = sender.send(LogMessage::new(
659                level,
660                module_path,
661                record.args().to_string(),
662            )) {
663                let msg = lang_tr!(
664                    cn = "日志记录失败,错误信息:",
665                    en = "Logging failed, error message:"
666                );
667                eprintln!("{}{}", msg, e);
668            };
669        }
670    }
671
672    /// 刷新日志缓冲区
673    /// - 实现 [`log::Log`] trait的要求方法,确保所有缓冲的日志消息被写入目标。
674    /// - 在当前实现中,由于使用通道异步处理,此方法为空实现。
675    ///
676    /// # 说明
677    /// - 当前日志系统使用通道进行异步日志处理,不需要手动刷新
678    /// - 如果需要确保日志立即写入,请考虑使用同步日志实现
679    /// - 此方法为满足trait要求而存在,实际不执行任何操作
680    fn flush(&self) {}
681}
682
683/// 日志文件时间分割规则枚举
684pub struct LoggerFormat {
685    /// 日志文件目录
686    dir_path: Box<str>,
687    /// 日志文件名
688    file_name: Box<str>,
689    /// 日志文件路径
690    file_path: Box<str>,
691    /// 日志文件格式
692    file_pattern: Box<str>,
693    /// 日志级别
694    level: LogLevel,
695    /// 日志日期
696    datetime: (u32, u8, u8, u8, u8, u8, u16),
697    /// 日志文件时间分割规则
698    time_division_rule: LoggerFormatTimeDivisionRule,
699    /// 日志文件大小触发策略,为0时默认不触发
700    max_file_triggering_policy: u64,
701    /// 日志通道容量
702    bound: u32,
703    /// 日志文件大小
704    file_size: u64,
705    /// 当前日志文件索引
706    index: u64,
707    /// 最大保留日志文件数,为0时默认不触发删除文件
708    max_retained_files: u64,
709}
710
711/// 日志文件时间分割规则枚举
712/// - 定义日志文件按时间维度进行分割的不同策略,用于自动管理日志文件的创建和轮转。
713/// - 根据不同的时间粒度,可以按年、月、日来组织日志文件。
714pub enum LoggerFormatTimeDivisionRule {
715    /// 不按时间分割
716    None,
717    /// 按年份分割日志文件,每年创建一个新文件
718    Year,
719    /// 按月份分割日志文件,每月创建一个新文件
720    Month,
721    /// 按日期分割日志文件,每天创建一个新文件
722    Day,
723    /// 按日期分割日志文件,每小时创建一个新文件
724    Hours,
725}
726
727impl LoggerFormat {
728    /// 根据当前时间设置日志文件名
729    /// - 根据提供的时间信息和文件模式,生成具体的日志文件名
730    ///
731    /// # 参数
732    /// - `self`: [`LoggerFormat`] 实例
733    /// - `today`: 当前时间,用于生成基于时间的文件名
734    ///
735    /// # 返回值
736    /// - `Self`: 更新文件名后的 [`LoggerFormat`] 实例
737    ///
738    /// # 示例
739    /// ```rust,ignore
740    /// let mut log_fmt = yan_log::Logger::init("logs", yan_log::LogLevel::Debug)
741    ///     .set_file_pattern("app_%Y-%m-%d.log", yan_log::LoggerFormatTimeDivisionRule::Day);
742    /// let mut timestamp = std::time::SystemTime::now()
743    ///     .duration_since(std::time::UNIX_EPOCH)
744    ///     .unwrap()
745    ///     .as_millis();
746    /// timestamp += 28_800_000;
747    /// let now = yan_log::util::timestamp_to_datetime(timestamp);
748    /// let log_fmt = log_fmt.update_filename_for_time(now);
749    /// ```
750    /// # 示例
751    /// 如果文件模式为 "app_%Y-%m-%d %H:%M:%S-%i.log",当前时间为 2023-10-25 10:19:12,索引为 1,
752    /// 则生成的文件名为 "app_2023-10-25 10:19:12-1"
753    #[inline]
754    pub(crate) fn update_filename_for_time(
755        mut self,
756        today: (u32, u8, u8, u8, u8, u8, u16),
757    ) -> Self {
758        let new_file_name = self.get_new_file_name(today);
759        self.file_name = Box::from(new_file_name);
760        self.file_path = Box::from(concat_str!(&*self.file_pattern, "/", &*self.file_name));
761        self
762    }
763
764    /// 根据文件大小更新日志文件名
765    /// - 当日志文件达到大小限制时,创建新的日志文件并更新索引。
766    /// - 如果日期发生变化,则重置文件索引为0。
767    ///
768    /// # 参数
769    /// - `self`: [`LoggerFormat`] 实例
770    /// - `today`: 当前时间组件元组
771    ///
772    /// # 返回值
773    /// - `Self`: 更新文件名、文件路径和索引后的 [`LoggerFormat`] 实例
774    ///
775    /// # 处理逻辑
776    /// - 如果日期时间变化,重置索引为0
777    /// - 如果日期不变,索引递增
778    /// - 根据新的时间和索引生成文件名
779    #[inline]
780    pub(crate) fn update_filename_for_filesize(
781        mut self,
782        today: (u32, u8, u8, u8, u8, u8, u16),
783    ) -> Self {
784        self.index = self.index.checked_add(1).unwrap_or(0);
785        let new_file_name = self.get_new_file_name(today);
786        self.file_name = Box::from(new_file_name);
787        self.file_path = Box::from(concat_str!(&*self.dir_path, "/", &*self.file_name));
788        self
789    }
790
791    /// 根据规则获取新日志文件名
792    fn get_new_file_name(&self, today: (u32, u8, u8, u8, u8, u8, u16)) -> String {
793        let mut buf = [0u8; 3];
794        let month = format_u8_as_padded_2_digits(today.1, &mut buf);
795        let mut buf = [0u8; 3];
796        let day = format_u8_as_padded_2_digits(today.2, &mut buf);
797        let mut buf = [0u8; 3];
798        let hour = format_u8_as_padded_2_digits(today.3, &mut buf);
799        let mut buf = [0u8; 3];
800        let minutes = format_u8_as_padded_2_digits(today.4, &mut buf);
801        let mut buf = [0u8; 3];
802        let seconds = format_u8_as_padded_2_digits(today.5, &mut buf);
803        replace_multiple_patterns(
804            &*self.file_pattern,
805            &[
806                ("%Y", &concat_vars!(today.0 : u32)),
807                ("%m", &concat_vars!(month : String)),
808                ("%d", &concat_vars!(day : String)),
809                ("%H", &concat_vars!(hour : String)),
810                ("%M", &concat_vars!(minutes : String)),
811                ("%S", &concat_vars!(seconds : String)),
812                ("%i", &concat_vars!(self.index : u64)),
813            ],
814        )
815    }
816
817    /// 检查是否应该根据时间规则分割日志文件
818    ///
819    /// # 参数
820    /// - `now`: 待记录的日志时间
821    ///
822    /// # 返回值
823    /// - `bool`: 是否需要创建新的日志文件
824    ///   - `true`: 需要按时间分割,创建新文件
825    ///   - `false`: 不需要分割,继续使用当前文件
826    ///
827    /// # 分割规则说明
828    /// - `None`: 不按时间分割,始终返回 `false`
829    /// - `Year`: 当年份不同时分割
830    /// - `Month`: 当年份或月份不同时分割
831    /// - `Day`: 当年份、月份或日期不同时分割
832    /// - `Hours`: 当年份、月份、日期或小时不同时分割
833    #[inline]
834    pub(crate) fn should_split_by_time(&self, now: &(u32, u8, u8, u8, u8, u8, u16)) -> bool {
835        match self.time_division_rule {
836            LoggerFormatTimeDivisionRule::None => false,
837            LoggerFormatTimeDivisionRule::Year => now.0 != self.datetime.0,
838            LoggerFormatTimeDivisionRule::Month => {
839                now.0 != self.datetime.0 || now.1 != self.datetime.1
840            }
841            LoggerFormatTimeDivisionRule::Day => {
842                now.0 != self.datetime.0 || now.1 != self.datetime.1 || now.2 != self.datetime.2
843            }
844            LoggerFormatTimeDivisionRule::Hours => {
845                now.0 != self.datetime.0
846                    || now.1 != self.datetime.1
847                    || now.2 != self.datetime.2
848                    || now.3 != self.datetime.3
849            }
850        }
851    }
852
853    /// 设置固定的日志文件名
854    /// - 直接指定日志文件名,不使用文件分割规则。
855    /// - 设置后将覆盖之前通过 [`LoggerFormat::set_file_pattern`] 设置文件模式生成的文件名。
856    /// - 如果使用了 [`LoggerFormat::set_max_file_triggering_policy`] 设置日志文件大小触发策略,在文件达到指定大小时,会覆盖原日志。
857    ///
858    /// # 参数
859    /// - `self`: [`LoggerFormat`] 实例
860    /// - `file_name`: 要设置的固定文件名
861    ///
862    /// # 返回值
863    /// - `Self`: 更新文件名后的 [`LoggerFormat`] 实例
864    #[inline]
865    pub fn set_file_name(mut self, file_name: &str) -> Self {
866        self.time_division_rule = LoggerFormatTimeDivisionRule::None;
867        self.file_name = Box::from(file_name);
868        self.file_path = Box::from(concat_str!(&*self.file_pattern, "/", &*self.file_name));
869        self
870    }
871
872    /// 设置日志消息通道的缓冲区大小
873    /// - 当通道中的日志消息数量达到边界值时,新的日志发送操作将会阻塞,直到有空间可用。
874    ///
875    /// # 参数
876    /// - `bound`: 通道缓冲区容量,表示可以排队等待处理的日志消息数量
877    ///
878    /// # 返回值
879    /// - `Self`: 返回修改后的配置对象,支持链式调用
880    ///
881    /// # 性能影响
882    /// - 较小的值:减少内存使用,但在高日志量时可能导致发送阻塞
883    /// - 较大的值:提高吞吐量,但会增加内存占用和潜在的消息延迟
884    ///
885    /// # 示例
886    /// ```
887    /// let format = yan_log::Logger::init("logs", yan_log::LogLevel::Debug)
888    ///     .set_bound(200)  // 设置通道容量为200
889    /// ```
890    ///
891    /// # 注意事项
892    /// - 边界值为0时,通道变为同步通道(每次发送都会阻塞直到接收)
893    /// - 在高并发场景中建议适当增大边界值
894    /// - 边界值过大会增加内存占用和消息处理延迟
895    #[inline]
896    pub fn set_bound(mut self, bound: u32) -> Self {
897        self.bound = bound;
898        self
899    }
900
901    /// 设置日志文件大小触发拆分文件策略
902    /// - 配置当日志文件达到指定大小时自动创建新文件的策略。
903    /// - 设置后会立即根据当前时间更新文件名并重置索引。
904    ///
905    /// # 参数
906    /// - `self`: [`LoggerFormat`] 实例
907    /// - `max_file_triggering_policy`: 最大文件大小(字节),超过此大小会触发文件分割
908    ///
909    /// # 返回值
910    /// - `Self`: 更新大小策略和文件名后的 [`LoggerFormat`] 实例
911    ///
912    /// # 说明
913    /// - 值为0表示禁用文件大小分割
914    /// - 非零值表示单个日志文件的最大字节数
915    /// - 达到大小时会创建新文件,文件名中的索引会递增
916    ///
917    /// # 示例
918    /// ```
919    /// let mut log_fmt = yan_log::Logger::init("logs", yan_log::LogLevel::Debug);
920    /// // 1024B * 1024 = 1048576B = 1024KB = 1MB
921    /// // 1048576B * 100 = 104857600B = 100MB
922    /// let format = log_fmt.set_max_file_triggering_policy(104857600); // 设置100MB的触发策略
923    /// ```
924    #[inline]
925    pub fn set_max_file_triggering_policy(mut self, max_file_triggering_policy: u64) -> Self {
926        self.max_file_triggering_policy = max_file_triggering_policy;
927        let datetime = self.datetime.clone();
928        let mut log_fmt = self.update_filename_for_filesize(datetime);
929        log_fmt.index = 0;
930        log_fmt
931    }
932
933    /// 设置日志文件命名模式和时间分割规则
934    ///
935    /// # 参数
936    /// - `self`: [`LoggerFormat`] 实例
937    /// - `file_pattern`: 文件命名模式,支持的模式占位符:
938    ///   - `%Y`: 四位年份
939    ///   - `%m`: 两位月份(01-12)
940    ///   - `%d`: 两位日期(01-31)
941    ///   - `%H`: 两位小时(00-23)
942    ///   - `%M`: 两位分钟(00-59)
943    ///   - `%S`: 两位秒数(00-59)
944    ///   - `%i`: 文件索引号
945    /// - `time_division_rule`: 明确的时间分割规则
946    ///
947    /// # 返回值
948    /// - `Self`: 更新文件模式和时间分割规则后的 [`LoggerFormat`] 实例
949    ///
950    /// # 示例
951    /// ```
952    /// let mut log_fmt = yan_log::Logger::init("logs", yan_log::LogLevel::Debug);
953    /// let log_fmt = log_fmt.set_file_pattern("app_%Y-%m-%d.log", yan_log::LoggerFormatTimeDivisionRule::Day);
954    /// ```
955    #[inline]
956    pub fn set_file_pattern(
957        mut self,
958        file_pattern: &str,
959        time_division_rule: LoggerFormatTimeDivisionRule,
960    ) -> Self {
961        self.time_division_rule = time_division_rule;
962        self.file_pattern = Box::from(file_pattern);
963        let mut timestamp = SystemTime::now()
964            .duration_since(UNIX_EPOCH)
965            .unwrap()
966            .as_millis();
967        timestamp += 28_800_000;
968        let now = timestamp_ms_to_datetime(timestamp);
969        self.update_filename_for_time(now)
970    }
971
972    /// 设置最大保留日志文件数量
973    ///
974    /// # 参数
975    /// - `self`: [`LoggerFormat`] 实例
976    /// - `max_retained_files`: 最大保留日志文件数
977    ///
978    /// # 返回值
979    /// - `Self`: 更新最大保留日志文件数后的 [`LoggerFormat`] 实例
980    ///
981    /// # 示例
982    /// ```
983    /// let mut log_fmt = yan_log::Logger::init("logs", yan_log::LogLevel::Debug);
984    /// let log_fmt = log_fmt.set_max_retained_files(3);
985    /// ```
986    #[inline]
987    pub fn set_max_retained_files(mut self, max_retained_files: u64) -> Self {
988        self.max_retained_files = max_retained_files;
989        self
990    }
991
992    /// 启动日志系统
993    /// - 完成日志系统的最终配置并启动所有相关组件。
994    /// - 此方法会设置全局日志级别、注册日志实现并启动日志处理线程。
995    ///
996    /// # 处理流程
997    /// 1. 设置全局日志级别过滤
998    /// 2. 注册 log crate 的日志实现
999    /// 3. 启动日志发送器和处理线程
1000    ///
1001    /// # 注意事项
1002    /// - 此方法会消费 [`LoggerFormat`] 实例
1003    /// - 调用后日志系统开始工作,可以记录日志
1004    /// - 通常在应用程序启动时调用一次
1005    ///
1006    /// # 示例
1007    /// ```
1008    /// yan_log::Logger::init("logs", yan_log::LogLevel::Debug)
1009    ///     .set_file_pattern("app_%Y-%m-%d.log", yan_log::LoggerFormatTimeDivisionRule::Day)
1010    ///     .start()
1011    ///     .unwrap();
1012    /// ```
1013    ///
1014    /// # 安全性
1015    /// - 使用 `Box::leak` 将日志记录器泄漏到静态生命周期,这是启动全局日志系统的常见模式
1016    ///
1017    /// # 注意事项
1018    /// - 默认情况,在 dev 模式会打印日志到控制台,release模式,不会打印日志到控制台
1019    /// - 开启 stdout 特性时,release模式,会和 dev 模式相同打印日志到控制台
1020    #[inline]
1021    pub fn start(self) -> Result<(), std::io::Error> {
1022        // 设置log日志门面的实现
1023        let box_logger = Box::from(Logger::new("None", self.level.clone()));
1024        log::set_logger(Box::leak(box_logger)).unwrap();
1025        // 设置全局日志级别
1026        log::set_max_level(self.level.to_level_filter());
1027        init_log_backend(self)
1028    }
1029}