dm_database_sqllog2db/
logging.rs1use crate::config::LoggingConfig;
2use crate::constants::LOG_LEVELS;
3use crate::error::{Error, FileError, Result};
4use chrono::Local;
5use log::{Level, LevelFilter, Metadata, Record};
6use std::collections::HashMap;
7use std::fs::OpenOptions;
8use std::io::Write;
9use std::path::Path;
10use std::sync::{Arc, LazyLock, Mutex};
11
12static LOG_LEVEL_MAP: LazyLock<HashMap<&'static str, LevelFilter>> = LazyLock::new(|| {
14 let mut map = HashMap::with_capacity(5);
15 map.insert("trace", LevelFilter::Trace);
16 map.insert("debug", LevelFilter::Debug);
17 map.insert("info", LevelFilter::Info);
18 map.insert("warn", LevelFilter::Warn);
19 map.insert("error", LevelFilter::Error);
20 map
21});
22
23pub fn init_logging(config: &LoggingConfig) -> Result<()> {
25 let level = parse_log_level(&config.level)?;
27
28 let log_path = Path::new(&config.file);
30 let parent_dir = log_path.parent().ok_or_else(|| {
31 Error::File(FileError::CreateDirectoryFailed {
32 path: log_path.to_path_buf(),
33 reason: "Failed to get parent directory".to_string(),
34 })
35 })?;
36
37 if !parent_dir.exists() {
39 std::fs::create_dir_all(parent_dir).map_err(|e| {
40 Error::File(FileError::CreateDirectoryFailed {
41 path: parent_dir.to_path_buf(),
42 reason: e.to_string(),
43 })
44 })?;
45 }
46
47 let file_stem = log_path
49 .file_stem()
50 .and_then(|n| n.to_str())
51 .ok_or_else(|| {
52 Error::File(FileError::CreateDirectoryFailed {
53 path: log_path.to_path_buf(),
54 reason: "Invalid filename".to_string(),
55 })
56 })?;
57
58 let extension = log_path
59 .extension()
60 .and_then(|e| e.to_str())
61 .unwrap_or("log");
62
63 let log_file_path = parent_dir.join(format!("{file_stem}.{extension}"));
65 let file = OpenOptions::new()
66 .create(true)
67 .append(true)
68 .open(&log_file_path)
69 .map_err(|e| {
70 Error::File(FileError::CreateDirectoryFailed {
71 path: log_file_path.clone(),
72 reason: e.to_string(),
73 })
74 })?;
75
76 let shared_file = Arc::new(Mutex::new(file));
77
78 struct SimpleLogger {
80 level: LevelFilter,
81 file: Arc<Mutex<std::fs::File>>,
82 }
83
84 impl log::Log for SimpleLogger {
85 fn enabled(&self, metadata: &Metadata) -> bool {
86 match self.level {
87 LevelFilter::Off => false,
88 LevelFilter::Error => metadata.level() == Level::Error,
89 LevelFilter::Warn => metadata.level() <= Level::Warn,
90 LevelFilter::Info => metadata.level() <= Level::Info,
91 LevelFilter::Debug => metadata.level() <= Level::Debug,
92 LevelFilter::Trace => true,
93 }
94 }
95
96 fn log(&self, record: &Record) {
97 if !self.enabled(record.metadata()) {
98 return;
99 }
100 let now = Local::now().format("%Y-%m-%d %H:%M:%S");
101 let msg = format!(
102 "[{}][{}] {} - {}\n",
103 now,
104 record.level(),
105 record.target(),
106 record.args()
107 );
108 let _ = std::io::stdout().write_all(msg.as_bytes());
110
111 if let Ok(mut f) = self.file.lock() {
113 let _ = f.write_all(msg.as_bytes());
114 }
115 }
116
117 fn flush(&self) {}
118 }
119
120 let logger = SimpleLogger {
121 level,
122 file: shared_file.clone(),
123 };
124
125 match log::set_boxed_logger(Box::new(logger)) {
127 Ok(()) => {
128 log::set_max_level(level);
129 }
130 Err(e) => {
131 let _ = e;
135 }
136 }
137
138 log::info!(
139 "Logging initialized - level: {:?}, file: {}, retention_days: {}",
140 level,
141 config.file,
142 config.retention_days()
143 );
144
145 Ok(())
146}
147
148fn parse_log_level(level_str: &str) -> Result<LevelFilter> {
150 let lower = level_str.to_lowercase();
151 LOG_LEVEL_MAP.get(lower.as_str()).copied().ok_or_else(|| {
152 Error::Config(crate::error::ConfigError::InvalidLogLevel {
153 level: level_str.to_string(),
154 valid_levels: LOG_LEVELS.iter().map(|s| (*s).to_string()).collect(),
155 })
156 })
157}