prettylogger/
lib.rs

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