postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! # Postmap 映射表管理组件解析器
//!
//! Postmap 是 Postfix 的映射表管理工具组件,负责:
//! - 创建和维护 Berkeley DB 映射表
//! - 处理别名、虚拟域和传输映射
//! - 验证映射表文件的完整性
//! - 提供映射表操作的错误诊断
//!
//! ## 核心功能
//!
//! - **映射表管理**: 创建、更新、验证各种映射表
//! - **数据库操作**: Berkeley DB、Hash、CDB 格式支持
//! - **文件访问**: 映射源文件的读取和验证
//! - **错误诊断**: 详细的文件和命令错误报告
//!
//! ## 支持的事件类型
//!
//! - **文件错误**: 映射文件访问失败、权限问题
//! - **命令错误**: 参数验证失败、命令格式错误
//!
//! ## 示例日志格式
//!
//! ```text
//! # 文件错误
//! fatal: open /etc/postfix/virtual: No such file or directory
//! fatal: open /etc/postfix/transport/: No such file or directory
//!
//! # 命令错误
//! fatal: specify -b -h or -m only with supported file types
//! ```

use super::ComponentParser;
use crate::error::ParseError;
use crate::events::base::BaseEvent;
use crate::events::postmap::{ErrorType, PostmapDetails, PostmapEvent, PostmapEventType};
use crate::events::ComponentEvent;

use regex::Regex;
use std::collections::HashMap;

/// POSTMAP组件解析器
///
/// 基于Postfix POSTMAP工具的真实日志格式开发
/// POSTMAP工具的特点:
/// - 管理映射表和数据库
/// - 文件访问错误处理
/// - 命令参数验证
/// - Berkeley DB操作
pub struct PostmapParser {
    // 文件错误模式
    file_error_regex: Regex,

    // 命令错误模式
    command_error_regex: Regex,
}

impl PostmapParser {
    pub fn new() -> Self {
        Self {
            // 文件错误正则表达式(MasterParser已移除fatal前缀)
            file_error_regex: Regex::new(r"^open (.+?): (.+)$")
                .expect("POSTMAP文件错误正则表达式编译失败"),

            // 命令错误正则表达式(MasterParser已移除fatal前缀)
            command_error_regex: Regex::new(r"^specify .+$")
                .expect("POSTMAP命令错误正则表达式编译失败"),
        }
    }

    /// 解析POSTMAP日志行
    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
        // 尝试解析文件错误
        if let Some(event) = self.parse_file_error(line, base_event.clone()) {
            return Some(event);
        }

        // 尝试解析命令错误
        if let Some(event) = self.parse_command_error(line, base_event) {
            return Some(event);
        }

        None
    }

    fn parse_file_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
        if let Some(caps) = self.file_error_regex.captures(line) {
            let file_path = caps.get(1).unwrap().as_str().to_string();
            let error_message = caps.get(2).unwrap().as_str().to_string();

            // 确定错误类型
            let error_type = if error_message.contains("No such file or directory") {
                if file_path.ends_with('/') {
                    ErrorType::DirectoryNotFound
                } else {
                    ErrorType::FileNotFound
                }
            } else {
                ErrorType::FileNotFound
            };

            let details = PostmapDetails::new_file_error(error_type, file_path, error_message);

            let event = PostmapEvent {
                timestamp: base_event.timestamp,
                process_id: base_event.process_id,
                event_type: PostmapEventType::FileError,
                details,
                extensions: HashMap::new(),
            };

            return Some(event);
        }

        None
    }

    fn parse_command_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
        if self.command_error_regex.is_match(line) {
            let error_message = line.to_string();

            // 检查是否为文件错误(避免重复解析)
            if error_message.starts_with("open ") {
                return None;
            }

            // 检查是否为命令参数错误
            let command_args = if error_message.contains("specify -b -h or -m only with") {
                Some("-b -h -m".to_string())
            } else {
                None
            };

            let details = PostmapDetails::new_command_error(error_message, command_args);

            let event = PostmapEvent {
                timestamp: base_event.timestamp,
                process_id: base_event.process_id,
                event_type: PostmapEventType::CommandError,
                details,
                extensions: HashMap::new(),
            };

            return Some(event);
        }

        None
    }
}

impl ComponentParser for PostmapParser {
    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
        // 创建基础事件信息(时间戳和进程ID会在MasterParser中设置)
        let base_event = BaseEvent {
            timestamp: chrono::Utc::now(), // 临时值,会被MasterParser覆盖
            hostname: String::new(),       // 临时值,会被MasterParser覆盖
            component: "postmap".to_string(),
            process_id: 0, // 临时值,会被MasterParser覆盖
            log_level: crate::events::base::PostfixLogLevel::Fatal, // POSTMAP错误通常是fatal级别
            raw_message: message.to_string(),
        };

        if let Some(event) = self.parse_line(message, base_event) {
            Ok(ComponentEvent::Postmap(event))
        } else {
            Err(ParseError::ComponentParseError {
                component: "postmap".to_string(),
                reason: format!("无法识别的POSTMAP消息格式: {}", message),
            })
        }
    }

    fn component_name(&self) -> &'static str {
        "postmap"
    }
}

impl Default for PostmapParser {
    fn default() -> Self {
        Self::new()
    }
}