1mod console;
2mod file;
3mod root;
4
5pub use console::{Console, ConsoleLogger};
7pub use file::{FileLogger, RotateOptions, RotateSize, RotateTime};
8
9use chrono::{DateTime, Local, SecondsFormat};
10use log::{Level, LevelFilter, Log, Record, SetLoggerError};
11use root::RootLogger;
12
13pub fn init(loggers: Vec<Box<dyn Log>>, level: LevelFilter) -> FlushGuard {
16 init_raw(loggers, level).unwrap()
17}
18
19#[allow(unused)]
27pub fn init_for_test(level: LevelFilter) -> FlushGuard {
28 let loggers = vec![ConsoleLogger::new_boxed(
29 Console::Stdout,
30 level,
31 default_filter,
32 default_formatter,
33 )];
34 if let Ok(flush) = init_raw(loggers, level) {
35 flush_on_panic();
37 flush
38 } else {
39 FlushGuard
41 }
42}
43
44fn init_raw(loggers: Vec<Box<dyn Log>>, level: LevelFilter) -> Result<FlushGuard, SetLoggerError> {
45 let log = RootLogger::new(loggers);
46 log::set_max_level(level);
47 log::set_boxed_logger(Box::new(log))?;
48
49 Ok(FlushGuard)
50}
51
52#[must_use]
53pub struct FlushGuard;
55
56impl Drop for FlushGuard {
57 fn drop(&mut self) {
58 log::logger().flush();
59 }
60}
61
62fn flush_on_panic() {
64 let old = std::panic::take_hook();
65 std::panic::set_hook(Box::new(move |info| {
66 old(info);
68 let _ = FlushGuard;
70 }));
71}
72
73pub struct FormatArgs<'a> {
74 pub timestamp: DateTime<Local>,
75 pub level: Level,
76 pub level_str: String,
77 pub target: &'a str,
78 pub body: String,
79 pub module: &'a str,
80 pub file: &'a str,
81 pub line: String,
82}
83
84pub fn default_formatter(args: FormatArgs) -> String {
85 match args.level {
86 Level::Trace => format!(
87 "{} {} {} ({} {}:{}) {}",
88 args.timestamp.to_rfc3339_opts(SecondsFormat::Secs, false),
89 args.level_str,
90 args.target,
91 args.module,
92 args.file,
93 args.line,
94 args.body,
95 ),
96 _ => format!(
97 "{} {} {} {}",
98 args.timestamp.to_rfc3339_opts(SecondsFormat::Secs, true),
99 args.level_str,
100 args.target,
101 args.body
102 ),
103 }
104}
105
106pub fn default_filter(_target: &str) -> bool {
107 true
108}
109
110fn translate_args<'a>(record: &Record<'a>, timestamp: DateTime<Local>) -> FormatArgs<'a> {
111 let level = record.level();
112 let level_str = format!("[{level:5}]");
113 let target = record.target();
114 let body = record.args().to_string();
115 let module = record.module_path().unwrap_or("unknown");
116 let file = record.file().unwrap_or("unknown");
117 let line = record
118 .line()
119 .map_or("unknown".to_string(), |n| n.to_string());
120
121 FormatArgs {
122 timestamp,
123 level,
124 level_str,
125 target,
126 body,
127 module,
128 file,
129 line,
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use super::*;
136
137 #[test]
138 fn log1() {
139 let _ = init_for_test(LevelFilter::Trace);
140
141 log::error!("This is a test {}", 42);
142 log::warn!("This is a test {}", 42);
143 log::info!("This is a test {}", 42);
144 log::debug!("This is a test {}", 42);
145 log::trace!("This is a test {}", 42);
146 }
147}