prettylogger/
lib.rs

1//! Highly customizable logger library.
2
3/// Highly customizable logger library.
4#[cfg(test)]
5mod tests;
6
7#[doc = include_str!("../README.md")]
8mod fileio;
9mod setters;
10mod json;
11mod getters;
12
13pub mod colors;
14pub mod config;
15
16use fileio::append_to_file;
17use chrono::{Local, DateTime};
18use serde::{Serialize, Deserialize};
19use colors::{Color, color_text};
20use config::{Verbosity, LogStruct, LogType, OnDropPolicy};
21
22/// The `Logger` struct used to print logs.
23///
24/// # Example
25/// ```
26/// # use prettylogger::Logger;
27/// // Create a `Logger` with default configuration:
28/// let mut logger = Logger::default();
29/// logger.debug("debug message");
30/// logger.info("info message");
31/// logger.warning("warning message");
32/// logger.error("error message");
33/// logger.fatal("fatal error message");
34/// ```
35#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize,
36    Deserialize)]
37pub struct Logger {
38    pub(crate) console_out_enabled: bool,
39
40    pub(crate) use_custom_log_buffer: bool,
41
42    pub(crate) verbosity: Verbosity,
43    pub(crate) filtering_enabled: bool,
44    pub(crate) log_header_color_enabled: bool,
45
46    pub(crate) debug_color: Color,
47    pub(crate) info_color: Color,
48    pub(crate) warning_color: Color,
49    pub(crate) error_color: Color,
50    pub(crate) fatal_color: Color,
51
52    pub(crate) debug_header: String,
53    pub(crate) info_header: String,
54    pub(crate) warning_header: String,
55    pub(crate) error_header: String,
56    pub(crate) fatal_header: String,
57
58    pub(crate) log_format: String,
59    pub(crate) datetime_format: String,
60
61    pub(crate) file_logging_enabled: bool,
62    pub(crate) log_file_path: String,
63    pub(crate) file_log_buffer_max_size: u32,
64    pub(crate) on_drop_policy: OnDropPolicy,
65
66    // Dynamic variables that shouldn't be included in the template file:
67    #[serde(skip)]
68    pub(crate) custom_log_buffer: Vec<LogStruct>,
69    #[serde(skip)]
70    pub(crate) file_log_buffer: Vec<LogStruct>,
71    #[serde(skip)]
72    pub(crate) show_datetime: bool,
73    #[serde(skip)]
74    pub(crate) log_file_lock: bool,
75    #[serde(skip)]
76    pub(crate) log_count: u128,
77}
78
79impl Logger {
80    pub(crate) fn get_log_headers(&self, log: &LogStruct)
81    -> (String, String, String) {
82        let header = self.get_log_type_header(log.log_type);
83        let datetime = self.get_datetime_formatted(&log.datetime);
84        return (header, datetime, log.message.clone())
85    }
86
87    pub(crate) fn get_log_type_header(&self, log_type: LogType) -> String {
88        match log_type {
89            LogType::Debug => {
90                return self.colorify(&self.debug_header,
91                    self.log_header_color(log_type))
92            }
93            LogType::Info => {
94                return self.colorify(&self.info_header,
95                    self.log_header_color(log_type))
96            }
97            LogType::Warning => {
98                return self.colorify(&self.warning_header,
99                    self.log_header_color(log_type))
100            }
101            LogType::Err => {
102                return self.colorify(&self.error_header,
103                    self.log_header_color(log_type))
104            }
105            LogType::FatalError => {
106                return self.colorify(&self.fatal_header,
107                    self.log_header_color(log_type))
108            }
109        }
110    }
111
112    pub(crate) fn get_datetime_formatted(&self,
113    datetime: &DateTime<Local>) -> String {
114        if self.show_datetime {
115            let datetime_formatted = datetime.format(&self.datetime_format);
116            return datetime_formatted.to_string()
117        }
118        return String::new();
119    }
120
121    pub(crate) fn colorify(&self, text: &str, color: Color) -> String {
122        if self.log_header_color_enabled {
123            return color_text(text, color);
124        }
125        return text.to_string()
126    }
127
128    pub(crate) fn filter_log(&self, log_type: LogType) -> bool {
129        if self.filtering_enabled {
130            return (log_type as i32) < self.verbosity as i32;
131        }
132        return false;
133    }
134
135    pub(crate) fn log_header_color(&self, log_type: LogType) -> Color {
136        match log_type {
137            LogType::Debug => self.debug_color,
138            LogType::Info => self.info_color,
139            LogType::Warning => self.warning_color,
140            LogType::Err => self.error_color,
141            LogType::FatalError => self.fatal_color,
142        }
143    }
144
145    pub(crate) fn drop_flush(&mut self) {
146        if self.file_logging_enabled {
147            let _ = self.flush_file_log_buffer(true);
148        }
149    }
150
151    pub(crate) fn flush_file_log_buffer(&mut self, is_drop_flush: bool)
152    -> Result<(), Error> {
153        if self.log_file_lock {
154            if is_drop_flush {
155                match self.on_drop_policy {
156                    OnDropPolicy::IgnoreLogFileLock => { }
157                    OnDropPolicy::DiscardLogBuffer => {
158                        return Err(Error::new(&format!("Log file lock enabled and on drop policy set to {}!",
159                            self.on_drop_policy)));
160                    }
161                }
162            }
163            else {
164               return Err(Error::new(&"Log file lock is enabled!"))
165            }
166        }
167        let mut buf = String::from("");
168
169        for log in &self.file_log_buffer {
170            buf += &self.format_log(log);
171        }
172
173        self.file_log_buffer = Vec::new();
174        let result = append_to_file(&self.log_file_path, &buf);
175
176        match result {
177            Ok(_) => Ok(()),
178            Err(_) => {
179                self.file_logging_enabled = false;
180                return Err(Error::new(&"Failed to write log buffer to a file!"))
181            },
182        }
183    }
184
185    /// Used to print a log from a `LogStruct`.
186    ///
187    /// # Example:
188    /// ```
189    /// # use prettylogger::{Logger, config::LogStruct};
190    /// # let mut logger = Logger::default();
191    /// logger.print_log(&LogStruct::error("&%$#@!"));
192    /// ```
193    pub fn print_log(&mut self, log: &LogStruct) {
194        self.log_count += 1;
195        let log_str = self.format_log(log);
196
197        if self.console_out_enabled {
198            if log.log_type == LogType::Warning
199            || log.log_type == LogType::Err
200            || log.log_type == LogType::FatalError {
201                eprint!("{}", log_str);
202            }
203            else {
204                print!("{}", log_str);
205            }
206        }
207
208        if self.use_custom_log_buffer {
209            self.custom_log_buffer.push(log.clone());
210        }
211
212        if self.file_logging_enabled {
213            self.file_log_buffer.push(log.clone());
214
215            if self.file_log_buffer_max_size != 0
216            && self.file_log_buffer.len() >=
217            self.file_log_buffer_max_size.try_into().unwrap() {
218                let _ = self.flush_file_log_buffer(false);
219            }
220        }
221    }
222
223    /// Returns a log entry from a `LogStruct` based on current `Logger`
224    /// configuration.
225    ///
226    /// # Example:
227    /// ```
228    /// # use prettylogger::{Logger, config::LogStruct};
229    /// # let mut logger = Logger::default();
230    /// let log_string = logger.format_log(&LogStruct::error("ZXJyb3IK"));
231    /// ```
232    pub fn format_log(&self, log: &LogStruct) -> String {
233        let headers = self.get_log_headers(log);
234        let mut result = String::new();
235        let mut char_iter = self
236            .log_format.char_indices().peekable();
237
238        while let Some((_, c)) = char_iter.next() {
239            match c {
240                '%' => {
241                    if let Some((_, nc)) = char_iter.peek() {
242                        match nc {
243                            'h' => result += &headers.0,
244                            'd' => result += &headers.1,
245                            'm' => result += &headers.2,
246                            'c' => result += &self.log_count.to_string(),
247                            _ => result += &nc.to_string(),
248                        }
249                        char_iter.next();
250                    }
251                }
252                _ => {
253                    result += &c.to_string();
254                }
255            }
256        }
257
258        result += "\n";
259        return result
260    }
261
262    /// Flushes log buffer (if file logging is enabled and log file lock
263    /// disabled, it writes the log buffer to a file).
264    ///
265    /// Returns an error when there is an issue writing to a file or log
266    /// file lock is enabled.
267    pub fn flush(&mut self) -> Result<(), Error> {
268        if self.file_logging_enabled {
269            match self.flush_file_log_buffer(false) {
270                Ok(_) => Ok(()),
271                Err(e) => { 
272                    return Err(Error::new(&e.to_string())); 
273                }
274            }
275        }
276        else {
277            return Err(Error::new(&"File logging is disabled!"));
278        }
279    }
280
281    /// Prints a **debug message** to `stdout`.
282    pub fn debug(&mut self, message: &str) {
283        if self.filter_log(LogType::Debug) {
284            return;
285        }
286        let log = LogStruct::debug(message);
287        self.print_log(&log);
288    }
289
290    /// Prints a **debug message** to `stdout`, bypasses filtering.
291    pub fn debug_no_filtering(&mut self, message: &str) {
292        let log = LogStruct::debug(message);
293        self.print_log(&log);
294    }
295
296    /// Prints an **informational message** to `stdout`.
297    pub fn info(&mut self, message: &str) {
298        if self.filter_log(LogType::Info) {
299            return;
300        }
301        let log = LogStruct::info(message);
302        self.print_log(&log);
303    }
304
305    /// Prints an **informational message** to `stdout`, bypasses filtering.
306    pub fn info_no_filtering(&mut self, message: &str) {
307        let log = LogStruct::info(message);
308        self.print_log(&log);
309    }
310
311    /// Prints a **warning** to `stdout`.
312    pub fn warning(&mut self, message: &str) {
313        if self.filter_log(LogType::Warning) {
314            return;
315        }
316        let log = LogStruct::warning(message);
317        self.print_log(&log);
318    }
319
320    /// Prints a **warning** to `stdout`, bypasses filtering.
321    pub fn warning_no_filtering(&mut self, message: &str) {
322        let log = LogStruct::warning(message);
323        self.print_log(&log);
324    }
325
326    /// Prints an **error** to  `stderr`.
327    pub fn error(&mut self, message: &str) {
328        let log = LogStruct::error(message);
329        self.print_log(&log);
330    }
331
332    /// Prints a **fatal error** to `stderr`.
333    pub fn fatal(&mut self, message: &str) {
334        let log = LogStruct::fatal_error(message);
335        self.print_log(&log);
336    }
337}
338
339impl Default for Logger {
340    fn default() -> Self {
341        let log_format = String::from("[%h] %m");
342        Logger {
343            console_out_enabled: true,
344
345            use_custom_log_buffer: false,
346
347            verbosity: Verbosity::default(),
348            filtering_enabled: true,
349            log_header_color_enabled: true,
350
351            debug_color: Color::Blue,
352            info_color: Color::Green,
353            warning_color: Color::Yellow,
354            error_color: Color::Red,
355            fatal_color: Color::Magenta,
356
357            debug_header: String::from("DBG"),
358            info_header: String::from("INF"),
359            warning_header: String::from("WAR"),
360            error_header: String::from("ERR"),
361            fatal_header: String::from("FATAL"),
362
363            log_format: log_format.clone(),
364            datetime_format: String::from("%Y-%m-%d %H:%M:%S"),
365
366            file_logging_enabled: false,
367            log_file_path: String::new(),
368            file_log_buffer_max_size: 128,
369            on_drop_policy: OnDropPolicy::default(),
370
371            custom_log_buffer: Vec::new(),
372            file_log_buffer: Vec::new(),
373            show_datetime: log_format.contains("%d"),
374            log_file_lock: false,
375            log_count: 1,
376        }
377    }
378}
379
380impl Drop for Logger {
381    fn drop(&mut self) {
382        self.drop_flush();
383    }
384}
385
386/// Represents an error thrown by the Logger.
387#[derive(Clone, Eq, PartialEq, Debug)]
388pub struct Error {
389    pub message: String,
390}
391
392impl Error {
393    pub fn new(msg: &str) -> Self {
394        Error {
395            message: msg.to_string(),
396        }
397    }
398}
399
400impl std::fmt::Display for Error {
401    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
402        return write!(f, "{}", self.message)
403    }
404}
405
406impl std::error::Error for Error { }