cyfs_debug/log/
log_config.rs

1use super::LogLevel;
2use cyfs_base::{BuckyError, BuckyErrorCode, BuckyResult};
3use cyfs_util::TomlHelper;
4
5use std::{collections::{HashMap, hash_map::Entry}, str::FromStr};
6use std::path::{Path, PathBuf};
7
8// 日志环境变量
9const CYFS_CONSOLE_LOG_LEVEL_KEY: &str = "CYFS_CONSOLE_LOG_LEVEL";
10const CYFS_FILE_LOG_LEVEL_KEY: &str = "CYFS_FILE_LOG_LEVEL";
11
12
13pub enum LogDebugInfoFlag {
14    Args = 0x01,
15    Env = 0x02,
16}
17
18#[derive(Clone, Debug)]
19pub struct LogDebugInfoFlags(u32);
20
21impl Default for LogDebugInfoFlags {
22    fn default() -> Self {
23        Self(u32::MAX)
24    }
25}
26
27impl LogDebugInfoFlags {
28    pub fn new(flags: u32) -> Self {
29        Self(flags)
30    }
31
32    pub fn without_args(mut self) -> Self {
33        self.0 &= !(LogDebugInfoFlag::Args as u32);
34        self
35    }
36
37    pub fn is_args_present(&self) -> bool {
38        self.0 & LogDebugInfoFlag::Args as u32 == LogDebugInfoFlag::Args as u32
39    }
40
41    pub fn without_env(mut self) -> Self {
42        self.0 &= !(LogDebugInfoFlag::Env as u32);
43        self
44    }
45
46    pub fn is_env_present(&self) -> bool {
47        self.0 & LogDebugInfoFlag::Env as u32 == LogDebugInfoFlag::Env as u32
48    }
49}
50
51impl Into<u32> for LogDebugInfoFlags {
52    fn into(self) -> u32 {
53        self.0
54    }
55}
56
57#[derive(Clone)]
58pub struct LogModuleConfig {
59    pub name: String,
60
61    pub level: LogLevel,
62
63    // 是否输出控制台日志
64    pub console: LogLevel,
65
66    // 是否输出文件日志
67    pub file: bool,
68
69    // 是否使用独立文件
70    pub file_name: Option<String>,
71
72    // 单个日志文件的最大大小,字节
73    pub file_max_size: u64,
74
75    // 日志文件最大个数,滚动输出
76    pub file_max_count: u32,
77
78    // flags to output debug info
79    pub debug_info_flags: u32,
80}
81
82impl LogModuleConfig {
83    pub fn new_default(name: &str) -> Self {
84        Self {
85            name: name.to_owned(),
86
87            level: LogLevel::default(),
88            console: LogLevel::default(),
89            file: true,
90            file_name: Some(name.to_string()),
91            file_max_size: 1024 * 1024 * 10,
92            file_max_count: 10,
93            debug_info_flags: LogDebugInfoFlags::default().into(),
94        }
95    }
96
97    pub fn is_global_module(&self) -> bool {
98        self.name == "global"
99    }
100
101    pub fn max_level(&self) -> LogLevel {
102        std::cmp::max(&self.level, &self.console).to_owned()
103    }
104    
105    pub fn set_level(&mut self, level: &str) {
106        self.level = LogLevel::from_str(level).expect(&format!("invalid level str: {}", level));
107    }
108
109    pub fn set_console(&mut self, level: &str) {
110        self.console = LogLevel::from_str(level).expect(&format!("invalid level str: {}", level));
111    }
112
113    pub fn set_file(&mut self, enable: bool) {
114        self.file = enable;
115    }
116
117    pub fn set_file_max_size(&mut self, file_max_size: u64) {
118        self.file_max_size = file_max_size;
119    }
120
121    pub fn set_file_max_count(&mut self, file_max_count: u32) {
122        self.file_max_count = file_max_count;
123    }
124
125    pub fn set_debug_info_flags(&mut self, flags: u32) {
126        self.debug_info_flags = flags;
127    }
128
129    pub fn get_debug_info_flags(&self) -> LogDebugInfoFlags {
130        LogDebugInfoFlags::new(self.debug_info_flags)
131    }
132
133    // 加载环境变量配置的日志级别
134    fn load_console_level_from_env() -> Option<LogLevel> {
135        match std::env::var(CYFS_CONSOLE_LOG_LEVEL_KEY) {
136            Ok(val) => {
137                match LogLevel::from_str(&val) {
138                    Ok(level) => Some(level),
139                    Err(e) => {
140                        println!("parse env log level error! {}, {}", val, e);
141                        None
142                    }
143                }
144            }
145            Err(_) => {
146                None
147            }
148        }
149    }
150
151    fn load_file_level_from_env() -> Option<LogLevel> {
152        match std::env::var(CYFS_FILE_LOG_LEVEL_KEY) {
153            Ok(val) => {
154                match LogLevel::from_str(&val) {
155                    Ok(level) => Some(level),
156                    Err(e) => {
157                        println!("parse env log level error! {}, {}", val, e);
158                        None
159                    }
160                }
161            }
162            Err(_) => {
163                None
164            }
165        }
166    }
167
168    pub fn load(&mut self, node: &toml::value::Table) {
169        let mut console: Option<LogLevel> = None;
170        let mut level: Option<LogLevel> = None;
171        for (k, v) in node {
172            match k.as_str() {
173                "console" => {
174                    if let Ok(v) = TomlHelper::decode_from_string(v) {
175                        console = Some(v);
176                    }
177                }
178
179                "file" => {
180                    if let Ok(v) = TomlHelper::decode_from_string::<String>(v) {
181                        if v.as_str() == "off" {
182                            self.file = false;
183                            self.file_name = None;
184                        } else {
185                            self.file = true;
186                            self.file_name = Some(v);
187                        }
188                    }
189                }
190
191                "level" => {
192                    if let Ok(v) = TomlHelper::decode_from_string(v) {
193                        level = Some(v);
194                    }
195                }
196
197                "file_max_size" => {
198                    match TomlHelper::decode_to_int::<u64>(v) {
199                        Ok(v) => self.file_max_size = v,
200                        Err(e) => println!("decode log.toml file_max_size field error! {}", e),
201                    }
202                }
203
204                "file_max_count" => {
205                    match TomlHelper::decode_to_int::<u32>(v) {
206                        Ok(v) => self.file_max_count = v,
207                        Err(e) => println!("decode log.toml file_max_count field error! {}", e),
208                    }
209                }
210
211                v @ _ => {
212                    println!("unknown module config field: {}", v);
213                }
214            }
215        }
216
217        // 如果只配置了level,但没配置console,那么console默认使用配置的level,而不是代码内置的debug级别
218        if console.is_none() && level.is_some() {
219            console = level.clone();
220        }
221
222        if let Some(v) = level {
223            self.level = v;
224        }
225        if let Some(v) = console {
226            self.console = v;
227        }
228
229        // 尝试读取环境变量来配置
230        if let Some(level) = Self::load_console_level_from_env() {
231            println!("load module console level from env: mod={}, level={}, old={}", self.name, level, self.console);
232            self.console = level;
233        }
234
235        if let Some(level) = Self::load_file_level_from_env() {
236            println!("load module file level from env: mod={}, level={}, old={}", self.name, level, self.level);
237            self.level = level;
238        }
239    }
240}
241
242pub struct LogConfig {
243    pub log_dir: PathBuf,
244    pub basename: String,
245
246    pub global: LogModuleConfig,
247    pub modules: HashMap<String, LogModuleConfig>,
248}
249
250impl LogConfig {
251    pub fn new(log_dir: PathBuf) -> Self {
252        let arg0 = std::env::args().next().unwrap_or_else(|| "main".to_owned());
253        let basename = Path::new(&arg0).file_stem().unwrap(/*cannot fail*/).to_string_lossy().to_string();
254
255        Self {
256            log_dir,
257            global: LogModuleConfig::new_default("global"),
258            modules: HashMap::new(),
259            basename,
260        }
261    }
262
263    pub fn set_log_dir(&mut self, log_dir: PathBuf) {
264        self.log_dir = log_dir;
265    }
266    
267    pub fn get_mod_config(&self, name: Option<&str>) -> &LogModuleConfig {
268        match name {
269            None => {
270                &self.global
271            }
272            Some(name) => {
273                if let Some(m) = self.modules.get(name) {
274                    return m;
275                }
276        
277                &self.global
278            }
279        }
280    }
281
282    pub fn load(&mut self, config_node: &toml::Value) -> BuckyResult<()> {
283        
284        let node = config_node.as_table().ok_or_else(|| {
285            let msg = format!(
286                "invalid log config format! content={}",
287                config_node,
288            );
289            error!("{}", msg);
290
291            BuckyError::new(BuckyErrorCode::InvalidFormat, msg)
292        })?;
293
294        for (k, v) in node {
295            if !v.is_table() {
296                error!("invalid config node format: {}", v);
297                continue;
298            }
299
300            let v = v.as_table().unwrap();
301            match k.as_str() {
302                "global" => {
303                    self.global.load(v);
304                }
305
306                key @ _ => {
307                    self.load_mod(key, v);
308                }
309            }
310        }
311
312        Ok(())
313    }
314
315    fn load_mod(&mut self, name: &str, node: &toml::value::Table) {
316        let mut module = LogModuleConfig::new_default(name);
317        module.load(node);
318
319       self.add_mod(module);
320    }
321
322    pub fn add_mod(&mut self, module: LogModuleConfig) {
323        if let Some(old) = self.modules.insert(module.name.clone(), module) {
324            println!("replace old log module: {}={}", old.name, old.level);
325        }
326    }
327
328    pub fn disable_module_log(&mut self, name: &str, level: &LogLevel) {
329        let mut module = LogModuleConfig::new_default(name);
330        module.level = *level;
331        module.console = *level;
332        module.file_name = None;
333
334        // 如果已经提前配置了,那么不需要覆盖
335        match self.modules.entry(name.to_owned()) {
336            Entry::Vacant(entry) => {
337                entry.insert(module);
338            },
339            Entry::Occupied(mut _entry)=>{
340                println!("module already in config: name={}", name);
341            }
342        };
343    }
344
345    // 屏蔽一些基础库的trace log等
346    pub fn disable_async_std_log(&mut self) {
347        let mod_list = [
348            ("async_io", LogLevel::Warn),
349            ("polling", LogLevel::Warn),
350            ("async_tungstenite", LogLevel::Warn),
351            ("tungstenite", LogLevel::Warn),
352            ("async_std", LogLevel::Warn),
353            ("tide", LogLevel::Off),
354        ];
355
356        mod_list.iter().for_each(|(name, level)| {
357            self.disable_module_log(name, level);
358        })
359    }
360}