utils_box/
logger.rs

1//! # Logger utilities
2//! A toolbox of small utilities to initialize and use loggers based on `simplelog` crate.
3//! Useful for binaries that you need a terminal or a file logger fast.
4
5use std::{fs::File, path::PathBuf, str::FromStr, sync::Mutex};
6
7use anyhow::Result;
8use chrono;
9use file_rotate::{ContentLimit, FileRotate, compression::Compression, suffix::AppendCount};
10use lazy_static::lazy_static;
11use log::info;
12use simplelog::*;
13
14/// Initialize a terminal logger with the provided log level
15pub fn terminal_logger_init(level: LevelFilter) -> Result<()> {
16    // Initialize the loggers
17    let log_config = ConfigBuilder::new()
18        .set_time_format("%d-%H:%M:%S%.3f".to_string())
19        .set_time_to_local(true)
20        .build();
21
22    Ok(TermLogger::init(
23        level,
24        log_config.clone(),
25        TerminalMode::Mixed,
26        ColorChoice::Auto,
27    )?)
28}
29
30// Initialize a terminal and a file logger with the provided log levels
31pub fn combined_logger_init(
32    terminal_level: LevelFilter,
33    file_level: LevelFilter,
34    log_path: &str,
35    filename_prefix: &str,
36) -> Result<()> {
37    let mut log_path = if log_path.is_empty() {
38        std::env::temp_dir()
39    } else {
40        PathBuf::from_str(log_path).unwrap()
41    };
42
43    let instance_folder = format!("{}_{:?}", filename_prefix, chrono::offset::Utc::now());
44    log_path.push(format!("{filename_prefix}-logs"));
45    log_path.push(&instance_folder);
46    log_path.push(format!("{filename_prefix}.log"));
47
48    println!("[logger_init] Calculated log path [{log_path:?}]");
49
50    let log_config = ConfigBuilder::new()
51        .set_time_format("%d-%H:%M:%S%.3f".to_string())
52        .set_time_to_local(true)
53        .build();
54
55    info!("[logger_init] Calculated log path [{log_path:?}]");
56
57    Ok(CombinedLogger::init(vec![
58        TermLogger::new(
59            terminal_level,
60            log_config.clone(),
61            TerminalMode::Mixed,
62            ColorChoice::Auto,
63        ),
64        WriteLogger::new(
65            file_level,
66            log_config,
67            FileRotate::new(
68                log_path.clone(),
69                AppendCount::new(5),
70                ContentLimit::Lines(30000),
71                Compression::None,
72                #[cfg(unix)]
73                None,
74            ),
75        ),
76    ])?)
77}
78
79lazy_static! {
80    pub static ref DUMMY: log::Metadata<'static> = log::MetadataBuilder::new().build();
81
82    /// Scanner Results File (Used for Demos)
83    pub static ref RESULTS_FILE: Mutex<File> = {
84        let mut log_path = std::env::temp_dir();
85        log_path.push("utils-results.log");
86
87        Mutex::new(File::options()
88            .append(true)
89            .create(true)
90            .open(log_path)
91            .expect("Results File FAILED!"))
92    };
93}
94
95pub enum LoggerPrintMode {
96    Results,
97    Info,
98}
99
100#[macro_export]
101macro_rules! results_info {
102    (mode:$mode:expr, $($arg:tt)+) => {{
103        if $mode == "results" {
104            $crate::results_info!($($arg)+);
105        }
106        else {
107            $crate::results_info!(info, $($arg)+);
108        }
109    }};
110
111    (info, $($arg:tt)+) => {{
112        $crate::log_info!($($arg)+);
113    }};
114
115    ($($arg:tt)+) => {{
116        use std::io::Write;
117        use chrono::Local;
118
119        $crate::log_info!($($arg)+);
120
121        let timestamp = Local::now().format("%d-%H:%M:%S%.3f").to_string();
122        let mut res_file = $crate::logger::RESULTS_FILE.lock().unwrap();
123        writeln!(res_file,"{} {}",timestamp, format!($($arg)+)).unwrap();
124    }};
125}
126
127#[macro_export]
128macro_rules! log_info {
129    ($($arg:tt)+) => {{
130        if log::log_enabled!(log::Level::Trace) {
131            if log::logger().enabled(&$crate::logger::DUMMY) {
132                log::info!($($arg)+);
133            }
134            else {
135                std::println!($($arg)+);
136            }
137        }
138    }};
139}
140
141#[macro_export]
142macro_rules! log_warn {
143    ($($arg:tt)+) => {{
144        if log::log_enabled!(log::Level::Warn) {
145            if log::logger().enabled(&$crate::logger::DUMMY) {
146                log::warn!($($arg)+);
147            }
148            else {
149                std::println!($($arg)+);
150            }
151        }
152    }};
153}
154
155#[macro_export]
156macro_rules! log_debug {
157    ($($arg:tt)+) => {{
158        if log::log_enabled!(log::Level::Debug) {
159            if log::logger().enabled(&$crate::logger::DUMMY) {
160                log::debug!($($arg)+);
161            }
162            else {
163                std::println!($($arg)+);
164            }
165        }
166    }};
167}
168
169#[macro_export]
170macro_rules! log_trace {
171    ($($arg:tt)+) => {{
172        if log::log_enabled!(log::Level::Trace) {
173            if log::logger().enabled(&$crate::logger::DUMMY) {
174                log::trace!($($arg)+);
175            }
176            else {
177                std::println!($($arg)+);
178            }
179        }
180    }};
181}
182
183#[macro_export]
184macro_rules! log_error {
185    ($($arg:tt)+) => {{
186        if log::log_enabled!(log::Level::Error) {
187            if log::logger().enabled(&$crate::logger::DUMMY) {
188                log::error!($($arg)+);
189            }
190            else {
191                std::println!($($arg)+);
192            }
193        }
194    }};
195}
196
197#[cfg(test)]
198mod tests {
199    use crate::logger::*;
200    use tempfile::tempdir;
201
202    #[test]
203    fn terminal_logger_init_test() {
204        log_info!("INFO Test TO PRINTLN!");
205        log_debug!("DEBUG Test TO PRINTLN!");
206
207        if !log::logger().enabled(&crate::logger::DUMMY) {
208            terminal_logger_init(LevelFilter::Debug).unwrap();
209        }
210
211        log_info!("INFO Test TO LOGGER!");
212        log_debug!("DEBUG Test TO LOGGER!");
213    }
214
215    #[test]
216    fn combined_logger_init_test() {
217        let log_dir = tempdir().expect("Failed to create temp directory!");
218
219        log_info!("INFO Test TO PRINTLN!");
220        log_debug!("DEBUG Test TO PRINTLN!");
221
222        if !log::logger().enabled(&crate::logger::DUMMY) {
223            combined_logger_init(
224                LevelFilter::Debug,
225                LevelFilter::Trace,
226                log_dir.path().to_str().unwrap(),
227                "test",
228            )
229            .unwrap();
230        }
231
232        log_info!("INFO Test TO combined LOGGER!");
233        log_debug!("DEBUG Test TO combined LOGGER!");
234    }
235
236    #[test]
237    fn results_macros_test() {
238        log_info!("INFO Test TO PRINTLN!");
239        results_info!("RESULTS Test from PRINTLN!");
240
241        if !log::logger().enabled(&crate::logger::DUMMY) {
242            terminal_logger_init(LevelFilter::Debug).unwrap();
243        }
244
245        log_info!("INFO Test TO LOGGER!");
246        results_info!("RESULTS Test from LOGGER!");
247
248        log_info!("INFO Test TO LOGGER!");
249        results_info!(info, "{}", "RESULTS Test from LOGGER2!");
250        results_info!("{}", "RESULTS Test from LOGGER3!");
251        let a = "results";
252        results_info!(mode: a, "{}", "TEST");
253        let b = "info";
254        results_info!(mode: b, "{}", "TEST2");
255    }
256}