1mod writer;
2use std::{path::PathBuf, sync::RwLock};
3
4use crate::writer::BufferedWriter;
5
6use log::{Log, SetLoggerError, LevelFilter};
7use time::{format_description::FormatItem, OffsetDateTime, UtcDateTime};
8
9const TIMESTMAMP_FORMAT: &[FormatItem] = time::macros::format_description!(
10    "[hour]:[minute]:[second]:[subsecond digits:6]"
11);
12
13
14#[derive(PartialEq)]
15enum Timestamps {
16    None, 
17    Local,
18    Utc,
19}
20
21pub struct Logger {
22    log_level: LevelFilter,
24    timestamps: Timestamps,
25    thread: bool,
26    target: bool,
27    writers: Vec<RwLock<BufferedWriter>>,
36}
37
38impl Logger {
39
40    #[must_use = "You must call init() to initialize the logger"]
51    pub fn new() -> Logger {
52        Logger { 
53            log_level: LevelFilter::Trace , 
54            timestamps: Timestamps::Local, 
55            target: false,
56            thread: false, 
57            writers: Vec::new() }
58    }
59
60    #[must_use = "You must call init() to initialize the logger"]
62    pub fn with_level(mut self, level: LevelFilter) -> Logger {
63        self.log_level = level;
64        self
65    }
66
67    #[must_use = "You must call init() to initialize the logger"]
69    pub fn with_utc_timestamps(mut self) -> Logger {
70        self.timestamps = Timestamps::Utc;
71        self
72    }
73
74    #[must_use = "You must call init() to initialize the logger"]
76    pub fn with_local_timestamps(mut self) -> Logger {
77        self.timestamps = Timestamps::Local;
78        self
79    }
80
81    #[must_use = "You must call init() to initialize the logger"]
83    pub fn without_timestamps(mut self) -> Logger {
84        self.timestamps = Timestamps::None;
85        self
86    }
87
88    #[must_use = "You must call init() to initialize the logger"]
90    pub fn with_thread(mut self) -> Logger {
91        self.thread = true;
92        self
93    }
94
95    #[must_use = "You must call init() to initialize the logger"]
97    pub fn with_target(mut self) -> Logger {
98        self.target = true;
99        self
100    }
101
102    #[must_use = "You must call init() to initialize the logger"]
104    pub fn without_target(mut self) -> Logger {
105        self.target = false;
106        self
107    }
108
109    #[must_use = "You must call init() to initialize the logger"]
116    pub fn add_writer_stdout(mut self, multi_thread: bool, capacity: Option<usize>) -> Logger {
117        let mut writer = BufferedWriter::new().on_stdout();
118
119        if multi_thread { writer = writer.with_separate_thread(); }
120        if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
121
122        match writer.init() {
123            Ok(initialized_writer) => self.writers.push(RwLock::new(initialized_writer)),
124            Err(error) => println!("Error while initializing writer. Details: {}", error),
125        }
126
127        self
128    }
129
130    #[must_use = "You must call init() to initialize the logger"]
138    pub fn add_writer_file(mut self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>) -> Logger {
139        let mut writer = BufferedWriter::new().on_file(file_path);
140        
141        if multi_thread { writer = writer.with_separate_thread(); }
142        if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
143
144        match writer.init() {
145            Ok(initialized_writer) => self.writers.push(RwLock::new(initialized_writer)),
146            Err(error) => println!("Error while initializing writer. Details: {}", error),
147        }
148
149        self
150    }
151
152    pub fn init(self) -> Result<(), SetLoggerError> {
153        log::set_max_level(self.log_level);
154        log::set_boxed_logger(Box::new(self))
155    }
156
157    pub fn log_level(&self) -> LevelFilter {
158        self.log_level
159    }
160}
161
162impl Default for Logger {
163    fn default() -> Self {
164        Logger::new()
165    }
166}
167
168impl Log for Logger {
169    fn enabled(&self, metadata: &log::Metadata) -> bool {
170        metadata.level().to_level_filter() <= self.log_level
171    }
172
173    fn log(&self, record: &log::Record) {
174        if !self.enabled(record.metadata()) {
175            return;
176        }
177
178        let mut target = "";
179
180        if self.target {
181            target = if !record.target().is_empty() {
182                    record.target()
183                } else {
184                    record.module_path().unwrap_or_default()
185                };
186        }
187
188
189        let thread = if self.thread {
190            format!("{}", std::thread::current().name().unwrap_or("?"))
191        } else {
192            "".to_string()
193        };
194        
195        let timestamp = match self.timestamps {
196            Timestamps::None => "".to_string(),
197            Timestamps::Local => format!(
198                "{}",
199                OffsetDateTime::now_local()
200                    .expect(concat!(
201                        "Could not determine the UTC offset on this system. ",
202                        "Consider displaying UTC time instead. ",
203                        "Possible causes are that the time crate does not implement \"local_offset_at\" ",
204                        "on your system, or that you are running in a multi-threaded environment and ",
205                        "the time crate is returning \"None\" from \"local_offset_at\" to avoid unsafe ",
206                        "behaviour. See the time crate's documentation for more information. ",
207                        "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)"
208                    ))
209                    .format(TIMESTMAMP_FORMAT)
210                    .unwrap()
211            ),
212            Timestamps::Utc => format!( "{}", UtcDateTime::now().format(TIMESTMAMP_FORMAT).unwrap()),
213        };
214
215        let message = format!("{timestamp}-[{target}][{thread}] -> {{{}}} {}", record.level().to_string(), record.args());
216
217        for writer in &self.writers {
218            if let Ok(writer_mut) = writer.write() {
219                writer_mut.write(message.as_str());
220            } else {
221                panic!("Cannot get writer as mutable. RWLock is poisoned!");
222            }
223        }
224    }
225
226    fn flush(&self) {
249        for writer in &self.writers {
250            if let Ok(mut writer_mut) = writer.write() {
251                writer_mut.flush_and_cleanup();
252            } else {
253                panic!("Cannot get writer as mutable. RWLock is poisoned!");
254            }
255        }
256    }
257
258}
259
260#[cfg(test)]
261mod tests {
262    use log::{Metadata, Level};
263
264    use super::*;
265
266    #[test]
267    fn test_default_level() {
268        let builder = Logger::new();
269        assert_eq!(builder.log_level(), LevelFilter::Trace);
270    }
271
272    #[test]
273    fn test_creation_level() {
274        let builder = Logger::new().with_level(LevelFilter::Debug);
275        assert_eq!(builder.log_level(), LevelFilter::Debug);
276    }
277
278    #[test]
279    fn test_logger_enabled() {
280        let logger = Logger::new().with_level(LevelFilter::Debug);
281        assert_eq!(logger.log_level(), LevelFilter::Debug);
282        assert!(logger.enabled(&create_log("test_enabled", Level::Debug)));
283    }
284
285    #[test]
286    fn test_timestamp_default() {
287        let builder = Logger::new();
288        assert!(builder.timestamps == Timestamps::Local);
289    }
290
291    #[test]
292    fn test_utc_timestamp() {
293        let builder = Logger::new().with_utc_timestamps();
294        assert!(builder.timestamps == Timestamps::Utc);
295    }
296
297
298    fn create_log(name: &str, level: Level) -> Metadata {
299        let mut builder = Metadata::builder();
300        builder.level(level);
301        builder.target(name);
302        builder.build()
303    }
304}