postfix_log_parser/components/
local.rs

1//! 本地投递(local)组件解析器
2
3use crate::error::ParseError;
4use crate::events::local::{LocalEvent, ConfigurationWarning, LocalDelivery, ExternalDelivery, WarningType, DeliveryStatus, DeliveryMethod};
5use crate::events::base::BaseEvent;
6use crate::events::ComponentEvent;
7use super::ComponentParser;
8
9use regex::Regex;
10use std::collections::HashMap;
11
12/// LOCAL组件解析器
13///
14/// 基于Postfix LOCAL守护进程的真实日志格式开发
15/// LOCAL守护进程的特点:
16/// - 处理本地邮件投递
17/// - 管理别名和转发
18/// - 处理配置警告
19/// - 执行外部命令和文件投递
20pub struct LocalParser {
21    // 配置警告模式
22    nis_domain_regex: Regex,
23    alias_not_found_regex: Regex,
24    
25    // 本地投递模式
26    local_delivery_regex: Regex,
27    
28    // 外部投递模式
29    external_delivery_regex: Regex,
30}
31
32impl LocalParser {
33    pub fn new() -> Self {
34        Self {
35            // 配置警告正则表达式(MasterParser已剥离"warning:"前缀)
36            nis_domain_regex: Regex::new(
37                r"^dict_nis_init: NIS domain name not set - NIS lookups disabled$"
38            ).expect("LOCAL NIS域名警告正则表达式编译失败"),
39            
40            alias_not_found_regex: Regex::new(
41                r"^required alias not found: (.+)$"
42            ).expect("LOCAL别名未找到警告正则表达式编译失败"),
43            
44            // 本地投递正则表达式
45            local_delivery_regex: Regex::new(
46                r"^([0-9A-F]+): to=<([^>]+)>(?:, orig_to=<([^>]+)>)?, relay=([^,]+), delay=([0-9.]+), delays=([0-9.]+/[0-9.]+/[0-9.]+/[0-9.]+), dsn=([^,]+), status=(\w+)(?: \(([^)]+)\))?$"
47            ).expect("LOCAL本地投递正则表达式编译失败"),
48            
49            // 外部投递正则表达式
50            external_delivery_regex: Regex::new(
51                r"([0-9A-F]+): to=<([^>]+)>, relay=([^,]+), delay=([0-9.]+), (?:cmd|file)=(.+), status=(\w+)"
52            ).expect("LOCAL外部投递正则表达式编译失败"),
53        }
54    }
55
56    /// 解析LOCAL日志行
57    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
58        // 尝试解析配置警告
59        if let Some(event) = self.parse_configuration_warning(line, base_event.clone()) {
60            return Some(event);
61        }
62
63        // 尝试解析本地投递
64        if let Some(event) = self.parse_local_delivery(line, base_event.clone()) {
65            return Some(event);
66        }
67
68        // 尝试解析外部投递
69        if let Some(event) = self.parse_external_delivery(line, base_event) {
70            return Some(event);
71        }
72
73        None
74    }
75
76    fn parse_configuration_warning(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
77        // NIS域名未设置警告
78        if self.nis_domain_regex.is_match(line) {
79            let warning = ConfigurationWarning {
80                timestamp: base_event.timestamp,
81                warning_type: WarningType::NisDomainNotSet,
82                message: "NIS domain name not set - NIS lookups disabled".to_string(),
83                details: HashMap::new(),
84            };
85            return Some(LocalEvent::ConfigurationWarning(warning));
86        }
87
88        // 必需别名未找到警告
89        if let Some(caps) = self.alias_not_found_regex.captures(line) {
90            let alias_name = caps.get(1).unwrap().as_str();
91            let mut details = HashMap::new();
92            details.insert("alias_name".to_string(), alias_name.to_string());
93            
94            let warning = ConfigurationWarning {
95                timestamp: base_event.timestamp,
96                warning_type: WarningType::RequiredAliasNotFound,
97                message: format!("required alias not found: {}", alias_name),
98                details,
99            };
100            return Some(LocalEvent::ConfigurationWarning(warning));
101        }
102
103        None
104    }
105
106    fn parse_local_delivery(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
107        if let Some(caps) = self.local_delivery_regex.captures(line) {
108            let queue_id = caps.get(1).unwrap().as_str().to_string();
109            let recipient = caps.get(2).unwrap().as_str().to_string();
110            let original_recipient = caps.get(3).map(|m| m.as_str().to_string());
111            let relay = caps.get(4).unwrap().as_str().to_string();
112            let delay = caps.get(5).unwrap().as_str().parse::<f64>().unwrap_or(0.0);
113            let delays_str = caps.get(6).unwrap().as_str();
114            let dsn = caps.get(7).unwrap().as_str().to_string();
115            let status_str = caps.get(8).unwrap().as_str();
116            let extra_info = caps.get(9).map(|m| m.as_str());
117
118            // 解析delays
119            let delays: Vec<f64> = delays_str
120                .split('/')
121                .map(|s| s.parse::<f64>().unwrap_or(0.0))
122                .collect();
123
124            // 确定投递状态
125            let status = match status_str.to_lowercase().as_str() {
126                "sent" => DeliveryStatus::Sent,
127                "bounced" => DeliveryStatus::Bounced,
128                "deferred" => DeliveryStatus::Deferred,
129                _ => DeliveryStatus::Sent,
130            };
131
132            // 确定投递方法
133            let delivery_method = if let Some(info) = extra_info {
134                if info.contains("discarded") {
135                    DeliveryMethod::Discarded
136                } else if info.contains("forwarded") {
137                    DeliveryMethod::Forwarded
138                } else if info.contains("piped") {
139                    DeliveryMethod::Piped
140                } else if info.contains("file") {
141                    DeliveryMethod::File
142                } else {
143                    DeliveryMethod::Mailbox
144                }
145            } else {
146                DeliveryMethod::Mailbox
147            };
148
149            let delivery = LocalDelivery {
150                timestamp: base_event.timestamp,
151                queue_id,
152                recipient,
153                original_recipient,
154                relay,
155                delay,
156                delays,
157                dsn,
158                status,
159                delivery_method,
160                size: None,
161                nrcpt: None,
162            };
163
164            return Some(LocalEvent::LocalDelivery(delivery));
165        }
166
167        None
168    }
169
170    fn parse_external_delivery(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
171        if let Some(caps) = self.external_delivery_regex.captures(line) {
172            let queue_id = caps.get(1).unwrap().as_str().to_string();
173            let recipient = caps.get(2).unwrap().as_str().to_string();
174            let relay = caps.get(3).unwrap().as_str().to_string();
175            let delay = caps.get(4).unwrap().as_str().parse::<f64>().unwrap_or(0.0);
176            let external_target = caps.get(5).unwrap().as_str().to_string();
177            let status_str = caps.get(6).unwrap().as_str();
178
179            let status = match status_str.to_lowercase().as_str() {
180                "sent" => DeliveryStatus::Sent,
181                "bounced" => DeliveryStatus::Bounced,
182                "deferred" => DeliveryStatus::Deferred,
183                _ => DeliveryStatus::Sent,
184            };
185
186            let mut details = HashMap::new();
187            details.insert("relay".to_string(), relay);
188
189            let (command, file_path) = if external_target.starts_with('/') {
190                // 文件路径
191                (None, Some(external_target))
192            } else {
193                // 命令
194                (Some(external_target), None)
195            };
196
197            let delivery = ExternalDelivery {
198                timestamp: base_event.timestamp,
199                queue_id,
200                recipient,
201                original_recipient: None,
202                command,
203                file_path,
204                status,
205                delay,
206                details,
207            };
208
209            return Some(LocalEvent::ExternalDelivery(delivery));
210        }
211
212        None
213    }
214}
215
216impl ComponentParser for LocalParser {
217    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
218        // 创建一个临时的BaseEvent,用于解析
219        // 在实际使用中,这些字段会被MasterParser正确填充
220        let base_event = BaseEvent {
221            timestamp: chrono::Utc::now(),
222            hostname: "temp".to_string(),
223            component: "local".to_string(),
224            process_id: 0,
225            log_level: crate::events::base::PostfixLogLevel::Info,
226            raw_message: message.to_string(),
227        };
228
229        if let Some(local_event) = self.parse_line(message, base_event) {
230            Ok(ComponentEvent::Local(local_event))
231        } else {
232            Err(ParseError::ComponentParseError {
233                component: "local".to_string(),
234                reason: "无法识别的local日志格式".to_string(),
235        })
236        }
237    }
238
239    fn component_name(&self) -> &'static str {
240        "local"
241    }
242}
243
244impl Default for LocalParser {
245    fn default() -> Self {
246        Self::new()
247    }
248}