1mod writer;
2use std::{path::PathBuf, sync::RwLock};
3
4use crate::writer::BufferedWriter;
5
6use log::{LevelFilter, Log, SetLoggerError};
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    writer_levels: Vec<LevelFilter>,
37}
38
39impl Logger {
40
41    #[must_use = "You must call init() to initialize the logger"]
52    pub fn new() -> Logger {
53        Logger { 
54            log_level: LevelFilter::Trace , 
55            timestamps: Timestamps::Local, 
56            target: false,
57            thread: false, 
58            writers: Vec::new(),
59            writer_levels: Vec::new()
60        }
61    }
62
63    #[must_use = "You must call init() to initialize the logger"]
65    pub fn with_level(mut self, level: LevelFilter) -> Logger {
66        self.log_level = level;
67        self
68    }
69
70    #[must_use = "You must call init() to initialize the logger"]
72    pub fn with_utc_timestamps(mut self) -> Logger {
73        self.timestamps = Timestamps::Utc;
74        self
75    }
76
77    #[must_use = "You must call init() to initialize the logger"]
79    pub fn with_local_timestamps(mut self) -> Logger {
80        self.timestamps = Timestamps::Local;
81        self
82    }
83
84    #[must_use = "You must call init() to initialize the logger"]
86    pub fn without_timestamps(mut self) -> Logger {
87        self.timestamps = Timestamps::None;
88        self
89    }
90
91    #[must_use = "You must call init() to initialize the logger"]
93    pub fn with_thread(mut self) -> Logger {
94        self.thread = true;
95        self
96    }
97
98    #[must_use = "You must call init() to initialize the logger"]
100    pub fn with_target(mut self) -> Logger {
101        self.target = true;
102        self
103    }
104
105    #[must_use = "You must call init() to initialize the logger"]
107    pub fn without_target(mut self) -> Logger {
108        self.target = false;
109        self
110    }
111
112    #[must_use = "You must call init() to initialize the logger"]
119    pub fn add_writer_stdout(mut self, multi_thread: bool, capacity: Option<usize>) -> Logger {
120        let default_level = self.log_level.clone();
121        self = self.add_writer_stdout_level(multi_thread, capacity, default_level);
122        self
123    }
124
125    #[must_use = "You must call init() to initialize the logger"]
133    pub fn add_writer_stdout_with_level(mut self, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
134        self = self.add_writer_stdout_level(multi_thread, capacity, level);
135        self
136    }
137
138    #[must_use = "You must call init() to initialize the logger"]
147    pub fn add_writer_file(self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>) -> Logger {
148        let default_level = self.log_level.clone();
149        self.add_writer_file_level(file_path, multi_thread, capacity, default_level)
150    }
151
152    #[must_use = "You must call init() to initialize the logger"]
160    pub fn add_writer_file_with_level(self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
161        self.add_writer_file_level(file_path, multi_thread, capacity, level)
162    }
163
164    pub fn init(self) -> Result<(), SetLoggerError> {
165        log::set_max_level(self.log_level);
166        log::set_boxed_logger(Box::new(self))
167    }
168
169    pub fn log_level(&self) -> LevelFilter {
170        self.log_level
171    }
172
173
174    fn add_writer_stdout_level(mut self, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
175        let mut writer = BufferedWriter::new().on_stdout();
176
177        if multi_thread { writer = writer.with_separate_thread(); }
178        if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
179
180        match writer.init() {
181            Ok(initialized_writer) => {
182                self.writers.push(RwLock::new(initialized_writer));
183                self.writer_levels.push(level.to_owned());
184            },
185            Err(error) => println!("Error while initializing writer. Details: {}", error),
186        }
187
188        self
189    }
190
191
192    fn add_writer_file_level(mut self, file_path: PathBuf, multi_thread: bool, capacity: Option<usize>, level: LevelFilter) -> Logger {
193        let mut writer = BufferedWriter::new().on_file(file_path);
194        
195        if multi_thread { writer = writer.with_separate_thread(); }
196        if let Some(buf_cap) = capacity { writer = writer.with_buffer_capacity(buf_cap) }
197
198        match writer.init() {
199            Ok(initialized_writer) => {
200                self.writers.push(RwLock::new(initialized_writer));
201                self.writer_levels.push(level.to_owned());
202            },
203            Err(error) => println!("Error while initializing writer. Details: {}", error),
204        }
205
206        self
207    }
208}
209
210impl Default for Logger {
211    fn default() -> Self {
212        Logger::new()
213    }
214}
215
216impl Log for Logger {
217    fn enabled(&self, metadata: &log::Metadata) -> bool {
218        metadata.level().to_level_filter() <= self.log_level
219    }
220
221    fn log(&self, record: &log::Record) {
222        if !self.enabled(record.metadata()) {
223            return;
224        }
225
226        let mut target = "";
227
228        if self.target {
229            target = if !record.target().is_empty() {
230                    record.target()
231                } else {
232                    record.module_path().unwrap_or_default()
233                };
234        }
235
236
237        let thread = if self.thread {
238            if let Some(thread_name) = std::thread::current().name() {
239                format!("{}", thread_name)
240            } else {
241                format!("{:?}", std::thread::current().id())
242            }
243        } else {
244            "".to_string()
245        };
246        
247        let timestamp = match self.timestamps {
248            Timestamps::None => "".to_string(),
249            Timestamps::Local => format!(
250                "{}",
251                OffsetDateTime::now_local()
252                    .expect(concat!(
253                        "Could not determine the UTC offset on this system. ",
254                        "Consider displaying UTC time instead. ",
255                        "Possible causes are that the time crate does not implement \"local_offset_at\" ",
256                        "on your system, or that you are running in a multi-threaded environment and ",
257                        "the time crate is returning \"None\" from \"local_offset_at\" to avoid unsafe ",
258                        "behaviour. See the time crate's documentation for more information. ",
259                        "(https://time-rs.github.io/internal-api/time/index.html#feature-flags)"
260                    ))
261                    .format(TIMESTMAMP_FORMAT)
262                    .unwrap()
263            ),
264            Timestamps::Utc => format!( "{}", UtcDateTime::now().format(TIMESTMAMP_FORMAT).unwrap()),
265        };
266
267        let message = format!("{timestamp}-[{target}][{thread}] -> {{{}}} {}", record.level().to_string(), record.args());
268
269        for (index, writer) in self.writers.iter().enumerate() {
270            if index >= self.writer_levels.len() {
271                panic!("Level index out of range!");
272            }
273
274            if record.metadata().level().to_level_filter() > self.writer_levels[index] {
276                continue;
277            }
278
279            if let Ok(writer_mut) = writer.write() {
280                writer_mut.write(message.as_str());
281            } else {
282                panic!("Cannot get writer as mutable. RWLock is poisoned!");
283            }
284        }
285    }
286
287    fn flush(&self) {
310        for writer in &self.writers {
311            if let Ok(mut writer_mut) = writer.write() {
312                writer_mut.flush_and_cleanup();
313            } else {
314                panic!("Cannot get writer as mutable. RWLock is poisoned!");
315            }
316        }
317    }
318
319}
320
321#[cfg(test)]
322mod tests {
323    use log::{Metadata, Level};
324
325    use super::*;
326
327    #[test]
328    fn test_default_level() {
329        let builder = Logger::new();
330        assert_eq!(builder.log_level(), LevelFilter::Trace);
331    }
332
333    #[test]
334    fn test_creation_level() {
335        let builder = Logger::new().with_level(LevelFilter::Debug);
336        assert_eq!(builder.log_level(), LevelFilter::Debug);
337    }
338
339    #[test]
340    fn test_logger_enabled() {
341        let logger = Logger::new().with_level(LevelFilter::Debug);
342        assert_eq!(logger.log_level(), LevelFilter::Debug);
343        assert!(logger.enabled(&create_log("test_enabled", Level::Debug)));
344    }
345
346    #[test]
347    fn test_timestamp_default() {
348        let builder = Logger::new();
349        assert!(builder.timestamps == Timestamps::Local);
350    }
351
352    #[test]
353    fn test_utc_timestamp() {
354        let builder = Logger::new().with_utc_timestamps();
355        assert!(builder.timestamps == Timestamps::Utc);
356    }
357
358
359    fn create_log(name: &str, level: Level) -> Metadata {
360        let mut builder = Metadata::builder();
361        builder.level(level);
362        builder.target(name);
363        builder.build()
364    }
365}