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, Mutex};
18use std::time::{Duration, Instant};
19
20// ============================================================================
21// 日志轮转
22// ============================================================================
23
24/// 日志轮转策略
25#[derive(Debug, Clone)]
26pub enum RotationPolicy {
27    /// 按字节大小轮转:当前日志达到 `max_bytes` 时轮转
28    Size(u64),
29    /// 按时间轮转:当前日志存活超过 `max_age` 时轮转
30    Time(Duration),
31    /// 大小或时间任一条件满足即轮转
32    SizeOrTime(u64, Duration),
33}
34
35impl RotationPolicy {
36    /// 判断当前日志是否应该轮转
37    ///
38    /// # 参数
39    /// - `current_size`:当前日志字节数
40    /// - `elapsed`:当前日志已存活时长
41    fn should_rotate(&self, current_size: u64, elapsed: Duration) -> bool {
42        match self {
43            RotationPolicy::Size(max_bytes) => current_size >= *max_bytes,
44            RotationPolicy::Time(max_age) => elapsed >= *max_age,
45            RotationPolicy::SizeOrTime(max_bytes, max_age) => {
46                current_size >= *max_bytes || elapsed >= *max_age
47            }
48        }
49    }
50}
51
52/// 日志轮转器(内存模拟)。
53///
54/// 维护一个当前日志缓冲与一组已轮转的历史日志。当当前缓冲满足轮转策略时,
55/// 将其移入历史列表并开启新的空缓冲。历史列表长度不超过 `max_files`,
56/// 超出时丢弃最旧的。
57///
58/// 实际生产环境中,轮转器会将缓冲刷盘到文件(如 `app.log` -> `app.log.1`),
59/// 此处用内存 `Vec<u8>` 模拟以便测试。
60pub struct LogRotator {
61    /// 轮转策略
62    policy: RotationPolicy,
63    /// 保留的最大历史文件数
64    max_files: usize,
65    /// 当前日志缓冲
66    current: Mutex<Vec<u8>>,
67    /// 已轮转的历史日志(按时间倒序,索引 0 为最近一次轮转)
68    rotated: Mutex<Vec<Vec<u8>>>,
69    /// 当前缓冲的开始时间
70    started_at: Mutex<Instant>,
71    /// 总轮转次数
72    rotation_count: Mutex<u64>,
73}
74
75impl LogRotator {
76    /// 创建轮转器
77    ///
78    /// # 参数
79    /// - `policy`:轮转策略
80    /// - `max_files`:保留的最大历史文件数(0 表示不保留)
81    pub fn new(policy: RotationPolicy, max_files: usize) -> Self {
82        Self {
83            policy,
84            max_files,
85            current: Mutex::new(Vec::new()),
86            rotated: Mutex::new(Vec::new()),
87            started_at: Mutex::new(Instant::now()),
88            rotation_count: Mutex::new(0),
89        }
90    }
91
92    /// 写入一条日志(字节形式)。写入后自动检查是否需要轮转。
93    pub fn write(&self, data: &[u8]) {
94        let should_rotate = {
95            let mut current = self.current.lock().unwrap();
96            current.extend_from_slice(data);
97            let started_at = self.started_at.lock().unwrap();
98            self.policy
99                .should_rotate(current.len() as u64, started_at.elapsed())
100        };
101
102        if should_rotate {
103            self.rotate();
104        }
105    }
106
107    /// 手动触发轮转:将当前缓冲移入历史列表,开启新的空缓冲。
108    pub fn rotate(&self) {
109        let mut current = self.current.lock().unwrap();
110        let mut rotated = self.rotated.lock().unwrap();
111        let mut started_at = self.started_at.lock().unwrap();
112        let mut count = self.rotation_count.lock().unwrap();
113
114        // 将当前缓冲移入历史列表头部
115        let old_buffer = std::mem::take(&mut *current);
116        if !old_buffer.is_empty() {
117            rotated.insert(0, old_buffer);
118        }
119
120        // 超出最大保留数时丢弃最旧的
121        while rotated.len() > self.max_files {
122            rotated.pop();
123        }
124
125        // 重置当前缓冲的开始时间
126        *started_at = Instant::now();
127        *count += 1;
128    }
129
130    /// 获取当前缓冲的字节大小
131    pub fn current_size(&self) -> usize {
132        self.current.lock().unwrap().len()
133    }
134
135    /// 获取已轮转的历史日志数量
136    pub fn rotated_count(&self) -> usize {
137        self.rotated.lock().unwrap().len()
138    }
139
140    /// 获取总轮转次数(含因 max_files 限制被丢弃的)
141    pub fn total_rotations(&self) -> u64 {
142        *self.rotation_count.lock().unwrap()
143    }
144
145    /// 获取当前缓冲内容的快照(拷贝)
146    pub fn current_content(&self) -> Vec<u8> {
147        self.current.lock().unwrap().clone()
148    }
149
150    /// 获取第 `index` 个历史日志的快照(0 = 最近一次轮转)
151    pub fn rotated_content(&self, index: usize) -> Option<Vec<u8>> {
152        let rotated = self.rotated.lock().unwrap();
153        rotated.get(index).cloned()
154    }
155
156    /// 获取当前缓冲已存活的时长
157    pub fn current_age(&self) -> Duration {
158        self.started_at.lock().unwrap().elapsed()
159    }
160
161    /// 获取轮转策略引用
162    pub fn policy(&self) -> &RotationPolicy {
163        &self.policy
164    }
165
166    /// 获取最大保留文件数
167    pub fn max_files(&self) -> usize {
168        self.max_files
169    }
170
171    /// 清空所有日志(当前缓冲 + 历史)
172    pub fn clear(&self) {
173        self.current.lock().unwrap().clear();
174        self.rotated.lock().unwrap().clear();
175        *self.started_at.lock().unwrap() = Instant::now();
176    }
177}
178
179// ============================================================================
180// 多输出(LogSink / MultiOutputLogger)
181// ============================================================================
182
183/// 日志输出目标 trait:将 [`LogEntry`] 写入某个后端。
184///
185/// 实现方需保证 `write` 不 panic(lock poisoned 时降级处理),
186/// 以免一个 sink 故障影响其他 sink 的日志写入。
187pub trait LogSink: Send + Sync {
188    /// 写入一条日志条目
189    fn write(&self, entry: &LogEntry);
190    /// sink 名称(用于调试与统计)
191    fn name(&self) -> &str;
192}
193
194/// 内存日志 sink:将日志条目存入内存 Vec,便于测试验证。
195pub struct MemorySink {
196    name: String,
197    entries: Mutex<Vec<LogEntry>>,
198}
199
200impl MemorySink {
201    pub fn new(name: impl Into<String>) -> Self {
202        Self {
203            name: name.into(),
204            entries: Mutex::new(Vec::new()),
205        }
206    }
207
208    /// 获取已存储的日志条目快照
209    pub fn entries(&self) -> Vec<LogEntry> {
210        self.entries.lock().unwrap().clone()
211    }
212
213    /// 获取已存储的日志条目数量
214    pub fn len(&self) -> usize {
215        self.entries.lock().unwrap().len()
216    }
217
218    /// 是否为空
219    pub fn is_empty(&self) -> bool {
220        self.entries.lock().unwrap().is_empty()
221    }
222
223    /// 清空存储的日志
224    pub fn clear(&self) {
225        self.entries.lock().unwrap().clear();
226    }
227}
228
229impl LogSink for MemorySink {
230    fn write(&self, entry: &LogEntry) {
231        // lock poisoned 时跳过写入而非 panic
232        if let Ok(mut entries) = self.entries.lock() {
233            entries.push(entry.clone());
234        }
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().unwrap().clone()
597    }
598
599    pub fn len(&self) -> usize {
600        self.entries.lock().unwrap().len()
601    }
602
603    pub fn is_empty(&self) -> bool {
604        self.entries.lock().unwrap().is_empty()
605    }
606
607    pub fn clear(&self) {
608        self.entries.lock().unwrap().clear();
609    }
610}
611
612impl StructuredSink for MemoryStructuredSink {
613    fn write(&self, entry: &StructuredLogEntry) {
614        if let Ok(mut entries) = self.entries.lock() {
615            entries.push(entry.clone());
616        }
617    }
618
619    fn name(&self) -> &str {
620        &self.name
621    }
622}
623
624impl StructuredLogWriter {
625    /// 创建结构化日志写入器
626    pub fn new(filter: LevelFilter) -> Self {
627        Self {
628            filter,
629            sinks: Vec::new(),
630        }
631    }
632
633    /// 添加结构化 sink
634    pub fn add_sink(&mut self, sink: Arc<dyn StructuredSink>) -> &mut Self {
635        self.sinks.push(sink);
636        self
637    }
638
639    /// 获取 sink 数量
640    pub fn sink_count(&self) -> usize {
641        self.sinks.len()
642    }
643
644    /// 获取 sink 名称列表
645    pub fn sink_names(&self) -> Vec<String> {
646        self.sinks.iter().map(|s| s.name().to_string()).collect()
647    }
648
649    /// 获取级别过滤器引用
650    pub fn filter(&self) -> &LevelFilter {
651        &self.filter
652    }
653
654    /// 获取级别过滤器可变引用
655    pub fn filter_mut(&mut self) -> &mut LevelFilter {
656        &mut self.filter
657    }
658
659    /// 记录一条结构化日志。
660    ///
661    /// 根据 target 通过 [`LevelFilter`] 过滤后扇出到所有 sink。
662    /// target 为 `None` 时使用默认级别。
663    pub fn log(&self, entry: &StructuredLogEntry) {
664        let target = entry.target.as_deref().unwrap_or("");
665        if !self.filter.should_log(target, entry.level) {
666            return;
667        }
668        for sink in &self.sinks {
669            sink.write(entry);
670        }
671    }
672
673    /// 便捷方法:记录一条带 target 和字段的日志
674    pub fn log_with_fields(
675        &self,
676        target: impl Into<String>,
677        level: LogLevel,
678        message: impl Into<String>,
679        fields: StructuredFields,
680    ) {
681        let entry = StructuredLogEntry::new(level, message)
682            .with_target(target)
683            .with_fields(fields);
684        self.log(&entry);
685    }
686}
687
688// 为 StructuredLogEntry 批量设置 fields 的扩展方法
689impl StructuredLogEntry {
690    /// 批量设置结构化字段
691    pub fn with_fields(mut self, fields: StructuredFields) -> Self {
692        self.fields.extend(fields);
693        self
694    }
695}
696
697#[cfg(test)]
698mod tests {
699    use super::*;
700    use std::thread;
701    use std::time::Duration;
702
703    // ===================== RotationPolicy 测试 =====================
704
705    #[test]
706    fn test_rotation_policy_size_met() {
707        let policy = RotationPolicy::Size(100);
708        assert!(policy.should_rotate(100, Duration::from_secs(0)));
709        assert!(policy.should_rotate(101, Duration::from_secs(0)));
710        assert!(!policy.should_rotate(99, Duration::from_secs(0)));
711    }
712
713    #[test]
714    fn test_rotation_policy_time_met() {
715        let policy = RotationPolicy::Time(Duration::from_secs(60));
716        assert!(policy.should_rotate(0, Duration::from_secs(60)));
717        assert!(policy.should_rotate(0, Duration::from_secs(61)));
718        assert!(!policy.should_rotate(0, Duration::from_secs(59)));
719    }
720
721    #[test]
722    fn test_rotation_policy_size_or_time_either() {
723        let policy = RotationPolicy::SizeOrTime(100, Duration::from_secs(60));
724        // 大小满足
725        assert!(policy.should_rotate(100, Duration::from_secs(0)));
726        // 时间满足
727        assert!(policy.should_rotate(0, Duration::from_secs(60)));
728        // 都不满足
729        assert!(!policy.should_rotate(99, Duration::from_secs(59)));
730        // 都满足
731        assert!(policy.should_rotate(200, Duration::from_secs(120)));
732    }
733
734    // ===================== LogRotator 测试 =====================
735
736    #[test]
737    fn test_log_rotator_new_empty() {
738        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
739        assert_eq!(rotator.current_size(), 0);
740        assert_eq!(rotator.rotated_count(), 0);
741        assert_eq!(rotator.total_rotations(), 0);
742        assert_eq!(rotator.max_files(), 3);
743    }
744
745    #[test]
746    fn test_log_rotator_write_accumulates() {
747        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
748        rotator.write(b"hello");
749        rotator.write(b" world");
750        assert_eq!(rotator.current_size(), 11);
751        assert_eq!(rotator.current_content(), b"hello world");
752    }
753
754    #[test]
755    fn test_log_rotator_size_triggers_rotation() {
756        let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
757        rotator.write(b"12345"); // 5 bytes
758        assert_eq!(rotator.rotated_count(), 0);
759        rotator.write(b"67890"); // 10 bytes -> triggers rotation
760        assert_eq!(rotator.rotated_count(), 1);
761        assert_eq!(rotator.total_rotations(), 1);
762        assert_eq!(rotator.current_size(), 0);
763    }
764
765    #[test]
766    fn test_log_rotator_rotation_preserves_content() {
767        let rotator = LogRotator::new(RotationPolicy::Size(10), 3);
768        rotator.write(b"hello world"); // 11 bytes -> triggers rotation
769        assert_eq!(rotator.rotated_count(), 1);
770        let rotated = rotator.rotated_content(0).expect("rotated[0] must exist");
771        assert_eq!(rotated, b"hello world");
772    }
773
774    #[test]
775    fn test_log_rotator_max_files_drops_oldest() {
776        let rotator = LogRotator::new(RotationPolicy::Size(5), 2);
777        rotator.write(b"AAAAAA"); // rotate 1
778        rotator.write(b"BBBBBB"); // rotate 2
779        rotator.write(b"CCCCCC"); // rotate 3 -> drops oldest (AAAAAA)
780        assert_eq!(rotator.rotated_count(), 2);
781        assert_eq!(rotator.total_rotations(), 3);
782        // rotated[0] = most recent = CCCCCC, rotated[1] = BBBBBB
783        assert_eq!(rotator.rotated_content(0), Some(b"CCCCCC".to_vec()));
784        assert_eq!(rotator.rotated_content(1), Some(b"BBBBBB".to_vec()));
785        assert_eq!(rotator.rotated_content(2), None);
786    }
787
788    #[test]
789    fn test_log_rotator_manual_rotate() {
790        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
791        rotator.write(b"some data");
792        rotator.rotate();
793        assert_eq!(rotator.rotated_count(), 1);
794        assert_eq!(rotator.current_size(), 0);
795    }
796
797    #[test]
798    fn test_log_rotator_manual_rotate_empty_no_op() {
799        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
800        rotator.rotate(); // empty buffer -> no rotation
801        assert_eq!(rotator.rotated_count(), 0);
802        assert_eq!(rotator.total_rotations(), 1); // count still increments
803    }
804
805    #[test]
806    fn test_log_rotator_clear() {
807        let rotator = LogRotator::new(RotationPolicy::Size(5), 3);
808        rotator.write(b"hello world"); // triggers rotation
809        rotator.write(b"more");
810        assert!(!rotator.current_content().is_empty());
811        assert_eq!(rotator.rotated_count(), 1);
812
813        rotator.clear();
814        assert_eq!(rotator.current_size(), 0);
815        assert_eq!(rotator.rotated_count(), 0);
816    }
817
818    #[test]
819    fn test_log_rotator_policy_accessor() {
820        let rotator = LogRotator::new(RotationPolicy::Size(256), 5);
821        match rotator.policy() {
822            RotationPolicy::Size(n) => assert_eq!(*n, 256),
823            _ => panic!("expected Size policy"),
824        }
825    }
826
827    #[test]
828    fn test_log_rotator_time_based_rotation() {
829        let rotator = LogRotator::new(RotationPolicy::Time(Duration::from_millis(50)), 3);
830        rotator.write(b"data");
831        assert_eq!(rotator.rotated_count(), 0);
832        thread::sleep(Duration::from_millis(60));
833        rotator.write(b"more"); // triggers time-based rotation
834        assert_eq!(rotator.rotated_count(), 1);
835    }
836
837    #[test]
838    fn test_log_rotator_current_age_increases() {
839        let rotator = LogRotator::new(RotationPolicy::Size(1024), 3);
840        let age1 = rotator.current_age();
841        thread::sleep(Duration::from_millis(10));
842        let age2 = rotator.current_age();
843        assert!(age2 > age1);
844    }
845
846    // ===================== MemorySink 测试 =====================
847
848    #[test]
849    fn test_memory_sink_new_empty() {
850        let sink = MemorySink::new("test");
851        assert_eq!(sink.name(), "test");
852        assert!(sink.is_empty());
853        assert_eq!(sink.len(), 0);
854    }
855
856    #[test]
857    fn test_memory_sink_write_stores_entry() {
858        let sink = MemorySink::new("mem");
859        let entry = LogEntry {
860            level: LogLevel::Info,
861            message: "hello".to_string(),
862            timestamp: "2024-01-01T00:00:00Z".to_string(),
863        };
864        sink.write(&entry);
865        assert_eq!(sink.len(), 1);
866        assert_eq!(sink.entries()[0].message, "hello");
867    }
868
869    #[test]
870    fn test_memory_sink_clear() {
871        let sink = MemorySink::new("mem");
872        let entry = LogEntry {
873            level: LogLevel::Info,
874            message: "x".to_string(),
875            timestamp: "t".to_string(),
876        };
877        sink.write(&entry);
878        sink.write(&entry);
879        assert_eq!(sink.len(), 2);
880        sink.clear();
881        assert!(sink.is_empty());
882    }
883
884    // ===================== ConsoleSink 测试 =====================
885
886    #[test]
887    fn test_console_sink_name_default() {
888        let sink = ConsoleSink::new();
889        assert_eq!(sink.name(), "console");
890    }
891
892    #[test]
893    fn test_console_sink_custom_name() {
894        let sink = ConsoleSink::with_name("stdout");
895        assert_eq!(sink.name(), "stdout");
896    }
897
898    #[test]
899    fn test_console_sink_write_does_not_panic() {
900        let sink = ConsoleSink::new();
901        let entry = LogEntry {
902            level: LogLevel::Info,
903            message: "test".to_string(),
904            timestamp: "t".to_string(),
905        };
906        sink.write(&entry); // should not panic
907    }
908
909    // ===================== CallbackSink 测试 =====================
910
911    #[test]
912    fn test_callback_sink_invokes_closure() {
913        let counter = Arc::new(Mutex::new(0u32));
914        let c = counter.clone();
915        let sink = CallbackSink::new("cb", move |_entry| {
916            *c.lock().unwrap() += 1;
917        });
918        let entry = LogEntry {
919            level: LogLevel::Info,
920            message: "x".to_string(),
921            timestamp: "t".to_string(),
922        };
923        sink.write(&entry);
924        sink.write(&entry);
925        assert_eq!(*counter.lock().unwrap(), 2);
926    }
927
928    // ===================== MultiOutputLogger 测试 =====================
929
930    #[test]
931    fn test_multi_output_logger_fans_out_to_all_sinks() {
932        let mut logger = MultiOutputLogger::new(LogLevel::Debug);
933        let sink1 = Arc::new(MemorySink::new("s1"));
934        let sink2 = Arc::new(MemorySink::new("s2"));
935
936        logger.add_sink(sink1.clone());
937        logger.add_sink(sink2.clone());
938
939        logger.log(LogLevel::Info, "hello");
940
941        assert_eq!(sink1.len(), 1);
942        assert_eq!(sink2.len(), 1);
943        assert_eq!(sink1.entries()[0].message, "hello");
944        assert_eq!(sink2.entries()[0].message, "hello");
945    }
946
947    #[test]
948    fn test_multi_output_logger_respects_level_filter() {
949        let mut logger = MultiOutputLogger::new(LogLevel::Warn);
950        let sink = Arc::new(MemorySink::new("s"));
951        logger.add_sink(sink.clone());
952
953        logger.log(LogLevel::Debug, "debug"); // filtered
954        logger.log(LogLevel::Info, "info"); // filtered
955        logger.log(LogLevel::Warn, "warn"); // passes
956        logger.log(LogLevel::Error, "error"); // passes
957
958        assert_eq!(sink.len(), 2);
959    }
960
961    #[test]
962    fn test_multi_output_logger_sink_names() {
963        let mut logger = MultiOutputLogger::new(LogLevel::Info);
964        logger.add_sink(Arc::new(MemorySink::new("alpha")));
965        logger.add_sink(Arc::new(MemorySink::new("beta")));
966        let names = logger.sink_names();
967        assert_eq!(names, vec!["alpha", "beta"]);
968        assert_eq!(logger.sink_count(), 2);
969    }
970
971    #[test]
972    fn test_multi_output_logger_empty_sinks_no_error() {
973        let logger = MultiOutputLogger::new(LogLevel::Info);
974        logger.log(LogLevel::Info, "msg"); // should not panic
975        assert_eq!(logger.sink_count(), 0);
976    }
977
978    #[test]
979    fn test_multi_output_logger_level_accessor() {
980        let logger = MultiOutputLogger::new(LogLevel::Error);
981        assert_eq!(logger.level(), LogLevel::Error);
982    }
983
984    // ===================== LevelFilter 测试 =====================
985
986    #[test]
987    fn test_level_filter_default_level() {
988        let filter = LevelFilter::new(LogLevel::Info);
989        assert_eq!(filter.default_level(), LogLevel::Info);
990        assert_eq!(filter.level_for("anything"), LogLevel::Info);
991        assert!(filter.should_log("anything", LogLevel::Info));
992        assert!(!filter.should_log("anything", LogLevel::Debug));
993    }
994
995    #[test]
996    fn test_level_filter_target_override() {
997        let filter = LevelFilter::new(LogLevel::Info)
998            .with_target_level("database", LogLevel::Debug)
999            .with_target_level("http", LogLevel::Warn);
1000
1001        // database 模块开 Debug
1002        assert!(filter.should_log("database", LogLevel::Debug));
1003        assert_eq!(filter.level_for("database"), LogLevel::Debug);
1004
1005        // http 模块只看 Warn+
1006        assert!(!filter.should_log("http", LogLevel::Info));
1007        assert!(filter.should_log("http", LogLevel::Warn));
1008        assert_eq!(filter.level_for("http"), LogLevel::Warn);
1009
1010        // 未注册的 target 用默认 Info
1011        assert_eq!(filter.level_for("cache"), LogLevel::Info);
1012        assert!(filter.should_log("cache", LogLevel::Info));
1013        assert!(!filter.should_log("cache", LogLevel::Debug));
1014    }
1015
1016    #[test]
1017    fn test_level_filter_remove_target() {
1018        let mut filter = LevelFilter::new(LogLevel::Info).with_target_level("db", LogLevel::Debug);
1019        assert_eq!(filter.level_for("db"), LogLevel::Debug);
1020
1021        let removed = filter.remove_target("db");
1022        assert_eq!(removed, Some(LogLevel::Debug));
1023        assert_eq!(filter.level_for("db"), LogLevel::Info); // falls back
1024    }
1025
1026    #[test]
1027    fn test_level_filter_remove_missing_target_returns_none() {
1028        let mut filter = LevelFilter::new(LogLevel::Info);
1029        assert_eq!(filter.remove_target("never"), None);
1030    }
1031
1032    #[test]
1033    fn test_level_filter_set_default_level() {
1034        let mut filter = LevelFilter::new(LogLevel::Info);
1035        filter.set_default_level(LogLevel::Debug);
1036        assert_eq!(filter.default_level(), LogLevel::Debug);
1037        assert!(filter.should_log("any", LogLevel::Debug));
1038    }
1039
1040    #[test]
1041    fn test_level_filter_target_count_and_names() {
1042        let filter = LevelFilter::new(LogLevel::Info)
1043            .with_target_level("a", LogLevel::Debug)
1044            .with_target_level("b", LogLevel::Warn);
1045        assert_eq!(filter.target_count(), 2);
1046        let mut targets = filter.targets();
1047        targets.sort();
1048        assert_eq!(targets, vec!["a", "b"]);
1049    }
1050
1051    #[test]
1052    fn test_level_filter_default_impl() {
1053        let filter = LevelFilter::default();
1054        assert_eq!(filter.default_level(), LogLevel::Info);
1055    }
1056
1057    // ===================== StructuredLogEntry 测试 =====================
1058
1059    #[test]
1060    fn test_structured_log_entry_new() {
1061        let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1062        assert_eq!(entry.level, LogLevel::Info);
1063        assert_eq!(entry.message, "hello");
1064        assert!(entry.target.is_none());
1065        assert!(entry.fields.is_empty());
1066        assert!(!entry.timestamp.is_empty());
1067    }
1068
1069    #[test]
1070    fn test_structured_log_entry_with_target() {
1071        let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_target("database");
1072        assert_eq!(entry.target, Some("database".to_string()));
1073    }
1074
1075    #[test]
1076    fn test_structured_log_entry_with_field() {
1077        let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1078            .with_field("user_id", "12345")
1079            .with_field("action", "login");
1080        assert_eq!(entry.fields.get("user_id"), Some(&"12345".to_string()));
1081        assert_eq!(entry.fields.get("action"), Some(&"login".to_string()));
1082        assert_eq!(entry.fields.len(), 2);
1083    }
1084
1085    #[test]
1086    fn test_structured_log_entry_with_fields_batch() {
1087        let mut fields = StructuredFields::new();
1088        fields.insert("k1".to_string(), "v1".to_string());
1089        fields.insert("k2".to_string(), "v2".to_string());
1090        let entry = StructuredLogEntry::new(LogLevel::Info, "msg").with_fields(fields);
1091        assert_eq!(entry.fields.len(), 2);
1092    }
1093
1094    #[test]
1095    fn test_structured_log_entry_from_log_entry() {
1096        let original = LogEntry {
1097            level: LogLevel::Warn,
1098            message: "warning msg".to_string(),
1099            timestamp: "2024-01-01T00:00:00Z".to_string(),
1100        };
1101        let structured = StructuredLogEntry::from_log_entry(&original);
1102        assert_eq!(structured.level, LogLevel::Warn);
1103        assert_eq!(structured.message, "warning msg");
1104        assert_eq!(structured.timestamp, "2024-01-01T00:00:00Z");
1105        assert!(structured.target.is_none());
1106        assert!(structured.fields.is_empty());
1107    }
1108
1109    #[test]
1110    fn test_structured_log_entry_to_log_entry() {
1111        let structured = StructuredLogEntry::new(LogLevel::Error, "err")
1112            .with_target("db")
1113            .with_field("code", "500");
1114        let plain = structured.to_log_entry();
1115        assert_eq!(plain.level, LogLevel::Error);
1116        assert_eq!(plain.message, "err");
1117    }
1118
1119    #[test]
1120    fn test_structured_log_entry_json_roundtrip() {
1121        let entry = StructuredLogEntry::new(LogLevel::Info, "test")
1122            .with_target("app")
1123            .with_field("key", "value");
1124        let json = entry.to_json().expect("serialize");
1125        let back = StructuredLogEntry::from_json(&json).expect("deserialize");
1126        assert_eq!(back.level, entry.level);
1127        assert_eq!(back.message, entry.message);
1128        assert_eq!(back.target, entry.target);
1129        assert_eq!(back.fields, entry.fields);
1130    }
1131
1132    #[test]
1133    fn test_structured_log_entry_format_fields_sorted() {
1134        let entry = StructuredLogEntry::new(LogLevel::Info, "msg")
1135            .with_field("zebra", "1")
1136            .with_field("alpha", "2")
1137            .with_field("middle", "3");
1138        let formatted = entry.format_fields();
1139        // 按字母序排列
1140        assert_eq!(formatted, "alpha=2 middle=3 zebra=1");
1141    }
1142
1143    #[test]
1144    fn test_structured_log_entry_format_fields_empty() {
1145        let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1146        assert_eq!(entry.format_fields(), "");
1147    }
1148
1149    // ===================== MemoryStructuredSink 测试 =====================
1150
1151    #[test]
1152    fn test_memory_structured_sink_new_empty() {
1153        let sink = MemoryStructuredSink::new("s");
1154        assert_eq!(sink.name(), "s");
1155        assert!(sink.is_empty());
1156    }
1157
1158    #[test]
1159    fn test_memory_structured_sink_stores_entry() {
1160        let sink = MemoryStructuredSink::new("s");
1161        let entry = StructuredLogEntry::new(LogLevel::Info, "hello").with_field("k", "v");
1162        sink.write(&entry);
1163        assert_eq!(sink.len(), 1);
1164        assert_eq!(sink.entries()[0].message, "hello");
1165        assert_eq!(sink.entries()[0].fields.get("k"), Some(&"v".to_string()));
1166    }
1167
1168    #[test]
1169    fn test_memory_structured_sink_clear() {
1170        let sink = MemoryStructuredSink::new("s");
1171        sink.write(&StructuredLogEntry::new(LogLevel::Info, "x"));
1172        sink.clear();
1173        assert!(sink.is_empty());
1174    }
1175
1176    // ===================== StructuredLogWriter 测试 =====================
1177
1178    #[test]
1179    fn test_structured_log_writer_filters_by_target() {
1180        let filter = LevelFilter::new(LogLevel::Info).with_target_level("verbose", LogLevel::Debug);
1181        let mut writer = StructuredLogWriter::new(filter);
1182        let sink = Arc::new(MemoryStructuredSink::new("mem"));
1183        writer.add_sink(sink.clone());
1184
1185        // verbose 模块的 Debug 日志应该被记录
1186        let debug_entry = StructuredLogEntry::new(LogLevel::Debug, "dbg").with_target("verbose");
1187        writer.log(&debug_entry);
1188        assert_eq!(sink.len(), 1);
1189
1190        // 默认 target 的 Debug 日志应该被过滤
1191        let filtered = StructuredLogEntry::new(LogLevel::Debug, "filtered");
1192        writer.log(&filtered);
1193        assert_eq!(sink.len(), 1); // still 1
1194    }
1195
1196    #[test]
1197    fn test_structured_log_writer_fans_out_to_multiple_sinks() {
1198        let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1199        let sink1 = Arc::new(MemoryStructuredSink::new("s1"));
1200        let sink2 = Arc::new(MemoryStructuredSink::new("s2"));
1201
1202        // 先添加 sink 再使用(需要可变引用)
1203        let mut writer = writer;
1204        writer.add_sink(sink1.clone());
1205        writer.add_sink(sink2.clone());
1206
1207        let entry = StructuredLogEntry::new(LogLevel::Info, "hello");
1208        writer.log(&entry);
1209
1210        assert_eq!(sink1.len(), 1);
1211        assert_eq!(sink2.len(), 1);
1212    }
1213
1214    #[test]
1215    fn test_structured_log_writer_log_with_fields() {
1216        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1217        let sink = Arc::new(MemoryStructuredSink::new("mem"));
1218        writer.add_sink(sink.clone());
1219
1220        let mut fields = StructuredFields::new();
1221        fields.insert("user_id".to_string(), "42".to_string());
1222        writer.log_with_fields("api", LogLevel::Info, "request", fields);
1223
1224        assert_eq!(sink.len(), 1);
1225        let entry = &sink.entries()[0];
1226        assert_eq!(entry.target, Some("api".to_string()));
1227        assert_eq!(entry.fields.get("user_id"), Some(&"42".to_string()));
1228    }
1229
1230    #[test]
1231    fn test_structured_log_writer_sink_names() {
1232        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1233        writer.add_sink(Arc::new(MemoryStructuredSink::new("alpha")));
1234        writer.add_sink(Arc::new(MemoryStructuredSink::new("beta")));
1235        let names = writer.sink_names();
1236        assert_eq!(names, vec!["alpha", "beta"]);
1237        assert_eq!(writer.sink_count(), 2);
1238    }
1239
1240    #[test]
1241    fn test_structured_log_writer_filter_mut() {
1242        let mut writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Info));
1243        writer.filter_mut().set_default_level(LogLevel::Error);
1244        assert_eq!(writer.filter().default_level(), LogLevel::Error);
1245    }
1246
1247    #[test]
1248    fn test_structured_log_writer_empty_sinks_no_error() {
1249        let writer = StructuredLogWriter::new(LevelFilter::new(LogLevel::Debug));
1250        let entry = StructuredLogEntry::new(LogLevel::Info, "msg");
1251        writer.log(&entry); // should not panic
1252        assert_eq!(writer.sink_count(), 0);
1253    }
1254
1255    #[test]
1256    fn test_multi_output_logger_implements_send_sync() {
1257        fn assert_send_sync<T: Send + Sync>() {}
1258        assert_send_sync::<MultiOutputLogger>();
1259        assert_send_sync::<MemorySink>();
1260        assert_send_sync::<ConsoleSink>();
1261    }
1262}