customlog/
lib.rs

1mod console;
2mod file;
3mod root;
4
5// Raname and export
6pub 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
13/// [FlushGuard] must be dropped on the end of the program.
14///  Panic if a logger is already set.
15pub fn init(loggers: Vec<Box<dyn Log>>, level: LevelFilter) -> FlushGuard {
16    init_raw(loggers, level).unwrap()
17}
18
19/// Initialize with [ConsoleLogger] + [Console::Stdout] + [default_formatter].
20///
21/// [FlushGuard] must be dropped on the end of the program.
22/// Ignore errors if a logger is already set.
23///
24/// This function is intended to be called on test start.
25/// `cargo test -- --nocapture` option is needed to see the log.
26#[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        // OK
36        flush_on_panic();
37        flush
38    } else {
39        // already registered
40        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]
53/// On drop, calls `log::logger()::flush()`.
54pub struct FlushGuard;
55
56impl Drop for FlushGuard {
57    fn drop(&mut self) {
58        log::logger().flush();
59    }
60}
61
62/// Set panic hook that calls [FlushGuard::drop].
63fn flush_on_panic() {
64    let old = std::panic::take_hook();
65    std::panic::set_hook(Box::new(move |info| {
66        // execute old handler
67        old(info);
68        // flush on drop
69        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}