yan_log/
lib.rs

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