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 pub fn new() -> Apollo {
47 Apollo {
48 logging_level: Levels::DEBUG,
49 }
50 }
51
52 fn get_time_as_string(&self) -> String {
54 Utc::now().format("%D %H:%M:%S%.3f").to_string()
55 }
56
57 pub fn debug(&self, s: &str) -> Option<String> {
73 if self.logging_level.as_u8() > Levels::DEBUG.as_u8() {
75 return None;
76 }
77
78 let current_time: String = self.get_time_as_string();
80
81 let location = self
83 .get_caller_location()
84 .unwrap_or(String::from("Unknown:0"));
85
86 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 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 pub fn info(&self, s: &str) -> Option<String> {
117 if self.logging_level.as_u8() > Levels::INFO.as_u8() {
119 return None;
120 }
121
122 let current_time: String = self.get_time_as_string();
124
125 let location = self
127 .get_caller_location()
128 .unwrap_or(String::from("Unknown:0"));
129
130 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 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 pub fn warn(&self, s: &str) -> Option<String> {
161 if self.logging_level.as_u8() > Levels::WARN.as_u8() {
163 return None;
164 }
165
166 let current_time: String = self.get_time_as_string();
168
169 let location = self
171 .get_caller_location()
172 .unwrap_or(String::from("Unknown:0"));
173
174 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 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 pub fn error(&self, s: &str) -> Option<String> {
205 if self.logging_level.as_u8() > Levels::ERROR.as_u8() {
207 return None;
208 }
209
210 let current_time: String = self.get_time_as_string();
212
213 let location = self
215 .get_caller_location()
216 .unwrap_or(String::from("Unknown:0"));
217
218 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 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 pub fn critical(&self, s: &str) -> Option<String> {
249 if self.logging_level.as_u8() > Levels::CRITICAL.as_u8() {
251 return None;
252 }
253
254 let current_time: String = self.get_time_as_string();
256
257 let location = self
259 .get_caller_location()
260 .unwrap_or(String::from("Unknown:0"));
261
262 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 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 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 if caller_location.is_some() {
288 return;
289 }
290
291 let file_name = symbol.filename();
293 let line_number = symbol.lineno();
294 let symbol_name = symbol.name();
295
296 if file_name.is_none() || line_number.is_none() || symbol_name.is_none() {
298 return;
299 }
300
301 let file_os_str = file_name.unwrap().to_str().unwrap();
303 if Self::filter_locations(file_os_str) {
304 return;
305 }
306
307 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 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 use super::*;
346
347 #[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]
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]
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]
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]
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]
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]
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]
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]
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]
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]
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}