postfix_log_parser/components/
anvil.rs

1//! # Anvil 连接统计组件解析器
2//!
3//! Anvil 是 Postfix 的连接统计和速率限制组件,负责:
4//! - 监控客户端连接频率和数量
5//! - 统计最大连接速率和消息速率  
6//! - 提供配置警告和系统统计信息
7//! - 支持基于来源的速率限制策略
8//!
9//! ## 支持的事件类型
10//!
11//! - **配置警告**: 配置文件中的参数覆盖警告
12//! - **连接统计**: 最大连接速率、连接数量、消息速率统计
13//! - **缓存统计**: 缓存大小统计信息
14//!
15//! ## 示例日志格式
16//!
17//! ```text
18//! # 配置警告
19//! /etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0
20//!
21//! # 连接统计  
22//! statistics: max connection rate 2/60s for (smtp:192.168.2.127) at Apr 10 11:47:05
23//! statistics: max cache size 1 at Apr 10 11:46:30
24//! ```
25
26use crate::components::ComponentParser;
27use crate::events::anvil::{AnvilEvent, StatisticType};
28use crate::events::ComponentEvent;
29use chrono::{DateTime, Datelike, Utc};
30use lazy_static::lazy_static;
31use regex::Regex;
32
33lazy_static! {
34    // 配置警告:/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0
35    static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
36        r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
37    ).unwrap();
38
39    // 统计信息:statistics: max connection rate 2/60s for (smtp:192.168.2.127) at Apr 10 11:47:05
40    static ref STATISTICS_REGEX: Regex = Regex::new(
41        r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
42    ).unwrap();
43
44    // 缓存大小统计:statistics: max cache size 1 at Apr 10 11:46:30
45    static ref CACHE_SIZE_REGEX: Regex = Regex::new(
46        r"^statistics: max cache size (\d+) at (.+)$"
47    ).unwrap();
48}
49
50pub struct AnvilParser;
51
52impl AnvilParser {
53    pub fn new() -> Self {
54        Self
55    }
56
57    /// 解析配置警告
58    fn parse_config_warning(&self, message: &str) -> Option<AnvilEvent> {
59        if let Some(captures) = CONFIG_WARNING_REGEX.captures(message) {
60            let file_path = captures.get(1)?.as_str().to_string();
61            let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
62            let parameter_name = captures.get(3)?.as_str().to_string();
63            let parameter_value = captures.get(4)?.as_str().to_string();
64
65            let warning_message = format!(
66                "overriding earlier entry: {}={}",
67                parameter_name, parameter_value
68            );
69
70            Some(AnvilEvent::config_warning(
71                Utc::now(),
72                None,
73                file_path,
74                line_number,
75                parameter_name,
76                warning_message,
77            ))
78        } else {
79            None
80        }
81    }
82
83    /// 解析统计信息
84    fn parse_statistics(&self, message: &str) -> Option<AnvilEvent> {
85        // 首先尝试解析缓存大小统计(特殊格式)
86        if let Some(captures) = CACHE_SIZE_REGEX.captures(message) {
87            let value: u32 = captures.get(1)?.as_str().parse().ok()?;
88            let time_str = captures.get(2)?.as_str();
89            let metric_timestamp = self.parse_metric_timestamp(time_str)?;
90
91            return Some(AnvilEvent::statistics(
92                Utc::now(),
93                None,
94                StatisticType::MaxCacheSize,
95                value,
96                None,                 // 缓存大小没有速率窗口
97                "system".to_string(), // 缓存大小是系统级别的
98                metric_timestamp,
99            ));
100        }
101
102        // 然后尝试解析其他统计信息
103        if let Some(captures) = STATISTICS_REGEX.captures(message) {
104            let metric_str = captures.get(1)?.as_str();
105            let value: u32 = captures.get(2)?.as_str().parse().ok()?;
106            let rate_window = captures.get(3).map(|m| m.as_str().to_string());
107            let service_client = captures.get(4)?.as_str().to_string();
108            let time_str = captures.get(5)?.as_str();
109
110            // 解析度量类型
111            let metric_type = match metric_str {
112                "connection rate" => StatisticType::MaxConnectionRate,
113                "connection count" => StatisticType::MaxConnectionCount,
114                "message rate" => StatisticType::MaxMessageRate,
115                _ => return None,
116            };
117
118            // 解析时间戳(格式:Apr 10 11:47:05)
119            let metric_timestamp = self.parse_metric_timestamp(time_str)?;
120
121            Some(AnvilEvent::statistics(
122                Utc::now(),
123                None,
124                metric_type,
125                value,
126                rate_window,
127                service_client,
128                metric_timestamp,
129            ))
130        } else {
131            None
132        }
133    }
134
135    /// 解析度量时间戳
136    fn parse_metric_timestamp(&self, time_str: &str) -> Option<DateTime<Utc>> {
137        use chrono::NaiveDateTime;
138
139        // 新格式:2025 Apr 10 11:47:05.123456
140        // 旧格式:Apr 10 11:47:05
141
142        // 首先尝试解析新格式(包含年份和可选毫秒)
143        if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S%.f") {
144            return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
145        }
146
147        // 尝试解析新格式但没有毫秒
148        if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S") {
149            return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
150        }
151
152        // 兼容旧格式处理
153        let year = Utc::now().year();
154        let datetime_str = format!("{} {}", year, time_str);
155
156        // 尝试解析旧格式(带毫秒)
157        if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S%.f") {
158            return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
159        }
160
161        // 尝试解析旧格式(不带毫秒)
162        if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S") {
163            return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
164        }
165
166        None
167    }
168}
169
170impl ComponentParser for AnvilParser {
171    fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
172        let clean_message = message.trim();
173
174        // 首先尝试解析配置警告
175        if let Some(event) = self.parse_config_warning(clean_message) {
176            return Ok(ComponentEvent::Anvil(event));
177        }
178
179        // 然后尝试解析统计信息
180        if let Some(event) = self.parse_statistics(clean_message) {
181            return Ok(ComponentEvent::Anvil(event));
182        }
183
184        Err(crate::error::ParseError::ComponentParseError {
185            component: "anvil".to_string(),
186            reason: format!("Unable to parse message: {}", message),
187        })
188    }
189
190    fn component_name(&self) -> &'static str {
191        "anvil"
192    }
193}
194
195impl Default for AnvilParser {
196    fn default() -> Self {
197        Self::new()
198    }
199}