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