Skip to main content

tsu_editor/
logger.rs

1use core::fmt;
2use std::{
3    fs::{File, OpenOptions},
4    io::Write,
5    sync::Mutex,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9/// Log severity levels
10///
11/// A coarser representation of the standard software log severity levels
12pub enum LogLevel {
13    /// For messages that contain information normally of use only when debugging a program
14    Debug = 0,
15    /// Confirmation that the program is working as expected
16    Info = 1,
17    /// Elevated notice that something may not be working correctly
18    Warn = 2,
19    /// Indication that some failure has occurred, but it does not break the system
20    Error = 3,
21    /// Hard device error
22    Critical = 4,
23    /// A panic condition wherein the program cannot continue
24    Emergency = 5,
25}
26
27impl fmt::Display for LogLevel {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            LogLevel::Debug => write!(f, "DEBUG"),
31            LogLevel::Info => write!(f, "INFO"),
32            LogLevel::Warn => write!(f, "WARN"),
33            LogLevel::Error => write!(f, "ERROR"),
34            LogLevel::Critical => write!(f, "CRITICAL"),
35            LogLevel::Emergency => write!(f, "EMERGENCY"),
36        }
37    }
38}
39
40impl LogLevel {
41    #[must_use]
42    /// Converts a string into a corresponding log severity level
43    ///
44    /// # Arguments
45    /// - `s`: A string that may represent a log level
46    ///
47    /// # Returns
48    /// - A log level enum variant if the string is able to be parsed, otherwise `None`
49    pub fn parse(s: &str) -> Option<Self> {
50        match s.to_uppercase().as_str() {
51            "DEBUG" => Some(LogLevel::Debug),
52            "INFO" => Some(LogLevel::Info),
53            "WARN" => Some(LogLevel::Warn),
54            "ERROR" => Some(LogLevel::Error),
55            "CRITICAL" => Some(LogLevel::Critical),
56            "EMERGENCY" => Some(LogLevel::Emergency),
57            _ => None,
58        }
59    }
60}
61
62/// The logger entity
63///
64/// Filters logs based on a provided severity level and writes those logs to a file
65pub struct Logger {
66    file: Mutex<File>,
67    log_level: LogLevel,
68}
69
70impl Logger {
71    #[must_use]
72    /// Create a new logger with a given log file path
73    ///
74    /// # Arguments
75    /// - `file`: A path to a file to which logs will be written
76    ///
77    /// # Panics
78    /// This function might panic if opening the log file returns an `Err`
79    pub fn new(file: &str) -> Self {
80        let file = OpenOptions::new()
81            .create(true)
82            .append(true)
83            .open(file)
84            .expect("log file successfully opens");
85
86        Logger {
87            file: Mutex::new(file),
88            log_level: LogLevel::Debug,
89        }
90    }
91
92    /// Set the logger's log level
93    ///
94    /// This log level indicates the minimum log level allowed to be written to the log file
95    ///
96    /// # Arguments
97    /// - `level`: The minimum log level for filtering
98    ///
99    pub fn set_level(&mut self, level: LogLevel) {
100        self.log_level = level;
101    }
102
103    /// Log a message without a specified log level
104    ///
105    /// # Arguments
106    /// - `message`: A message string to log to the log file
107    ///
108    /// # Panics
109    /// This function might panic if it fails to get a lock on the file mutex
110    pub fn log(&self, message: &str) {
111        self.log_with_level(LogLevel::Info, message);
112    }
113
114    /// Log a message with a specified log level
115    ///
116    /// # Arguments
117    /// - `level`: The log level of the message
118    /// - `message`: The message to log to the log file
119    ///
120    /// # Panics
121    /// This function might panic if retrieving the duration since the Unix epoch fails
122    pub fn log_with_level(&self, level: LogLevel, message: &str) {
123        if level < self.log_level {
124            return;
125        }
126
127        let timestamp = std::time::SystemTime::now()
128            .duration_since(std::time::UNIX_EPOCH)
129            .unwrap()
130            .as_secs();
131        let formatted = format!("[{timestamp}] [{level}] {message}");
132
133        let mut file = self.file.lock().unwrap();
134        writeln!(file, "{formatted}").expect("write to file works");
135    }
136
137    /// Write a debug log
138    ///
139    /// # Arguments
140    /// - `message`: The message to log to the log file
141    ///
142    pub fn debug(&self, message: &str) {
143        self.log_with_level(LogLevel::Debug, message);
144    }
145
146    /// Write an info log
147    ///
148    /// # Arguments
149    /// - `message`: The message to log to the log file
150    ///
151    pub fn info(&self, message: &str) {
152        self.log_with_level(LogLevel::Info, message);
153    }
154
155    /// Write a warning log
156    ///
157    /// # Arguments
158    /// - `message`: The message to log to the log file
159    ///
160    pub fn warn(&self, message: &str) {
161        self.log_with_level(LogLevel::Warn, message);
162    }
163
164    /// Write an error log
165    ///
166    /// # Arguments
167    /// - `message`: The message to log to the log file
168    ///
169    pub fn error(&self, message: &str) {
170        self.log_with_level(LogLevel::Error, message);
171    }
172
173    /// Write a critical log
174    ///
175    /// # Arguments
176    /// - `message`: The message to log to the log file
177    ///
178    pub fn critical(&self, message: &str) {
179        self.log_with_level(LogLevel::Critical, message);
180    }
181
182    /// Write an emergency log
183    ///
184    /// # Arguments
185    /// - `message`: The message to log to the log file
186    ///
187    pub fn emergency(&self, message: &str) {
188        self.log_with_level(LogLevel::Emergency, message);
189    }
190}