1use std::collections::HashMap;
7use std::fmt;
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum LogLevel {
14 Error = 0,
16 Warn = 1,
18 Info = 2,
20 Debug = 3,
22 Trace = 4,
24}
25
26impl fmt::Display for LogLevel {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 LogLevel::Error => write!(f, "ERROR"),
30 LogLevel::Warn => write!(f, "WARN "),
31 LogLevel::Info => write!(f, "INFO "),
32 LogLevel::Debug => write!(f, "Debug"),
33 LogLevel::Trace => write!(f, "TRACE"),
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct TimingInfo {
41 pub operation: String,
43 pub start_time: Instant,
45 pub end_time: Option<Instant>,
47 pub duration_ms: Option<u64>,
49 pub metadata: HashMap<String, String>,
51}
52
53impl TimingInfo {
54 pub fn new(operation: String) -> Self {
56 Self {
57 operation,
58 start_time: Instant::now(),
59 end_time: None,
60 duration_ms: None,
61 metadata: HashMap::new(),
62 }
63 }
64
65 pub fn complete(&mut self) {
67 let end_time = Instant::now();
68 let duration = end_time.duration_since(self.start_time);
69 self.end_time = Some(end_time);
70 self.duration_ms = Some(duration.as_millis() as u64);
71 }
72
73 pub fn add_metadata(&mut self, key: String, value: String) {
75 self.metadata.insert(key, value);
76 }
77
78 pub fn get_duration_ms(&self) -> Option<u64> {
80 self.duration_ms
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct ProgressInfo {
87 pub current_step: usize,
89 pub total_steps: usize,
91 pub operation: String,
93 pub items_processed: usize,
95 pub total_items: usize,
97 pub start_time: Instant,
99}
100
101impl ProgressInfo {
102 pub fn new(total_steps: usize, operation: String) -> Self {
104 Self {
105 current_step: 0,
106 total_steps,
107 operation,
108 items_processed: 0,
109 total_items: 0,
110 start_time: Instant::now(),
111 }
112 }
113
114 pub fn next_step(&mut self, operation: String, total_items: usize) {
116 self.current_step += 1;
117 self.operation = operation;
118 self.items_processed = 0;
119 self.total_items = total_items;
120 self.start_time = Instant::now();
121 }
122
123 pub fn update_items(&mut self, processed: usize) {
125 self.items_processed = processed;
126 }
127
128 pub fn get_overall_progress(&self) -> f64 {
130 if self.total_steps == 0 {
131 return 100.0;
132 }
133
134 let step_progress = if self.total_items > 0 {
135 self.items_processed as f64 / self.total_items as f64
136 } else {
137 1.0
138 };
139
140 ((self.current_step as f64 + step_progress) / self.total_steps as f64) * 100.0
141 }
142
143 pub fn get_step_progress(&self) -> f64 {
145 if self.total_items == 0 {
146 return 100.0;
147 }
148 (self.items_processed as f64 / self.total_items as f64) * 100.0
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct DebugConfig {
155 pub log_level: LogLevel,
157 pub enable_timing: bool,
159 pub enable_progress: bool,
161 pub enable_memory_tracking: bool,
163 pub enable_file_ops: bool,
165 pub enable_json_details: bool,
167 pub progress_interval_ms: u64,
169 pub include_timestamps: bool,
171}
172
173impl Default for DebugConfig {
174 fn default() -> Self {
175 Self {
176 log_level: LogLevel::Info,
177 enable_timing: true,
178 enable_progress: true,
179 enable_memory_tracking: false,
180 enable_file_ops: false,
181 enable_json_details: false,
182 progress_interval_ms: 1000,
183 include_timestamps: true,
184 }
185 }
186}
187
188#[derive(Debug, Default, Clone)]
190pub struct PerformanceStats {
191 pub total_time_ms: u64,
193 pub discovery_time_ms: u64,
195 pub loading_time_ms: u64,
197 pub normalization_time_ms: u64,
199 pub integration_time_ms: u64,
201 pub template_time_ms: u64,
203 pub files_processed: usize,
205 pub data_size_bytes: usize,
207 pub peak_memory_bytes: usize,
209 pub error_count: usize,
211 pub warning_count: usize,
213}
214
215impl PerformanceStats {
216 pub fn get_throughput_mb_per_sec(&self) -> f64 {
218 if self.total_time_ms == 0 {
219 return 0.0;
220 }
221 (self.data_size_bytes as f64 / 1024.0 / 1024.0) / (self.total_time_ms as f64 / 1000.0)
222 }
223
224 pub fn get_efficiency_score(&self) -> f64 {
226 let base_score = 100.0;
227 let error_penalty = (self.error_count as f64) * 10.0;
228 let warning_penalty = (self.warning_count as f64) * 2.0;
229
230 (base_score - error_penalty - warning_penalty).max(0.0)
231 }
232}
233
234pub struct DebugLogger {
236 config: DebugConfig,
238 active_timings: Arc<Mutex<HashMap<String, TimingInfo>>>,
240 completed_timings: Arc<Mutex<Vec<TimingInfo>>>,
242 progress: Arc<Mutex<Option<ProgressInfo>>>,
244 stats: Arc<Mutex<PerformanceStats>>,
246 start_time: Instant,
248 last_progress_time: Arc<Mutex<Instant>>,
250}
251
252impl DebugLogger {
253 pub fn new() -> Self {
255 Self::with_config(DebugConfig::default())
256 }
257
258 pub fn with_config(config: DebugConfig) -> Self {
260 Self {
261 config,
262 active_timings: Arc::new(Mutex::new(HashMap::new())),
263 completed_timings: Arc::new(Mutex::new(Vec::new())),
264 progress: Arc::new(Mutex::new(None)),
265 stats: Arc::new(Mutex::new(PerformanceStats::default())),
266 start_time: Instant::now(),
267 last_progress_time: Arc::new(Mutex::new(Instant::now())),
268 }
269 }
270
271 pub fn log(&self, level: LogLevel, message: &str) {
273 if level <= self.config.log_level {
274 let timestamp = if self.config.include_timestamps {
275 let elapsed = self.start_time.elapsed();
276 format!("[{:>8.3}s] ", elapsed.as_secs_f64())
277 } else {
278 String::new()
279 };
280
281 let level_icon = match level {
282 LogLevel::Error => "❌",
283 LogLevel::Warn => "⚠️ ",
284 LogLevel::Info => "ℹ️ ",
285 LogLevel::Debug => "🔍",
286 LogLevel::Trace => "🔎",
287 };
288
289 tracing::info!("{}{} {} {}", timestamp, level_icon, level, message);
290 }
291 }
292
293 pub fn error(&self, message: &str) {
295 self.log(LogLevel::Error, message);
296 if let Ok(mut stats) = self.stats.lock() {
297 stats.error_count += 1;
298 }
299 }
300
301 pub fn warn(&self, message: &str) {
303 self.log(LogLevel::Warn, message);
304 if let Ok(mut stats) = self.stats.lock() {
305 stats.warning_count += 1;
306 }
307 }
308
309 pub fn info(&self, message: &str) {
311 self.log(LogLevel::Info, message);
312 }
313
314 pub fn debug(&self, message: &str) {
316 self.log(LogLevel::Debug, message);
317 }
318
319 pub fn trace(&self, message: &str) {
321 self.log(LogLevel::Trace, message);
322 }
323
324 pub fn start_timing(&self, operation: &str) -> String {
326 if !self.config.enable_timing {
327 return operation.to_string();
328 }
329
330 let timing_id = format!("{}_{}", operation, self.start_time.elapsed().as_millis());
331 let timing_info = TimingInfo::new(operation.to_string());
332
333 if let Ok(mut timings) = self.active_timings.lock() {
334 timings.insert(timing_id.clone(), timing_info);
335 }
336
337 self.debug(&format!("Started timing: {operation}"));
338 timing_id
339 }
340
341 pub fn end_timing(&self, timing_id: &str) -> Option<u64> {
343 if !self.config.enable_timing {
344 return None;
345 }
346
347 let duration = if let Ok(mut active) = self.active_timings.lock() {
348 if let Some(mut timing) = active.remove(timing_id) {
349 timing.complete();
350 let duration = timing.get_duration_ms();
351
352 self.debug(&format!(
353 "Completed timing: {} in {}ms",
354 timing.operation,
355 duration.unwrap_or(0)
356 ));
357
358 if let Ok(mut completed) = self.completed_timings.lock() {
359 completed.push(timing);
360 }
361
362 duration
363 } else {
364 None
365 }
366 } else {
367 None
368 };
369
370 duration
371 }
372
373 pub fn start_progress(&self, total_steps: usize, initial_operation: &str) {
375 if !self.config.enable_progress {
376 return;
377 }
378
379 let progress_info = ProgressInfo::new(total_steps, initial_operation.to_string());
380
381 if let Ok(mut progress) = self.progress.lock() {
382 *progress = Some(progress_info);
383 }
384
385 self.info(&format!("Started progress tracking: {total_steps} steps"));
386 }
387
388 pub fn next_progress_step(&self, operation: &str, total_items: usize) {
390 if !self.config.enable_progress {
391 return;
392 }
393
394 if let Ok(mut progress) = self.progress.lock() {
395 if let Some(ref mut prog) = *progress {
396 prog.next_step(operation.to_string(), total_items);
397 self.info(&format!(
398 "Progress: Step {}/{} - {operation}",
399 prog.current_step, prog.total_steps
400 ));
401 }
402 }
403 }
404
405 pub fn update_progress_items(&self, processed: usize) {
407 if !self.config.enable_progress {
408 return;
409 }
410
411 let should_report = if let Ok(mut last_time) = self.last_progress_time.lock() {
412 let now = Instant::now();
413 let elapsed = now.duration_since(*last_time);
414 if elapsed.as_millis() >= self.config.progress_interval_ms as u128 {
415 *last_time = now;
416 true
417 } else {
418 false
419 }
420 } else {
421 false
422 };
423
424 if let Ok(mut progress) = self.progress.lock() {
425 if let Some(ref mut prog) = *progress {
426 prog.update_items(processed);
427
428 if should_report {
429 let overall = prog.get_overall_progress();
430 let step = prog.get_step_progress();
431 self.debug(&format!(
432 "Progress: {overall:.1}% overall, {step:.1}% current step ({}/{})",
433 prog.items_processed, prog.total_items
434 ));
435 }
436 }
437 }
438 }
439
440 pub fn log_file_operation(&self, operation: &str, file_path: &str, size_bytes: Option<usize>) {
442 if !self.config.enable_file_ops {
443 return;
444 }
445
446 let size_info = if let Some(size) = size_bytes {
447 format!(" ({:.1} KB)", size as f64 / 1024.0)
448 } else {
449 String::new()
450 };
451
452 self.debug(&format!("File {operation}: {file_path}{size_info}"));
453 }
454
455 pub fn log_json_processing(
457 &self,
458 file_type: &str,
459 objects_count: usize,
460 processing_time_ms: u64,
461 ) {
462 if !self.config.enable_json_details {
463 return;
464 }
465
466 self.debug(&format!(
467 "JSON processing: {file_type} - {objects_count} objects in {processing_time_ms}ms",
468 ));
469 }
470
471 pub fn update_stats<F>(&self, updater: F)
473 where
474 F: FnOnce(&mut PerformanceStats),
475 {
476 if let Ok(mut stats) = self.stats.lock() {
477 updater(&mut stats);
478 }
479 }
480
481 pub fn get_stats(&self) -> PerformanceStats {
483 if let Ok(stats) = self.stats.lock() {
484 stats.clone()
485 } else {
486 PerformanceStats::default()
487 }
488 }
489
490 pub fn print_performance_report(&self) {
492 if !self.config.enable_timing {
493 return;
494 }
495
496 let stats = self.get_stats();
497 let total_elapsed = self.start_time.elapsed().as_millis() as u64;
498
499 tracing::info!("\n📊 Performance Report:");
500 tracing::info!(" Total time: {}ms", total_elapsed);
501 tracing::info!(
502 " Discovery: {}ms ({:.1}%)",
503 stats.discovery_time_ms,
504 (stats.discovery_time_ms as f64 / total_elapsed as f64) * 100.0
505 );
506 tracing::info!(
507 " Loading: {}ms ({:.1}%)",
508 stats.loading_time_ms,
509 (stats.loading_time_ms as f64 / total_elapsed as f64) * 100.0
510 );
511 tracing::info!(
512 " Normalization: {}ms ({:.1}%)",
513 stats.normalization_time_ms,
514 (stats.normalization_time_ms as f64 / total_elapsed as f64) * 100.0
515 );
516 tracing::info!(
517 " Integration: {}ms ({:.1}%)",
518 stats.integration_time_ms,
519 (stats.integration_time_ms as f64 / total_elapsed as f64) * 100.0
520 );
521 tracing::info!(
522 " Template: {}ms ({:.1}%)",
523 stats.template_time_ms,
524 (stats.template_time_ms as f64 / total_elapsed as f64) * 100.0
525 );
526
527 tracing::info!("\n📈 Processing Statistics:");
528 tracing::info!(" Files processed: {}", stats.files_processed);
529 tracing::info!(
530 " Data size: {:.1} MB",
531 stats.data_size_bytes as f64 / 1024.0 / 1024.0
532 );
533 tracing::info!(
534 " Throughput: {:.1} MB/s",
535 stats.get_throughput_mb_per_sec()
536 );
537 tracing::info!(
538 " Efficiency score: {:.1}/100",
539 stats.get_efficiency_score()
540 );
541
542 if stats.error_count > 0 || stats.warning_count > 0 {
543 tracing::info!("\n⚠️ Issues:");
544 if stats.error_count > 0 {
545 tracing::info!(" Errors: {}", stats.error_count);
546 }
547 if stats.warning_count > 0 {
548 tracing::info!(" Warnings: {}", stats.warning_count);
549 }
550 }
551
552 if let Ok(completed) = self.completed_timings.lock() {
554 if !completed.is_empty() {
555 tracing::info!("\n🔍 Detailed Timing Breakdown:");
556 for timing in completed.iter() {
557 if let Some(duration) = timing.duration_ms {
558 tracing::info!(" {}: {}ms", timing.operation, duration);
559 for (key, value) in &timing.metadata {
560 tracing::info!(" {}: {}", key, value);
561 }
562 }
563 }
564 }
565 }
566 }
567
568 pub fn print_memory_info(&self) {
570 if !self.config.enable_memory_tracking {
571 return;
572 }
573
574 let stats = self.get_stats();
575 if stats.peak_memory_bytes > 0 {
576 tracing::info!("\n💾 Memory Usage:");
577 tracing::info!(
578 " Peak memory: {:.1} MB",
579 stats.peak_memory_bytes as f64 / 1024.0 / 1024.0
580 );
581
582 if stats.data_size_bytes > 0 {
583 let memory_efficiency =
584 (stats.data_size_bytes as f64 / stats.peak_memory_bytes as f64) * 100.0;
585 tracing::info!(" Memory efficiency: {:.1}%", memory_efficiency);
586 }
587 }
588 }
589}
590
591impl Default for DebugLogger {
592 fn default() -> Self {
593 Self::new()
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use std::thread;
601 use std::time::Duration;
602
603 #[test]
604 fn test_debug_logger_creation() {
605 let logger = DebugLogger::new();
606 assert_eq!(logger.config.log_level, LogLevel::Info);
607 assert!(logger.config.enable_timing);
608 }
609
610 #[test]
611 fn test_timing_operations() {
612 let logger = DebugLogger::new();
613 let timing_id = logger.start_timing("test_operation");
614
615 thread::sleep(Duration::from_millis(10));
617
618 let duration = logger.end_timing(&timing_id);
619 assert!(duration.is_some());
620 assert!(duration.expect("Failed to get duration") >= 10);
621 }
622
623 #[test]
624 fn test_progress_tracking() {
625 let logger = DebugLogger::new();
626 logger.start_progress(3, "Initial operation");
627
628 logger.next_progress_step("Step 1", 10);
629 logger.update_progress_items(5);
630
631 {
632 let progress = logger.progress.lock().expect("Failed to acquire lock");
633 if let Some(ref prog) = *progress {
634 assert_eq!(prog.current_step, 1);
635 assert_eq!(prog.items_processed, 5);
636 assert_eq!(prog.total_items, 10);
637 assert_eq!(prog.get_step_progress(), 50.0);
638 }
639 }
640 }
641
642 #[test]
643 fn test_performance_stats() {
644 let logger = DebugLogger::new();
645
646 logger.update_stats(|stats| {
647 stats.files_processed = 5;
648 stats.data_size_bytes = 1024 * 1024; stats.total_time_ms = 1000; });
651
652 let stats = logger.get_stats();
653 assert_eq!(stats.files_processed, 5);
654 assert_eq!(stats.get_throughput_mb_per_sec(), 1.0);
655 }
656
657 #[test]
658 fn test_log_levels() {
659 let config = DebugConfig {
660 log_level: LogLevel::Debug,
661 ..Default::default()
662 };
663 let logger = DebugLogger::with_config(config);
664
665 logger.error("Test error");
667 logger.warn("Test warning");
668 logger.info("Test info");
669 logger.debug("Test debug");
670 logger.trace("Test trace"); }
672}