Skip to main content

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