use env_logger::{Builder, Env};
use std::{
fs::File,
io::{self, Write},
};
pub fn init_logger(verbose: bool) {
let env = Env::default()
.filter_or("MY_LOG_LEVEL", "info")
.write_style("MY_LOG_STYLE");
let mut builder = Builder::from_env(env);
builder.format(move |buf, record| {
let timestamp = buf.timestamp();
writeln!(
buf,
"[{timestamp}] {level}: {message}",
level = record.level(),
message = record.args()
)
});
if verbose {
let log_file = File::create("output.log").expect("Unable to create log file");
let dual_writer = DualWriter {
stdout: io::stdout(),
log_file,
};
builder.target(env_logger::Target::Pipe(Box::new(dual_writer)));
}
builder.init();
}
pub struct DualWriter {
stdout: io::Stdout,
log_file: File,
}
impl Write for DualWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stdout.lock().write_all(buf)?;
self.log_file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stdout.lock().flush()?;
self.log_file.flush()
}
}
#[cfg(test)]
mod tests {
use env_logger::{Builder, Env};
use log::{debug, error, info, warn};
use std::{
fs::{self, File},
io::{self, Write},
thread,
};
use super::DualWriter;
fn init_logger(verbose: bool) {
let env = Env::default()
.filter_or("MY_LOG_LEVEL", "info")
.write_style("MY_LOG_STYLE");
let mut builder = Builder::from_env(env);
builder.format(move |buf, record| {
let timestamp = buf.timestamp();
writeln!(
buf,
"[{timestamp}] {level}: {message}",
level = record.level(),
message = record.args()
)
});
if verbose {
let log_file = File::create("output.log").expect("Unable to create log file");
let dual_writer = DualWriter {
stdout: io::stdout(),
log_file,
};
builder.target(env_logger::Target::Pipe(Box::new(dual_writer)));
}
builder.is_test(true).try_init().unwrap();
}
#[test]
fn test_logger_behavior() {
init_logger(true);
let a = 1;
let b = 2;
debug!("checking whether {} + {} = 3", a, b);
assert_eq!(3, a + b);
let msg = "Test message";
info!("{}", msg);
let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
assert!(log_contents.contains("Test message"));
info!("This is an info message");
warn!("This is a warning message");
error!("This is an error message");
let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
assert!(log_contents.contains("This is an info message"));
assert!(log_contents.contains("This is a warning message"));
assert!(log_contents.contains("This is an error message"));
info!("Logging to both stdout and file");
let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
assert!(log_contents.contains("Logging to both stdout and file"));
let handles: Vec<_> = (0..10)
.map(|i| {
thread::spawn(move || {
for _ in 0..10 {
info!("Thread {} logging", i);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
for i in 0..10 {
assert!(log_contents.contains(&format!("Thread {} logging", i)));
}
fs::remove_file("output.log").unwrap();
}
}