postfix_log_parser/components/
anvil.rs

1use crate::components::ComponentParser;
2use crate::events::anvil::{AnvilEvent, StatisticType};
3use crate::events::ComponentEvent;
4use chrono::{DateTime, Datelike, TimeZone, Utc};
5use lazy_static::lazy_static;
6use regex::Regex;
7use std::collections::HashMap;
8
9lazy_static! {
10    // 配置警告:/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0
11    static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
12        r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
13    ).unwrap();
14
15    // 统计信息:statistics: max connection rate 2/60s for (smtp:192.168.2.127) at Apr 10 11:47:05
16    static ref STATISTICS_REGEX: Regex = Regex::new(
17        r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
18    ).unwrap();
19
20    // 缓存大小统计:statistics: max cache size 1 at Apr 10 11:46:30
21    static ref CACHE_SIZE_REGEX: Regex = Regex::new(
22        r"^statistics: max cache size (\d+) at (.+)$"
23    ).unwrap();
24}
25
26pub struct AnvilParser;
27
28impl AnvilParser {
29    pub fn new() -> Self {
30        Self
31    }
32
33    /// 解析配置警告
34    fn parse_config_warning(&self, message: &str) -> Option<AnvilEvent> {
35        if let Some(captures) = CONFIG_WARNING_REGEX.captures(message) {
36            let file_path = captures.get(1)?.as_str().to_string();
37            let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
38            let parameter_name = captures.get(3)?.as_str().to_string();
39            let parameter_value = captures.get(4)?.as_str().to_string();
40
41            let warning_message = format!(
42                "overriding earlier entry: {}={}",
43                parameter_name, parameter_value
44            );
45
46            Some(AnvilEvent::config_warning(
47                Utc::now(),
48                None,
49                file_path,
50                line_number,
51                parameter_name,
52                warning_message,
53            ))
54        } else {
55            None
56        }
57    }
58
59    /// 解析统计信息
60    fn parse_statistics(&self, message: &str) -> Option<AnvilEvent> {
61        // 首先尝试解析缓存大小统计(特殊格式)
62        if let Some(captures) = CACHE_SIZE_REGEX.captures(message) {
63            let value: u32 = captures.get(1)?.as_str().parse().ok()?;
64            let time_str = captures.get(2)?.as_str();
65            let metric_timestamp = self.parse_metric_timestamp(time_str)?;
66
67            return Some(AnvilEvent::statistics(
68                Utc::now(),
69                None,
70                StatisticType::MaxCacheSize,
71                value,
72                None,                 // 缓存大小没有速率窗口
73                "system".to_string(), // 缓存大小是系统级别的
74                metric_timestamp,
75            ));
76        }
77
78        // 然后尝试解析其他统计信息
79        if let Some(captures) = STATISTICS_REGEX.captures(message) {
80            let metric_str = captures.get(1)?.as_str();
81            let value: u32 = captures.get(2)?.as_str().parse().ok()?;
82            let rate_window = captures.get(3).map(|m| m.as_str().to_string());
83            let service_client = captures.get(4)?.as_str().to_string();
84            let time_str = captures.get(5)?.as_str();
85
86            // 解析度量类型
87            let metric_type = match metric_str {
88                "connection rate" => StatisticType::MaxConnectionRate,
89                "connection count" => StatisticType::MaxConnectionCount,
90                "message rate" => StatisticType::MaxMessageRate,
91                _ => return None,
92            };
93
94            // 解析时间戳(格式:Apr 10 11:47:05)
95            let metric_timestamp = self.parse_metric_timestamp(time_str)?;
96
97            Some(AnvilEvent::statistics(
98                Utc::now(),
99                None,
100                metric_type,
101                value,
102                rate_window,
103                service_client,
104                metric_timestamp,
105            ))
106        } else {
107            None
108        }
109    }
110
111    /// 解析度量时间戳(简化处理,使用当前年份)
112    fn parse_metric_timestamp(&self, time_str: &str) -> Option<DateTime<Utc>> {
113        // 格式:Apr 10 11:47:05
114        // 使用当前年份
115        let year = Utc::now().year();
116
117        // 月份映射
118        let month_map: HashMap<&str, u32> = [
119            ("Jan", 1),
120            ("Feb", 2),
121            ("Mar", 3),
122            ("Apr", 4),
123            ("May", 5),
124            ("Jun", 6),
125            ("Jul", 7),
126            ("Aug", 8),
127            ("Sep", 9),
128            ("Oct", 10),
129            ("Nov", 11),
130            ("Dec", 12),
131        ]
132        .iter()
133        .cloned()
134        .collect();
135
136        let parts: Vec<&str> = time_str.split_whitespace().collect();
137        if parts.len() != 3 {
138            return None;
139        }
140
141        let month_str = parts[0];
142        let day: u32 = parts[1].parse().ok()?;
143        let time_parts: Vec<&str> = parts[2].split(':').collect();
144
145        if time_parts.len() != 3 {
146            return None;
147        }
148
149        let hour: u32 = time_parts[0].parse().ok()?;
150        let minute: u32 = time_parts[1].parse().ok()?;
151        let second: u32 = time_parts[2].parse().ok()?;
152
153        let month = month_map.get(month_str)?;
154
155        Utc.with_ymd_and_hms(year, *month, day, hour, minute, second)
156            .single()
157    }
158}
159
160impl ComponentParser for AnvilParser {
161    fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
162        let clean_message = message.trim();
163
164        // 首先尝试解析配置警告
165        if let Some(event) = self.parse_config_warning(clean_message) {
166            return Ok(ComponentEvent::Anvil(event));
167        }
168
169        // 然后尝试解析统计信息
170        if let Some(event) = self.parse_statistics(clean_message) {
171            return Ok(ComponentEvent::Anvil(event));
172        }
173
174        Err(crate::error::ParseError::ComponentParseError {
175            component: "anvil".to_string(),
176            reason: format!("Unable to parse message: {}", message),
177        })
178    }
179
180    fn component_name(&self) -> &'static str {
181        "anvil"
182    }
183}