dm_database_sqllog2db/
logging.rs

1use crate::config::LoggingConfig;
2use crate::constants::LOG_LEVELS;
3use crate::error::{Error, FileError, Result};
4use chrono::Local;
5use log::SetLoggerError;
6use log::{Level, LevelFilter, Metadata, Record};
7use once_cell::sync::Lazy;
8use std::collections::HashMap;
9use std::fs::OpenOptions;
10use std::io::Write;
11use std::path::Path;
12use std::sync::{Arc, Mutex};
13
14// 使用 once_cell 缓存日志级别映射表,避免每次查找时重新构建
15static LOG_LEVEL_MAP: Lazy<HashMap<&'static str, LevelFilter>> = Lazy::new(|| {
16    let mut map = HashMap::new();
17    map.insert("trace", LevelFilter::Trace);
18    map.insert("debug", LevelFilter::Debug);
19    map.insert("info", LevelFilter::Info);
20    map.insert("warn", LevelFilter::Warn);
21    map.insert("error", LevelFilter::Error);
22    map
23});
24
25/// 初始化日志系统
26pub fn init_logging(config: &LoggingConfig) -> Result<()> {
27    // 解析日志级别
28    let level = parse_log_level(&config.level)?;
29
30    // 获取日志文件路径和目录
31    let log_path = Path::new(&config.file);
32    let parent_dir = log_path.parent().ok_or_else(|| {
33        Error::File(FileError::CreateDirectoryFailed {
34            path: log_path.to_path_buf(),
35            reason: "Failed to get parent directory".to_string(),
36        })
37    })?;
38
39    // 创建日志目录(如果不存在)
40    if !parent_dir.exists() {
41        std::fs::create_dir_all(parent_dir).map_err(|e| {
42            Error::File(FileError::CreateDirectoryFailed {
43                path: parent_dir.to_path_buf(),
44                reason: e.to_string(),
45            })
46        })?;
47    }
48
49    // 从路径中提取基础文件名(去掉扩展名)
50    let file_stem = log_path
51        .file_stem()
52        .and_then(|n| n.to_str())
53        .ok_or_else(|| {
54            Error::File(FileError::CreateDirectoryFailed {
55                path: log_path.to_path_buf(),
56                reason: "Invalid filename".to_string(),
57            })
58        })?;
59
60    let extension = log_path
61        .extension()
62        .and_then(|e| e.to_str())
63        .unwrap_or("log");
64
65    // 创建简单的追加日志文件(不做滚动),更轻量:使用 Arc<Mutex<File>> 作为共享 writer
66    let log_file_path = parent_dir.join(format!("{}.{}", file_stem, extension));
67    let file = OpenOptions::new()
68        .create(true)
69        .append(true)
70        .open(&log_file_path)
71        .map_err(|e| {
72            Error::File(FileError::CreateDirectoryFailed {
73                path: log_file_path.clone(),
74                reason: e.to_string(),
75            })
76        })?;
77
78    let shared_file = Arc::new(Mutex::new(file));
79
80    // 自定义简单 Logger,写入文件与 stdout
81    struct SimpleLogger {
82        level: LevelFilter,
83        file: Arc<Mutex<std::fs::File>>,
84    }
85
86    impl log::Log for SimpleLogger {
87        fn enabled(&self, metadata: &Metadata) -> bool {
88            match self.level {
89                LevelFilter::Off => false,
90                LevelFilter::Error => metadata.level() == Level::Error,
91                LevelFilter::Warn => metadata.level() <= Level::Warn,
92                LevelFilter::Info => metadata.level() <= Level::Info,
93                LevelFilter::Debug => metadata.level() <= Level::Debug,
94                LevelFilter::Trace => true,
95            }
96        }
97
98        fn log(&self, record: &Record) {
99            if !self.enabled(record.metadata()) {
100                return;
101            }
102            let now = Local::now().format("%Y-%m-%d %H:%M:%S");
103            let msg = format!(
104                "[{}][{}] {} - {}\n",
105                now,
106                record.level(),
107                record.target(),
108                record.args()
109            );
110            // 写到 stdout
111            let _ = std::io::stdout().write_all(msg.as_bytes());
112            // 写到文件
113            if let Ok(mut f) = self.file.lock() {
114                let _ = f.write_all(msg.as_bytes());
115            }
116        }
117
118        fn flush(&self) {}
119    }
120
121    let logger = SimpleLogger {
122        level,
123        file: shared_file.clone(),
124    };
125
126    // 注册 logger
127    log::set_max_level(level);
128    log::set_boxed_logger(Box::new(logger)).map_err(|e: SetLoggerError| {
129        Error::File(FileError::CreateDirectoryFailed {
130            path: log_file_path.clone(),
131            reason: format!("Failed to set logger: {}", e),
132        })
133    })?;
134
135    log::info!(
136        "Logging initialized - level: {:?}, file: {}, retention_days: {}",
137        level,
138        config.file,
139        config.retention_days()
140    );
141
142    Ok(())
143}
144
145/// 解析日志级别字符串
146fn parse_log_level(level_str: &str) -> Result<LevelFilter> {
147    let lower = level_str.to_lowercase();
148    LOG_LEVEL_MAP.get(lower.as_str()).copied().ok_or_else(|| {
149        Error::Config(crate::error::ConfigError::InvalidLogLevel {
150            level: level_str.to_string(),
151            valid_levels: LOG_LEVELS.iter().map(|s| s.to_string()).collect(),
152        })
153    })
154}