postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! Bounce退信处理模块
//!
//! 处理Postfix bounce守护进程的事件,包括邮件退信、延迟通知和递送状态报告

use serde::{Deserialize, Serialize};

use super::base::BaseEvent;

/// BOUNCE事件类型定义
///
/// 基于Postfix BOUNCE守护进程的实际功能开发
/// BOUNCE守护进程负责:
/// - 生成投递失败通知邮件
/// - 生成发送者和邮件管理员通知
/// - 处理格式错误请求
/// - 管理退信和延迟通知的生成
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BounceEvent {
    /// 发送者投递失败通知事件
    ///
    /// 当邮件投递失败时,BOUNCE守护进程为原始发送者生成一个投递失败通知邮件
    /// 这是BOUNCE组件最主要的功能,占95%+的日志量
    ///
    /// 示例日志:
    /// "5FC392A20996: sender non-delivery notification: 732B92A209A3"
    SenderNotification {
        /// 基础事件信息
        base: BaseEvent,
        /// 原始邮件的队列ID
        original_queue_id: String,
        /// 生成的退信通知邮件的队列ID
        bounce_queue_id: String,
    },

    /// 邮件管理员投递失败通知事件
    ///
    /// 当邮件投递失败且需要通知邮件管理员时生成
    /// 通常在复杂的投递失败场景中出现
    ///
    /// 示例日志:
    /// "633F488423: postmaster non-delivery notification: 6DFA788422"
    PostmasterNotification {
        /// 基础事件信息
        base: BaseEvent,
        /// 原始邮件的队列ID
        original_queue_id: String,
        /// 生成的管理员通知邮件的队列ID
        bounce_queue_id: String,
    },

    /// BOUNCE守护进程警告事件
    ///
    /// 处理BOUNCE服务过程中出现的各种警告
    /// 包括格式错误、配置问题、资源限制等
    ///
    /// 示例日志:
    /// "warning: malformed request"
    Warning {
        /// 基础事件信息
        base: BaseEvent,
        /// 警告类型
        warning_type: BounceWarningType,
        /// 警告详细信息
        details: String,
    },
}

/// BOUNCE警告类型枚举
///
/// 定义BOUNCE守护进程可能产生的各种警告类型
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum BounceWarningType {
    /// 格式错误请求
    ///
    /// 当BOUNCE守护进程接收到格式不正确的请求时产生
    /// 这是在真实日志中发现的最常见警告类型
    ///
    /// 示例:
    /// - "warning: malformed request"
    MalformedRequest,

    /// 配置相关警告
    ///
    /// BOUNCE服务配置文件或配置参数相关的警告
    ///
    /// 示例:
    /// - "warning: configuration file not found"
    /// - "warning: invalid configuration parameter"
    Configuration,

    /// 资源限制警告
    ///
    /// 内存、磁盘空间、文件描述符等系统资源相关的警告
    ///
    /// 示例:
    /// - "warning: memory allocation failed"
    /// - "warning: disk space low"
    ResourceLimit,

    /// 其他类型警告
    ///
    /// 不属于上述特定类别的其他警告
    Other,
}

impl BounceEvent {
    /// 获取事件的基础信息
    pub fn base(&self) -> &BaseEvent {
        match self {
            BounceEvent::SenderNotification { base, .. } => base,
            BounceEvent::PostmasterNotification { base, .. } => base,
            BounceEvent::Warning { base, .. } => base,
        }
    }

    /// 获取事件类型的字符串表示
    pub fn event_type(&self) -> &'static str {
        match self {
            BounceEvent::SenderNotification { .. } => "sender_notification",
            BounceEvent::PostmasterNotification { .. } => "postmaster_notification",
            BounceEvent::Warning { .. } => "warning",
        }
    }

    /// 检查是否为通知事件(投递失败通知)
    pub fn is_notification(&self) -> bool {
        matches!(
            self,
            BounceEvent::SenderNotification { .. } | BounceEvent::PostmasterNotification { .. }
        )
    }

    /// 检查是否为警告事件
    pub fn is_warning(&self) -> bool {
        matches!(self, BounceEvent::Warning { .. })
    }

    /// 获取通知相关的队列ID(如果是通知事件)
    pub fn get_queue_ids(&self) -> Option<(&str, &str)> {
        match self {
            BounceEvent::SenderNotification {
                original_queue_id,
                bounce_queue_id,
                ..
            } => Some((original_queue_id, bounce_queue_id)),
            BounceEvent::PostmasterNotification {
                original_queue_id,
                bounce_queue_id,
                ..
            } => Some((original_queue_id, bounce_queue_id)),
            BounceEvent::Warning { .. } => None,
        }
    }

    /// 获取警告信息(如果是警告事件)
    pub fn get_warning_info(&self) -> Option<(&BounceWarningType, &str)> {
        match self {
            BounceEvent::Warning {
                warning_type,
                details,
                ..
            } => Some((warning_type, details)),
            _ => None,
        }
    }

    /// 格式化为人类可读的描述
    pub fn format_description(&self) -> String {
        match self {
            BounceEvent::SenderNotification {
                original_queue_id,
                bounce_queue_id,
                ..
            } => {
                format!(
                    "为原始邮件 {} 生成发送者投递失败通知,通知邮件队列ID: {}",
                    original_queue_id, bounce_queue_id
                )
            }
            BounceEvent::PostmasterNotification {
                original_queue_id,
                bounce_queue_id,
                ..
            } => {
                format!(
                    "为原始邮件 {} 生成邮件管理员投递失败通知,通知邮件队列ID: {}",
                    original_queue_id, bounce_queue_id
                )
            }
            BounceEvent::Warning {
                warning_type,
                details,
                ..
            } => {
                let type_desc = match warning_type {
                    BounceWarningType::MalformedRequest => "格式错误请求",
                    BounceWarningType::Configuration => "配置警告",
                    BounceWarningType::ResourceLimit => "资源限制警告",
                    BounceWarningType::Other => "其他警告",
                };
                format!("{}: {}", type_desc, details)
            }
        }
    }
}

impl BounceWarningType {
    /// 获取警告类型的严重程度(1-5,5为最严重)
    pub fn severity(&self) -> u8 {
        match self {
            BounceWarningType::MalformedRequest => 2, // 中低严重性,通常是客户端问题
            BounceWarningType::Configuration => 4,    // 高严重性,影响服务正常运行
            BounceWarningType::ResourceLimit => 5,    // 最高严重性,可能导致服务不可用
            BounceWarningType::Other => 3,            // 中等严重性,需要关注
        }
    }

    /// 获取警告类型的建议处理方式
    pub fn suggested_action(&self) -> &'static str {
        match self {
            BounceWarningType::MalformedRequest => {
                "检查客户端发送的请求格式,可能需要联系发送方修正"
            }
            BounceWarningType::Configuration => "检查并修正BOUNCE服务相关配置,重启服务使配置生效",
            BounceWarningType::ResourceLimit => "立即检查系统资源使用情况,释放资源或扩容",
            BounceWarningType::Other => "分析具体警告内容,采取相应的处理措施",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::events::base::PostfixLogLevel;
    use chrono::{DateTime, Utc};

    fn create_test_base_event() -> BaseEvent {
        BaseEvent {
            timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48+00:00")
                .unwrap()
                .with_timezone(&Utc),
            hostname: "m01".to_string(),
            component: "bounce".to_string(),
            process_id: 133,
            log_level: PostfixLogLevel::Info,
            raw_message: "test message".to_string(),
        }
    }

    #[test]
    fn test_sender_notification_event() {
        let base = create_test_base_event();
        let event = BounceEvent::SenderNotification {
            base: base.clone(),
            original_queue_id: "5FC392A20996".to_string(),
            bounce_queue_id: "732B92A209A3".to_string(),
        };

        assert_eq!(event.event_type(), "sender_notification");
        assert!(event.is_notification());
        assert!(!event.is_warning());
        assert_eq!(event.base(), &base);

        let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
        assert_eq!(orig_id, "5FC392A20996");
        assert_eq!(bounce_id, "732B92A209A3");

        assert!(event.get_warning_info().is_none());
    }

    #[test]
    fn test_postmaster_notification_event() {
        let base = create_test_base_event();
        let event = BounceEvent::PostmasterNotification {
            base: base.clone(),
            original_queue_id: "633F488423".to_string(),
            bounce_queue_id: "6DFA788422".to_string(),
        };

        assert_eq!(event.event_type(), "postmaster_notification");
        assert!(event.is_notification());
        assert!(!event.is_warning());

        let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
        assert_eq!(orig_id, "633F488423");
        assert_eq!(bounce_id, "6DFA788422");
    }

    #[test]
    fn test_warning_event() {
        let base = create_test_base_event();
        let event = BounceEvent::Warning {
            base: base.clone(),
            warning_type: BounceWarningType::MalformedRequest,
            details: "malformed request".to_string(),
        };

        assert_eq!(event.event_type(), "warning");
        assert!(!event.is_notification());
        assert!(event.is_warning());

        let (warning_type, details) = event.get_warning_info().unwrap();
        assert_eq!(*warning_type, BounceWarningType::MalformedRequest);
        assert_eq!(details, "malformed request");

        assert!(event.get_queue_ids().is_none());
    }

    #[test]
    fn test_warning_type_severity() {
        assert_eq!(BounceWarningType::MalformedRequest.severity(), 2);
        assert_eq!(BounceWarningType::Configuration.severity(), 4);
        assert_eq!(BounceWarningType::ResourceLimit.severity(), 5);
        assert_eq!(BounceWarningType::Other.severity(), 3);
    }

    #[test]
    fn test_format_description() {
        let base = create_test_base_event();

        let sender_event = BounceEvent::SenderNotification {
            base: base.clone(),
            original_queue_id: "5FC392A20996".to_string(),
            bounce_queue_id: "732B92A209A3".to_string(),
        };
        let desc = sender_event.format_description();
        assert!(desc.contains("5FC392A20996"));
        assert!(desc.contains("732B92A209A3"));
        assert!(desc.contains("发送者投递失败通知"));

        let warning_event = BounceEvent::Warning {
            base: base.clone(),
            warning_type: BounceWarningType::MalformedRequest,
            details: "malformed request".to_string(),
        };
        let desc = warning_event.format_description();
        assert!(desc.contains("格式错误请求"));
        assert!(desc.contains("malformed request"));
    }

    #[test]
    fn test_serialization() {
        let base = create_test_base_event();
        let event = BounceEvent::SenderNotification {
            base,
            original_queue_id: "5FC392A20996".to_string(),
            bounce_queue_id: "732B92A209A3".to_string(),
        };

        // 测试序列化
        let serialized = serde_json::to_string(&event).unwrap();
        assert!(
            serialized.contains("sender_notification") || serialized.contains("SenderNotification")
        );

        // 测试反序列化
        let deserialized: BounceEvent = serde_json::from_str(&serialized).unwrap();
        assert_eq!(event, deserialized);
    }
}