termivar_scanner/
logging.rs1use std::fmt;
17use std::time::{SystemTime, UNIX_EPOCH};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
21pub enum LogLevel {
22 Debug,
23 Info,
24 Warn,
25 Error,
26}
27
28impl fmt::Display for LogLevel {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 LogLevel::Debug => write!(f, "DEBUG"),
32 LogLevel::Info => write!(f, "INFO"),
33 LogLevel::Warn => write!(f, "WARN"),
34 LogLevel::Error => write!(f, "ERROR"),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct LogEntry {
42 pub timestamp: u64,
44 pub level: LogLevel,
46 pub phase: Option<u8>,
48 pub message: String,
50 pub context: Option<String>,
52 pub duration_ms: Option<u64>,
54}
55
56impl LogEntry {
57 pub fn new(level: LogLevel, message: String) -> Self {
59 Self {
60 timestamp: current_timestamp_ms(),
61 level,
62 phase: None,
63 message,
64 context: None,
65 duration_ms: None,
66 }
67 }
68
69 pub fn with_phase(mut self, phase: u8) -> Self {
71 self.phase = Some(phase);
72 self
73 }
74
75 pub fn with_context(mut self, context: String) -> Self {
77 self.context = Some(context);
78 self
79 }
80
81 pub fn with_duration(mut self, duration_ms: u64) -> Self {
83 self.duration_ms = Some(duration_ms);
84 self
85 }
86
87 pub fn format(&self) -> String {
89 let mut output = format!("[{}] [{}]", self.timestamp, self.level);
90
91 if let Some(phase) = self.phase {
92 output.push_str(&format!(" [Phase {phase}]"));
93 }
94
95 output.push_str(&format!(" {}", self.message));
96
97 if let Some(ctx) = &self.context {
98 output.push_str(&format!(" | {ctx}"));
99 }
100
101 if let Some(duration) = self.duration_ms {
102 output.push_str(&format!(" | {duration}ms"));
103 }
104
105 output
106 }
107}
108
109impl fmt::Display for LogEntry {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}", self.format())
112 }
113}
114
115pub struct Logger {
117 level: LogLevel,
118}
119
120impl Logger {
121 pub fn new(level: LogLevel) -> Self {
123 Self { level }
124 }
125
126 pub fn log(&self, entry: LogEntry) {
128 if entry.level >= self.level {
129 println!("{}", entry.format());
130 }
131 }
132
133 pub fn debug(&self, message: String) {
135 self.log(LogEntry::new(LogLevel::Debug, message));
136 }
137
138 pub fn info(&self, message: String) {
140 self.log(LogEntry::new(LogLevel::Info, message));
141 }
142
143 pub fn warn(&self, message: String) {
145 self.log(LogEntry::new(LogLevel::Warn, message));
146 }
147
148 pub fn error(&self, message: String) {
150 self.log(LogEntry::new(LogLevel::Error, message));
151 }
152
153 pub fn phase_start(&self, phase: u8, name: &str) {
155 self.log(
156 LogEntry::new(LogLevel::Info, format!("Starting Phase {phase}: {name}"))
157 .with_phase(phase),
158 );
159 }
160
161 pub fn phase_complete(&self, phase: u8, count: usize, duration_ms: u64) {
163 self.log(
164 LogEntry::new(
165 LogLevel::Info,
166 format!("Phase {phase} completed. Found {count} issues."),
167 )
168 .with_phase(phase)
169 .with_duration(duration_ms),
170 );
171 }
172}
173
174impl Default for Logger {
175 fn default() -> Self {
176 Self::new(LogLevel::Info)
177 }
178}
179
180fn current_timestamp_ms() -> u64 {
182 SystemTime::now()
183 .duration_since(UNIX_EPOCH)
184 .map(|d| d.as_millis() as u64)
185 .unwrap_or(0)
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_log_entry_creation() {
194 let entry = LogEntry::new(LogLevel::Info, "Test message".to_string());
195 assert_eq!(entry.level, LogLevel::Info);
196 assert_eq!(entry.message, "Test message");
197 assert!(entry.phase.is_none());
198 }
199
200 #[test]
201 fn test_log_entry_with_context() {
202 let entry = LogEntry::new(LogLevel::Warn, "Warning".to_string())
203 .with_context("https://example.com".to_string());
204 assert_eq!(entry.context, Some("https://example.com".to_string()));
205 }
206
207 #[test]
208 fn test_log_entry_with_phase() {
209 let entry = LogEntry::new(LogLevel::Debug, "Debug message".to_string()).with_phase(5);
210 assert_eq!(entry.phase, Some(5));
211 }
212
213 #[test]
214 fn test_log_entry_with_duration() {
215 let entry = LogEntry::new(LogLevel::Info, "Completed".to_string()).with_duration(1234);
216 assert_eq!(entry.duration_ms, Some(1234));
217 }
218
219 #[test]
220 fn test_log_level_ordering() {
221 assert!(LogLevel::Error > LogLevel::Warn);
222 assert!(LogLevel::Warn > LogLevel::Info);
223 assert!(LogLevel::Info > LogLevel::Debug);
224 }
225
226 #[test]
227 fn test_logger_filtering() {
228 let logger = Logger::new(LogLevel::Warn);
229 logger.debug("Debug message".to_string());
231 logger.info("Info message".to_string());
232 logger.warn("Warning message".to_string());
233 logger.phase_start(3, "bounded review");
234 logger.phase_complete(3, 2, 17);
235 }
236}