apollo_logger/
lib.rs

1pub mod background_colors;
2pub mod font_mode;
3pub mod foreground_colors;
4pub mod levels;
5
6use crate::background_colors::BackgroundColors;
7use crate::font_mode::FontMode;
8use crate::foreground_colors::ForegroundColors;
9use crate::levels::Levels;
10use chrono::Utc;
11use std::str::from_utf8;
12
13pub struct Apollo {
14    pub logging_level: Levels,
15}
16
17impl Default for Apollo {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl Apollo {
24    /// Creates a new Apollo instance
25    ///
26    /// # Examples
27    ///
28    /// ```
29    /// use crate::apollo_logger::Apollo;
30    ///
31    /// let l = Apollo::new(); // The default logging level is Debug
32    ///
33    /// l.debug("This message will be printed");
34    /// l.warn("This message will also be printed");
35    /// ```
36    /// If you require a different logging level, please use the following code instead
37    /// ```
38    /// use crate::apollo_logger::Apollo;
39    /// use crate::apollo_logger::levels::Levels;
40    ///
41    /// let l = Apollo { logging_level: Levels::INFO };
42    ///
43    /// l.debug("This message will NOT printed");
44    /// l.warn("This message will be printed");
45    /// ```
46    pub fn new() -> Apollo {
47        Apollo {
48            logging_level: Levels::DEBUG,
49        }
50    }
51
52    /// Gets the current time in Day/Months/Year Hour:Minute:Second.Millisecond format
53    fn get_time_as_string(&self) -> String {
54        Utc::now().format("%D %H:%M:%S%.3f").to_string()
55    }
56
57    /// Prints a message to the console with the DEBUG label_format
58    ///
59    /// # Arguments
60    ///
61    /// * `s`: String to print to the console
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// use crate::apollo_logger::Apollo;
67    ///
68    /// let l = Apollo::new();
69    ///
70    /// l.warn("This is an debug message");
71    /// ```
72    pub fn debug(&self, s: &str) -> Option<String> {
73        // Check if the logging level is high enough
74        if self.logging_level.as_u8() > Levels::DEBUG.as_u8() {
75            return None;
76        }
77
78        // Get current time
79        let current_time: String = self.get_time_as_string();
80
81        // Get caller file and line number
82        let location = self
83            .get_caller_location()
84            .unwrap_or(String::from("Unknown:0"));
85
86        // Get colors to print
87        let date_format = ForegroundColors::bright_green();
88        let label_format = ForegroundColors::cyan();
89        let location_format = FontMode::italic();
90        let text_format = ForegroundColors::bright_cyan();
91
92        // Print to console
93        let message = format!(
94            "{date_format}[{current_time}]\x1B[0m {label_format}[ DEBUG ]\x1B[0m | {location_format}{location}\x1B[0m | {text_format}{s}\x1B[0m"
95        );
96        println!("{message}");
97
98        Some(message)
99    }
100
101    /// Prints a message to the console with the INFO label_format
102    ///
103    /// # Arguments
104    ///
105    /// * `s`: String to print to the console
106    ///
107    /// # Examples
108    ///
109    /// ```
110    /// use crate::apollo_logger::Apollo;
111    ///
112    /// let l = Apollo::new();
113    ///
114    /// l.info("This is an info message");
115    /// ```
116    pub fn info(&self, s: &str) -> Option<String> {
117        // Check if the logging level is high enough
118        if self.logging_level.as_u8() > Levels::INFO.as_u8() {
119            return None;
120        }
121
122        // Get current time is [day/month/year hour:minute:second] format
123        let current_time: String = self.get_time_as_string();
124
125        // Get caller file and line number
126        let location = self
127            .get_caller_location()
128            .unwrap_or(String::from("Unknown:0"));
129
130        // Get colors to print
131        let date_format = ForegroundColors::bright_green();
132        let label_format = ForegroundColors::blue();
133        let location_format = FontMode::italic();
134        let text_format = ForegroundColors::bright_white();
135
136        // Print to console
137        let message = format!(
138            "{date_format}[{current_time}]\x1B[0m {label_format}[ INFO  ]\x1B[0m | {location_format}{location}\x1B[0m | {text_format}{s}\x1B[0m"
139        );
140        println!("{message}");
141
142        Some(message)
143    }
144
145    /// Prints a message to the console with the WARN label_format
146    ///
147    /// # Arguments
148    ///
149    /// * `s`: String to print to the console
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use crate::apollo_logger::Apollo;
155    ///
156    /// let l = Apollo::new();
157    ///
158    /// l.warn("This is an warning message");
159    /// ```
160    pub fn warn(&self, s: &str) -> Option<String> {
161        // Check if the logging level is high enough
162        if self.logging_level.as_u8() > Levels::WARN.as_u8() {
163            return None;
164        }
165
166        // Get current time is [day/month/year hour:minute:second] format
167        let current_time: String = self.get_time_as_string();
168
169        // Get caller file and line number
170        let location = self
171            .get_caller_location()
172            .unwrap_or(String::from("Unknown:0"));
173
174        // Get colors to print
175        let date_format = ForegroundColors::bright_green();
176        let label_format = ForegroundColors::yellow();
177        let location_format = FontMode::italic();
178        let text_format = ForegroundColors::yellow() + FontMode::bold();
179
180        // Print to console
181        let message: String = format!(
182            "{date_format}[{current_time}]\x1B[0m {label_format}[ WARN  ]\x1B[0m | {location_format}{location}\x1B[0m | {text_format}{s}\x1B[0m"
183        );
184        println!("{message}");
185
186        Some(message)
187    }
188
189    /// Prints a message to the console with the ERROR label_format
190    ///
191    /// # Arguments
192    ///
193    /// * `s`: String to print to the console
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use crate::apollo_logger::Apollo;
199    ///
200    /// let l = Apollo::new();
201    ///
202    /// l.error("This is an error message");
203    /// ```
204    pub fn error(&self, s: &str) -> Option<String> {
205        // Check if the logging level is high enough
206        if self.logging_level.as_u8() > Levels::ERROR.as_u8() {
207            return None;
208        }
209
210        // Get current time is [day/month/year hour:minute:second] format
211        let current_time: String = self.get_time_as_string();
212
213        // Get caller file and line number
214        let location = self
215            .get_caller_location()
216            .unwrap_or(String::from("Unknown:0"));
217
218        // Get colors to print
219        let date_format = ForegroundColors::bright_green();
220        let label_format = ForegroundColors::red();
221        let location_format = FontMode::italic();
222        let text_format = ForegroundColors::red() + FontMode::bold();
223
224        // Print to console
225        let message: String = format!(
226            "{date_format}[{current_time}]\x1B[0m {label_format}[ ERROR ]\x1B[0m | {location_format}{location}\x1B[0m | {text_format}{s}\x1B[0m"
227        );
228        eprintln!("{message}");
229
230        Some(message)
231    }
232
233    /// Prints a message to the console with the CRITICAL label_format
234    ///
235    /// # Arguments
236    ///
237    /// * `s`: String to print to the console
238    ///
239    /// # Examples
240    ///
241    /// ```
242    /// use crate::apollo_logger::Apollo;
243    ///
244    /// let l = Apollo::new();
245    ///
246    /// l.critical("This is an critical message");
247    /// ```
248    pub fn critical(&self, s: &str) -> Option<String> {
249        // Check if the logging level is high enough
250        if self.logging_level.as_u8() > Levels::CRITICAL.as_u8() {
251            return None;
252        }
253
254        // Get current time is [day/month/year hour:minute:second] format
255        let current_time: String = self.get_time_as_string();
256
257        // Get caller file and line number
258        let location = self
259            .get_caller_location()
260            .unwrap_or(String::from("Unknown:0"));
261
262        // Get colors to print
263        let date_format = ForegroundColors::bright_green();
264        let label_format = ForegroundColors::bright_red();
265        let location_format = FontMode::italic();
266        let text_format = ForegroundColors::bright_white()
267            + BackgroundColors::bright_red()
268            + FontMode::bold()
269            + FontMode::underline();
270
271        // Print to console
272        let message: String = format!(
273            "{date_format}[{current_time}]\x1B[0m {label_format}[ CRIT  ]\x1B[0m | {location_format}{location}\x1B[0m | {text_format}{s}\x1B[0m"
274        );
275        eprintln!("{message}");
276
277        Some(message)
278    }
279
280    /// Gets the filename and location of the parent function that called this function
281    fn get_caller_location(&self) -> Option<String> {
282        let mut caller_location: Option<String> = None;
283
284        backtrace::trace(|frame| {
285            backtrace::resolve_frame(frame, |symbol| {
286                // Check if location has been found
287                if caller_location.is_some() {
288                    return;
289                }
290
291                // Get data
292                let file_name = symbol.filename();
293                let line_number = symbol.lineno();
294                let symbol_name = symbol.name();
295
296                // If any of the data is None, continue to next frame
297                if file_name.is_none() || line_number.is_none() || symbol_name.is_none() {
298                    return;
299                }
300
301                // Filter out internal function calls
302                let file_os_str = file_name.unwrap().to_str().unwrap();
303                if Self::filter_locations(file_os_str) {
304                    return;
305                }
306
307                // Filter out backtrace symbols
308                let symbol_str = from_utf8(symbol_name.unwrap().as_bytes()).unwrap();
309                if Self::filter_locations(symbol_str) {
310                    return;
311                }
312
313                caller_location = Some(format!(
314                    "{}:{}",
315                    file_name.unwrap().file_name().unwrap().display(),
316                    line_number.unwrap()
317                ));
318            });
319            caller_location.is_none()
320        });
321        caller_location
322    }
323
324    // Check if location contains any of these blacklisted locations
325    fn filter_locations(location: &str) -> bool {
326        location.contains(".cargo\\registry")
327            || location.contains("src\\libstd")
328            || location.contains("src\\libcore")
329            || location.contains("src\\backtrace")
330            || location.contains("get_caller_location")
331            || location.contains("backtrace::trace::{{closure}}")
332            || location.contains("backtrace::resolve_frame::{{closure}}")
333            || location.contains("core::")
334            || location.contains("Apollo::critical")
335            || location.contains("Apollo::error")
336            || location.contains("Apollo::warn")
337            || location.contains("Apollo::info")
338            || location.contains("Apollo::debug")
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    // Note this useful idiom: importing names from outer (for mod tests) scope.
345    use super::*;
346
347    /// Test if debug will log to console with default logger level
348    #[test]
349    fn test_debug() {
350        let logger = Apollo::new();
351        assert!(logger.debug("This is a test debug message").is_some());
352    }
353
354    /// Test if debug will return None when logging level is too high
355    #[test]
356    fn test_debug_under_level() {
357        let logger = Apollo {
358            logging_level: Levels::INFO,
359        };
360        assert!(logger.debug("This is a test debug message").is_none());
361    }
362
363    /// Test if info will log to console with default logger level
364    #[test]
365    fn test_info() {
366        let logger = Apollo::new();
367        assert!(logger.info("This is a test info message").is_some());
368    }
369
370    /// Test if info will return None when logging level is too high
371    #[test]
372    fn test_info_under_level() {
373        let logger = Apollo {
374            logging_level: Levels::WARN,
375        };
376        assert!(logger.info("This is a test info message").is_none());
377    }
378
379    /// Test if warn will log to console with default logger level
380    #[test]
381    fn test_warn() {
382        let logger = Apollo::new();
383        assert!(logger.warn("This is a test warning message").is_some());
384    }
385
386    /// Test if warn will return None when logging level is too high
387    #[test]
388    fn test_warn_under_level() {
389        let logger = Apollo {
390            logging_level: Levels::ERROR,
391        };
392        assert!(logger.warn("This is a test warning message").is_none());
393    }
394
395    /// Test if error will log to console with default logger level
396    #[test]
397    fn test_error() {
398        let logger = Apollo::new();
399        assert!(logger.error("This is a test error message").is_some());
400    }
401
402    /// Test if error will return None when logging level is too high
403    #[test]
404    fn test_error_under_level() {
405        let logger = Apollo {
406            logging_level: Levels::CRITICAL,
407        };
408        assert!(logger.error("This is a test error message").is_none());
409    }
410
411    /// Test if critical will log to console with default logger level
412    #[test]
413    fn test_critical() {
414        let logger = Apollo::new();
415        assert!(logger.critical("This is a test critical message").is_some());
416    }
417
418    /// Test if critical will return None when logging level is too high
419    #[test]
420    fn test_critical_under_level() {
421        let logger = Apollo {
422            logging_level: Levels::NONE,
423        };
424        assert!(logger.critical("This is a test critical message").is_none());
425    }
426
427    /// Test if nothing gets logged when logging level is None
428    #[test]
429    fn test_logging_level_none() {
430        let logger = Apollo {
431            logging_level: Levels::NONE,
432        };
433        assert!(logger.debug("This is a test debug message").is_none());
434        assert!(logger.info("This is a test info message").is_none());
435        assert!(logger.warn("This is a test warning message").is_none());
436        assert!(logger.error("This is a test error message").is_none());
437        assert!(logger.critical("This is a test critical message").is_none());
438    }
439
440    #[test]
441    fn test_default_creates_new_instance() {
442        let logger = Apollo::default();
443
444        assert!(logger.debug("This is a test debug message").is_some());
445    }
446}