rust_loguru/
level.rs

1use colored::*;
2use serde::{Deserialize, Serialize};
3use std::cmp::Ordering;
4use std::fmt;
5use std::str::FromStr;
6
7/// Represents the severity level of a log message.
8/// Levels are ordered from least to most severe.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum LogLevel {
11    /// The lowest level of logging, used for very detailed debugging information.
12    Trace,
13    /// Used for debugging information that might be useful in diagnosing problems.
14    Debug,
15    /// Used for informational messages that highlight the progress of the application.
16    Info,
17    /// Used to indicate successful operations or positive outcomes.
18    Success,
19    /// Used for potentially harmful situations that might still allow the application to continue running.
20    Warning,
21    /// Used for error events that might still allow the application to continue running.
22    Error,
23    /// Used for very severe error events that will presumably lead the application to abort.
24    Critical,
25}
26
27impl LogLevel {
28    /// Returns the string representation of the log level.
29    pub fn as_str(&self) -> &'static str {
30        match self {
31            LogLevel::Trace => "TRACE",
32            LogLevel::Debug => "DEBUG",
33            LogLevel::Info => "INFO",
34            LogLevel::Success => "SUCCESS",
35            LogLevel::Warning => "WARNING",
36            LogLevel::Error => "ERROR",
37            LogLevel::Critical => "CRITICAL",
38        }
39    }
40
41    /// Returns the numeric value of the log level.
42    /// Uses standard logging level values (5, 10, 20, etc.)
43    pub fn as_u8(&self) -> u8 {
44        match self {
45            LogLevel::Trace => 5,
46            LogLevel::Debug => 10,
47            LogLevel::Info => 20,
48            LogLevel::Success => 25,
49            LogLevel::Warning => 30,
50            LogLevel::Error => 40,
51            LogLevel::Critical => 50,
52        }
53    }
54
55    /// Returns the ANSI color code for this log level.
56    pub fn color(&self) -> &'static str {
57        match self {
58            LogLevel::Trace => "\x1b[37m",    // white
59            LogLevel::Debug => "\x1b[34m",    // blue
60            LogLevel::Info => "\x1b[32m",     // green
61            LogLevel::Success => "\x1b[36m",  // cyan
62            LogLevel::Warning => "\x1b[33m",  // yellow
63            LogLevel::Error => "\x1b[31m",    // red
64            LogLevel::Critical => "\x1b[35m", // purple
65        }
66    }
67
68    /// Returns the emoji representation of the log level.
69    pub fn emoji(&self) -> &'static str {
70        match self {
71            LogLevel::Trace => "🔍",
72            LogLevel::Debug => "🐛",
73            LogLevel::Info => "â„šī¸",
74            LogLevel::Success => "✅",
75            LogLevel::Warning => "âš ī¸",
76            LogLevel::Error => "❌",
77            LogLevel::Critical => "đŸ’Ĩ",
78        }
79    }
80
81    /// Returns the ANSI reset code.
82    pub fn reset_color() -> &'static str {
83        "\x1b[0m"
84    }
85
86    /// Checks if a module path matches the given pattern.
87    /// The pattern can be a simple string match or a glob pattern.
88    pub fn matches_module_pattern(&self, module_path: &str, pattern: &str) -> bool {
89        if pattern.contains('*') {
90            // Simple glob pattern matching
91            let pattern = pattern.replace('*', ".*");
92            match regex::Regex::new(&format!("^{}$", pattern)) {
93                Ok(regex) => regex.is_match(module_path),
94                Err(_) => {
95                    eprintln!("Invalid regex pattern: {}", pattern);
96                    false
97                }
98            }
99        } else {
100            // Simple string matching
101            module_path.contains(pattern)
102        }
103    }
104
105    pub fn to_string_colored(&self) -> String {
106        let level_str = self.to_string();
107        match self {
108            LogLevel::Error => level_str.red().to_string(),
109            LogLevel::Warning => level_str.yellow().to_string(),
110            LogLevel::Info => level_str.green().to_string(),
111            LogLevel::Debug => level_str.blue().to_string(),
112            LogLevel::Trace => level_str.cyan().to_string(),
113            LogLevel::Success => level_str.green().to_string(),
114            LogLevel::Critical => level_str.red().to_string(),
115        }
116    }
117}
118
119impl PartialOrd for LogLevel {
120    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
121        Some(self.as_u8().cmp(&other.as_u8()))
122    }
123}
124
125impl Ord for LogLevel {
126    fn cmp(&self, other: &Self) -> Ordering {
127        self.as_u8().cmp(&other.as_u8())
128    }
129}
130
131impl fmt::Display for LogLevel {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            LogLevel::Trace => write!(f, "TRACE"),
135            LogLevel::Debug => write!(f, "DEBUG"),
136            LogLevel::Info => write!(f, "INFO"),
137            LogLevel::Warning => write!(f, "WARN"),
138            LogLevel::Error => write!(f, "ERROR"),
139            LogLevel::Success => write!(f, "SUCCESS"),
140            LogLevel::Critical => write!(f, "CRITICAL"),
141        }
142    }
143}
144
145impl FromStr for LogLevel {
146    type Err = String;
147
148    fn from_str(s: &str) -> Result<Self, Self::Err> {
149        match s.to_uppercase().as_str() {
150            "TRACE" => Ok(LogLevel::Trace),
151            "DEBUG" => Ok(LogLevel::Debug),
152            "INFO" => Ok(LogLevel::Info),
153            "SUCCESS" => Ok(LogLevel::Success),
154            "WARNING" => Ok(LogLevel::Warning),
155            "ERROR" => Ok(LogLevel::Error),
156            "CRITICAL" => Ok(LogLevel::Critical),
157            _ => Err(format!("Invalid log level: {}", s)),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_level_ordering() {
168        assert!(LogLevel::Trace < LogLevel::Debug);
169        assert!(LogLevel::Debug < LogLevel::Info);
170        assert!(LogLevel::Info < LogLevel::Success);
171        assert!(LogLevel::Success < LogLevel::Warning);
172        assert!(LogLevel::Warning < LogLevel::Error);
173        assert!(LogLevel::Error < LogLevel::Critical);
174    }
175
176    #[test]
177    fn test_level_string_representation() {
178        assert_eq!(LogLevel::Trace.as_str(), "TRACE");
179        assert_eq!(LogLevel::Debug.as_str(), "DEBUG");
180        assert_eq!(LogLevel::Info.as_str(), "INFO");
181        assert_eq!(LogLevel::Success.as_str(), "SUCCESS");
182        assert_eq!(LogLevel::Warning.as_str(), "WARNING");
183        assert_eq!(LogLevel::Error.as_str(), "ERROR");
184        assert_eq!(LogLevel::Critical.as_str(), "CRITICAL");
185    }
186
187    #[test]
188    fn test_level_display() {
189        assert_eq!(format!("{}", LogLevel::Info), "INFO");
190        assert_eq!(format!("{}", LogLevel::Error), "ERROR");
191    }
192
193    #[test]
194    fn test_level_numeric_values() {
195        assert_eq!(LogLevel::Trace.as_u8(), 5);
196        assert_eq!(LogLevel::Debug.as_u8(), 10);
197        assert_eq!(LogLevel::Info.as_u8(), 20);
198        assert_eq!(LogLevel::Success.as_u8(), 25);
199        assert_eq!(LogLevel::Warning.as_u8(), 30);
200        assert_eq!(LogLevel::Error.as_u8(), 40);
201        assert_eq!(LogLevel::Critical.as_u8(), 50);
202    }
203
204    #[test]
205    fn test_level_colors() {
206        assert_eq!(LogLevel::Trace.color(), "\x1b[37m");
207        assert_eq!(LogLevel::Debug.color(), "\x1b[34m");
208        assert_eq!(LogLevel::Info.color(), "\x1b[32m");
209        assert_eq!(LogLevel::Success.color(), "\x1b[36m");
210        assert_eq!(LogLevel::Warning.color(), "\x1b[33m");
211        assert_eq!(LogLevel::Error.color(), "\x1b[31m");
212        assert_eq!(LogLevel::Critical.color(), "\x1b[35m");
213    }
214
215    #[test]
216    fn test_level_from_str() {
217        assert_eq!("trace".parse::<LogLevel>(), Ok(LogLevel::Trace));
218        assert_eq!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug));
219        assert_eq!("info".parse::<LogLevel>(), Ok(LogLevel::Info));
220        assert_eq!("SUCCESS".parse::<LogLevel>(), Ok(LogLevel::Success));
221        assert_eq!("warning".parse::<LogLevel>(), Ok(LogLevel::Warning));
222        assert_eq!("ERROR".parse::<LogLevel>(), Ok(LogLevel::Error));
223        assert_eq!("critical".parse::<LogLevel>(), Ok(LogLevel::Critical));
224        assert!("invalid".parse::<LogLevel>().is_err());
225    }
226
227    #[test]
228    fn test_level_from_str_edge_cases() {
229        // Test mixed case
230        assert_eq!("TrAcE".parse::<LogLevel>(), Ok(LogLevel::Trace));
231        assert_eq!("DeBuG".parse::<LogLevel>(), Ok(LogLevel::Debug));
232
233        // Test with whitespace
234        assert!(" trace ".parse::<LogLevel>().is_err());
235        assert!("debug ".parse::<LogLevel>().is_err());
236
237        // Test empty string
238        assert!("".parse::<LogLevel>().is_err());
239    }
240
241    #[test]
242    fn test_level_comparisons() {
243        // Test equality
244        assert_eq!(LogLevel::Info, LogLevel::Info);
245        assert_ne!(LogLevel::Info, LogLevel::Error);
246
247        // Test ordering
248        assert!(LogLevel::Trace < LogLevel::Critical);
249        assert!(LogLevel::Critical > LogLevel::Trace);
250
251        // Test PartialOrd implementation
252        assert!(LogLevel::Trace <= LogLevel::Debug);
253        assert!(LogLevel::Debug >= LogLevel::Trace);
254        assert!(LogLevel::Info <= LogLevel::Info);
255        assert!(LogLevel::Info >= LogLevel::Info);
256    }
257
258    #[test]
259    fn test_color_code_format() {
260        // Test that all color codes start with \x1b[
261        assert!(LogLevel::Trace.color().starts_with("\x1b["));
262        assert!(LogLevel::Debug.color().starts_with("\x1b["));
263        assert!(LogLevel::Info.color().starts_with("\x1b["));
264        assert!(LogLevel::Success.color().starts_with("\x1b["));
265        assert!(LogLevel::Warning.color().starts_with("\x1b["));
266        assert!(LogLevel::Error.color().starts_with("\x1b["));
267        assert!(LogLevel::Critical.color().starts_with("\x1b["));
268
269        // Test reset color format
270        assert_eq!(LogLevel::reset_color(), "\x1b[0m");
271    }
272
273    #[test]
274    fn test_level_emoji() {
275        assert_eq!(LogLevel::Trace.emoji(), "🔍");
276        assert_eq!(LogLevel::Debug.emoji(), "🐛");
277        assert_eq!(LogLevel::Info.emoji(), "â„šī¸");
278        assert_eq!(LogLevel::Success.emoji(), "✅");
279        assert_eq!(LogLevel::Warning.emoji(), "âš ī¸");
280        assert_eq!(LogLevel::Error.emoji(), "❌");
281        assert_eq!(LogLevel::Critical.emoji(), "đŸ’Ĩ");
282    }
283
284    #[test]
285    fn test_module_pattern_matching() {
286        let level = LogLevel::Info;
287
288        // Simple string matching
289        assert!(level.matches_module_pattern("my_app::module", "my_app"));
290        assert!(level.matches_module_pattern("my_app::module", "module"));
291        assert!(!level.matches_module_pattern("my_app::module", "other"));
292
293        // Glob pattern matching
294        assert!(level.matches_module_pattern("my_app::module", "my_app::*"));
295        assert!(level.matches_module_pattern("my_app::module", "*::module"));
296        assert!(!level.matches_module_pattern("my_app::module", "other::*"));
297    }
298
299    #[test]
300    fn test_serialization() {
301        let level = LogLevel::Info;
302        let serialized = serde_json::to_string(&level).unwrap();
303        assert_eq!(serialized, "\"Info\"");
304
305        let deserialized: LogLevel = serde_json::from_str(&serialized).unwrap();
306        assert_eq!(deserialized, level);
307    }
308}