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