Skip to main content

sz_orm_logger/
advanced.rs

1//! 高级日志功能:日志轮转、多输出、级别过滤、结构化字段
2//!
3//! 本模块在 [`StructuredLogger`] 基础上补充生产级日志所需的核心能力:
4//!
5//! - **日志轮转**([`LogRotator`]):按大小或时间自动轮转日志缓冲,
6//!   保留最近 N 份历史日志,防止单一日志无限增长。
7//! - **多输出**([`MultiOutputLogger`] / [`LogSink`]):将日志扇出到多个
8//!   后端(内存、控制台、回调),便于同时写入文件、终端与远程采集器。
9//! - **级别过滤**([`LevelFilter`]):在全局级别之上支持按 target(模块名)
10//!   细粒度过滤,例如全局 Info 但 `database` 模块开 Debug。
11//! - **结构化字段**([`StructuredFields`] / [`StructuredLogEntry`]):
12//!   在消息之外附加键值对字段,便于日志聚合系统(ELK/Loki)索引与查询。
13
14use crate::{LogEntry, LogLevel, Logger};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::sync::Arc;
18use parking_lot::Mutex;
19use std::time::{Duration, Instant};
20
21// ============================================================================
22// 日志轮转
23// ============================================================================
24
25/// 日志轮转策略
26#[derive(Debug, Clone)]
27pub enum RotationPolicy {
28    /// 按字节大小轮转:当前日志达到 `max_bytes` 时轮转
29    Size(u64),
30    /// 按时间轮转:当前日志存活超过 `max_age` 时轮转
31    Time(Duration),
32    /// 大小或时间任一条件满足即轮转
33    SizeOrTime(u64, Duration),
34}
35
36impl RotationPolicy {
37    /// 判断当前日志是否应该轮转
38    ///
39    /// # 参数
40    /// - `current_size`:当前日志字节数
41    /// - `elapsed`:当前日志已存活时长
42    fn should_rotate(&self, current_size: u64, elapsed: Duration) -> bool {
43        match self {
44            RotationPolicy::Size(max_bytes) => current_size >= *max_bytes,
45            RotationPolicy::Time(max_age) => elapsed >= *max_age,
46            RotationPolicy::SizeOrTime(max_bytes, max_age) => {
47                current_size >= *max_bytes || elapsed >= *max_age
48            }
49        }
50    }
51}
52
53/// 日志轮转器(内存模拟)。
54///
55/// 维护一个当前日志缓冲与一组已轮转的历史日志。当当前缓冲满足轮转策略时,
56/// 将其移入历史列表并开启新的空缓冲。历史列表长度不超过 `max_files`,
57/// 超出时丢弃最旧的。
58///
59/// 实际生产环境中,轮转器会将缓冲刷盘到文件(如 `app.log` -> `app.log.1`),
60/// 此处用内存 `Vec<u8>` 模拟以便测试。
61pub struct LogRotator {
62    /// 轮转策略
63    policy: RotationPolicy,
64    /// 保留的最大历史文件数
65    max_files: usize,
66    /// 当前日志缓冲
67    current: Mutex<Vec<u8>>,
68    /// 已轮转的历史日志(按时间倒序,索引 0 为最近一次轮转)
69    rotated: Mutex<Vec<Vec<u8>>>,
70    /// 当前缓冲的开始时间
71    started_at: Mutex<Instant>,
72    /// 总轮转次数
73    rotation_count: Mutex<u64>,
74}
75
76impl LogRotator {
77    /// 创建轮转器
78    ///
79    /// # 参数
80    /// - `policy`:轮转策略
81    /// - `max_files`:保留的最大历史文件数(0 表示不保留)
82    pub fn new(policy: RotationPolicy, max_files: usize) -> Self {
83        Self {
84            policy,
85            max_files,
86            current: Mutex::new(Vec::new()),
87            rotated: Mutex::new(Vec::new()),
88            started_at: Mutex::new(Instant::now()),
89            rotation_count: Mutex::new(0),
90        }
91    }
92
93    /// 写入一条日志(字节形式)。写入后自动检查是否需要轮转。
94    pub fn write(&self, data: &[u8]) {
95        let should_rotate = {
96            let mut current = self.current.lock();
97            current.extend_from_slice(data);
98            let started_at = self.started_at.lock();
99            self.policy
100                .should_rotate(current.len() as u64, started_at.elapsed())
101        };
102
103        if should_rotate {
104            self.rotate();
105        }
106    }
107
108    /// 手动触发轮转:将当前缓冲移入历史列表,开启新的空缓冲。
109    pub fn rotate(&self) {
110        let mut current = self.current.lock();
111        let mut rotated = self.rotated.lock();
112        let mut started_at = self.started_at.lock();
113        let mut count = self.rotation_count.lock();
114
115        // 将当前缓冲移入历史列表头部
116        let old_buffer = std::mem::take(&mut *current);
117        if !old_buffer.is_empty() {
118            rotated.insert(0, old_buffer);
119        }
120
121        // 超出最大保留数时丢弃最旧的
122        while rotated.len() > self.max_files {
123            rotated.pop();
124        }
125
126        // 重置当前缓冲的开始时间
127        *started_at = Instant::now();
128        *count += 1;
129    }
130
131    /// 获取当前缓冲的字节大小
132    pub fn current_size(&self) -> usize {
133        self.current.lock().len()
134    }
135
136    /// 获取已轮转的历史日志数量
137    pub fn rotated_count(&self) -> usize {
138        self.rotated.lock().len()
139    }
140
141    /// 获取总轮转次数(含因 max_files 限制被丢弃的)
142    pub fn total_rotations(&self) -> u64 {
143        *self.rotation_count.lock()
144    }
145
146    /// 获取当前缓冲内容的快照(拷贝)
147    pub fn current_content(&self) -> Vec<u8> {
148        self.current.lock().clone()
149    }
150
151    /// 获取第 `index` 个历史日志的快照(0 = 最近一次轮转)
152    pub fn rotated_content(&self, index: usize) -> Option<Vec<u8>> {
153        let rotated = self.rotated.lock();
154        rotated.get(index).cloned()
155    }
156
157    /// 获取当前缓冲已存活的时长
158    pub fn current_age(&self) -> Duration {
159        self.started_at.lock().elapsed()
160    }
161
162    /// 获取轮转策略引用
163    pub fn policy(&self) -> &RotationPolicy {
164        &self.policy
165    }
166
167    /// 获取最大保留文件数
168    pub fn max_files(&self) -> usize {
169        self.max_files
170    }
171
172    /// 清空所有日志(当前缓冲 + 历史)
173    pub fn clear(&self) {
174        self.current.lock().clear();
175        self.rotated.lock().clear();
176        *self.started_at.lock() = Instant::now();
177    }
178}
179
180// ============================================================================
181// 多输出(LogSink / MultiOutputLogger)
182// ============================================================================
183
184/// 日志输出目标 trait:将 [`LogEntry`] 写入某个后端。
185///
186/// 实现方需保证 `write` 不 panic(lock poisoned 时降级处理),
187/// 以免一个 sink 故障影响其他 sink 的日志写入。
188pub trait LogSink: Send + Sync {
189    /// 写入一条日志条目
190    fn write(&self, entry: &LogEntry);
191    /// sink 名称(用于调试与统计)
192    fn name(&self) -> &str;
193}
194
195/// 内存日志 sink:将日志条目存入内存 Vec,便于测试验证。
196pub struct MemorySink {
197    name: String,
198    entries: Mutex<Vec<LogEntry>>,
199}
200
201impl MemorySink {
202    pub fn new(name: impl Into<String>) -> Self {
203        Self {
204            name: name.into(),
205            entries: Mutex::new(Vec::new()),
206        }
207    }
208
209    /// 获取已存储的日志条目快照
210    pub fn entries(&self) -> Vec<LogEntry> {
211        self.entries.lock().clone()
212    }
213
214    /// 获取已存储的日志条目数量
215    pub fn len(&self) -> usize {
216        self.entries.lock().len()
217    }
218
219    /// 是否为空
220    pub fn is_empty(&self) -> bool {
221        self.entries.lock().is_empty()
222    }
223
224    /// 清空存储的日志
225    pub fn clear(&self) {
226        self.entries.lock().clear();
227    }
228}
229
230impl LogSink for MemorySink {
231    fn write(&self, entry: &LogEntry) {
232        // lock poisoned 时跳过写入而非 panic
233        let mut entries = self.entries.lock();
234            entries.push(entry.clone());
235    }
236
237    fn name(&self) -> &str {
238        &self.name
239    }
240}
241
242/// 控制台日志 sink:将日志输出到 stdout。
243///
244/// 不持有状态,输出格式为 `[LEVEL] timestamp - message`。
245pub struct ConsoleSink {
246    name: String,
247}
248
249impl ConsoleSink {
250    pub fn new() -> Self {
251        Self {
252            name: "console".to_string(),
253        }
254    }
255
256    pub fn with_name(name: impl Into<String>) -> Self {
257        Self { name: name.into() }
258    }
259}
260
261impl Default for ConsoleSink {
262    fn default() -> Self {
263        Self::new()
264    }
265}
266
267impl LogSink for ConsoleSink {
268    fn write(&self, entry: &LogEntry) {
269        println!(
270            "[{}] {} - {}",
271            entry.level.as_str(),
272            entry.timestamp,
273            entry.message
274        );
275    }
276
277    fn name(&self) -> &str {
278        &self.name
279    }
280}
281
282/// 回调日志 sink:将日志通过闭包传递给调用方。
283///
284/// 适用于将日志桥接到外部日志框架(如 `log` crate、`tracing`)。
285/// 闭包必须实现 `Send + Sync`。
286pub struct CallbackSink<F>
287where
288    F: Fn(&LogEntry) + Send + Sync,
289{
290    name: String,
291    callback: F,
292}
293
294impl<F> CallbackSink<F>
295where
296    F: Fn(&LogEntry) + Send + Sync,
297{
298    pub fn new(name: impl Into<String>, callback: F) -> Self {
299        Self {
300            name: name.into(),
301            callback,
302        }
303    }
304}
305
306impl<F> LogSink for CallbackSink<F>
307where
308    F: Fn(&LogEntry) + Send + Sync,
309{
310    fn write(&self, entry: &LogEntry) {
311        (self.callback)(entry);
312    }
313
314    fn name(&self) -> &str {
315        &self.name
316    }
317}
318
319/// 多输出日志器:将日志扇出到多个 [`LogSink`]。
320///
321/// 每条日志会依次写入所有已注册的 sink。单个 sink 写入失败(panic)
322/// 不会影响其他 sink,因为 [`LogSink::write`] 要求实现方自行降级处理。
323pub struct MultiOutputLogger {
324    level: LogLevel,
325    sinks: Vec<Arc<dyn LogSink>>,
326}
327
328impl MultiOutputLogger {
329    /// 创建多输出日志器
330    pub fn new(level: LogLevel) -> Self {
331        Self {
332            level,
333            sinks: Vec::new(),
334        }
335    }
336
337    /// 添加一个输出 sink
338    pub fn add_sink(&mut self, sink: Arc<dyn LogSink>) -> &mut Self {
339        self.sinks.push(sink);
340        self
341    }
342
343    /// 获取已注册的 sink 名称列表
344    pub fn sink_names(&self) -> Vec<String> {
345        self.sinks.iter().map(|s| s.name().to_string()).collect()
346    }
347
348    /// 获取已注册的 sink 数量
349    pub fn sink_count(&self) -> usize {
350        self.sinks.len()
351    }
352
353    /// 获取当前日志级别
354    pub fn level(&self) -> LogLevel {
355        self.level
356    }
357}
358
359impl Logger for MultiOutputLogger {
360    fn log(&self, level: LogLevel, msg: &str) {
361        if level < self.level {
362            return;
363        }
364        let entry = LogEntry {
365            level,
366            message: msg.to_string(),
367            timestamp: chrono::Utc::now().to_rfc3339(),
368        };
369        for sink in &self.sinks {
370            sink.write(&entry);
371        }
372    }
373}
374
375// ============================================================================
376// 级别过滤(LevelFilter)
377// ============================================================================
378
379/// 级别过滤器:支持全局级别 + 按 target 细粒度级别。
380///
381/// `target` 是日志的模块/组件名(如 `"database"`、`"http"`、`"cache"`)。
382/// 当 target 未在 `target_levels` 中注册时,使用 `default_level`。
383///
384/// # 示例
385///
386/// ```
387/// use sz_orm_logger::{LogLevel, advanced::LevelFilter};
388///
389/// let filter = LevelFilter::new(LogLevel::Info)
390///     .with_target_level("database", LogLevel::Debug)
391///     .with_target_level("http", LogLevel::Warn);
392///
393/// // 全局 Info:Debug 被过滤
394/// assert!(!filter.should_log("app", LogLevel::Debug));
395/// assert!(filter.should_log("app", LogLevel::Info));
396///
397/// // database 模块开 Debug
398/// assert!(filter.should_log("database", LogLevel::Debug));
399///
400/// // http 模块只看 Warn 及以上
401/// assert!(!filter.should_log("http", LogLevel::Info));
402/// assert!(filter.should_log("http", LogLevel::Warn));
403/// ```
404#[derive(Debug, Clone)]
405pub struct LevelFilter {
406    /// 默认级别(未注册 target 使用此级别)
407    default_level: LogLevel,
408    /// 按 target 名设置的级别
409    target_levels: HashMap<String, LogLevel>,
410}
411
412impl LevelFilter {
413    /// 创建级别过滤器,默认级别为 `default_level`
414    pub fn new(default_level: LogLevel) -> Self {
415        Self {
416            default_level,
417            target_levels: HashMap::new(),
418        }
419    }
420
421    /// 为指定 target 设置日志级别
422    pub fn with_target_level(mut self, target: impl Into<String>, level: LogLevel) -> Self {
423        self.target_levels.insert(target.into(), level);
424        self
425    }
426
427    /// 移除指定 target 的级别覆盖,回退到 `default_level`
428    pub fn remove_target(&mut self, target: &str) -> Option<LogLevel> {
429        self.target_levels.remove(target)
430    }
431
432    /// 获取指定 target 的日志级别(优先 target_levels,其次 default_level)
433    pub fn level_for(&self, target: &str) -> LogLevel {
434        self.target_levels
435            .get(target)
436            .copied()
437            .unwrap_or(self.default_level)
438    }
439
440    /// 获取默认级别
441    pub fn default_level(&self) -> LogLevel {
442        self.default_level
443    }
444
445    /// 设置默认级别
446    pub fn set_default_level(&mut self, level: LogLevel) {
447        self.default_level = level;
448    }
449
450    /// 判断指定 target 的日志是否应该被记录
451    pub fn should_log(&self, target: &str, level: LogLevel) -> bool {
452        level >= self.level_for(target)
453    }
454
455    /// 获取已注册的 target 数量
456    pub fn target_count(&self) -> usize {
457        self.target_levels.len()
458    }
459
460    /// 获取所有已注册的 target 名称
461    pub fn targets(&self) -> Vec<String> {
462        self.target_levels.keys().cloned().collect()
463    }
464}
465
466impl Default for LevelFilter {
467    fn default() -> Self {
468        Self::new(LogLevel::Info)
469    }
470}
471
472// ============================================================================
473// 结构化字段(StructuredFields / StructuredLogEntry)
474// ============================================================================
475
476/// 结构化日志字段:键值对集合。
477///
478/// 用于在日志消息之外附加可被日志聚合系统索引的结构化数据,
479/// 如 `user_id=12345`、`request_id=abc`、`latency_ms=42`。
480pub type StructuredFields = HashMap<String, String>;
481
482/// 带结构化字段与 target 的日志条目。
483///
484/// 相比 [`LogEntry`],增加了:
485/// - `target`:日志来源模块名(用于 [`LevelFilter`] 过滤)
486/// - `fields`:结构化键值对字段
487#[derive(Debug, Clone, Serialize, Deserialize)]
488pub struct StructuredLogEntry {
489    pub level: LogLevel,
490    pub message: String,
491    pub timestamp: String,
492    pub target: Option<String>,
493    pub fields: StructuredFields,
494}
495
496impl StructuredLogEntry {
497    /// 创建结构化日志条目
498    pub fn new(level: LogLevel, message: impl Into<String>) -> Self {
499        Self {
500            level,
501            message: message.into(),
502            timestamp: chrono::Utc::now().to_rfc3339(),
503            target: None,
504            fields: HashMap::new(),
505        }
506    }
507
508    /// 设置 target(模块名)
509    pub fn with_target(mut self, target: impl Into<String>) -> Self {
510        self.target = Some(target.into());
511        self
512    }
513
514    /// 添加一个结构化字段
515    pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
516        self.fields.insert(key.into(), value.into());
517        self
518    }
519
520    /// 从普通 [`LogEntry`] 转换(target 和 fields 为空)
521    pub fn from_log_entry(entry: &LogEntry) -> Self {
522        Self {
523            level: entry.level,
524            message: entry.message.clone(),
525            timestamp: entry.timestamp.clone(),
526            target: None,
527            fields: HashMap::new(),
528        }
529    }
530
531    /// 转换为普通 [`LogEntry`](丢弃 target 和 fields)
532    pub fn to_log_entry(&self) -> LogEntry {
533        LogEntry {
534            level: self.level,
535            message: self.message.clone(),
536            timestamp: self.timestamp.clone(),
537        }
538    }
539
540    /// 序列化为 JSON 字符串(便于写入文件或发送到远程)
541    pub fn to_json(&self) -> Result<String, serde_json::Error> {
542        serde_json::to_string(self)
543    }
544
545    /// 从 JSON 字符串反序列化
546    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
547        serde_json::from_str(json)
548    }
549
550    /// 将结构化字段格式化为 `key=value` 列表(用于日志文本输出)
551    pub fn format_fields(&self) -> String {
552        let mut pairs: Vec<String> = self
553            .fields
554            .iter()
555            .map(|(k, v)| format!("{}={}", k, v))
556            .collect();
557        pairs.sort(); // 按字母序排列,保证输出确定性
558        pairs.join(" ")
559    }
560}
561
562/// 结构化日志器:支持结构化字段、target 过滤与多 sink 输出。
563///
564/// 结合 [`LevelFilter`] 与 [`LogSink`],提供生产级日志能力:
565/// 1. 按 target 过滤日志级别
566/// 2. 记录结构化字段
567/// 3. 扇出到多个输出后端
568pub struct StructuredLogWriter {
569    filter: LevelFilter,
570    sinks: Vec<Arc<dyn StructuredSink>>,
571}
572
573/// 结构化日志 sink:接收 [`StructuredLogEntry`]。
574///
575/// 与 [`LogSink`] 的区别在于接收结构化条目,可以索引 fields。
576pub trait StructuredSink: Send + Sync {
577    fn write(&self, entry: &StructuredLogEntry);
578    fn name(&self) -> &str;
579}
580
581/// 内存结构化 sink:存储 [`StructuredLogEntry`]。
582pub struct MemoryStructuredSink {
583    name: String,
584    entries: Mutex<Vec<StructuredLogEntry>>,
585}
586
587impl MemoryStructuredSink {
588    pub fn new(name: impl Into<String>) -> Self {
589        Self {
590            name: name.into(),
591            entries: Mutex::new(Vec::new()),
592        }
593    }
594
595    pub fn entries(&self) -> Vec<StructuredLogEntry> {
596        self.entries.lock().clone()
597    }
598
599    pub fn len(&self) -> usize {
600        self.entries.lock().len()
601    }
602
603    pub fn is_empty(&self) -> bool {
604        self.entries.lock().is_empty()
605    }
606
607    pub fn clear(&self) {
608        self.entries.lock().clear();
609    }
610}
611
612impl StructuredSink for MemoryStructuredSink {
613    fn write(&self, entry: &StructuredLogEntry) {
614        let mut entries = self.entries.lock();
615            entries.push(entry.clone());
616    }
617
618    fn name(&self) -> &str {
619        &self.name
620    }
621}
622
623impl StructuredLogWriter {
624    /// 创建结构化日志写入器
625    pub fn new(filter: LevelFilter) -> Self {
626        Self {
627            filter,
628            sinks: Vec::new(),
629        }
630    }
631
632    /// 添加结构化 sink
633    pub fn add_sink(&mut self, sink: Arc<dyn StructuredSink>) -> &mut Self {
634        self.sinks.push(sink);
635        self
636    }
637
638    /// 获取 sink 数量
639    pub fn sink_count(&self) -> usize {
640        self.sinks.len()
641    }
642
643    /// 获取 sink 名称列表
644    pub fn sink_names(&self) -> Vec<String> {
645        self.sinks.iter().map(|s| s.name().to_string()).collect()
646    }
647
648    /// 获取级别过滤器引用
649    pub fn filter(&self) -> &LevelFilter {
650        &self.filter
651    }
652
653    /// 获取级别过滤器可变引用
654    pub fn filter_mut(&mut self) -> &mut LevelFilter {
655        &mut self.filter
656    }
657
658    /// 记录一条结构化日志。
659    ///
660    /// 根据 target 通过 [`LevelFilter`] 过滤后扇出到所有 sink。
661    /// target 为 `None` 时使用默认级别。
662    pub fn log(&self, entry: &StructuredLogEntry) {
663        let target = entry.target.as_deref().unwrap_or("");
664        if !self.filter.should_log(target, entry.level) {
665            return;
666        }
667        for sink in &self.sinks {
668            sink.write(entry);
669        }
670    }
671
672    /// 便捷方法:记录一条带 target 和字段的日志
673    pub fn log_with_fields(
674        &self,
675        target: impl Into<String>,
676        level: LogLevel,
677        message: impl Into<String>,
678        fields: StructuredFields,
679    ) {
680        let entry = StructuredLogEntry::new(level, message)
681            .with_target(target)
682            .with_fields(fields);
683        self.log(&entry);
684    }
685}
686
687// 为 StructuredLogEntry 批量设置 fields 的扩展方法
688impl StructuredLogEntry {
689    /// 批量设置结构化字段
690    pub fn with_fields(mut self, fields: StructuredFields) -> Self {
691        self.fields.extend(fields);
692        self
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use super::*;
699    use std::thread;
700    use std::time::Duration;
701
702    // ===================== RotationPolicy 测试 =====================
703
704    #[test]
705    fn test_rotation_policy_size_met() {
706        let policy = RotationPolicy::Size(100);
707        assert!(policy.should_rotate(100, Duration::from_secs(0)));
708        assert!(policy.should_rotate(101, Duration::from_secs(0)));
709        assert!(!policy.should_rotate(99, Duration::from_secs(0)));
710    }
711
712    #[test]
713    fn test_rotation_policy_time_met() {
714        let policy = RotationPolicy::Time(Duration::from_secs(60));
715        assert!(policy.should_rotate(0, Duration::from_secs(60)));
716        assert!(policy.should_rotate(0, Duration::from_secs(61)));
717        assert!(!policy.should_rotate(0, Duration::from_secs(59)));
718    }
719
720    #[test]
721    fn test_rotation_policy_size_or_time_either() {
722        let policy = RotationPolicy::SizeOrTime(100, Duration::from_secs(60));
723        // 大小满足
724        assert!(policy.should_rotate(100, Duration::from_secs(0)));
725        // 时间满足
726        assert!(policy.should_rotate(0, Duration::from_secs(60)));
727        // 都不满足
728        assert!(!policy.should_rotate(99, Duration::from_secs(59)));
729        // 都满足
730        assert!(policy.should_rotate(200, Duration::from_secs(120)));
731    }
732
733    // ===================== LogRotator 测试 =====================
734
735    #[test]
736    fn test_log_rotator_new_empty() {
737        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
738        assert_eq!(rotator.current_size(), 0);
739        assert_eq!(rotator.rotated_count(), 0);
740        assert_eq!(rotator.total_rotations(), 0);
741        assert_eq!(rotator.max_files(), 3);
742    }
743
744    #[test]
745    fn test_log_rotator_write_accumulates() {
746        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
747        rotator.write(b"hello");
748        rotator.write(b" world");
749        assert_eq!(rotator.current_size(), 11);
750        assert_eq!(rotator.current_content(), b"hello world");
751    }
752
753    #[test]
754    fn test_log_rotator_size_triggers_rotation() {
755        let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
756        rotator.write(b"12345"); // 5 bytes
757        assert_eq!(rotator.rotated_count(), 0);
758        rotator.write(b"67890"); // 10 bytes -> triggers rotation
759        assert_eq!(rotator.rotated_count(), 1);
760        assert_eq!(rotator.total_rotations(), 1);
761        assert_eq!(rotator.current_size(), 0);
762    }
763
764    #[test]
765    fn test_log_rotator_rotation_preserves_content() {
766        let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
767        rotator.write(b"hello world"); // 11 bytes -> triggers rotation
768        assert_eq!(rotator.rotated_count(), 1);
769        let rotated = rotator.rotated_content(0).expect("rotated[0] must exist");
770        assert_eq!(rotated, b"hello world");
771    }
772
773    #[test]
774    fn test_log_rotator_max_files_drops_oldest() {
775        let rotator = LogRotator::new(RotationPolicy::Size(5), 2);
776        rotator.write(b"AAAAAA"); // rotate 1
777        rotator.write(b"BBBBBB"); // rotate 2
778        rotator.write(b"CCCCCC"); // rotate 3 -> drops oldest (AAAAAA)
779        assert_eq!(rotator.rotated_count(), 2);
780        assert_eq!(rotator.total_rotations(), 3);
781        // rotated[0] = most recent = CCCCCC, rotated[1] = BBBBBB
782        assert_eq!(rotator.rotated_content(0), Some(b"CCCCCC".to_vec()));
783        assert_eq!(rotator.rotated_content(1), Some(b"BBBBBB".to_vec()));
784        assert_eq!(rotator.rotated_content(2), None);
785    }
786
787    #[test]
788    fn test_log_rotator_manual_rotate() {
789        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
790        rotator.write(b"some data");
791        rotator.rotate();
792        assert_eq!(rotator.rotated_count(), 1);
793        assert_eq!(rotator.current_size(), 0);
794    }
795
796    #[test]
797    fn test_log_rotator_manual_rotate_empty_no_op() {
798        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
799        rotator.rotate(); // empty buffer -> no rotation
800        assert_eq!(rotator.rotated_count(), 0);
801        assert_eq!(rotator.total_rotations(), 1); // count still increments
802    }
803
804    #[test]
805    fn test_log_rotator_clear() {
806        let rotator = LogRotator::new(RotationPolicy::Size(5), 3);
807        rotator.write(b"hello world"); // triggers rotation
808        rotator.write(b"more");
809        assert!(!rotator.current_content().is_empty());
810        assert_eq!(rotator.rotated_count(), 1);
811
812        rotator.clear();
813        assert_eq!(rotator.current_size(), 0);
814        assert_eq!(rotator.rotated_count(), 0);
815    }
816
817    #[test]
818    fn test_log_rotator_policy_accessor() {
819        let rotator = LogRotator::new(RotationPolicy::Size(256), 5);
820        match rotator.policy() {
821            RotationPolicy::Size(n) => assert_eq!(*n, 256),
822            _ => panic!("expected Size policy"),
823        }
824    }
825
826    #[test]
827    fn test_log_rotator_time_based_rotation() {
828        let rotator = LogRotator::new(RotationPolicy::Time(Duration::from_millis(50)), 3);
829        rotator.write(b"data");
830        assert_eq!(rotator.rotated_count(), 0);
831        thread::sleep(Duration::from_millis(60));
832        rotator.write(b"more"); // triggers time-based rotation
833        assert_eq!(rotator.rotated_count(), 1);
834    }
835
836    #[test]
837    fn test_log_rotator_current_age_increases() {
838        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
839        let age1 = rotator.current_age();
840        thread::sleep(Duration::from_millis(10));
841        let age2 = rotator.current_age();
842        assert!(age2 > age1);
843    }
844
845    // ===================== MemorySink 测试 =====================
846
847    #[test]
848    fn test_memory_sink_new_empty() {
849        let sink = MemorySink::new("test");
850        assert_eq!(sink.name(), "test");
851        assert!(sink.is_empty());
852        assert_eq!(sink.len(), 0);
853    }
854
855    #[test]
856    fn test_memory_sink_write_stores_entry() {
857        let sink = MemorySink::new("mem");
858        let entry = LogEntry {
859            level: LogLevel::Info,
860            message: "hello".to_string(),
861            timestamp: "2024-01-01T00:00:00Z".to_string(),
862        };
863        sink.write(&entry);
864        assert_eq!(sink.len(), 1);
865        assert_eq!(sink.entries()[0].message, "hello");
866    }
867
868    #[test]
869    fn test_memory_sink_clear() {
870        let sink = MemorySink::new("mem");
871        let entry = LogEntry {
872            level: LogLevel::Info,
873            message: "x".to_string(),
874            timestamp: "t".to_string(),
875        };
876        sink.write(&entry);
877        sink.write(&entry);
878        assert_eq!(sink.len(), 2);
879        sink.clear();
880        assert!(sink.is_empty());
881    }
882
883    // ===================== ConsoleSink 测试 =====================
884
885    #[test]
886    fn test_console_sink_name_default() {
887        let sink = ConsoleSink::new();
888        assert_eq!(sink.name(), "console");
889    }
890
891    #[test]
892    fn test_console_sink_custom_name() {
893        let sink = ConsoleSink::with_name("stdout");
894        assert_eq!(sink.name(), "stdout");
895    }
896
897    #[test]
898    fn test_console_sink_write_does_not_panic() {
899        let sink = ConsoleSink::new();
900        let entry = LogEntry {
901            level: LogLevel::Info,
902            message: "test".to_string(),
903            timestamp: "t".to_string(),
904        };
905        sink.write(&entry); // should not panic
906    }
907
908    // ===================== CallbackSink 测试 =====================
909
910    #[test]
911    fn test_callback_sink_invokes_closure() {
912        let counter = Arc::new(Mutex::new(0u32));
913        let c = counter.clone();
914        let sink = CallbackSink::new("cb", move |_entry| {
915            *c.lock() += 1;
916        });
917        let entry = LogEntry {
918            level: LogLevel::Info,
919            message: "x".to_string(),
920            timestamp: "t".to_string(),
921        };
922        sink.write(&entry);
923        sink.write(&entry);
924        assert_eq!(*counter.lock(), 2);
925    }
926
927    // ===================== MultiOutputLogger 测试 =====================
928
929    #[test]
930    fn test_multi_output_logger_fans_out_to_all_sinks() {
931        let mut logger = MultiOutputLogger::new(LogLevel::Debug);
932        let sink1 = Arc::new(MemorySink::new("s1"));
933        let sink2 = Arc::new(MemorySink::new("s2"));
934
935        logger.add_sink(sink1.clone());
936        logger.add_sink(sink2.clone());
937
938        logger.log(LogLevel::Info, "hello");
939
940        assert_eq!(sink1.len(), 1);
941        assert_eq!(sink2.len(), 1);
942        assert_eq!(sink1.entries()[0].message, "hello");
943        assert_eq!(sink2.entries()[0].message, "hello");
944    }
945
946    #[test]
947    fn test_multi_output_logger_respects_level_filter() {
948        let mut logger = MultiOutputLogger::new(LogLevel::Warn);
949        let sink = Arc::new(MemorySink::new("s"));
950        logger.add_sink(sink.clone());
951
952        logger.log(LogLevel::Debug, "debug"); // filtered
953        logger.log(LogLevel::Info, "info"); // filtered
954        logger.log(LogLevel::Warn, "warn"); // passes
955        logger.log(LogLevel::Error, "error"); // passes
956
957        assert_eq!(sink.len(), 2);
958    }
959
960    #[test]
961    fn test_multi_output_logger_sink_names() {
962        let mut logger = MultiOutputLogger::new(LogLevel::Info);
963        logger.add_sink(Arc::new(MemorySink::new("alpha")));
964        logger.add_sink(Arc::new(MemorySink::new("beta")));
965        let names = logger.sink_names();
966        assert_eq!(names, vec!["alpha", "beta"]);
967        assert_eq!(logger.sink_count(), 2);
968    }
969
970    #[test]
971    fn test_multi_output_logger_empty_sinks_no_error() {
972        let logger = MultiOutputLogger::new(LogLevel::Info);
973        logger.log(LogLevel::Info, "msg"); // should not panic
974        assert_eq!(logger.sink_count(), 0);
975    }
976
977    #[test]
978    fn test_multi_output_logger_level_accessor() {
979        let logger = MultiOutputLogger::new(LogLevel::Error);
980        assert_eq!(logger.level(), LogLevel::Error);
981    }
982
983    // ===================== LevelFilter 测试 =====================
984
985    #[test]
986    fn test_level_filter_default_level() {
987        let filter = LevelFilter::new(LogLevel::Info);
988        assert_eq!(filter.default_level(), LogLevel::Info);
989        assert_eq!(filter.level_for("anything"), LogLevel::Info);
990        assert!(filter.should_log("anything", LogLevel::Info));
991        assert!(!filter.should_log("anything", LogLevel::Debug));
992    }
993
994    #[test]
995    fn test_level_filter_target_override() {
996        let filter = LevelFilter::new(LogLevel::Info)
997            .with_target_level("database", LogLevel::Debug)
998            .with_target_level("http", LogLevel::Warn);
999
1000        // database 模块开 Debug
1001        assert!(filter.should_log("database", LogLevel::Debug));
1002        assert_eq!(filter.level_for("database"), LogLevel::Debug);
1003
1004        // http 模块只看 Warn+
1005        assert!(!filter.should_log("http", LogLevel::Info));
1006        assert!(filter.should_log("http", LogLevel::Warn));
1007        assert_eq!(filter.level_for("http"), LogLevel::Warn);
1008
1009        // 未注册的 target 用默认 Info
1010        assert_eq!(filter.level_for("cache"), LogLevel::Info);
1011        assert!(filter.should_log("cache", LogLevel::Info));
1012        assert!(!filter.should_log("cache", LogLevel::Debug));
1013    }
1014
1015    #[test]
1016    fn test_level_filter_remove_target() {
1017        let mut filter = LevelFilter::new(LogLevel::Info).with_target_level("db", LogLevel::Debug);
1018        assert_eq!(filter.level_for("db"), LogLevel::Debug);
1019
1020        let removed = filter.remove_target("db");
1021        assert_eq!(removed, Some(LogLevel::Debug));
1022        assert_eq!(filter.level_for("db"), LogLevel::Info); // falls back
1023    }
1024
1025    #[test]
1026    fn test_level_filter_remove_missing_target_returns_none() {
1027        let mut filter = LevelFilter::new(LogLevel::Info);
1028        assert_eq!(filter.remove_target("never"), None);
1029    }
1030
1031    #[test]
1032    fn test_level_filter_set_default_level() {
1033        let mut filter = LevelFilter::new(LogLevel::Info);
1034        filter.set_default_level(LogLevel::Debug);
1035        assert_eq!(filter.default_level(), LogLevel::Debug);
1036        assert!(filter.should_log("any", LogLevel::Debug));
1037    }
1038
1039    #[test]
1040    fn test_level_filter_target_count_and_names() {
1041        let filter = LevelFilter::new(LogLevel::Info)
1042            .with_target_level("a", LogLevel::Debug)
1043            .with_target_level("b", LogLevel::Warn);
1044        assert_eq!(filter.target_count(), 2);
1045        let mut targets = filter.targets();
1046        targets.sort();
1047        assert_eq!(targets, vec!["a", "b"]);
1048    }
1049
1050    #[test]
1051    fn test_level_filter_default_impl() {
1052        let filter = LevelFilter::default();
1053        assert_eq!(filter.default_level(), LogLevel::Info);
1054    }
1055
1056    // ===================== StructuredLogEntry 测试 =====================
1057
1058    #[test]
1059    fn test_structured_log_entry_new() {
1060        let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1061        assert_eq!(entry.level, LogLevel::Info);
1062        assert_eq!(entry.message, "hello");
1063        assert!(entry.target.is_none());
1064        assert!(entry.fields.is_empty());
1065        assert!(!entry.timestamp.is_empty());
1066    }
1067
1068    #[test]
1069    fn test_structured_log_entry_with_target() {
1070        let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_target("database");
1071        assert_eq!(entry.target, Some("database".to_string()));
1072    }
1073
1074    #[test]
1075    fn test_structured_log_entry_with_field() {
1076        let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1077            .with_field("user_id", "12345")
1078            .with_field("action", "login");
1079        assert_eq!(entry.fields.get("user_id"), Some(&"12345".to_string()));
1080        assert_eq!(entry.fields.get("action"), Some(&"login".to_string()));
1081        assert_eq!(entry.fields.len(), 2);
1082    }
1083
1084    #[test]
1085    fn test_structured_log_entry_with_fields_batch() {
1086        let mut fields = StructuredFields::new();
1087        fields.insert("k1".to_string(), "v1".to_string());
1088        fields.insert("k2".to_string(), "v2".to_string());
1089        let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_fields(fields);
1090        assert_eq!(entry.fields.len(), 2);
1091    }
1092
1093    #[test]
1094    fn test_structured_log_entry_from_log_entry() {
1095        let original = LogEntry {
1096            level: LogLevel::Warn,
1097            message: "warning msg".to_string(),
1098            timestamp: "2024-01-01T00:00:00Z".to_string(),
1099        };
1100        let structured = StructuredLogEntry::from_log_entry(&original);
1101        assert_eq!(structured.level, LogLevel::Warn);
1102        assert_eq!(structured.message, "warning msg");
1103        assert_eq!(structured.timestamp, "2024-01-01T00:00:00Z");
1104        assert!(structured.target.is_none());
1105        assert!(structured.fields.is_empty());
1106    }
1107
1108    #[test]
1109    fn test_structured_log_entry_to_log_entry() {
1110        let structured = StructuredLogEntry::new(LogLevel::Error, "err")
1111            .with_target("db")
1112            .with_field("code", "500");
1113        let plain = structured.to_log_entry();
1114        assert_eq!(plain.level, LogLevel::Error);
1115        assert_eq!(plain.message, "err");
1116    }
1117
1118    #[test]
1119    fn test_structured_log_entry_json_roundtrip() {
1120        let entry = StructuredLogEntry::new(LogLevel::Info, "test")
1121            .with_target("app")
1122            .with_field("key", "value");
1123        let json = entry.to_json().expect("serialize");
1124        let back = StructuredLogEntry::from_json(&json).expect("deserialize");
1125        assert_eq!(back.level, entry.level);
1126        assert_eq!(back.message, entry.message);
1127        assert_eq!(back.target, entry.target);
1128        assert_eq!(back.fields, entry.fields);
1129    }
1130
1131    #[test]
1132    fn test_structured_log_entry_format_fields_sorted() {
1133        let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1134            .with_field("zebra", "1")
1135            .with_field("alpha", "2")
1136            .with_field("middle", "3");
1137        let formatted = entry.format_fields();
1138        // 按字母序排列
1139        assert_eq!(formatted, "alpha=2 middle=3 zebra=1");
1140    }
1141
1142    #[test]
1143    fn test_structured_log_entry_format_fields_empty() {
1144        let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1145        assert_eq!(entry.format_fields(), "");
1146    }
1147
1148    // ===================== MemoryStructuredSink 测试 =====================
1149
1150    #[test]
1151    fn test_memory_structured_sink_new_empty() {
1152        let sink = MemoryStructuredSink::new("s");
1153        assert_eq!(sink.name(), "s");
1154        assert!(sink.is_empty());
1155    }
1156
1157    #[test]
1158    fn test_memory_structured_sink_stores_entry() {
1159        let sink = MemoryStructuredSink::new("s");
1160        let entry = StructuredLogEntry::new(LogLevel::Info, "hello").with_field("k", "v");
1161        sink.write(&entry);
1162        assert_eq!(sink.len(), 1);
1163        assert_eq!(sink.entries()[0].message, "hello");
1164        assert_eq!(sink.entries()[0].fields.get("k"), Some(&"v".to_string()));
1165    }
1166
1167    #[test]
1168    fn test_memory_structured_sink_clear() {
1169        let sink = MemoryStructuredSink::new("s");
1170        sink.write(&StructuredLogEntry::new(LogLevel::Info, "x"));
1171        sink.clear();
1172        assert!(sink.is_empty());
1173    }
1174
1175    // ===================== StructuredLogWriter 测试 =====================
1176
1177    #[test]
1178    fn test_structured_log_writer_filters_by_target() {
1179        let filter = LevelFilter::new(LogLevel::Info).with_target_level("verbose", LogLevel::Debug);
1180        let mut writer = StructuredLogWriter::new(filter);
1181        let sink = Arc::new(MemoryStructuredSink::new("mem"));
1182        writer.add_sink(sink.clone());
1183
1184        // verbose 模块的 Debug 日志应该被记录
1185        let debug_entry = StructuredLogEntry::new(LogLevel::Debug, "dbg").with_target("verbose");
1186        writer.log(&debug_entry);
1187        assert_eq!(sink.len(), 1);
1188
1189        // 默认 target 的 Debug 日志应该被过滤
1190        let filtered = StructuredLogEntry::new(LogLevel::Debug, "filtered");
1191        writer.log(&filtered);
1192        assert_eq!(sink.len(), 1); // still 1
1193    }
1194
1195    #[test]
1196    fn test_structured_log_writer_fans_out_to_multiple_sinks() {
1197        let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1198        let sink1 = Arc::new(MemoryStructuredSink::new("s1"));
1199        let sink2 = Arc::new(MemoryStructuredSink::new("s2"));
1200
1201        // 先添加 sink 再使用(需要可变引用)
1202        let mut writer = writer;
1203        writer.add_sink(sink1.clone());
1204        writer.add_sink(sink2.clone());
1205
1206        let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1207        writer.log(&entry);
1208
1209        assert_eq!(sink1.len(), 1);
1210        assert_eq!(sink2.len(), 1);
1211    }
1212
1213    #[test]
1214    fn test_structured_log_writer_log_with_fields() {
1215        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1216        let sink = Arc::new(MemoryStructuredSink::new("mem"));
1217        writer.add_sink(sink.clone());
1218
1219        let mut fields = StructuredFields::new();
1220        fields.insert("user_id".to_string(), "42".to_string());
1221        writer.log_with_fields("api", LogLevel::Info, "request", fields);
1222
1223        assert_eq!(sink.len(), 1);
1224        let entry = &sink.entries()[0];
1225        assert_eq!(entry.target, Some("api".to_string()));
1226        assert_eq!(entry.fields.get("user_id"), Some(&"42".to_string()));
1227    }
1228
1229    #[test]
1230    fn test_structured_log_writer_sink_names() {
1231        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1232        writer.add_sink(Arc::new(MemoryStructuredSink::new("alpha")));
1233        writer.add_sink(Arc::new(MemoryStructuredSink::new("beta")));
1234        let names = writer.sink_names();
1235        assert_eq!(names, vec!["alpha", "beta"]);
1236        assert_eq!(writer.sink_count(), 2);
1237    }
1238
1239    #[test]
1240    fn test_structured_log_writer_filter_mut() {
1241        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1242        writer.filter_mut().set_default_level(LogLevel::Error);
1243        assert_eq!(writer.filter().default_level(), LogLevel::Error);
1244    }
1245
1246    #[test]
1247    fn test_structured_log_writer_empty_sinks_no_error() {
1248        let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1249        let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1250        writer.log(&entry); // should not panic
1251        assert_eq!(writer.sink_count(), 0);
1252    }
1253
1254    #[test]
1255    fn test_multi_output_logger_implements_send_sync() {
1256        fn assert_send_sync<T: Send + Sync>() {}
1257        assert_send_sync::<MultiOutputLogger>();
1258        assert_send_sync::<MemorySink>();
1259        assert_send_sync::<ConsoleSink>();
1260    }
1261}