1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use chrono::Local;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::Display;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::sync::Mutex;

// Global instance of the logger
pub static LOGGER: OnceCell<Logger> = OnceCell::new();

// Define logger
#[derive(Debug)]
pub struct Logger {
    file_path: Mutex<String>,
    level: LogLevel,
}

impl Logger {
    /// Instantiates a new `Logger`
    pub fn new<S: Into<String>>(log_file_path: S, log_level: LogLevel) -> Self {
        Self {
            file_path: Mutex::new(log_file_path.into()),
            level: log_level,
        }
    }

    /// Initialize logger with file and log level
    ///
    ///
    /// # Errors
    /// - Could not set global logger
    pub fn init<S: Into<String>>(file_path: S, log_level: LogLevel) -> Result<(), LoggerError> {
        let logger = Logger::new(file_path.into(), log_level);
        LOGGER
            .set(logger)
            .map_err(|_| LoggerError::new("Failed to initialize logger"))
    }

    /// Set a new file path for the `Logger`
    pub fn set_file_path(&self, file_path: String) {
        if let Ok(mut path) = self.file_path.lock() {
            *path = file_path;
        }
    }

    /// Log a message in debug level with the global logger.
    pub fn debug<S: AsRef<str>>(message: S) {
        if let Some(logger) = LOGGER.get() {
            let _ = logger.log(LogLevel::Debug, message);
        }
    }

    /// Log a message in critical level with the global logger.
    pub fn critical<S: AsRef<str>>(message: S) {
        if let Some(logger) = LOGGER.get() {
            let _ = logger.log(LogLevel::Critical, message);
        }
    }

    /// Open or create log file
    fn open_log_file(&self) -> Result<File, LoggerError> {
        let path = self
            .file_path
            .lock()
            .map_err(|e| LoggerError::new(format!("Could not obtain lock: {e}")))?;
        OpenOptions::new()
            .create(true)
            .append(true)
            .open(&*path)
            .map_err(|e| LoggerError::new(format!("Failed to open or create log file: {e}")))
    }

    // Log a message
    fn log<S: AsRef<str>>(&self, level: LogLevel, message: S) -> Result<(), LoggerError> {
        if level >= self.level {
            let mut file = self.open_log_file()?;
            let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
            writeln!(file, "[{level}] {} [{now}]", message.as_ref())
                .map_err(|e| LoggerError::new(format!("Failed to write to log file: {e}")))?;
        }
        Ok(())
    }
}

// Defines log levels
#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
pub enum LogLevel {
    #[default]
    Debug,
    Critical,
    None,
}

impl Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let name = match self {
            Self::None => "None",
            Self::Debug => "Debug",
            Self::Critical => "Critical",
        };
        write!(f, "{name}")
    }
}

// Defines errors happened during logging
#[derive(Debug)]
pub struct LoggerError {
    /// The error message
    pub error_msg: String,
}

impl LoggerError {
    /// Instantiates `LoggerError`
    pub fn new<S: Into<String>>(error_msg: S) -> Self {
        Self {
            error_msg: error_msg.into(),
        }
    }
}

impl Display for LoggerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.error_msg)
    }
}

impl Error for LoggerError {
    fn description(&self) -> &str {
        &self.error_msg
    }
}