use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Trace => write!(f, "TRACE"),
Self::Debug => write!(f, "DEBUG"),
Self::Info => write!(f, "INFO"),
Self::Warn => write!(f, "WARN"),
Self::Error => write!(f, "ERROR"),
}
}
}
pub struct LogEntry {
pub level: LogLevel,
pub target: &'static str,
pub message: String,
pub elapsed_us: Option<u64>,
}
pub trait Logger: Send + Sync {
fn log(&self, entry: LogEntry);
fn is_enabled(&self, level: LogLevel) -> bool;
fn flush(&self);
}
pub struct NoopLogger;
impl Logger for NoopLogger {
fn log(&self, _entry: LogEntry) {}
fn is_enabled(&self, _level: LogLevel) -> bool {
false
}
fn flush(&self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_level_ordering() {
assert!(LogLevel::Trace < LogLevel::Debug);
assert!(LogLevel::Debug < LogLevel::Info);
assert!(LogLevel::Info < LogLevel::Warn);
assert!(LogLevel::Warn < LogLevel::Error);
}
#[test]
fn log_level_display() {
assert_eq!(LogLevel::Trace.to_string(), "TRACE");
assert_eq!(LogLevel::Info.to_string(), "INFO");
assert_eq!(LogLevel::Error.to_string(), "ERROR");
}
#[test]
fn noop_logger_accepts_entries() {
let logger = NoopLogger;
logger.log(LogEntry {
level: LogLevel::Info,
target: "test",
message: "hello".into(),
elapsed_us: None,
});
}
#[test]
fn noop_logger_is_never_enabled() {
let logger = NoopLogger;
assert!(!logger.is_enabled(LogLevel::Trace));
assert!(!logger.is_enabled(LogLevel::Error));
}
#[test]
fn noop_logger_flush_is_safe() {
let logger = NoopLogger;
logger.flush();
}
#[test]
fn log_entry_with_timing() {
let entry = LogEntry {
level: LogLevel::Debug,
target: "tui",
message: "frame render".into(),
elapsed_us: Some(1234),
};
assert_eq!(entry.elapsed_us, Some(1234));
assert_eq!(entry.target, "tui");
}
}