Skip to main content

sz_orm_logger/
lib.rs

1//! # SZ-ORM Logger — 结构化日志
2//!
3//! 提供多级别(Debug/Info/Warn/Error)、多输出目标的日志记录,支持异步写入与
4//! 结构化字段,可组合多个 Logger 实现输出到不同后端。
5//!
6//! ## 主要类型
7//!
8//! - [`Logger`] trait — 日志器接口
9//! - [`LogLevel`] — 日志级别
10//! - [`LogEntry`] — 日志条目
11//!
12//! ## 高级日志功能(`advanced` 模块)
13//!
14//! - [`advanced::LogRotator`] — 日志轮转(按大小/时间)
15//! - [`advanced::MultiOutputLogger`] / [`advanced::LogSink`] — 多输出扇出
16//! - [`advanced::LevelFilter`] — 按 target 细粒度级别过滤
17//! - [`advanced::StructuredLogEntry`] / [`advanced::StructuredLogWriter`] — 结构化字段
18
19pub 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    /// Convenience method equivalent to `log(LogLevel::Info, msg)`.
74    pub fn output(&self, msg: &str) {
75        self.log(LogLevel::Info, msg);
76    }
77
78    /// Return a snapshot of all log entries that passed the level filter.
79    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    /// Internal shared handle so multiple loggers can write to the same sink.
89    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        // Filter: anything strictly below the configured level is dropped.
103        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        // Also emit to stdout for runtime observability, with level + timestamp.
117        println!("[{}] {} - {}", level.as_str(), timestamp, msg);
118    }
119}
120
121/// Factory that creates loggers with different configurations.
122pub 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        // Verifies the PartialOrd derivation: Debug < Info < Warn < Error
216        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(); // default Info
224        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        // Timestamp should be a non-empty RFC3339 string
231        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); // overwrite
327        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        // Snapshot is independent of subsequent changes
345        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        // RFC3339 timestamps contain 'T' separator and 'Z' for UTC
358        assert!(e.timestamp.contains('T'));
359        assert!(e.timestamp.ends_with('Z') || e.timestamp.contains('+'));
360    }
361}