1use crate::log_impl::setup_log;
2use crate::{
3 console_impl::LoggerSinkConsole,
4 file_impl::LoggerSinkFile,
5 formatter::{FormatRecord, TimeFormatter},
6 log_impl::LoggerSink,
7 time::Timer,
8};
9use log::{Level, LevelFilter, Record};
10use std::hash::{DefaultHasher, Hash, Hasher};
11use std::path::{Path, PathBuf};
12use std::str::FromStr;
13
14#[derive(Default)]
17pub struct Builder {
18 pub dynamic: bool,
25
26 pub rotation_signals: Vec<i32>,
29
30 pub panic: bool,
32
33 pub continue_when_panic: bool,
35
36 pub sinks: Vec<Box<dyn SinkConfigTrait>>,
38}
39
40impl Builder {
41 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn test(mut self) -> Self {
48 self.dynamic = true;
49 self.rotation_signals.clear();
50 self
51 }
52
53 pub fn signal(mut self, signal: i32) -> Self {
55 self.rotation_signals.push(signal);
56 self
57 }
58
59 pub fn raw_file(mut self, config: LogRawFile) -> Self {
61 self.sinks.push(Box::new(config));
62 self
63 }
64
65 pub fn console(mut self, config: LogConsole) -> Self {
67 self.sinks.push(Box::new(config));
68 self
69 }
70
71 pub fn get_max_level(&self) -> LevelFilter {
73 let mut max_level = Level::Error;
74 for sink in &self.sinks {
75 let level = sink.get_level();
76 if level > max_level {
77 max_level = level;
78 }
79 }
80 return max_level.to_level_filter();
81 }
82
83 pub(crate) fn cal_checksum(&self) -> u64 {
85 let mut hasher = Box::new(DefaultHasher::new()) as Box<dyn Hasher>;
86 self.dynamic.hash(&mut hasher);
87 self.rotation_signals.hash(&mut hasher);
88 self.panic.hash(&mut hasher);
89 self.continue_when_panic.hash(&mut hasher);
90 for sink in &self.sinks {
91 sink.write_hash(&mut hasher);
92 }
93 hasher.finish()
94 }
95
96 pub fn build(self) -> Result<(), ()> {
99 setup_log(self)
100 }
101}
102
103pub trait SinkConfigTrait {
104 fn get_level(&self) -> Level;
106 fn get_file_path(&self) -> Option<Box<Path>>;
108 fn write_hash(&self, hasher: &mut Box<dyn Hasher>);
110 fn build(&self) -> LoggerSink;
112}
113
114pub type FormatFunc = fn(FormatRecord) -> String;
115
116#[derive(Clone, Hash)]
118pub struct LogFormat {
119 time_fmt: &'static str,
120 format_fn: FormatFunc,
121}
122
123impl LogFormat {
124 pub const fn new(time_fmt: &'static str, format_fn: FormatFunc) -> Self {
147 Self { time_fmt, format_fn }
148 }
149
150 #[inline(always)]
151 pub(crate) fn process(&self, now: &Timer, record: &Record) -> String {
152 let time = TimeFormatter { now, fmt_str: self.time_fmt };
153 let r = FormatRecord { record, time };
154 return (self.format_fn)(r);
155 }
156}
157
158#[derive(Hash)]
161pub struct LogRawFile {
162 pub level: Level,
164
165 pub format: LogFormat,
166
167 pub file_path: Box<Path>,
169}
170
171impl LogRawFile {
172 pub fn new<P1, P2>(dir: P1, file_name: P2, level: Level, format: LogFormat) -> Self
178 where
179 P1: Into<PathBuf>,
180 P2: Into<PathBuf>,
181 {
182 let dir_path: PathBuf = dir.into();
183 if !dir_path.exists() {
184 std::fs::create_dir(&dir_path).expect("create dir for log");
185 }
186 let file_path = dir_path.join(file_name.into()).into_boxed_path();
187 Self { level, format, file_path }
188 }
189}
190
191impl SinkConfigTrait for LogRawFile {
192 fn get_level(&self) -> Level {
193 self.level
194 }
195
196 fn get_file_path(&self) -> Option<Box<Path>> {
197 Some(self.file_path.clone())
198 }
199
200 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
201 self.hash(hasher);
202 hasher.write(b"LogRawFile");
203 }
204
205 fn build(&self) -> LoggerSink {
206 LoggerSink::File(LoggerSinkFile::new(self))
207 }
208}
209
210#[derive(Copy, Clone, Debug, Hash, PartialEq)]
211#[repr(u8)]
212pub enum ConsoleTarget {
213 Stdout = 1,
214 Stderr = 2,
215}
216
217impl FromStr for ConsoleTarget {
218 type Err = ();
219
220 fn from_str(s: &str) -> Result<Self, ()> {
222 let v = s.to_lowercase();
223 match v.as_str() {
224 "stdout" => Ok(ConsoleTarget::Stdout),
225 "stderr" => Ok(ConsoleTarget::Stderr),
226 "out" => Ok(ConsoleTarget::Stdout),
227 "err" => Ok(ConsoleTarget::Stderr),
228 "1" => Ok(ConsoleTarget::Stdout),
229 "2" => Ok(ConsoleTarget::Stderr),
230 _ => Err(()),
231 }
232 }
233}
234
235#[derive(Hash)]
236pub struct LogConsole {
237 pub target: ConsoleTarget,
238
239 pub level: Level,
241
242 pub format: LogFormat,
243}
244
245impl LogConsole {
246 pub fn new(target: ConsoleTarget, level: Level, format: LogFormat) -> Self {
247 Self { target, level, format }
248 }
249}
250
251impl SinkConfigTrait for LogConsole {
252 fn get_level(&self) -> Level {
253 self.level
254 }
255
256 fn get_file_path(&self) -> Option<Box<Path>> {
257 None
258 }
259
260 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
261 self.hash(hasher);
262 hasher.write(b"LogConsole");
263 }
264
265 fn build(&self) -> LoggerSink {
266 LoggerSink::Console(LoggerSinkConsole::new(self))
267 }
268}
269
270pub struct EnvVarDefault<'a, T> {
271 name: &'a str,
272 default: T,
273}
274
275pub fn env_or<'a, T>(name: &'a str, default: T) -> EnvVarDefault<'a, T> {
290 EnvVarDefault { name, default }
291}
292
293impl<'a> Into<String> for EnvVarDefault<'a, &'a str> {
294 fn into(self) -> String {
295 if let Ok(v) = std::env::var(&self.name) {
296 return v;
297 }
298 return self.default.to_string();
299 }
300}
301
302impl<'a, P: AsRef<Path>> Into<PathBuf> for EnvVarDefault<'a, P> {
303 fn into(self) -> PathBuf {
304 if let Some(v) = std::env::var_os(&self.name) {
305 if v.len() > 0 {
306 return PathBuf::from(v);
307 }
308 }
309 return self.default.as_ref().to_path_buf();
310 }
311}
312
313macro_rules! impl_from_env {
314 ($type: tt) => {
315 impl<'a> Into<$type> for EnvVarDefault<'a, $type> {
316 #[inline]
317 fn into(self) -> $type {
318 if let Ok(v) = std::env::var(&self.name) {
319 match $type::from_str(&v) {
320 Ok(r) => return r,
321 Err(_) => {
322 eprintln!(
323 "env {}={} is not valid, set to {:?}",
324 self.name, v, self.default
325 );
326 }
327 }
328 }
329 return self.default;
330 }
331 }
332 };
333}
334
335impl_from_env!(ConsoleTarget);
338impl_from_env!(Level);
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::recipe;
344
345 #[test]
346 fn test_raw_file() {
347 let _file_sink = LogRawFile::new("/tmp", "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
348 let dir_path = Path::new("/tmp/test_dir");
349 if dir_path.is_dir() {
350 std::fs::remove_dir(&dir_path).expect("ok");
351 }
352 let _file_sink =
353 LogRawFile::new(&dir_path, "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
354 assert!(dir_path.is_dir());
355 std::fs::remove_dir(&dir_path).expect("ok");
356 }
357
358 #[test]
359 fn test_env_config() {
360 unsafe { std::env::set_var("LEVEL", "warn") };
362 let level: Level = env_or("LEVEL", Level::Debug).into();
363 assert_eq!(level, Level::Warn);
364 unsafe { std::env::set_var("LEVEL", "WARN") };
365 let level: Level = env_or("LEVEL", Level::Debug).into();
366 assert_eq!(level, Level::Warn);
367
368 assert_eq!(ConsoleTarget::from_str("Stdout").unwrap(), ConsoleTarget::Stdout);
369 assert_eq!(ConsoleTarget::from_str("StdERR").unwrap(), ConsoleTarget::Stderr);
370 assert_eq!(ConsoleTarget::from_str("1").unwrap(), ConsoleTarget::Stdout);
371 assert_eq!(ConsoleTarget::from_str("2").unwrap(), ConsoleTarget::Stderr);
372 assert_eq!(ConsoleTarget::from_str("0").unwrap_err(), ());
373
374 unsafe { std::env::set_var("CONSOLE", "stderr") };
376 let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
377 assert_eq!(target, ConsoleTarget::Stderr);
378 unsafe { std::env::set_var("CONSOLE", "") };
379 let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
380 assert_eq!(target, ConsoleTarget::Stdout);
381
382 unsafe { std::env::set_var("LOG_PATH", "/tmp/test.log") };
384 let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
385 assert_eq!(path, Path::new("/tmp/test.log").to_path_buf());
386
387 unsafe { std::env::set_var("LOG_PATH", "") };
388 let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
389 assert_eq!(path, Path::new("/tmp/other.log").to_path_buf());
390
391 let _builder = recipe::raw_file_logger(env_or("LOG_PATH", "/tmp/other.log"), Level::Info);
392 let _builder =
393 recipe::raw_file_logger(env_or("LOG_PATH", "/tmp/other.log".to_string()), Level::Info);
394 }
395}