1use crate::{
2 colors::*, fileio::append_to_file, filtering::*
3};
4use chrono::{Local, DateTime};
5use serde::{Serialize, Deserialize};
6use std::fmt::{Display, Formatter};
7
8#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
9 Serialize, Deserialize)]
10pub enum OnDropPolicy {
21 IgnoreLogFileLock,
23 #[default]
24 DiscardLogBuffer,
26}
27
28impl Display for OnDropPolicy {
29 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
30 let level_str = match *self {
31 OnDropPolicy::IgnoreLogFileLock => "IgnoreLogFileLock",
32 OnDropPolicy::DiscardLogBuffer => "DiscardLogBuffer",
33 };
34 write!(f, "{}", level_str)
35 }
36}
37
38
39#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
40 Serialize, Deserialize)]
41pub enum LogType {
58 Debug = 0,
59 #[default]
60 Info = 1,
61 Warning = 2,
62 Err = 3,
63 FatalError = 4,
64}
65
66impl TryFrom<i32> for LogType {
67 type Error = &'static str;
68 fn try_from(value: i32) -> Result<Self, Self::Error> {
69 match value {
70 0 => Ok(LogType::Debug),
71 1 => Ok(LogType::Info),
72 2 => Ok(LogType::Warning),
73 3 => Ok(LogType::Err),
74 4 => Ok(LogType::FatalError),
75 _ => Err("Invalid value! Please provide a value in range 0-9."),
76 }
77 }
78}
79
80impl Display for LogType {
81 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
82 let level_str = match *self {
83 LogType::Debug => "Debug",
84 LogType::Info => "Info",
85 LogType::Warning => "Warning",
86 LogType::Err => "Error",
87 LogType::FatalError => "FatalError",
88 };
89 write!(f, "{}", level_str)
90 }
91}
92
93impl AsRef<str> for LogType {
94 fn as_ref(&self) -> &str {
95 match self {
96 LogType::Debug => "Debug",
97 LogType::Info => "Info",
98 LogType::Warning => "Warning",
99 LogType::Err => "Err",
100 LogType::FatalError => "Fatal Error",
101 }
102 }
103}
104
105
106#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
107pub struct LogStruct {
119 pub message: String,
120 pub log_type: LogType,
121 pub datetime: DateTime<Local>,
123}
124
125impl Display for LogStruct {
126 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127 write!(
128 f,
129 "Log: {}\nType: {:?}\nDateTime: {}",
130 self.message,
131 self.log_type,
132 self.datetime
133 )
134 }
135}
136
137
138#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default,
162 Serialize, Deserialize)]
163pub struct Logger {
164 pub(crate) verbosity: Verbosity,
165 pub(crate) filtering_enabled: bool,
166 pub(crate) log_header_color_enabled: bool,
167
168 pub(crate) debug_color: Color,
169 pub(crate) info_color: Color,
170 pub(crate) warning_color: Color,
171 pub(crate) error_color: Color,
172 pub(crate) fatal_color: Color,
173
174 pub(crate) debug_header: String,
175 pub(crate) info_header: String,
176 pub(crate) warning_header: String,
177 pub(crate) error_header: String,
178 pub(crate) fatal_header: String,
179
180 pub(crate) log_format: String,
181 pub(crate) datetime_format: String,
182
183 pub(crate) file_logging_enabled: bool,
184 pub(crate) log_file_path: String,
185 pub(crate) log_buffer_max_size: usize,
186 pub(crate) on_drop_policy: OnDropPolicy,
187
188 #[serde(skip)]
190 pub(crate) log_buffer: Vec<LogStruct>,
191 #[serde(skip)]
192 pub(crate) show_datetime: bool,
193 #[serde(skip)]
194 pub(crate) log_file_lock: bool,
195}
196
197impl Drop for Logger {
198 fn drop(&mut self) {
199 let _ = self.drop_flush();
200 }
201}
202
203impl Logger {
204 pub(crate) fn print_log(&mut self, log: &LogStruct) {
207 let log_str = self.format_log(log);
208
209 if self.file_logging_enabled {
210 self.log_buffer.push(log.clone());
211
212 if self.log_buffer_max_size != 0
213 && self.log_buffer.len() >= self.log_buffer_max_size {
214 let _ = self.flush_file_log_buffer(false);
215 }
216 }
217
218 print!("{}", log_str);
219 }
220
221 pub(crate) fn get_log_headers(&self, log: &LogStruct)
222 -> (String, String, String) {
223 let header = self.get_main_header(log.log_type);
224 let datetime = self.get_datetime_formatted(&log.datetime);
225 return (header, datetime, log.message.clone());
226 }
227
228 pub(crate) fn get_main_header(&self, log_type: LogType) -> String {
229 match log_type {
230 LogType::Debug => {
231 self.colorify(&self.debug_header,
232 self.log_header_color(log_type))
233 }
234 LogType::Info => {
235 self.colorify(&self.info_header,
236 self.log_header_color(log_type))
237 }
238 LogType::Warning => {
239 self.colorify(&self.warning_header,
240 self.log_header_color(log_type))
241 }
242 LogType::Err => {
243 self.colorify(&self.error_header,
244 self.log_header_color(log_type))
245 }
246 LogType::FatalError => {
247 self.colorify(&self.fatal_header,
248 self.log_header_color(log_type))
249 }
250 }
251 }
252
253 pub(crate) fn get_datetime_formatted(&self,
254 datetime: &DateTime<Local>) -> String {
255 if self.show_datetime {
256 let datetime_formatted = datetime.format(&self.datetime_format);
257 return datetime_formatted.to_string();
258 }
259 else {
260 return String::from("");
261 }
262 }
263
264 pub(crate) fn colorify(&self, text: &str, color: Color) -> String {
265 if self.log_header_color_enabled {
266 if color != Color::None {
267 return get_color_code(color) + text + &RESET;
268 }
269 else {
270 return text.to_string();
271 }
272 }
273 else {
274 return text.to_string();
275 }
276 }
277
278 pub(crate) fn filter_log(&self, log_type: LogType) -> bool {
279 return !self.filtering_enabled
280 || ((log_type as i32) < self.verbosity as i32)
281 }
282
283 pub(crate) fn get_datetime(&self) -> DateTime<Local> {
284 return Local::now();
285 }
286
287 pub(crate) fn log_header_color(&self, log_type: LogType) -> Color {
288 match log_type {
289 LogType::Debug => { self.debug_color.clone() }
290 LogType::Info => { self.info_color.clone() }
291 LogType::Warning => { self.warning_color.clone() }
292 LogType::Err => { self.error_color.clone() }
293 LogType::FatalError => { self.fatal_color.clone() }
294 }
295 }
296
297 pub(crate) fn drop_flush(&mut self) {
298 if self.file_logging_enabled {
299 let _ = self.flush_file_log_buffer(true);
300 }
301 }
302
303 pub(crate) fn flush_file_log_buffer(&mut self, is_drop_flush: bool)
304 -> Result<(), String> {
305 if self.log_file_lock {
306 if is_drop_flush {
307 match self.on_drop_policy {
308 OnDropPolicy::IgnoreLogFileLock => { }
309 OnDropPolicy::DiscardLogBuffer => {
310 let message = format!("Log file lock enabled and on
311 drop policy set to {}!",
312 self.on_drop_policy);
313 return Err(message);
314 }
315 }
316 }
317 else {
318 return Err(String::from("Log file lock enabled!"))
319 }
320 }
321 let mut buf = String::from("");
322
323 for log in &self.log_buffer {
324 buf += &self.format_log(&log);
325 }
326
327 self.log_buffer = Vec::new();
328 let result = append_to_file(&self.log_file_path, &buf);
329
330 match result {
331 Ok(_) => Ok(()),
332 Err(_) => {
333 self.file_logging_enabled = false;
334 Err(String::from("Failed to write log buffer to a file!"))
335 },
336 }
337 }
338
339 pub fn default() -> Self {
343 Logger {
344 verbosity: Verbosity::default(),
345 filtering_enabled: true,
346 log_header_color_enabled: true,
347
348 debug_color: Color::Blue,
349 info_color: Color::Green,
350 warning_color: Color::Yellow,
351 error_color: Color::Red,
352 fatal_color: Color::Magenta,
353
354 debug_header: String::from("DBG"),
355 info_header: String::from("INF"),
356 warning_header: String::from("WAR"),
357 error_header: String::from("ERR"),
358 fatal_header: String::from("FATAL"),
359
360 log_format: String::from("[%h] %m"),
361 datetime_format: String::from("%Y-%m-%d %H:%M:%S"),
362
363 file_logging_enabled: false,
364 log_file_path: String::new(),
365 log_buffer_max_size: 128,
366 on_drop_policy: OnDropPolicy::default(),
367
368 show_datetime: false,
369 log_buffer: Vec::new(),
370 log_file_lock: false,
371 }
372 }
373
374
375 pub fn format_log(&self, log: &LogStruct) -> String {
380 let headers = self.get_log_headers(&log);
381 let mut result = String::new();
382 let mut char_iter = self.log_format.char_indices().peekable();
383
384 while let Some((_, c)) = char_iter.next() {
385 match c {
386 '%' => {
387 if let Some((_, nc)) = char_iter.peek() {
388 match nc {
389 'h' => {
390 result += &headers.0;
391 char_iter.next();
392 }
393 'd' => {
394 result += &headers.1;
395 char_iter.next();
396 }
397 'm' => {
398 result += &headers.2;
399 char_iter.next();
400 }
401 _ => {
402 result += &nc.to_string();
403 char_iter.next();
404 }
405 }
406 }
407 }
408 _ => {
409 result += &c.to_string();
410 }
411 }
412 }
413
414 result += &"\n";
415 return result;
416 }
417
418 pub fn flush(&mut self) -> Result<(), String> {
425 if self.file_logging_enabled {
426 self.flush_file_log_buffer(false)?;
427 }
428 return Ok(());
429 }
430
431 pub fn debug(&mut self, message: &str) {
433 if self.filter_log(LogType::Debug)
434 {
435 return;
436 }
437 let log = LogStruct {
438 message: message.to_string(),
439 log_type: LogType::Debug,
440 datetime: self.get_datetime(),
441 };
442 self.print_log(&log);
443 }
444
445 pub fn debug_no_filtering(&mut self, message: &str) {
447 let log = LogStruct {
448 message: message.to_string(),
449 log_type: LogType::Debug,
450 datetime: self.get_datetime(),
451 };
452 self.print_log(&log);
453 }
454
455 pub fn info(&mut self, message: &str) {
457 if self.filter_log(LogType::Info)
458 {
459 return;
460 }
461 let log = LogStruct {
462 message: message.to_string(),
463 log_type: LogType::Info,
464 datetime: self.get_datetime(),
465 };
466 self.print_log(&log);
467 }
468
469 pub fn info_no_filtering(&mut self, message: &str) {
471 let log = LogStruct {
472 message: message.to_string(),
473 log_type: LogType::Info,
474 datetime: self.get_datetime(),
475 };
476 self.print_log(&log);
477 }
478
479 pub fn warning(&mut self, message: &str) {
481 if self.filter_log(LogType::Warning)
482 {
483 return;
484 }
485 let log = LogStruct {
486 message: message.to_string(),
487 log_type: LogType::Warning,
488 datetime: self.get_datetime(),
489 };
490 self.print_log(&log);
491 }
492
493 pub fn warning_no_filtering(&mut self, message: &str) {
495 let log = LogStruct {
496 message: message.to_string(),
497 log_type: LogType::Warning,
498 datetime: self.get_datetime(),
499 };
500 self.print_log(&log);
501 }
502
503 pub fn error(&mut self, message: &str) {
505 let log = LogStruct {
506 message: message.to_string(),
507 log_type: LogType::Err,
508 datetime: self.get_datetime(),
509 };
510 self.print_log(&log);
511 }
512
513 pub fn fatal(&mut self, message: &str) {
515 let log = LogStruct {
516 message: message.to_string(),
517 log_type: LogType::FatalError,
518 datetime: self.get_datetime(),
519 };
520 self.print_log(&log);
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use std::{
528 env, io, path::PathBuf
529 };
530
531 fn get_current_dir() -> io::Result<PathBuf> {
532 let current_dir = env::current_dir()?;
533 Ok(current_dir)
534 }
535
536 #[test]
537 fn test_log_filtering() {
538 let mut l = Logger::default();
539 l.toggle_log_filtering(true);
540 l.set_verbosity(Verbosity::ErrorsOnly);
541
542 if !l.filter_log(LogType::Debug) {
543 panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
544 }
545 if !l.filter_log(LogType::Info) {
546 panic!("An informative log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
547 }
548 if !l.filter_log(LogType::Warning) {
549 panic!("A warning log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
550 }
551
552 l.set_verbosity(Verbosity::Quiet);
553 if !l.filter_log(LogType::Debug) {
554 panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
555 }
556 if !l.filter_log(LogType::Info) {
557 panic!("An informative log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
558 }
559 if l.filter_log(LogType::Warning) {
560 panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
561 }
562
563 l.set_verbosity(Verbosity::Standard);
564 if !l.filter_log(LogType::Debug) {
565 panic!("A debug log should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
566 }
567 if l.filter_log(LogType::Info) {
568 panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
569 }
570 if l.filter_log(LogType::Warning) {
571 panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
572 }
573
574 l.set_verbosity(Verbosity::All);
575 if l.filter_log(LogType::Debug) {
576 panic!("A debug log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
577 }
578 if l.filter_log(LogType::Info) {
579 panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
580 }
581 if l.filter_log(LogType::Warning) {
582 panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
583 }
584
585 l.set_verbosity(Verbosity::All);
586 l.toggle_log_filtering(true);
587 if l.filter_log(LogType::Debug) {
588 panic!("A debug log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
589 }
590 if l.filter_log(LogType::Info) {
591 panic!("An informative log should not get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
592 }
593 if l.filter_log(LogType::Warning) {
594 panic!("A warning log not should get filtered for verbosity set to: {}", Verbosity::ErrorsOnly);
595 }
596 }
597
598 #[test]
599 fn test_log_headers() {
600 let header = "askljdfh";
602
603 let mut l = Logger::default();
604
605 l.set_debug_header(header);
606 if l.get_main_header(LogType::Debug) !=
607 l.colorify(header, l.log_header_color(LogType::Debug)) {
608 panic!("Debug headers do not match!");
609 }
610 l.set_info_header(header);
611 if l.get_main_header(LogType::Info) !=
612 l.colorify(header, l.log_header_color(LogType::Info)) {
613 panic!("Info headers do not match!");
614 }
615 l.set_warning_header(header);
616 if l.get_main_header(LogType::Warning) !=
617 l.colorify(header, l.log_header_color(LogType::Warning)) {
618 panic!("Warning headers do not match!");
619 }
620 l.set_error_header(header);
621 if l.get_main_header(LogType::Err) !=
622 l.colorify(header, l.log_header_color(LogType::Err)) {
623 panic!("Error headers do not match!");
624 }
625 l.set_fatal_header(header);
626 if l.get_main_header(LogType::FatalError) !=
627 l.colorify(header, l.log_header_color(LogType::FatalError)) {
628 panic!("Fatal error headers do not match!");
629 }
630 }
631
632 #[test]
633 fn test_log_colors() {
634 let l = Logger::default();
636 if l.colorify("a", Color::Red) != "\x1b[31ma\x1b[0m"
637 {
638 panic!("Failed to colorify a string!");
639 }
640 }
641
642 #[test]
643 fn test_templates() {
644 let file_name = "/templates/test.json";
645 match get_current_dir() {
646 Ok(current_dir) => {
647 let path = current_dir
648 .to_str()
649 .map(|s| s.to_string() + file_name)
650 .unwrap_or_else(|| String::from(file_name));
651
652 let mut l = Logger::default();
653 l.save_template(&path);
654 l = Logger::from_template(&path);
655
656 if l != Logger::default() {
657 panic!("Templates are not the same!");
658 }
659 }
660 Err(e) => {
661 eprintln!("Error getting current directory: {}", e);
662 }
663 }
664 }
665
666 #[test]
667 fn test_formats() {
668 let mut l = Logger::default();
669
670 l.set_datetime_format("aaa");
671 l.set_debug_header("d");
672 l.set_info_header("i");
673 l.set_warning_header("W");
674 l.set_error_header("E");
675 l.set_fatal_header("!");
676 let _ = l.set_log_format("<l> <h>%h</h> <d>%d</d> <m>%m</m> </l>");
677
678 let mut logstruct = LogStruct {
679 datetime: l.get_datetime(),
680 log_type: LogType::Debug,
681 message: "aaa".to_string(),
682 };
683 let mut comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
684 l.colorify("d", l.log_header_color(LogType::Debug))
685 );
686
687 if l.format_log(&logstruct) != comp {
688 panic!("Bad log formatting, expected \n'{}', got \n'{}'",
689 comp,
690 l.format_log(&logstruct));
691 }
692
693 logstruct.log_type = LogType::Info;
694 comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
695 l.colorify("i", l.log_header_color(LogType::Info))
696 );
697 if l.format_log(&logstruct) != comp {
698 panic!("Bad log formatting, expected \n'{}', got \n'{}'",
699 comp,
700 l.format_log(&logstruct));
701 }
702
703 logstruct.log_type = LogType::Warning;
704 comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
705 l.colorify("W", l.log_header_color(LogType::Warning))
706 );
707 if l.format_log(&logstruct) != comp {
708 panic!("Bad log formatting, expected \n'{}', got \n'{}'",
709 comp,
710 l.format_log(&logstruct));
711 }
712
713 logstruct.log_type = LogType::Err;
714 comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
715 l.colorify("E", l.log_header_color(LogType::Err))
716 );
717 if l.format_log(&logstruct) != comp {
718 panic!("Bad log formatting, expected \n'{}', got \n'{}'",
719 comp,
720 l.format_log(&logstruct));
721 }
722
723 logstruct.log_type = LogType::FatalError;
724 comp = format!("<l> <h>{}</h> <d>aaa</d> <m>aaa</m> </l>\n",
725 l.colorify("!", l.log_header_color(LogType::FatalError))
726 );
727 if l.format_log(&logstruct) != comp {
728 panic!("Bad log formatting, expected \n'{}', got \n'{}'",
729 comp,
730 l.format_log(&logstruct));
731 }
732 }
733
734 #[test]
735 fn test_file_logging() {
736 let file_name = "/output.log";
737 let max_size: usize = 16;
738 let mut l = Logger::default();
739 l.set_max_log_buffer_size(max_size);
740
741 let current_dir = get_current_dir();
742
743 match current_dir {
744 Ok(current_dir) => {
745 let path = current_dir
746 .to_str()
747 .map(|s| s.to_string() + file_name)
748 .unwrap_or_else(|| String::from(file_name));
749
750 let result = l.set_log_file_path(&path);
751
752 match result {
753 Ok(()) => {
754 let _ = l.toggle_file_logging(true);
755 let mut i = 0;
756 loop {
757 l.fatal(&format!("i: {}", i));
758
759 if i >= max_size {
760 break;
761 }
762 i += 1;
763 }
764 },
765 Err(_) => { panic!("Failed to set the log file path to
766 '{}'!", path) },
767 }
768 },
769 Err(_) => { panic!("Failed to get current directory!") },
770 }
771 }
772}