use chrono::Local;
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
pub trait DebugProvider {
fn component_name(&self) -> &str;
fn debug_info(&self) -> String;
fn debug_summary(&self) -> Option<String> {
None
}
}
pub struct DebugService {
entries: Arc<Mutex<Vec<DebugEntry>>>,
max_entries: usize,
enabled: Arc<Mutex<bool>>,
}
#[derive(Clone, Debug)]
pub struct DebugEntry {
pub timestamp: String,
pub component: String,
pub level: DebugLevel,
pub message: String,
pub context: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DebugLevel {
Info,
Warning,
Error,
Trace,
}
impl DebugService {
#[must_use]
pub fn new(max_entries: usize) -> Self {
Self {
entries: Arc::new(Mutex::new(Vec::new())),
max_entries,
enabled: Arc::new(Mutex::new(false)),
}
}
#[must_use]
pub fn clone_service(&self) -> Self {
Self {
entries: Arc::clone(&self.entries),
max_entries: self.max_entries,
enabled: Arc::clone(&self.enabled),
}
}
pub fn set_enabled(&self, enabled: bool) {
if let Ok(mut e) = self.enabled.lock() {
*e = enabled;
}
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.enabled.lock().map(|e| *e).unwrap_or(false)
}
pub fn log(
&self,
component: &str,
level: DebugLevel,
message: String,
context: Option<String>,
) {
if !self.is_enabled() {
return;
}
let entry = DebugEntry {
timestamp: Local::now().format("%H:%M:%S%.3f").to_string(),
component: component.to_string(),
level,
message,
context,
};
if let Ok(mut entries) = self.entries.lock() {
entries.push(entry);
if entries.len() > self.max_entries {
let remove_count = entries.len() - self.max_entries;
entries.drain(0..remove_count);
}
}
}
pub fn info(&self, component: &str, message: String) {
self.log(component, DebugLevel::Info, message, None);
}
pub fn warn(&self, component: &str, message: String) {
self.log(component, DebugLevel::Warning, message, None);
}
pub fn error(&self, component: &str, message: String) {
self.log(component, DebugLevel::Error, message, None);
}
pub fn trace(&self, component: &str, message: String, context: String) {
self.log(component, DebugLevel::Trace, message, Some(context));
}
#[must_use]
pub fn get_entries(&self) -> Vec<DebugEntry> {
self.entries.lock().map(|e| e.clone()).unwrap_or_default()
}
#[must_use]
pub fn get_recent_entries(&self, count: usize) -> Vec<DebugEntry> {
if let Ok(entries) = self.entries.lock() {
let start = entries.len().saturating_sub(count);
entries[start..].to_vec()
} else {
Vec::new()
}
}
pub fn clear(&self) {
if let Ok(mut entries) = self.entries.lock() {
entries.clear();
}
}
#[must_use]
pub fn generate_dump(&self) -> String {
let mut dump = String::new();
dump.push_str("=== DEBUG SERVICE LOG ===\n\n");
if let Ok(entries) = self.entries.lock() {
if entries.is_empty() {
dump.push_str("No debug entries collected.\n");
} else {
dump.push_str(&format!(
"Total entries: {} (max: {})\n\n",
entries.len(),
self.max_entries
));
for entry in entries.iter() {
let level_str = match entry.level {
DebugLevel::Info => "INFO ",
DebugLevel::Warning => "WARN ",
DebugLevel::Error => "ERROR",
DebugLevel::Trace => "TRACE",
};
dump.push_str(&format!(
"[{}] {} [{}] {}\n",
entry.timestamp, level_str, entry.component, entry.message
));
if let Some(ref ctx) = entry.context {
dump.push_str(&format!(" Context: {ctx}\n"));
}
}
}
}
dump.push_str("\n=== END DEBUG LOG ===\n");
dump
}
#[must_use]
pub fn generate_summary(&self) -> String {
let mut summary = String::new();
summary.push_str("=== DEBUG SUMMARY ===\n\n");
if let Ok(entries) = self.entries.lock() {
use std::collections::HashMap;
let mut component_counts: HashMap<String, (usize, usize, usize)> = HashMap::new();
for entry in entries.iter() {
let counts = component_counts
.entry(entry.component.clone())
.or_insert((0, 0, 0));
match entry.level {
DebugLevel::Error => counts.0 += 1,
DebugLevel::Warning => counts.1 += 1,
_ => counts.2 += 1,
}
}
for (component, (errors, warnings, others)) in component_counts {
summary.push_str(&format!(
"{component}: {errors} errors, {warnings} warnings, {others} info/trace\n"
));
}
}
summary
}
}
#[macro_export]
macro_rules! debug_trace {
($service:expr, $component:expr, $msg:expr, $ctx:expr) => {
$service.trace($component, $msg.to_string(), $ctx.to_string())
};
}