postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation

//! # Postsuper 邮件队列管理组件解析器
//!
//! Postsuper 是 Postfix 的邮件队列管理工具组件,负责:
//! - 管理邮件队列中的消息
//! - 删除指定的邮件消息
//! - 提供队列维护和清理功能
//! - 统计队列操作的结果
//!
//! ## 核心功能
//!
//! - **邮件删除**: 根据队列 ID 删除特定邮件
//! - **批量操作**: 批量删除多个邮件消息
//! - **队列维护**: 清理过期和无效的队列条目
//! - **操作统计**: 记录删除操作的数量和结果
//!
//! ## 支持的事件类型
//!
//! - **邮件删除**: 单个邮件被成功删除
//! - **批量删除**: 批量删除操作的统计结果
//!
//! ## 示例日志格式
//!
//! ```text
//! # 单个邮件删除
//! 61563640322461696: removed
//!
//! # 批量删除统计
//! Deleted: 1 message
//! Deleted: 5 messages
//! ```

use crate::components::ComponentParser;
use crate::error::ParseError;
use crate::events::{ComponentEvent, PostsuperEvent};
use chrono::Utc;
use regex::Regex;

/// Postsuper组件解析器
pub struct PostsuperParser {
    /// 邮件删除正则表达式 - 匹配 "queue_id: removed"
    message_removed_regex: Regex,
    /// 批量删除正则表达式 - 匹配 "Deleted: N message(s)"
    bulk_deleted_regex: Regex,
}

impl PostsuperParser {
    /// 创建新的Postsuper解析器
    pub fn new() -> Result<Self, ParseError> {
        let message_removed_regex =
            Regex::new(r"^([A-F0-9]+):\s*removed\s*$").map_err(ParseError::RegexError)?;

        let bulk_deleted_regex =
            Regex::new(r"^Deleted:\s*(\d+)\s*messages?\s*$").map_err(ParseError::RegexError)?;

        Ok(Self {
            message_removed_regex,
            bulk_deleted_regex,
        })
    }

    /// 解析邮件删除事件
    fn parse_message_removed(&self, message: &str) -> Option<PostsuperEvent> {
        if let Some(caps) = self.message_removed_regex.captures(message) {
            let queue_id = caps.get(1)?.as_str().to_string();
            return Some(PostsuperEvent::message_removed(Utc::now(), queue_id));
        }
        None
    }

    /// 解析批量删除事件
    fn parse_bulk_deleted(&self, message: &str) -> Option<PostsuperEvent> {
        if let Some(caps) = self.bulk_deleted_regex.captures(message) {
            let count_str = caps.get(1)?.as_str();
            if let Ok(count) = count_str.parse::<u32>() {
                return Some(PostsuperEvent::bulk_deleted(Utc::now(), count));
            }
        }
        None
    }
}

impl ComponentParser for PostsuperParser {
    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
        // 尝试解析邮件删除事件
        if let Some(event) = self.parse_message_removed(message) {
            return Ok(ComponentEvent::Postsuper(event));
        }

        // 尝试解析批量删除事件
        if let Some(event) = self.parse_bulk_deleted(message) {
            return Ok(ComponentEvent::Postsuper(event));
        }

        // 如果都不匹配,返回解析错误
        Err(ParseError::ComponentParseError {
            component: "postsuper".to_string(),
            reason: format!("Unknown message format: {}", message),
        })
    }

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

    fn can_parse(&self, message: &str) -> bool {
        self.message_removed_regex.is_match(message) || self.bulk_deleted_regex.is_match(message)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_message_removed() {
        let parser = PostsuperParser::new().unwrap();

        // 测试邮件删除格式
        let message = "61563640322461696: removed";
        let result = parser.parse(message).unwrap();

        if let ComponentEvent::Postsuper(event) = result {
            assert_eq!(
                event.event_type,
                crate::events::postsuper::PostsuperEventType::MessageRemoved
            );
            assert_eq!(event.queue_id, Some("61563640322461696".to_string()));
            assert_eq!(event.description, Some("removed".to_string()));
        } else {
            panic!("Expected PostsuperEvent::MessageRemoved");
        }
    }

    #[test]
    fn test_parse_bulk_deleted() {
        let parser = PostsuperParser::new().unwrap();

        // 测试批量删除格式
        let message = "Deleted: 1 message";
        let result = parser.parse(message).unwrap();

        if let ComponentEvent::Postsuper(event) = result {
            assert_eq!(
                event.event_type,
                crate::events::postsuper::PostsuperEventType::BulkDeleted
            );
            assert_eq!(event.message_count, Some(1));
            assert_eq!(event.description, Some("Deleted: 1 message".to_string()));
        } else {
            panic!("Expected PostsuperEvent::BulkDeleted");
        }
    }

    #[test]
    fn test_parse_bulk_deleted_multiple() {
        let parser = PostsuperParser::new().unwrap();

        // 测试多个邮件删除格式
        let message = "Deleted: 5 messages";
        let result = parser.parse(message).unwrap();

        if let ComponentEvent::Postsuper(event) = result {
            assert_eq!(
                event.event_type,
                crate::events::postsuper::PostsuperEventType::BulkDeleted
            );
            assert_eq!(event.message_count, Some(5));
            assert_eq!(event.description, Some("Deleted: 5 messages".to_string()));
        } else {
            panic!("Expected PostsuperEvent::BulkDeleted");
        }
    }

    #[test]
    fn test_can_parse() {
        let parser = PostsuperParser::new().unwrap();

        assert!(parser.can_parse("61563640322461696: removed"));
        assert!(parser.can_parse("Deleted: 1 message"));
        assert!(parser.can_parse("Deleted: 10 messages"));
        assert!(!parser.can_parse("some other message"));
    }
}