dm_database_sqllog2db/
logging.rs1use 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
13static 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
24pub fn init_logging(config: &LoggingConfig) -> Result<()> {
26 let level = parse_log_level(&config.level)?;
28
29 let log_path = Path::new(&config.file);
31 let parent_dir = log_path.parent().ok_or_else(|| {
32 Error::File(FileError::CreateDirectoryFailed {
33 path: log_path.to_path_buf(),
34 reason: "Failed to get parent directory".to_string(),
35 })
36 })?;
37
38 if !parent_dir.exists() {
40 std::fs::create_dir_all(parent_dir).map_err(|e| {
41 Error::File(FileError::CreateDirectoryFailed {
42 path: parent_dir.to_path_buf(),
43 reason: e.to_string(),
44 })
45 })?;
46 }
47
48 let file_stem = log_path
50 .file_stem()
51 .and_then(|n| n.to_str())
52 .ok_or_else(|| {
53 Error::File(FileError::CreateDirectoryFailed {
54 path: log_path.to_path_buf(),
55 reason: "Invalid filename".to_string(),
56 })
57 })?;
58
59 let extension = log_path
60 .extension()
61 .and_then(|e| e.to_str())
62 .unwrap_or("log");
63
64 let log_file_path = parent_dir.join(format!("{file_stem}.{extension}"));
66 let file = OpenOptions::new()
67 .create(true)
68 .append(true)
69 .open(&log_file_path)
70 .map_err(|e| {
71 Error::File(FileError::CreateDirectoryFailed {
72 path: log_file_path.clone(),
73 reason: e.to_string(),
74 })
75 })?;
76
77 let shared_file = Arc::new(Mutex::new(file));
78
79 struct SimpleLogger {
81 level: LevelFilter,
82 file: Arc<Mutex<std::fs::File>>,
83 }
84
85 impl log::Log for SimpleLogger {
86 fn enabled(&self, metadata: &Metadata) -> bool {
87 match self.level {
88 LevelFilter::Off => false,
89 LevelFilter::Error => metadata.level() == Level::Error,
90 LevelFilter::Warn => metadata.level() <= Level::Warn,
91 LevelFilter::Info => metadata.level() <= Level::Info,
92 LevelFilter::Debug => metadata.level() <= Level::Debug,
93 LevelFilter::Trace => true,
94 }
95 }
96
97 fn log(&self, record: &Record) {
98 if !self.enabled(record.metadata()) {
99 return;
100 }
101 let now = Local::now().format("%Y-%m-%d %H:%M:%S");
102 let msg = format!(
103 "[{}][{}] {} - {}\n",
104 now,
105 record.level(),
106 record.target(),
107 record.args()
108 );
109 let _ = std::io::stdout().write_all(msg.as_bytes());
111
112 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 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,
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
145fn 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}