wireman_logger/
lib.rs

1use chrono::Local;
2use once_cell::sync::OnceCell;
3use serde::{Deserialize, Serialize};
4use std::error::Error;
5use std::fmt::Display;
6use std::fs::File;
7use std::fs::OpenOptions;
8use std::io::Write;
9use std::sync::Mutex;
10
11// Global instance of the logger
12pub static LOGGER: OnceCell<Logger> = OnceCell::new();
13
14// Define logger
15#[derive(Debug)]
16pub struct Logger {
17    file_path: Mutex<String>,
18    level: LogLevel,
19}
20
21impl Logger {
22    /// Instantiates a new `Logger`
23    pub fn new<S: Into<String>>(log_file_path: S, log_level: LogLevel) -> Self {
24        Self {
25            file_path: Mutex::new(log_file_path.into()),
26            level: log_level,
27        }
28    }
29
30    /// Initialize logger with file and log level
31    ///
32    ///
33    /// # Errors
34    /// - Could not set global logger
35    pub fn init<S: Into<String>>(file_path: S, log_level: LogLevel) -> Result<(), LoggerError> {
36        let logger = Logger::new(file_path.into(), log_level);
37        LOGGER
38            .set(logger)
39            .map_err(|_| LoggerError::new("Failed to initialize logger"))
40    }
41
42    /// Set a new file path for the `Logger`
43    pub fn set_file_path(&self, file_path: String) {
44        if let Ok(mut path) = self.file_path.lock() {
45            *path = file_path;
46        }
47    }
48
49    /// Log a message in debug level with the global logger.
50    pub fn debug<S: AsRef<str>>(message: S) {
51        if let Some(logger) = LOGGER.get() {
52            let _ = logger.log(LogLevel::Debug, message);
53        }
54    }
55
56    /// Log a message in critical level with the global logger.
57    pub fn critical<S: AsRef<str>>(message: S) {
58        if let Some(logger) = LOGGER.get() {
59            let _ = logger.log(LogLevel::Critical, message);
60        }
61    }
62
63    /// Open or create log file
64    fn open_log_file(&self) -> Result<File, LoggerError> {
65        let path = self
66            .file_path
67            .lock()
68            .map_err(|e| LoggerError::new(format!("Could not obtain lock: {e}")))?;
69        OpenOptions::new()
70            .create(true)
71            .append(true)
72            .open(&*path)
73            .map_err(|e| LoggerError::new(format!("Failed to open or create log file: {e}")))
74    }
75
76    // Log a message
77    fn log<S: AsRef<str>>(&self, level: LogLevel, message: S) -> Result<(), LoggerError> {
78        if level >= self.level {
79            let mut file = self.open_log_file()?;
80            let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
81            writeln!(file, "[{level}] {} [{now}]", message.as_ref())
82                .map_err(|e| LoggerError::new(format!("Failed to write to log file: {e}")))?;
83        }
84        Ok(())
85    }
86}
87
88// Defines log levels
89#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd)]
90pub enum LogLevel {
91    #[default]
92    Debug,
93    Critical,
94    None,
95}
96
97impl Display for LogLevel {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        let name = match self {
100            Self::None => "None",
101            Self::Debug => "Debug",
102            Self::Critical => "Critical",
103        };
104        write!(f, "{name}")
105    }
106}
107
108// Defines errors happened during logging
109#[derive(Debug)]
110pub struct LoggerError {
111    /// The error message
112    pub error_msg: String,
113}
114
115impl LoggerError {
116    /// Instantiates `LoggerError`
117    pub fn new<S: Into<String>>(error_msg: S) -> Self {
118        Self {
119            error_msg: error_msg.into(),
120        }
121    }
122}
123
124impl Display for LoggerError {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(f, "{}", self.error_msg)
127    }
128}
129
130impl Error for LoggerError {
131    fn description(&self) -> &str {
132        &self.error_msg
133    }
134}