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}