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
175 where
176 P1: Into<PathBuf>,
177 P2: Into<PathBuf>,
178 {
179 let dir_path: PathBuf = dir.into();
180 if !dir_path.exists() {
181 std::fs::create_dir(&dir_path).expect("create dir for log");
182 }
183 let file_path = dir_path.join(file_name.into()).into_boxed_path();
184 Self { level, format, file_path }
185 }
186}
187
188impl SinkConfigTrait for LogRawFile {
189 fn get_level(&self) -> Level {
190 self.level
191 }
192
193 fn get_file_path(&self) -> Option<Box<Path>> {
194 Some(self.file_path.clone())
195 }
196
197 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
198 self.hash(hasher);
199 hasher.write(b"LogRawFile");
200 }
201
202 fn build(&self) -> LoggerSink {
203 LoggerSink::File(LoggerSinkFile::new(self))
204 }
205}
206
207#[derive(Copy, Clone, Debug, Hash, PartialEq)]
208#[repr(u8)]
209pub enum ConsoleTarget {
210 Stdout = 1,
211 Stderr = 2,
212}
213
214impl FromStr for ConsoleTarget {
215 type Err = ();
216
217 fn from_str(s: &str) -> Result<Self, ()> {
219 let v = s.to_lowercase();
220 match v.as_str() {
221 "stdout" => Ok(ConsoleTarget::Stdout),
222 "stderr" => Ok(ConsoleTarget::Stderr),
223 "out" => Ok(ConsoleTarget::Stdout),
224 "err" => Ok(ConsoleTarget::Stderr),
225 "1" => Ok(ConsoleTarget::Stdout),
226 "2" => Ok(ConsoleTarget::Stderr),
227 _ => Err(()),
228 }
229 }
230}
231
232#[derive(Hash)]
233pub struct LogConsole {
234 pub target: ConsoleTarget,
235
236 pub level: Level,
238
239 pub format: LogFormat,
240}
241
242impl LogConsole {
243 pub fn new(target: ConsoleTarget, level: Level, format: LogFormat) -> Self {
244 Self { target, level, format }
245 }
246}
247
248impl SinkConfigTrait for LogConsole {
249 fn get_level(&self) -> Level {
250 self.level
251 }
252
253 fn get_file_path(&self) -> Option<Box<Path>> {
254 None
255 }
256
257 fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
258 self.hash(hasher);
259 hasher.write(b"LogConsole");
260 }
261
262 fn build(&self) -> LoggerSink {
263 LoggerSink::Console(LoggerSinkConsole::new(self))
264 }
265}
266
267pub struct EnvVarDefault<'a, T> {
268 name: &'a str,
269 default: T,
270}
271
272pub fn env_or<'a, T>(name: &'a str, default: T) -> EnvVarDefault<'a, T> {
287 EnvVarDefault { name, default }
288}
289
290impl<'a> Into<String> for EnvVarDefault<'a, &'a str> {
291 fn into(self) -> String {
292 if let Ok(v) = std::env::var(&self.name) {
293 return v;
294 }
295 return self.default.to_string();
296 }
297}
298
299impl<'a, P: AsRef<Path>> Into<PathBuf> for EnvVarDefault<'a, P> {
300 fn into(self) -> PathBuf {
301 if let Some(v) = std::env::var_os(&self.name) {
302 if v.len() > 0 {
303 return PathBuf::from(v);
304 }
305 }
306 return self.default.as_ref().to_path_buf();
307 }
308}
309
310macro_rules! impl_from_env {
311 ($type: tt) => {
312 impl<'a> Into<$type> for EnvVarDefault<'a, $type> {
313 #[inline]
314 fn into(self) -> $type {
315 if let Ok(v) = std::env::var(&self.name) {
316 match $type::from_str(&v) {
317 Ok(r) => return r,
318 Err(_) => {
319 eprintln!(
320 "env {}={} is not valid, set to {:?}",
321 self.name, v, self.default
322 );
323 }
324 }
325 }
326 return self.default;
327 }
328 }
329 };
330}
331
332impl_from_env!(ConsoleTarget);
335impl_from_env!(Level);
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::recipe;
341
342 #[test]
343 fn test_raw_file() {
344 let _file_sink = LogRawFile::new("/tmp", "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
345 let dir_path = Path::new("/tmp/test_dir");
346 if dir_path.is_dir() {
347 std::fs::remove_dir(&dir_path).expect("ok");
348 }
349 let _file_sink =
350 LogRawFile::new(&dir_path, "test.log", Level::Info, recipe::LOG_FORMAT_DEBUG);
351 assert!(dir_path.is_dir());
352 std::fs::remove_dir(&dir_path).expect("ok");
353 }
354
355 #[test]
356 fn test_env_config() {
357 unsafe { std::env::set_var("LEVEL", "warn") };
359 let level: Level = env_or("LEVEL", Level::Debug).into();
360 assert_eq!(level, Level::Warn);
361 unsafe { std::env::set_var("LEVEL", "WARN") };
362 let level: Level = env_or("LEVEL", Level::Debug).into();
363 assert_eq!(level, Level::Warn);
364
365 assert_eq!(ConsoleTarget::from_str("Stdout").unwrap(), ConsoleTarget::Stdout);
366 assert_eq!(ConsoleTarget::from_str("StdERR").unwrap(), ConsoleTarget::Stderr);
367 assert_eq!(ConsoleTarget::from_str("1").unwrap(), ConsoleTarget::Stdout);
368 assert_eq!(ConsoleTarget::from_str("2").unwrap(), ConsoleTarget::Stderr);
369 assert_eq!(ConsoleTarget::from_str("0").unwrap_err(), ());
370
371 unsafe { std::env::set_var("CONSOLE", "stderr") };
373 let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
374 assert_eq!(target, ConsoleTarget::Stderr);
375 unsafe { std::env::set_var("CONSOLE", "") };
376 let target: ConsoleTarget = env_or("CONSOLE", ConsoleTarget::Stdout).into();
377 assert_eq!(target, ConsoleTarget::Stdout);
378
379 unsafe { std::env::set_var("LOG_PATH", "/tmp/test.log") };
381 let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
382 assert_eq!(path, Path::new("/tmp/test.log").to_path_buf());
383
384 unsafe { std::env::set_var("LOG_PATH", "") };
385 let path: PathBuf = env_or("LOG_PATH", "/tmp/other.log").into();
386 assert_eq!(path, Path::new("/tmp/other.log").to_path_buf());
387
388 let _builder = recipe::raw_file_logger(env_or("LOG_PATH", "/tmp/other.log"), Level::Info);
389 }
390}