1use env_logger::{Builder, Env};
2use std::{
3 fs::File,
4 io::{self, Write},
5};
6
7pub fn init_logger(verbose: bool) {
8 let env = Env::default()
9 .filter_or("MY_LOG_LEVEL", "info")
10 .write_style("MY_LOG_STYLE");
11
12 let mut builder = Builder::from_env(env);
13
14 builder.format(move |buf, record| {
15 let timestamp = buf.timestamp();
16 writeln!(
17 buf,
18 "[{timestamp}] {level}: {message}",
19 level = record.level(),
20 message = record.args()
21 )
22 });
23
24 if verbose {
25 let log_file = File::create("output.log").expect("Unable to create log file");
27 let dual_writer = DualWriter {
28 stdout: io::stdout(),
29 log_file,
30 };
31
32 builder.target(env_logger::Target::Pipe(Box::new(dual_writer)));
33 }
34
35 builder.init();
36}
37
38pub struct DualWriter {
40 stdout: io::Stdout,
41 log_file: File,
42}
43
44impl Write for DualWriter {
45 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
46 self.stdout.lock().write_all(buf)?;
47 self.log_file.write(buf)
48 }
49
50 fn flush(&mut self) -> io::Result<()> {
51 self.stdout.lock().flush()?;
52 self.log_file.flush()
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use env_logger::{Builder, Env};
59 use log::{debug, error, info, warn};
60 use std::{
61 fs::{self, File},
62 io::{self, Write},
63 thread,
64 };
65
66 use super::DualWriter;
67
68 fn init_logger(verbose: bool) {
69 let env = Env::default()
70 .filter_or("MY_LOG_LEVEL", "info")
71 .write_style("MY_LOG_STYLE");
72
73 let mut builder = Builder::from_env(env);
74 builder.format(move |buf, record| {
75 let timestamp = buf.timestamp();
76 writeln!(
77 buf,
78 "[{timestamp}] {level}: {message}",
79 level = record.level(),
80 message = record.args()
81 )
82 });
83 if verbose {
84 let log_file = File::create("output.log").expect("Unable to create log file");
86 let dual_writer = DualWriter {
87 stdout: io::stdout(),
88 log_file,
89 };
90
91 builder.target(env_logger::Target::Pipe(Box::new(dual_writer)));
92 }
93
94 builder.is_test(true).try_init().unwrap();
95 }
96
97 #[test]
98 fn test_logger_behavior() {
99 init_logger(true);
100
101 let a = 1;
103 let b = 2;
104
105 debug!("checking whether {} + {} = 3", a, b);
106 assert_eq!(3, a + b);
107
108 let msg = "Test message";
110 info!("{}", msg);
111 let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
112 assert!(log_contents.contains("Test message"));
113
114 info!("This is an info message");
116 warn!("This is a warning message");
117 error!("This is an error message");
118 let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
119 assert!(log_contents.contains("This is an info message"));
120 assert!(log_contents.contains("This is a warning message"));
121 assert!(log_contents.contains("This is an error message"));
122
123 info!("Logging to both stdout and file");
125 let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
126 assert!(log_contents.contains("Logging to both stdout and file"));
127
128 let handles: Vec<_> = (0..10)
130 .map(|i| {
131 thread::spawn(move || {
132 for _ in 0..10 {
133 info!("Thread {} logging", i);
134 }
135 })
136 })
137 .collect();
138
139 for handle in handles {
140 handle.join().unwrap();
141 }
142
143 let log_contents = fs::read_to_string("output.log").expect("Unable to read log file");
144 for i in 0..10 {
145 assert!(log_contents.contains(&format!("Thread {} logging", i)));
146 }
147
148 fs::remove_file("output.log").unwrap();
149 }
150}