Skip to main content

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