use colored::*;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum LogLevel {
Trace,
Debug,
Info,
Success,
Warning,
Error,
Critical,
}
impl LogLevel {
pub fn as_str(&self) -> &'static str {
match self {
LogLevel::Trace => "TRACE",
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Success => "SUCCESS",
LogLevel::Warning => "WARNING",
LogLevel::Error => "ERROR",
LogLevel::Critical => "CRITICAL",
}
}
pub fn as_u8(&self) -> u8 {
match self {
LogLevel::Trace => 5,
LogLevel::Debug => 10,
LogLevel::Info => 20,
LogLevel::Success => 25,
LogLevel::Warning => 30,
LogLevel::Error => 40,
LogLevel::Critical => 50,
}
}
pub fn color(&self) -> &'static str {
match self {
LogLevel::Trace => "\x1b[37m", LogLevel::Debug => "\x1b[34m", LogLevel::Info => "\x1b[32m", LogLevel::Success => "\x1b[36m", LogLevel::Warning => "\x1b[33m", LogLevel::Error => "\x1b[31m", LogLevel::Critical => "\x1b[35m", }
}
pub fn emoji(&self) -> &'static str {
match self {
LogLevel::Trace => "đ",
LogLevel::Debug => "đ",
LogLevel::Info => "âšī¸",
LogLevel::Success => "â
",
LogLevel::Warning => "â ī¸",
LogLevel::Error => "â",
LogLevel::Critical => "đĨ",
}
}
pub fn reset_color() -> &'static str {
"\x1b[0m"
}
pub fn matches_module_pattern(&self, module_path: &str, pattern: &str) -> bool {
if pattern.contains('*') {
let pattern = pattern.replace('*', ".*");
match regex::Regex::new(&format!("^{}$", pattern)) {
Ok(regex) => regex.is_match(module_path),
Err(_) => {
eprintln!("Invalid regex pattern: {}", pattern);
false
}
}
} else {
module_path.contains(pattern)
}
}
pub fn to_string_colored(&self) -> String {
let level_str = self.to_string();
match self {
LogLevel::Error => level_str.red().to_string(),
LogLevel::Warning => level_str.yellow().to_string(),
LogLevel::Info => level_str.green().to_string(),
LogLevel::Debug => level_str.blue().to_string(),
LogLevel::Trace => level_str.cyan().to_string(),
LogLevel::Success => level_str.green().to_string(),
LogLevel::Critical => level_str.red().to_string(),
}
}
}
impl PartialOrd for LogLevel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.as_u8().cmp(&other.as_u8()))
}
}
impl Ord for LogLevel {
fn cmp(&self, other: &Self) -> Ordering {
self.as_u8().cmp(&other.as_u8())
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogLevel::Trace => write!(f, "TRACE"),
LogLevel::Debug => write!(f, "DEBUG"),
LogLevel::Info => write!(f, "INFO"),
LogLevel::Warning => write!(f, "WARN"),
LogLevel::Error => write!(f, "ERROR"),
LogLevel::Success => write!(f, "SUCCESS"),
LogLevel::Critical => write!(f, "CRITICAL"),
}
}
}
impl FromStr for LogLevel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"TRACE" => Ok(LogLevel::Trace),
"DEBUG" => Ok(LogLevel::Debug),
"INFO" => Ok(LogLevel::Info),
"SUCCESS" => Ok(LogLevel::Success),
"WARNING" => Ok(LogLevel::Warning),
"ERROR" => Ok(LogLevel::Error),
"CRITICAL" => Ok(LogLevel::Critical),
_ => Err(format!("Invalid log level: {}", s)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_level_ordering() {
assert!(LogLevel::Trace < LogLevel::Debug);
assert!(LogLevel::Debug < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Success);
assert!(LogLevel::Success < LogLevel::Warning);
assert!(LogLevel::Warning < LogLevel::Error);
assert!(LogLevel::Error < LogLevel::Critical);
}
#[test]
fn test_level_string_representation() {
assert_eq!(LogLevel::Trace.as_str(), "TRACE");
assert_eq!(LogLevel::Debug.as_str(), "DEBUG");
assert_eq!(LogLevel::Info.as_str(), "INFO");
assert_eq!(LogLevel::Success.as_str(), "SUCCESS");
assert_eq!(LogLevel::Warning.as_str(), "WARNING");
assert_eq!(LogLevel::Error.as_str(), "ERROR");
assert_eq!(LogLevel::Critical.as_str(), "CRITICAL");
}
#[test]
fn test_level_display() {
assert_eq!(format!("{}", LogLevel::Info), "INFO");
assert_eq!(format!("{}", LogLevel::Error), "ERROR");
}
#[test]
fn test_level_numeric_values() {
assert_eq!(LogLevel::Trace.as_u8(), 5);
assert_eq!(LogLevel::Debug.as_u8(), 10);
assert_eq!(LogLevel::Info.as_u8(), 20);
assert_eq!(LogLevel::Success.as_u8(), 25);
assert_eq!(LogLevel::Warning.as_u8(), 30);
assert_eq!(LogLevel::Error.as_u8(), 40);
assert_eq!(LogLevel::Critical.as_u8(), 50);
}
#[test]
fn test_level_colors() {
assert_eq!(LogLevel::Trace.color(), "\x1b[37m");
assert_eq!(LogLevel::Debug.color(), "\x1b[34m");
assert_eq!(LogLevel::Info.color(), "\x1b[32m");
assert_eq!(LogLevel::Success.color(), "\x1b[36m");
assert_eq!(LogLevel::Warning.color(), "\x1b[33m");
assert_eq!(LogLevel::Error.color(), "\x1b[31m");
assert_eq!(LogLevel::Critical.color(), "\x1b[35m");
}
#[test]
fn test_level_from_str() {
assert_eq!("trace".parse::<LogLevel>(), Ok(LogLevel::Trace));
assert_eq!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug));
assert_eq!("info".parse::<LogLevel>(), Ok(LogLevel::Info));
assert_eq!("SUCCESS".parse::<LogLevel>(), Ok(LogLevel::Success));
assert_eq!("warning".parse::<LogLevel>(), Ok(LogLevel::Warning));
assert_eq!("ERROR".parse::<LogLevel>(), Ok(LogLevel::Error));
assert_eq!("critical".parse::<LogLevel>(), Ok(LogLevel::Critical));
assert!("invalid".parse::<LogLevel>().is_err());
}
#[test]
fn test_level_from_str_edge_cases() {
assert_eq!("TrAcE".parse::<LogLevel>(), Ok(LogLevel::Trace));
assert_eq!("DeBuG".parse::<LogLevel>(), Ok(LogLevel::Debug));
assert!(" trace ".parse::<LogLevel>().is_err());
assert!("debug ".parse::<LogLevel>().is_err());
assert!("".parse::<LogLevel>().is_err());
}
#[test]
fn test_level_comparisons() {
assert_eq!(LogLevel::Info, LogLevel::Info);
assert_ne!(LogLevel::Info, LogLevel::Error);
assert!(LogLevel::Trace < LogLevel::Critical);
assert!(LogLevel::Critical > LogLevel::Trace);
assert!(LogLevel::Trace <= LogLevel::Debug);
assert!(LogLevel::Debug >= LogLevel::Trace);
assert!(LogLevel::Info <= LogLevel::Info);
assert!(LogLevel::Info >= LogLevel::Info);
}
#[test]
fn test_color_code_format() {
assert!(LogLevel::Trace.color().starts_with("\x1b["));
assert!(LogLevel::Debug.color().starts_with("\x1b["));
assert!(LogLevel::Info.color().starts_with("\x1b["));
assert!(LogLevel::Success.color().starts_with("\x1b["));
assert!(LogLevel::Warning.color().starts_with("\x1b["));
assert!(LogLevel::Error.color().starts_with("\x1b["));
assert!(LogLevel::Critical.color().starts_with("\x1b["));
assert_eq!(LogLevel::reset_color(), "\x1b[0m");
}
#[test]
fn test_level_emoji() {
assert_eq!(LogLevel::Trace.emoji(), "đ");
assert_eq!(LogLevel::Debug.emoji(), "đ");
assert_eq!(LogLevel::Info.emoji(), "âšī¸");
assert_eq!(LogLevel::Success.emoji(), "â
");
assert_eq!(LogLevel::Warning.emoji(), "â ī¸");
assert_eq!(LogLevel::Error.emoji(), "â");
assert_eq!(LogLevel::Critical.emoji(), "đĨ");
}
#[test]
fn test_module_pattern_matching() {
let level = LogLevel::Info;
assert!(level.matches_module_pattern("my_app::module", "my_app"));
assert!(level.matches_module_pattern("my_app::module", "module"));
assert!(!level.matches_module_pattern("my_app::module", "other"));
assert!(level.matches_module_pattern("my_app::module", "my_app::*"));
assert!(level.matches_module_pattern("my_app::module", "*::module"));
assert!(!level.matches_module_pattern("my_app::module", "other::*"));
}
#[test]
fn test_serialization() {
let level = LogLevel::Info;
let serialized = serde_json::to_string(&level).unwrap();
assert_eq!(serialized, "\"Info\"");
let deserialized: LogLevel = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, level);
}
}