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 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
14static 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
25pub fn init_logging(config: &LoggingConfig) -> Result<()> {
27 let level = parse_log_level(&config.level)?;
29
30 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 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 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 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 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 let _ = std::io::stdout().write_all(msg.as_bytes());
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.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
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}