postfix_log_parser/components/
relay.rs

1use lazy_static::lazy_static;
2use regex::Regex;
3use std::net::IpAddr;
4use std::str::FromStr;
5
6use super::ComponentParser;
7use crate::error::ParseError;
8use crate::events::base::BaseEvent;
9use crate::events::relay::{
10    ConnectionIssueType, DelayBreakdown, DeliveryStatus, RelayConfigType, RelayEvent,
11};
12use crate::events::ComponentEvent;
13
14/// RELAY组件解析器
15///
16/// 处理Postfix relay/smtp传输代理的日志
17/// relay组件负责通过SMTP将邮件发送到远程或本地目标
18pub struct RelayParser;
19
20lazy_static! {
21    /// 主要投递状态模式 - 匹配95%+的relay日志
22    /// 例如: "4D2952A00AD6: to=<m01@zcloud.center>, relay=mail.zcloud.center[223.223.197.126]:25, delay=88, delays=0/0.01/88/0, dsn=4.4.2, status=deferred (lost connection)"
23    static ref DELIVERY_STATUS_PATTERN: Regex = Regex::new(
24        r"^([A-F0-9]+):\s+to=<([^>]+)>,\s+relay=([^,\[\]]+)(?:\[([^\]]+)\])?(?::(\d+))?,\s+delay=([0-9.]+),\s+delays=([0-9./]+),\s+dsn=([0-9.]+),\s+status=(\w+)\s*\(([^)]*)\)"
25    ).unwrap();
26
27    /// 无法投递模式 - 当没有可用的relay时
28    /// 例如: "4D2952A00AD6: to=<user@domain.com>, orig_to=<user>, relay=none, delay=0, delays=0/0/0/0, dsn=5.4.6, status=bounced (mail for domain.com loops back to myself)"
29    static ref NO_RELAY_PATTERN: Regex = Regex::new(
30        r"^([A-F0-9]+):\s+to=<([^>]+)>(?:,\s+orig_to=<[^>]+>)?,\s+relay=none,\s+delay=([0-9.]+),\s+delays=([0-9./]+),\s+dsn=([0-9.]+),\s+status=(\w+)\s*\(([^)]*)\)"
31    ).unwrap();
32
33    /// 连接问题模式 - 匹配连接失败、超时等问题
34    /// 例如: "warning: smtp_connect_timeout: stream not ready"
35    static ref CONNECTION_ISSUE_PATTERN: Regex = Regex::new(
36        r"^([A-F0-9]+):\s+to=<([^>]+)>,\s+relay=([^,\[\]]+)(?:\[([^\]]+)\])?(?::(\d+))?,.*?(?:connection\s+(lost|refused|timeout|failed)|host\s+unreachable|network\s+unreachable)"
37    ).unwrap();
38
39    /// TLS相关错误模式
40    static ref TLS_ERROR_PATTERN: Regex = Regex::new(
41        r"^([A-F0-9]+):\s+to=<([^>]+)>,\s+relay=([^,\[\]]+).*?(?:TLS|SSL|certificate)"
42    ).unwrap();
43
44    /// 传输映射配置模式
45    static ref TRANSPORT_MAP_PATTERN: Regex = Regex::new(
46        r"transport\s+maps?\s+(?:lookup|configuration|error)"
47    ).unwrap();
48}
49
50impl RelayParser {
51    pub fn new() -> Self {
52        Self
53    }
54
55    /// 解析单行relay日志
56    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<RelayEvent> {
57        // 按频率优先级处理,最常见的模式优先
58
59        // 1. 投递状态模式(最常见,95%+)
60        if let Some(caps) = DELIVERY_STATUS_PATTERN.captures(line) {
61            return self.parse_delivery_status(caps, base_event);
62        }
63
64        // 2. 无relay可用模式
65        if let Some(caps) = NO_RELAY_PATTERN.captures(line) {
66            return self.parse_no_relay_status(caps, base_event);
67        }
68
69        // 3. 连接问题模式
70        if let Some(caps) = CONNECTION_ISSUE_PATTERN.captures(line) {
71            return self.parse_connection_issue(caps, base_event, line);
72        }
73
74        // 4. TLS相关错误
75        if let Some(caps) = TLS_ERROR_PATTERN.captures(line) {
76            return self.parse_tls_issue(caps, base_event, line);
77        }
78
79        // 5. 传输配置相关
80        if TRANSPORT_MAP_PATTERN.is_match(line) {
81            return Some(RelayEvent::RelayConfiguration {
82                base: base_event,
83                config_type: RelayConfigType::TransportMapping,
84                details: line.to_string(),
85            });
86        }
87
88        None
89    }
90
91    /// 解析投递状态信息
92    fn parse_delivery_status(
93        &self,
94        caps: regex::Captures,
95        base_event: BaseEvent,
96    ) -> Option<RelayEvent> {
97        let queue_id = caps.get(1)?.as_str().to_string();
98        let recipient = caps.get(2)?.as_str().to_string();
99        let relay_host = caps.get(3)?.as_str().to_string();
100        let relay_ip = caps.get(4).and_then(|m| IpAddr::from_str(m.as_str()).ok());
101        let relay_port = caps.get(5).and_then(|m| m.as_str().parse().ok());
102        let delay = caps.get(6)?.as_str().parse().ok()?;
103        let delays_str = caps.get(7)?.as_str();
104        let dsn = caps.get(8)?.as_str().to_string();
105        let status_str = caps.get(9)?.as_str();
106        let status_description = caps.get(10)?.as_str().to_string();
107
108        let delays = DelayBreakdown::from_delays_string(delays_str)?;
109        let status = self.parse_delivery_status_type(status_str)?;
110
111        Some(RelayEvent::DeliveryStatus {
112            base: base_event,
113            queue_id,
114            recipient,
115            relay_host,
116            relay_ip,
117            relay_port,
118            delay,
119            delays,
120            dsn,
121            status,
122            status_description,
123        })
124    }
125
126    /// 解析无relay可用的投递状态
127    fn parse_no_relay_status(
128        &self,
129        caps: regex::Captures,
130        base_event: BaseEvent,
131    ) -> Option<RelayEvent> {
132        let queue_id = caps.get(1)?.as_str().to_string();
133        let recipient = caps.get(2)?.as_str().to_string();
134        let delay = caps.get(3)?.as_str().parse().ok()?;
135        let delays_str = caps.get(4)?.as_str();
136        let dsn = caps.get(5)?.as_str().to_string();
137        let status_str = caps.get(6)?.as_str();
138        let status_description = caps.get(7)?.as_str().to_string();
139
140        let delays = DelayBreakdown::from_delays_string(delays_str)?;
141        let status = self.parse_delivery_status_type(status_str)?;
142
143        Some(RelayEvent::DeliveryStatus {
144            base: base_event,
145            queue_id,
146            recipient,
147            relay_host: "none".to_string(),
148            relay_ip: None,
149            relay_port: None,
150            delay,
151            delays,
152            dsn,
153            status,
154            status_description,
155        })
156    }
157
158    /// 解析连接问题
159    fn parse_connection_issue(
160        &self,
161        caps: regex::Captures,
162        base_event: BaseEvent,
163        full_line: &str,
164    ) -> Option<RelayEvent> {
165        let queue_id = caps.get(1)?.as_str().to_string();
166        let recipient = caps.get(2)?.as_str().to_string();
167        let relay_host = caps.get(3)?.as_str().to_string();
168        let relay_ip = caps.get(4).and_then(|m| IpAddr::from_str(m.as_str()).ok());
169
170        let issue_type = if full_line.contains("lost connection") {
171            ConnectionIssueType::LostConnection
172        } else if full_line.contains("connection refused") {
173            ConnectionIssueType::ConnectionRefused
174        } else if full_line.contains("timeout") {
175            ConnectionIssueType::ConnectionTimeout
176        } else if full_line.contains("host unreachable")
177            || full_line.contains("network unreachable")
178        {
179            ConnectionIssueType::DnsResolutionFailed
180        } else {
181            ConnectionIssueType::Other
182        };
183
184        Some(RelayEvent::ConnectionIssue {
185            base: base_event,
186            queue_id,
187            recipient,
188            relay_host,
189            relay_ip,
190            issue_type,
191            error_message: full_line.to_string(),
192        })
193    }
194
195    /// 解析TLS相关问题
196    fn parse_tls_issue(
197        &self,
198        caps: regex::Captures,
199        base_event: BaseEvent,
200        full_line: &str,
201    ) -> Option<RelayEvent> {
202        let queue_id = caps.get(1)?.as_str().to_string();
203        let recipient = caps.get(2)?.as_str().to_string();
204        let relay_host = caps.get(3)?.as_str().to_string();
205
206        Some(RelayEvent::ConnectionIssue {
207            base: base_event,
208            queue_id,
209            recipient,
210            relay_host,
211            relay_ip: None,
212            issue_type: ConnectionIssueType::TlsHandshakeFailed,
213            error_message: full_line.to_string(),
214        })
215    }
216
217    /// 解析投递状态类型
218    fn parse_delivery_status_type(&self, status_str: &str) -> Option<DeliveryStatus> {
219        match status_str.to_lowercase().as_str() {
220            "sent" => Some(DeliveryStatus::Sent),
221            "deferred" => Some(DeliveryStatus::Deferred),
222            "bounced" => Some(DeliveryStatus::Bounced),
223            "failed" => Some(DeliveryStatus::Failed),
224            "rejected" => Some(DeliveryStatus::Rejected),
225            _ => None,
226        }
227    }
228
229    /// 获取解析器信息
230    pub fn info(&self) -> &'static str {
231        "RELAY组件解析器 - 处理Postfix relay/smtp传输代理日志,支持投递状态、连接问题和配置事件"
232    }
233}
234
235impl ComponentParser for RelayParser {
236    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
237        // 创建一个临时的BaseEvent,用于解析
238        // 在实际使用中,这些字段会被MasterParser正确填充
239        let base_event = BaseEvent {
240            timestamp: chrono::Utc::now(),
241            hostname: "temp".to_string(),
242            component: "relay".to_string(),
243            process_id: 0,
244            log_level: crate::events::base::PostfixLogLevel::Info,
245            raw_message: message.to_string(),
246        };
247
248        if let Some(relay_event) = self.parse_line(message, base_event) {
249            Ok(ComponentEvent::Relay(relay_event))
250        } else {
251            Err(ParseError::ComponentParseError {
252                component: "relay".to_string(),
253                reason: "无法识别的relay日志格式".to_string(),
254            })
255        }
256    }
257
258    fn component_name(&self) -> &'static str {
259        "relay"
260    }
261
262    fn can_parse(&self, message: &str) -> bool {
263        // 检查是否包含relay特征
264        DELIVERY_STATUS_PATTERN.is_match(message)
265            || NO_RELAY_PATTERN.is_match(message)
266            || CONNECTION_ISSUE_PATTERN.is_match(message)
267            || TLS_ERROR_PATTERN.is_match(message)
268            || TRANSPORT_MAP_PATTERN.is_match(message)
269    }
270}