1use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
17pub enum LogLevel {
18 Debug,
19 Info,
20 Warn,
21 Error,
22}
23
24impl LogLevel {
25 pub fn as_str(&self) -> &'static str {
26 match self {
27 LogLevel::Debug => "DEBUG",
28 LogLevel::Info => "INFO",
29 LogLevel::Warn => "WARN",
30 LogLevel::Error => "ERROR",
31 }
32 }
33}
34
35pub trait Logger: Send + Sync {
36 fn log(&self, level: LogLevel, msg: &str);
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct LogEntry {
41 pub level: LogLevel,
42 pub message: String,
43 pub timestamp: String,
44}
45
46pub struct StructuredLogger {
47 level: LogLevel,
48 entries: Arc<Mutex<Vec<LogEntry>>>,
49}
50
51impl StructuredLogger {
52 pub fn new() -> Self {
53 Self::with_level(LogLevel::Info)
54 }
55
56 pub fn with_level(level: LogLevel) -> Self {
57 Self {
58 level,
59 entries: Arc::new(Mutex::new(vec![])),
60 }
61 }
62
63 pub fn output(&self, msg: &str) {
65 self.log(LogLevel::Info, msg);
66 }
67
68 pub fn entries(&self) -> Vec<LogEntry> {
70 let entries = self.entries.lock().unwrap();
71 entries.iter().cloned().collect()
72 }
73
74 pub fn level(&self) -> LogLevel {
75 self.level
76 }
77
78 pub fn shared_handle(&self) -> Arc<Mutex<Vec<LogEntry>>> {
80 Arc::clone(&self.entries)
81 }
82}
83
84impl Default for StructuredLogger {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Logger for StructuredLogger {
91 fn log(&self, level: LogLevel, msg: &str) {
92 if level < self.level {
94 return;
95 }
96 let timestamp = chrono::Utc::now().to_rfc3339();
97 let entry = LogEntry {
98 level,
99 message: msg.to_string(),
100 timestamp: timestamp.clone(),
101 };
102 {
103 let mut entries = self.entries.lock().unwrap();
104 entries.push(entry);
105 }
106 println!("[{}] {} - {}", level.as_str(), timestamp, msg);
108 }
109}
110
111pub struct LoggerFactory;
113
114impl LoggerFactory {
115 pub fn new() -> Self {
116 Self
117 }
118
119 pub fn create(&self, level: LogLevel) -> StructuredLogger {
120 StructuredLogger::with_level(level)
121 }
122
123 pub fn debug(&self) -> StructuredLogger {
124 self.create(LogLevel::Debug)
125 }
126
127 pub fn info(&self) -> StructuredLogger {
128 self.create(LogLevel::Info)
129 }
130
131 pub fn warn(&self) -> StructuredLogger {
132 self.create(LogLevel::Warn)
133 }
134
135 pub fn error(&self) -> StructuredLogger {
136 self.create(LogLevel::Error)
137 }
138}
139
140impl Default for LoggerFactory {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct MetricsSnapshot {
148 pub counters: HashMap<String, u64>,
149 pub gauges: HashMap<String, f64>,
150}
151
152pub struct Metrics {
153 counters: HashMap<String, u64>,
154 gauges: HashMap<String, f64>,
155}
156
157impl Metrics {
158 pub fn new() -> Self {
159 Self {
160 counters: HashMap::new(),
161 gauges: HashMap::new(),
162 }
163 }
164
165 pub fn increment_counter(&mut self, name: &str) {
166 *self.counters.entry(name.to_string()).or_insert(0) += 1;
167 }
168
169 pub fn add_counter(&mut self, name: &str, value: u64) {
170 *self.counters.entry(name.to_string()).or_insert(0) += value;
171 }
172
173 pub fn set_gauge(&mut self, name: &str, value: f64) {
174 self.gauges.insert(name.to_string(), value);
175 }
176
177 pub fn get_counter(&self, name: &str) -> Option<u64> {
178 self.counters.get(name).copied()
179 }
180
181 pub fn get_gauge(&self, name: &str) -> Option<f64> {
182 self.gauges.get(name).copied()
183 }
184
185 pub fn snapshot(&self) -> MetricsSnapshot {
186 MetricsSnapshot {
187 counters: self.counters.clone(),
188 gauges: self.gauges.clone(),
189 }
190 }
191}
192
193impl Default for Metrics {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_log_level_ordering() {
205 assert!(LogLevel::Debug < LogLevel::Info);
207 assert!(LogLevel::Info < LogLevel::Warn);
208 assert!(LogLevel::Warn < LogLevel::Error);
209 }
210
211 #[test]
212 fn test_logger_default_level_info_filters_debug() {
213 let l = StructuredLogger::new(); l.log(LogLevel::Debug, "debug msg");
215 l.log(LogLevel::Info, "info msg");
216 let entries = l.entries();
217 assert_eq!(entries.len(), 1, "debug should be filtered out");
218 assert_eq!(entries[0].message, "info msg");
219 assert_eq!(entries[0].level, LogLevel::Info);
220 assert!(!entries[0].timestamp.is_empty());
222 assert!(entries[0].timestamp.contains('T'));
223 }
224
225 #[test]
226 fn test_logger_with_level_warn_filters_info() {
227 let l = StructuredLogger::with_level(LogLevel::Warn);
228 l.log(LogLevel::Debug, "debug msg");
229 l.log(LogLevel::Info, "info msg");
230 l.log(LogLevel::Warn, "warn msg");
231 l.log(LogLevel::Error, "error msg");
232 let entries = l.entries();
233 assert_eq!(entries.len(), 2, "only Warn and Error should pass");
234 assert!(entries.iter().all(|e| e.level >= LogLevel::Warn));
235 assert!(entries.iter().any(|e| e.message == "warn msg"));
236 assert!(entries.iter().any(|e| e.message == "error msg"));
237 }
238
239 #[test]
240 fn test_logger_with_level_error_only_error() {
241 let l = StructuredLogger::with_level(LogLevel::Error);
242 l.log(LogLevel::Warn, "warn msg");
243 l.log(LogLevel::Error, "error msg");
244 let entries = l.entries();
245 assert_eq!(entries.len(), 1);
246 assert_eq!(entries[0].level, LogLevel::Error);
247 }
248
249 #[test]
250 fn test_logger_with_level_debug_passes_everything() {
251 let l = StructuredLogger::with_level(LogLevel::Debug);
252 l.log(LogLevel::Debug, "d");
253 l.log(LogLevel::Info, "i");
254 l.log(LogLevel::Warn, "w");
255 l.log(LogLevel::Error, "e");
256 assert_eq!(l.entries().len(), 4);
257 }
258
259 #[test]
260 fn test_logger_factory_creates_loggers_with_different_levels() {
261 let factory = LoggerFactory::new();
262 let debug_logger = factory.debug();
263 let error_logger = factory.error();
264 debug_logger.log(LogLevel::Debug, "debug");
265 debug_logger.log(LogLevel::Info, "info");
266 error_logger.log(LogLevel::Info, "should be filtered");
267 error_logger.log(LogLevel::Error, "error");
268 assert_eq!(debug_logger.entries().len(), 2);
269 assert_eq!(error_logger.entries().len(), 1);
270 assert_eq!(error_logger.entries()[0].message, "error");
271 }
272
273 #[test]
274 fn test_logger_factory_default_creates_info_logger() {
275 let factory = LoggerFactory;
276 let logger = factory.info();
277 logger.log(LogLevel::Debug, "should be filtered");
278 logger.log(LogLevel::Info, "should pass");
279 assert_eq!(logger.entries().len(), 1);
280 }
281
282 #[test]
283 fn test_logger_output_method_logs_at_info() {
284 let l = StructuredLogger::with_level(LogLevel::Debug);
285 l.output("hello");
286 let entries = l.entries();
287 assert_eq!(entries.len(), 1);
288 assert_eq!(entries[0].level, LogLevel::Info);
289 assert_eq!(entries[0].message, "hello");
290 }
291
292 #[test]
293 fn test_metrics_increment_and_read() {
294 let mut m = Metrics::new();
295 m.increment_counter("req");
296 m.increment_counter("req");
297 m.increment_counter("err");
298 assert_eq!(m.get_counter("req"), Some(2));
299 assert_eq!(m.get_counter("err"), Some(1));
300 assert_eq!(m.get_counter("missing"), None);
301 }
302
303 #[test]
304 fn test_metrics_add_counter() {
305 let mut m = Metrics::new();
306 m.add_counter("bytes", 100);
307 m.add_counter("bytes", 50);
308 assert_eq!(m.get_counter("bytes"), Some(150));
309 }
310
311 #[test]
312 fn test_metrics_gauge_overwrites() {
313 let mut m = Metrics::new();
314 m.set_gauge("cpu", 0.5);
315 assert_eq!(m.get_gauge("cpu"), Some(0.5));
316 m.set_gauge("cpu", 0.8); assert_eq!(m.get_gauge("cpu"), Some(0.8));
318 assert_eq!(m.get_gauge("missing"), None);
319 }
320
321 #[test]
322 fn test_metrics_snapshot_captures_state() {
323 let mut m = Metrics::new();
324 m.increment_counter("a");
325 m.increment_counter("a");
326 m.increment_counter("b");
327 m.set_gauge("g1", 1.0);
328 m.set_gauge("g2", 2.5);
329 let snap = m.snapshot();
330 assert_eq!(snap.counters.get("a"), Some(&2));
331 assert_eq!(snap.counters.get("b"), Some(&1));
332 assert_eq!(snap.gauges.get("g1"), Some(&1.0));
333 assert_eq!(snap.gauges.get("g2"), Some(&2.5));
334 m.increment_counter("a");
336 assert_eq!(snap.counters.get("a"), Some(&2));
337 assert_eq!(m.get_counter("a"), Some(3));
338 }
339
340 #[test]
341 fn test_log_entry_has_timestamp_and_level() {
342 let l = StructuredLogger::with_level(LogLevel::Debug);
343 l.log(LogLevel::Warn, "warning");
344 let e = &l.entries()[0];
345 assert_eq!(e.level, LogLevel::Warn);
346 assert_eq!(e.message, "warning");
347 assert!(e.timestamp.contains('T'));
349 assert!(e.timestamp.ends_with('Z') || e.timestamp.contains('+'));
350 }
351}