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