postfix_log_parser/components/
postmap.rs

1//! POSTMAP组件解析器
2
3use super::ComponentParser;
4use crate::error::ParseError;
5use crate::events::base::BaseEvent;
6use crate::events::postmap::{ErrorType, PostmapDetails, PostmapEvent, PostmapEventType};
7use crate::events::ComponentEvent;
8
9use regex::Regex;
10use std::collections::HashMap;
11
12/// POSTMAP组件解析器
13///
14/// 基于Postfix POSTMAP工具的真实日志格式开发
15/// POSTMAP工具的特点:
16/// - 管理映射表和数据库
17/// - 文件访问错误处理
18/// - 命令参数验证
19/// - Berkeley DB操作
20pub struct PostmapParser {
21    // 文件错误模式
22    file_error_regex: Regex,
23
24    // 命令错误模式
25    command_error_regex: Regex,
26}
27
28impl PostmapParser {
29    pub fn new() -> Self {
30        Self {
31            // 文件错误正则表达式(MasterParser已移除fatal前缀)
32            file_error_regex: Regex::new(r"^open (.+?): (.+)$")
33                .expect("POSTMAP文件错误正则表达式编译失败"),
34
35            // 命令错误正则表达式(MasterParser已移除fatal前缀)
36            command_error_regex: Regex::new(r"^specify .+$")
37                .expect("POSTMAP命令错误正则表达式编译失败"),
38        }
39    }
40
41    /// 解析POSTMAP日志行
42    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
43        // 尝试解析文件错误
44        if let Some(event) = self.parse_file_error(line, base_event.clone()) {
45            return Some(event);
46        }
47
48        // 尝试解析命令错误
49        if let Some(event) = self.parse_command_error(line, base_event) {
50            return Some(event);
51        }
52
53        None
54    }
55
56    fn parse_file_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
57        if let Some(caps) = self.file_error_regex.captures(line) {
58            let file_path = caps.get(1).unwrap().as_str().to_string();
59            let error_message = caps.get(2).unwrap().as_str().to_string();
60
61            // 确定错误类型
62            let error_type = if error_message.contains("No such file or directory") {
63                if file_path.ends_with('/') {
64                    ErrorType::DirectoryNotFound
65                } else {
66                    ErrorType::FileNotFound
67                }
68            } else {
69                ErrorType::FileNotFound
70            };
71
72            let details = PostmapDetails::new_file_error(error_type, file_path, error_message);
73
74            let event = PostmapEvent {
75                timestamp: base_event.timestamp,
76                process_id: base_event.process_id,
77                event_type: PostmapEventType::FileError,
78                details,
79                extensions: HashMap::new(),
80            };
81
82            return Some(event);
83        }
84
85        None
86    }
87
88    fn parse_command_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
89        if self.command_error_regex.is_match(line) {
90            let error_message = line.to_string();
91
92            // 检查是否为文件错误(避免重复解析)
93            if error_message.starts_with("open ") {
94                return None;
95            }
96
97            // 检查是否为命令参数错误
98            let command_args = if error_message.contains("specify -b -h or -m only with") {
99                Some("-b -h -m".to_string())
100            } else {
101                None
102            };
103
104            let details = PostmapDetails::new_command_error(error_message, command_args);
105
106            let event = PostmapEvent {
107                timestamp: base_event.timestamp,
108                process_id: base_event.process_id,
109                event_type: PostmapEventType::CommandError,
110                details,
111                extensions: HashMap::new(),
112            };
113
114            return Some(event);
115        }
116
117        None
118    }
119}
120
121impl ComponentParser for PostmapParser {
122    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
123        // 创建基础事件信息(时间戳和进程ID会在MasterParser中设置)
124        let base_event = BaseEvent {
125            timestamp: chrono::Utc::now(), // 临时值,会被MasterParser覆盖
126            hostname: String::new(),       // 临时值,会被MasterParser覆盖
127            component: "postmap".to_string(),
128            process_id: 0, // 临时值,会被MasterParser覆盖
129            log_level: crate::events::base::PostfixLogLevel::Fatal, // POSTMAP错误通常是fatal级别
130            raw_message: message.to_string(),
131        };
132
133        if let Some(event) = self.parse_line(message, base_event) {
134            Ok(ComponentEvent::Postmap(event))
135        } else {
136            Err(ParseError::ComponentParseError {
137                component: "postmap".to_string(),
138                reason: format!("无法识别的POSTMAP消息格式: {}", message),
139            })
140        }
141    }
142
143    fn component_name(&self) -> &'static str {
144        "postmap"
145    }
146}
147