use std::collections::HashMap;
use std::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
}
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::Warn => write!(f, " WARN"),
LogLevel::Error => write!(f, "ERROR"),
}
}
}
impl Default for LogLevel {
fn default() -> Self {
LogLevel::Info
}
}
#[derive(Debug, Clone)]
pub struct EvaluationEvent {
pub timestamp: Instant,
pub level: LogLevel,
pub component: String,
pub message: String,
pub metadata: HashMap<String, String>,
pub duration: Option<Duration>,
}
impl EvaluationEvent {
pub fn new(level: LogLevel, component: &str, message: &str) -> Self {
Self {
timestamp: Instant::now(),
level,
component: component.to_string(),
message: message.to_string(),
metadata: HashMap::new(),
duration: None,
}
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
}
impl fmt::Display for EvaluationEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}: {}", self.level, self.component, self.message)?;
if let Some(duration) = self.duration {
write!(f, " (took {:?})", duration)?;
}
if !self.metadata.is_empty() {
write!(f, " [")?;
for (i, (key, value)) in self.metadata.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}={}", key, value)?;
}
write!(f, "]")?;
}
Ok(())
}
}
pub trait LogBackend: Send + Sync {
fn log(&self, event: &EvaluationEvent);
fn is_enabled(&self, level: LogLevel) -> bool;
}
#[derive(Debug)]
pub struct ConsoleLogger {
min_level: LogLevel,
}
impl ConsoleLogger {
pub fn new(min_level: LogLevel) -> Self {
Self { min_level }
}
}
impl LogBackend for ConsoleLogger {
fn log(&self, event: &EvaluationEvent) {
if event.level >= self.min_level {
println!("{}", event);
}
}
fn is_enabled(&self, level: LogLevel) -> bool {
level >= self.min_level
}
}
#[derive(Debug, Default)]
pub struct MemoryLogger {
events: std::sync::Mutex<Vec<EvaluationEvent>>,
min_level: LogLevel,
}
impl MemoryLogger {
pub fn new(min_level: LogLevel) -> Self {
Self {
events: std::sync::Mutex::new(Vec::new()),
min_level,
}
}
pub fn events(&self) -> Vec<EvaluationEvent> {
self.events
.lock()
.expect("lock should not be poisoned")
.clone()
}
pub fn clear(&self) {
self.events
.lock()
.expect("lock should not be poisoned")
.clear();
}
pub fn events_for_component(&self, component: &str) -> Vec<EvaluationEvent> {
self.events()
.into_iter()
.filter(|e| e.component == component)
.collect()
}
pub fn events_by_level(&self, level: LogLevel) -> Vec<EvaluationEvent> {
self.events()
.into_iter()
.filter(|e| e.level == level)
.collect()
}
}
impl LogBackend for MemoryLogger {
fn log(&self, event: &EvaluationEvent) {
if event.level >= self.min_level {
self.events
.lock()
.expect("lock should not be poisoned")
.push(event.clone());
}
}
fn is_enabled(&self, level: LogLevel) -> bool {
level >= self.min_level
}
}
pub struct EvaluationLogger {
backend: Box<dyn LogBackend>,
}
impl EvaluationLogger {
pub fn console(min_level: LogLevel) -> Self {
Self {
backend: Box::new(ConsoleLogger::new(min_level)),
}
}
pub fn memory(min_level: LogLevel) -> Self {
Self {
backend: Box::new(MemoryLogger::new(min_level)),
}
}
pub fn with_backend(backend: Box<dyn LogBackend>) -> Self {
Self { backend }
}
pub fn log(&self, event: EvaluationEvent) {
self.backend.log(&event);
}
pub fn is_enabled(&self, level: LogLevel) -> bool {
self.backend.is_enabled(level)
}
pub fn trace(&self, component: &str, message: &str) {
if self.is_enabled(LogLevel::Trace) {
self.log(EvaluationEvent::new(LogLevel::Trace, component, message));
}
}
pub fn debug(&self, component: &str, message: &str) {
if self.is_enabled(LogLevel::Debug) {
self.log(EvaluationEvent::new(LogLevel::Debug, component, message));
}
}
pub fn info(&self, component: &str, message: &str) {
if self.is_enabled(LogLevel::Info) {
self.log(EvaluationEvent::new(LogLevel::Info, component, message));
}
}
pub fn warn(&self, component: &str, message: &str) {
if self.is_enabled(LogLevel::Warn) {
self.log(EvaluationEvent::new(LogLevel::Warn, component, message));
}
}
pub fn error(&self, component: &str, message: &str) {
if self.is_enabled(LogLevel::Error) {
self.log(EvaluationEvent::new(LogLevel::Error, component, message));
}
}
}
pub struct PerformanceTimer {
start_time: Instant,
component: String,
operation: String,
logger: Option<std::sync::Arc<EvaluationLogger>>,
}
impl PerformanceTimer {
pub fn start(component: &str, operation: &str) -> Self {
Self {
start_time: Instant::now(),
component: component.to_string(),
operation: operation.to_string(),
logger: None,
}
}
pub fn start_with_logger(
component: &str,
operation: &str,
logger: std::sync::Arc<EvaluationLogger>,
) -> Self {
let mut timer = Self::start(component, operation);
timer.logger = Some(logger);
if let Some(ref logger) = timer.logger {
logger.debug(component, &format!("Starting {}", operation));
}
timer
}
pub fn elapsed(&self) -> Duration {
self.start_time.elapsed()
}
pub fn stop(self) -> Duration {
let duration = self.elapsed();
if let Some(logger) = &self.logger {
logger.log(
EvaluationEvent::new(
LogLevel::Debug,
&self.component,
&format!("Completed {}", self.operation),
)
.with_duration(duration),
);
}
duration
}
}
#[macro_export]
macro_rules! eval_log {
($logger:expr, trace, $component:expr, $($arg:tt)*) => {
$logger.trace($component, &format!($($arg)*))
};
($logger:expr, debug, $component:expr, $($arg:tt)*) => {
$logger.debug($component, &format!($($arg)*))
};
($logger:expr, info, $component:expr, $($arg:tt)*) => {
$logger.info($component, &format!($($arg)*))
};
($logger:expr, warn, $component:expr, $($arg:tt)*) => {
$logger.warn($component, &format!($($arg)*))
};
($logger:expr, error, $component:expr, $($arg:tt)*) => {
$logger.error($component, &format!($($arg)*))
};
}
#[derive(Debug, Clone)]
pub struct DebugContext {
pub operation: String,
pub parameters: HashMap<String, String>,
pub intermediate_results: HashMap<String, String>,
pub performance_metrics: HashMap<String, Duration>,
pub warnings: Vec<String>,
}
impl DebugContext {
pub fn new(operation: &str) -> Self {
Self {
operation: operation.to_string(),
parameters: HashMap::new(),
intermediate_results: HashMap::new(),
performance_metrics: HashMap::new(),
warnings: Vec::new(),
}
}
pub fn parameter(mut self, key: &str, value: &str) -> Self {
self.parameters.insert(key.to_string(), value.to_string());
self
}
pub fn intermediate(mut self, key: &str, value: &str) -> Self {
self.intermediate_results
.insert(key.to_string(), value.to_string());
self
}
pub fn timing(mut self, key: &str, duration: Duration) -> Self {
self.performance_metrics.insert(key.to_string(), duration);
self
}
pub fn warning(mut self, warning: &str) -> Self {
self.warnings.push(warning.to_string());
self
}
pub fn report(&self) -> String {
let mut report = format!("Debug Report for {}\n", self.operation);
report.push_str("=".repeat(50).as_str());
report.push('\n');
if !self.parameters.is_empty() {
report.push_str("\nParameters:\n");
for (key, value) in &self.parameters {
report.push_str(&format!(" {}: {}\n", key, value));
}
}
if !self.intermediate_results.is_empty() {
report.push_str("\nIntermediate Results:\n");
for (key, value) in &self.intermediate_results {
report.push_str(&format!(" {}: {}\n", key, value));
}
}
if !self.performance_metrics.is_empty() {
report.push_str("\nPerformance Metrics:\n");
for (key, duration) in &self.performance_metrics {
report.push_str(&format!(" {}: {:?}\n", key, duration));
}
}
if !self.warnings.is_empty() {
report.push_str("\nWarnings:\n");
for warning in &self.warnings {
report.push_str(&format!(" - {}\n", warning));
}
}
report
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_log_levels() {
assert!(LogLevel::Error > LogLevel::Warn);
assert!(LogLevel::Warn > LogLevel::Info);
assert!(LogLevel::Info > LogLevel::Debug);
assert!(LogLevel::Debug > LogLevel::Trace);
}
#[test]
fn test_evaluation_event() {
let event = EvaluationEvent::new(LogLevel::Info, "test", "test message")
.with_metadata("key", "value")
.with_duration(Duration::from_millis(100));
assert_eq!(event.level, LogLevel::Info);
assert_eq!(event.component, "test");
assert_eq!(event.message, "test message");
assert_eq!(event.metadata.get("key"), Some(&"value".to_string()));
assert!(event.duration.is_some());
}
#[test]
fn test_memory_logger() {
let logger = MemoryLogger::new(LogLevel::Debug);
logger.log(&EvaluationEvent::new(
LogLevel::Info,
"test",
"info message",
));
logger.log(&EvaluationEvent::new(
LogLevel::Debug,
"test",
"debug message",
));
logger.log(&EvaluationEvent::new(
LogLevel::Trace,
"test",
"trace message",
));
let events = logger.events();
assert_eq!(events.len(), 2);
assert_eq!(events[0].level, LogLevel::Info);
assert_eq!(events[1].level, LogLevel::Debug);
}
#[test]
fn test_performance_timer() {
let timer = PerformanceTimer::start("test", "operation");
thread::sleep(Duration::from_millis(10));
let duration = timer.stop();
assert!(duration >= Duration::from_millis(10));
}
#[test]
fn test_debug_context() {
let context = DebugContext::new("test_operation")
.parameter("param1", "value1")
.intermediate("result1", "intermediate1")
.timing("step1", Duration::from_millis(100))
.warning("test warning");
let report = context.report();
assert!(report.contains("test_operation"));
assert!(report.contains("param1: value1"));
assert!(report.contains("result1: intermediate1"));
assert!(report.contains("step1:"));
assert!(report.contains("test warning"));
}
#[test]
fn test_evaluation_logger() {
let logger = EvaluationLogger::memory(LogLevel::Info);
logger.info("test", "info message");
logger.debug("test", "debug message"); logger.warn("test", "warning message");
}
}