safer_ring/
logging.rs

1//! Comprehensive logging and debugging support for safer-ring.
2//!
3//! This module provides structured logging, performance metrics, and debugging
4//! utilities to help diagnose issues and optimize performance in safer-ring applications.
5
6use crate::error::{Result, SaferRingError};
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
11/// Log level for safer-ring operations.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum LogLevel {
14    /// Trace-level logging (very verbose)
15    Trace = 0,
16    /// Debug-level logging
17    Debug = 1,
18    /// Info-level logging
19    Info = 2,
20    /// Warning-level logging
21    Warn = 3,
22    /// Error-level logging
23    Error = 4,
24}
25
26impl std::fmt::Display for LogLevel {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            LogLevel::Trace => write!(f, "TRACE"),
30            LogLevel::Debug => write!(f, "DEBUG"),
31            LogLevel::Info => write!(f, "INFO"),
32            LogLevel::Warn => write!(f, "WARN"),
33            LogLevel::Error => write!(f, "ERROR"),
34        }
35    }
36}
37
38/// Log entry containing structured information about safer-ring operations.
39#[derive(Debug, Clone)]
40pub struct LogEntry {
41    /// Timestamp when the log entry was created
42    pub timestamp: SystemTime,
43    /// Log level
44    pub level: LogLevel,
45    /// Component that generated the log
46    pub component: String,
47    /// Operation ID if applicable
48    pub operation_id: Option<u64>,
49    /// File descriptor if applicable
50    pub fd: Option<i32>,
51    /// Message content
52    pub message: String,
53    /// Additional structured data
54    pub metadata: HashMap<String, String>,
55    /// Duration if this is a timing log
56    pub duration: Option<Duration>,
57}
58
59impl LogEntry {
60    /// Create a new log entry.
61    pub fn new(level: LogLevel, component: &str, message: &str) -> Self {
62        Self {
63            timestamp: SystemTime::now(),
64            level,
65            component: component.to_string(),
66            operation_id: None,
67            fd: None,
68            message: message.to_string(),
69            metadata: HashMap::new(),
70            duration: None,
71        }
72    }
73
74    /// Add operation ID to the log entry.
75    pub fn with_operation_id(mut self, operation_id: u64) -> Self {
76        self.operation_id = Some(operation_id);
77        self
78    }
79
80    /// Add file descriptor to the log entry.
81    pub fn with_fd(mut self, fd: i32) -> Self {
82        self.fd = Some(fd);
83        self
84    }
85
86    /// Add metadata to the log entry.
87    pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
88        self.metadata.insert(key.to_string(), value.to_string());
89        self
90    }
91
92    /// Add duration to the log entry.
93    pub fn with_duration(mut self, duration: Duration) -> Self {
94        self.duration = Some(duration);
95        self
96    }
97
98    /// Format the log entry as a human-readable string.
99    pub fn format(&self) -> String {
100        let timestamp = self
101            .timestamp
102            .duration_since(UNIX_EPOCH)
103            .unwrap_or_default()
104            .as_millis();
105
106        let mut parts = vec![
107            format!("[{}]", timestamp),
108            format!("{}", self.level),
109            format!("{}", self.component),
110        ];
111
112        if let Some(op_id) = self.operation_id {
113            parts.push(format!("op:{op_id}"));
114        }
115
116        if let Some(fd) = self.fd {
117            parts.push(format!("fd:{fd}"));
118        }
119
120        parts.push(self.message.clone());
121
122        if let Some(duration) = self.duration {
123            parts.push(format!("duration:{}μs", duration.as_micros()));
124        }
125
126        if !self.metadata.is_empty() {
127            let metadata_str = self
128                .metadata
129                .iter()
130                .map(|(k, v)| format!("{k}:{v}"))
131                .collect::<Vec<_>>()
132                .join(",");
133            parts.push(format!("metadata:{{{metadata_str}}}"));
134        }
135
136        parts.join(" ")
137    }
138
139    /// Format the log entry as JSON.
140    pub fn format_json(&self) -> String {
141        let timestamp = self
142            .timestamp
143            .duration_since(UNIX_EPOCH)
144            .unwrap_or_default()
145            .as_millis();
146
147        let mut json_parts = vec![
148            format!("\"timestamp\":{}", timestamp),
149            format!("\"level\":\"{}\"", self.level),
150            format!("\"component\":\"{}\"", self.component),
151            format!("\"message\":\"{}\"", self.message.replace('"', "\\\"")),
152        ];
153
154        if let Some(op_id) = self.operation_id {
155            json_parts.push(format!("\"operation_id\":{op_id}"));
156        }
157
158        if let Some(fd) = self.fd {
159            json_parts.push(format!("\"fd\":{fd}"));
160        }
161
162        if let Some(duration) = self.duration {
163            json_parts.push(format!("\"duration_us\":{}", duration.as_micros()));
164        }
165
166        if !self.metadata.is_empty() {
167            let metadata_json = self
168                .metadata
169                .iter()
170                .map(|(k, v)| format!("\"{}\":\"{}\"", k, v.replace('"', "\\\"")))
171                .collect::<Vec<_>>()
172                .join(",");
173            json_parts.push(format!("\"metadata\":{{{metadata_json}}}"));
174        }
175
176        format!("{{{}}}", json_parts.join(","))
177    }
178}
179
180/// Trait for log output destinations.
181pub trait LogOutput: Send + Sync {
182    /// Write a log entry to the output.
183    fn write(&self, entry: &LogEntry) -> Result<()>;
184
185    /// Flush any buffered output.
186    fn flush(&self) -> Result<()>;
187}
188
189/// Console log output that writes to stderr.
190#[derive(Debug)]
191pub struct ConsoleOutput {
192    /// Whether to use JSON format
193    json_format: bool,
194}
195
196impl ConsoleOutput {
197    /// Create a new console output with text format.
198    pub fn new() -> Self {
199        Self { json_format: false }
200    }
201
202    /// Create a new console output with JSON format.
203    pub fn new_json() -> Self {
204        Self { json_format: true }
205    }
206}
207
208impl Default for ConsoleOutput {
209    fn default() -> Self {
210        Self::new()
211    }
212}
213
214impl LogOutput for ConsoleOutput {
215    fn write(&self, entry: &LogEntry) -> Result<()> {
216        let formatted = if self.json_format {
217            entry.format_json()
218        } else {
219            entry.format()
220        };
221
222        eprintln!("{formatted}");
223        Ok(())
224    }
225
226    fn flush(&self) -> Result<()> {
227        use std::io::Write;
228        std::io::stderr().flush().map_err(SaferRingError::Io)?;
229        Ok(())
230    }
231}
232
233/// File log output that writes to a file.
234#[derive(Debug)]
235pub struct FileOutput {
236    /// Path to the log file
237    path: std::path::PathBuf,
238    /// Whether to use JSON format
239    json_format: bool,
240}
241
242impl FileOutput {
243    /// Create a new file output.
244    pub fn new<P: AsRef<std::path::Path>>(path: P) -> Self {
245        Self {
246            path: path.as_ref().to_path_buf(),
247            json_format: false,
248        }
249    }
250
251    /// Create a new file output with JSON format.
252    pub fn new_json<P: AsRef<std::path::Path>>(path: P) -> Self {
253        Self {
254            path: path.as_ref().to_path_buf(),
255            json_format: true,
256        }
257    }
258}
259
260impl LogOutput for FileOutput {
261    fn write(&self, entry: &LogEntry) -> Result<()> {
262        use std::io::Write;
263
264        let formatted = if self.json_format {
265            entry.format_json()
266        } else {
267            entry.format()
268        };
269
270        let mut file = std::fs::OpenOptions::new()
271            .create(true)
272            .append(true)
273            .open(&self.path)
274            .map_err(SaferRingError::Io)?;
275
276        writeln!(file, "{formatted}").map_err(SaferRingError::Io)?;
277        Ok(())
278    }
279
280    fn flush(&self) -> Result<()> {
281        // File is opened and closed for each write, so no explicit flush needed
282        Ok(())
283    }
284}
285
286/// Central logger for safer-ring operations.
287pub struct Logger {
288    /// Minimum log level to output
289    min_level: LogLevel,
290    /// Output destinations
291    outputs: Vec<Box<dyn LogOutput>>,
292    /// Performance metrics
293    metrics: Arc<Mutex<PerformanceMetrics>>,
294}
295
296impl Logger {
297    /// Create a new logger with console output.
298    pub fn new() -> Self {
299        Self {
300            min_level: LogLevel::Info,
301            outputs: vec![Box::new(ConsoleOutput::new())],
302            metrics: Arc::new(Mutex::new(PerformanceMetrics::new())),
303        }
304    }
305
306    /// Set the minimum log level.
307    pub fn set_level(&mut self, level: LogLevel) {
308        self.min_level = level;
309    }
310
311    /// Add an output destination.
312    pub fn add_output(&mut self, output: Box<dyn LogOutput>) {
313        self.outputs.push(output);
314    }
315
316    /// Log a message at the specified level.
317    pub fn log(&self, level: LogLevel, component: &str, message: &str) {
318        if level >= self.min_level {
319            let entry = LogEntry::new(level, component, message);
320            self.write_entry(&entry);
321        }
322    }
323
324    /// Log a message with operation context.
325    pub fn log_operation(
326        &self,
327        level: LogLevel,
328        component: &str,
329        operation_id: u64,
330        fd: Option<i32>,
331        message: &str,
332    ) {
333        if level >= self.min_level {
334            let mut entry =
335                LogEntry::new(level, component, message).with_operation_id(operation_id);
336
337            if let Some(fd) = fd {
338                entry = entry.with_fd(fd);
339            }
340
341            self.write_entry(&entry);
342        }
343    }
344
345    /// Log a timing measurement.
346    pub fn log_timing(&self, component: &str, operation: &str, duration: Duration) {
347        if LogLevel::Debug >= self.min_level {
348            let entry = LogEntry::new(
349                LogLevel::Debug,
350                component,
351                &format!("{operation} completed"),
352            )
353            .with_duration(duration);
354            self.write_entry(&entry);
355
356            // Update performance metrics
357            if let Ok(mut metrics) = self.metrics.lock() {
358                metrics.record_operation(operation, duration);
359            }
360        }
361    }
362
363    /// Log an error with context.
364    pub fn log_error(&self, component: &str, error: &SaferRingError, context: &str) {
365        let message = format!("{context}: {error}");
366        let entry = LogEntry::new(LogLevel::Error, component, &message);
367        self.write_entry(&entry);
368    }
369
370    /// Write a log entry to all outputs.
371    fn write_entry(&self, entry: &LogEntry) {
372        for output in &self.outputs {
373            if let Err(e) = output.write(entry) {
374                eprintln!("Failed to write log entry: {e}");
375            }
376        }
377    }
378
379    /// Flush all outputs.
380    pub fn flush(&self) {
381        for output in &self.outputs {
382            if let Err(e) = output.flush() {
383                eprintln!("Failed to flush log output: {e}");
384            }
385        }
386    }
387
388    /// Get performance metrics.
389    pub fn get_metrics(&self) -> Result<PerformanceMetrics> {
390        self.metrics.lock().map(|m| m.clone()).map_err(|_| {
391            SaferRingError::Io(std::io::Error::other("Failed to acquire metrics lock"))
392        })
393    }
394
395    /// Reset performance metrics.
396    pub fn reset_metrics(&self) -> Result<()> {
397        self.metrics.lock().map(|mut m| m.reset()).map_err(|_| {
398            SaferRingError::Io(std::io::Error::other("Failed to acquire metrics lock"))
399        })
400    }
401}
402
403impl Default for Logger {
404    fn default() -> Self {
405        Self::new()
406    }
407}
408
409/// Performance metrics for safer-ring operations.
410#[derive(Debug, Clone)]
411pub struct PerformanceMetrics {
412    /// Operation counts by type
413    operation_counts: HashMap<String, u64>,
414    /// Total duration by operation type
415    operation_durations: HashMap<String, Duration>,
416    /// Minimum duration by operation type
417    min_durations: HashMap<String, Duration>,
418    /// Maximum duration by operation type
419    max_durations: HashMap<String, Duration>,
420    /// Start time for metrics collection
421    start_time: Instant,
422}
423
424impl PerformanceMetrics {
425    /// Create new performance metrics.
426    pub fn new() -> Self {
427        Self {
428            operation_counts: HashMap::new(),
429            operation_durations: HashMap::new(),
430            min_durations: HashMap::new(),
431            max_durations: HashMap::new(),
432            start_time: Instant::now(),
433        }
434    }
435
436    /// Record an operation timing.
437    pub fn record_operation(&mut self, operation: &str, duration: Duration) {
438        let count = self
439            .operation_counts
440            .entry(operation.to_string())
441            .or_insert(0);
442        *count += 1;
443
444        let total_duration = self
445            .operation_durations
446            .entry(operation.to_string())
447            .or_insert(Duration::ZERO);
448        *total_duration += duration;
449
450        let min_duration = self
451            .min_durations
452            .entry(operation.to_string())
453            .or_insert(duration);
454        if duration < *min_duration {
455            *min_duration = duration;
456        }
457
458        let max_duration = self
459            .max_durations
460            .entry(operation.to_string())
461            .or_insert(duration);
462        if duration > *max_duration {
463            *max_duration = duration;
464        }
465    }
466
467    /// Get the count for a specific operation type.
468    pub fn get_count(&self, operation: &str) -> u64 {
469        self.operation_counts.get(operation).copied().unwrap_or(0)
470    }
471
472    /// Get the average duration for a specific operation type.
473    pub fn get_average_duration(&self, operation: &str) -> Option<Duration> {
474        let count = self.get_count(operation);
475        if count == 0 {
476            return None;
477        }
478
479        self.operation_durations
480            .get(operation)
481            .map(|total| *total / count as u32)
482    }
483
484    /// Get the minimum duration for a specific operation type.
485    pub fn get_min_duration(&self, operation: &str) -> Option<Duration> {
486        self.min_durations.get(operation).copied()
487    }
488
489    /// Get the maximum duration for a specific operation type.
490    pub fn get_max_duration(&self, operation: &str) -> Option<Duration> {
491        self.max_durations.get(operation).copied()
492    }
493
494    /// Get all operation types.
495    pub fn get_operation_types(&self) -> Vec<String> {
496        self.operation_counts.keys().cloned().collect()
497    }
498
499    /// Get total operations across all types.
500    pub fn get_total_operations(&self) -> u64 {
501        self.operation_counts.values().sum()
502    }
503
504    /// Get the duration since metrics collection started.
505    pub fn get_collection_duration(&self) -> Duration {
506        self.start_time.elapsed()
507    }
508
509    /// Reset all metrics.
510    pub fn reset(&mut self) {
511        self.operation_counts.clear();
512        self.operation_durations.clear();
513        self.min_durations.clear();
514        self.max_durations.clear();
515        self.start_time = Instant::now();
516    }
517
518    /// Generate a summary report.
519    pub fn generate_report(&self) -> String {
520        let mut report = String::new();
521        report.push_str("=== Safer-Ring Performance Metrics ===\n");
522        report.push_str(&format!(
523            "Collection Duration: {:?}\n",
524            self.get_collection_duration()
525        ));
526        report.push_str(&format!(
527            "Total Operations: {}\n\n",
528            self.get_total_operations()
529        ));
530
531        for operation in self.get_operation_types() {
532            report.push_str(&format!("Operation: {operation}\n"));
533            report.push_str(&format!("  Count: {}\n", self.get_count(&operation)));
534
535            if let Some(avg) = self.get_average_duration(&operation) {
536                report.push_str(&format!("  Average Duration: {avg:?}\n"));
537            }
538
539            if let Some(min) = self.get_min_duration(&operation) {
540                report.push_str(&format!("  Min Duration: {min:?}\n"));
541            }
542
543            if let Some(max) = self.get_max_duration(&operation) {
544                report.push_str(&format!("  Max Duration: {max:?}\n"));
545            }
546
547            report.push('\n');
548        }
549
550        report
551    }
552}
553
554impl Default for PerformanceMetrics {
555    fn default() -> Self {
556        Self::new()
557    }
558}
559
560/// Global logger instance.
561static GLOBAL_LOGGER: std::sync::OnceLock<Arc<Mutex<Logger>>> = std::sync::OnceLock::new();
562
563/// Initialize the global logger.
564pub fn init_logger() -> Arc<Mutex<Logger>> {
565    GLOBAL_LOGGER
566        .get_or_init(|| Arc::new(Mutex::new(Logger::new())))
567        .clone()
568}
569
570/// Log a message using the global logger.
571pub fn log(level: LogLevel, component: &str, message: &str) {
572    if let Some(logger) = GLOBAL_LOGGER.get() {
573        if let Ok(logger) = logger.lock() {
574            logger.log(level, component, message);
575        }
576    }
577}
578
579/// Log an operation using the global logger.
580pub fn log_operation(
581    level: LogLevel,
582    component: &str,
583    operation_id: u64,
584    fd: Option<i32>,
585    message: &str,
586) {
587    if let Some(logger) = GLOBAL_LOGGER.get() {
588        if let Ok(logger) = logger.lock() {
589            logger.log_operation(level, component, operation_id, fd, message);
590        }
591    }
592}
593
594/// Log timing using the global logger.
595pub fn log_timing(component: &str, operation: &str, duration: Duration) {
596    if let Some(logger) = GLOBAL_LOGGER.get() {
597        if let Ok(logger) = logger.lock() {
598            logger.log_timing(component, operation, duration);
599        }
600    }
601}
602
603/// Convenience macros for logging.
604///
605/// Log a trace-level message using the global logger.
606#[macro_export]
607macro_rules! log_trace {
608    ($component:expr, $($arg:tt)*) => {
609        $crate::logging::log($crate::logging::LogLevel::Trace, $component, &format!($($arg)*))
610    };
611}
612
613/// Log a debug-level message using the global logger.
614#[macro_export]
615macro_rules! log_debug {
616    ($component:expr, $($arg:tt)*) => {
617        $crate::logging::log($crate::logging::LogLevel::Debug, $component, &format!($($arg)*))
618    };
619}
620
621/// Log an info-level message using the global logger.
622#[macro_export]
623macro_rules! log_info {
624    ($component:expr, $($arg:tt)*) => {
625        $crate::logging::log($crate::logging::LogLevel::Info, $component, &format!($($arg)*))
626    };
627}
628
629/// Log a warning-level message using the global logger.
630#[macro_export]
631macro_rules! log_warn {
632    ($component:expr, $($arg:tt)*) => {
633        $crate::logging::log($crate::logging::LogLevel::Warn, $component, &format!($($arg)*))
634    };
635}
636
637/// Log an error-level message using the global logger.
638#[macro_export]
639macro_rules! log_error {
640    ($component:expr, $($arg:tt)*) => {
641        $crate::logging::log($crate::logging::LogLevel::Error, $component, &format!($($arg)*))
642    };
643}
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648    // Arc import removed as unused
649
650    #[test]
651    fn test_log_entry_creation() {
652        let entry = LogEntry::new(LogLevel::Info, "test", "test message")
653            .with_operation_id(123)
654            .with_fd(4)
655            .with_metadata("key", "value")
656            .with_duration(Duration::from_millis(10));
657
658        assert_eq!(entry.level, LogLevel::Info);
659        assert_eq!(entry.component, "test");
660        assert_eq!(entry.message, "test message");
661        assert_eq!(entry.operation_id, Some(123));
662        assert_eq!(entry.fd, Some(4));
663        assert_eq!(entry.metadata.get("key"), Some(&"value".to_string()));
664        assert_eq!(entry.duration, Some(Duration::from_millis(10)));
665    }
666
667    #[test]
668    fn test_log_entry_formatting() {
669        let entry = LogEntry::new(LogLevel::Info, "test", "test message");
670        let formatted = entry.format();
671
672        assert!(formatted.contains("INFO"));
673        assert!(formatted.contains("test"));
674        assert!(formatted.contains("test message"));
675    }
676
677    #[test]
678    fn test_log_entry_json_formatting() {
679        let entry = LogEntry::new(LogLevel::Info, "test", "test message");
680        let json = entry.format_json();
681
682        assert!(json.contains("\"level\":\"INFO\""));
683        assert!(json.contains("\"component\":\"test\""));
684        assert!(json.contains("\"message\":\"test message\""));
685    }
686
687    #[test]
688    fn test_logger_creation() {
689        let logger = Logger::new();
690        assert_eq!(logger.min_level, LogLevel::Info);
691        assert_eq!(logger.outputs.len(), 1);
692    }
693
694    #[test]
695    fn test_performance_metrics() {
696        let mut metrics = PerformanceMetrics::new();
697
698        metrics.record_operation("read", Duration::from_millis(10));
699        metrics.record_operation("read", Duration::from_millis(20));
700        metrics.record_operation("write", Duration::from_millis(15));
701
702        assert_eq!(metrics.get_count("read"), 2);
703        assert_eq!(metrics.get_count("write"), 1);
704        assert_eq!(metrics.get_total_operations(), 3);
705
706        assert_eq!(
707            metrics.get_average_duration("read"),
708            Some(Duration::from_millis(15))
709        );
710        assert_eq!(
711            metrics.get_min_duration("read"),
712            Some(Duration::from_millis(10))
713        );
714        assert_eq!(
715            metrics.get_max_duration("read"),
716            Some(Duration::from_millis(20))
717        );
718    }
719
720    #[test]
721    fn test_global_logger() {
722        let _logger = init_logger();
723
724        log(LogLevel::Info, "test", "test message");
725        log_operation(LogLevel::Debug, "test", 123, Some(4), "operation message");
726        log_timing("test", "read", Duration::from_millis(10));
727    }
728}