secra_logger/
config.rs

1use crate::error::{LoggerError, Result};
2use std::path::{Path, PathBuf};
3use tracing::Level;
4
5/// 日志输出目标
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum LogOutput {
8    /// 仅输出到标准输出
9    Stdout,
10    /// 仅输出到文件
11    File,
12    /// 同时输出到标准输出和文件
13    Both,
14}
15
16/// 日志系统配置
17#[derive(Debug, Clone)]
18pub struct LoggerConfig {
19    /// 日志级别
20    pub level: Level,
21    /// 输出目标
22    pub output: LogOutput,
23    /// 日志路径(可以是目录或文件)
24    pub path: PathBuf,
25    /// 单个日志文件最大大小(字节)
26    pub max_size: u64,
27    /// 最大保留文件数(None 表示无限)
28    pub max_files: Option<usize>,
29    /// 应用名称
30    pub app_name: String,
31}
32
33impl LoggerConfig {
34    /// 创建新的配置
35    pub fn new(
36        level: Level,
37        output: LogOutput,
38        path: impl AsRef<Path>,
39        max_size: u64,
40        max_files: Option<usize>,
41        app_name: impl Into<String>,
42    ) -> Self {
43        Self {
44            level,
45            output,
46            path: path.as_ref().to_path_buf(),
47            max_size,
48            max_files,
49            app_name: app_name.into(),
50        }
51    }
52
53    /// 验证配置的有效性
54    pub fn validate(&self) -> Result<()> {
55        if self.max_size == 0 {
56            return Err(LoggerError::ConfigError {
57                message: "max_size 必须大于 0".to_string(),
58            });
59        }
60
61        if let Some(max_files) = self.max_files {
62            if max_files == 0 {
63                return Err(LoggerError::ConfigError {
64                    message: "max_files 必须大于 0 或为 None".to_string(),
65                });
66            }
67        }
68
69        if self.app_name.is_empty() {
70            return Err(LoggerError::ConfigError {
71                message: "app_name 不能为空".to_string(),
72            });
73        }
74
75        Ok(())
76    }
77
78    /// 获取基础文件名(base)
79    /// 如果 path 是目录,返回 app_name
80    /// 如果 path 是文件,返回文件名(不含扩展名)
81    pub fn get_base_name(&self) -> Result<String> {
82        if self.path.is_dir() || !self.path.extension().is_some() {
83            Ok(self.app_name.clone())
84        } else {
85            self.path
86                .file_stem()
87                .and_then(|s| s.to_str())
88                .map(|s| s.to_string())
89                .ok_or_else(|| LoggerError::PathError {
90                    path: self.path.clone(),
91                })
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_get_base_name_dir() {
102        let config = LoggerConfig::new(
103            Level::INFO,
104            LogOutput::File,
105            "./logs",
106            10 * 1024 * 1024,
107            None,
108            "myapp",
109        );
110        assert_eq!(config.get_base_name().unwrap(), "myapp");
111    }
112
113    #[test]
114    fn test_get_base_name_file() {
115        let config = LoggerConfig::new(
116            Level::INFO,
117            LogOutput::File,
118            "./logs/app.log",
119            10 * 1024 * 1024,
120            None,
121            "myapp",
122        );
123        assert_eq!(config.get_base_name().unwrap(), "app");
124    }
125}
126