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