postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! Discard邮件丢弃模块
//!
//! 处理Postfix discard传输代理的事件,用于"假装投递"但实际丢弃邮件的场景

use serde::{Deserialize, Serialize};

use super::base::BaseEvent;

/// DISCARD组件事件类型
/// 基于Postfix源码和真实生产数据分析
/// DISCARD是"假装投递"的邮件丢弃代理,不进行实际网络投递
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event_type")]
pub enum DiscardEvent {
    /// 邮件丢弃事件 - DISCARD的主要功能,假装投递但实际丢弃
    MessageDiscard {
        #[serde(flatten)]
        base: BaseEvent,
        queue_id: String,
        recipient: String,
        /// DISCARD总是为"none",表示无实际中继
        relay: String,
        /// 处理延迟时间(秒)
        delay: f64,
        /// 延迟时间细分 (queue/conn_setup/conn/transmission)
        delays: DelayBreakdown,
        /// DSN状态码,通常为2.0.0表示"成功丢弃"
        dsn: String,
        /// 投递状态,DISCARD总是报告为"sent"
        status: String,
        /// 丢弃原因,通常是下一跳目的地或配置的原因
        discard_reason: String,
    },
    /// 配置事件 - DISCARD代理的配置相关事件
    Configuration {
        #[serde(flatten)]
        base: BaseEvent,
        config_type: DiscardConfigType,
        details: String,
    },
}

/// 延迟时间细分 - 与其他投递代理保持一致的格式
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelayBreakdown {
    /// 等待在队列中的时间
    pub queue_wait: f64,
    /// 连接建立前的延迟(DISCARD中通常为0)
    pub connection_setup: f64,
    /// 建立连接的时间(DISCARD中通常为0)
    pub connection_time: f64,
    /// 数据传输时间(DISCARD中通常为0)
    pub transmission_time: f64,
}

/// DISCARD配置类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiscardConfigType {
    /// 传输映射配置
    TransportMapping,
    /// 丢弃规则配置
    DiscardRules,
    /// 服务启动配置
    ServiceStartup,
    /// 其他配置
    Other,
}

impl DiscardEvent {
    /// 获取队列ID(如果存在)
    pub fn queue_id(&self) -> Option<&str> {
        match self {
            DiscardEvent::MessageDiscard { queue_id, .. } => Some(queue_id),
            DiscardEvent::Configuration { .. } => None,
        }
    }

    /// 获取收件人地址(如果存在)
    pub fn recipient(&self) -> Option<&str> {
        match self {
            DiscardEvent::MessageDiscard { recipient, .. } => Some(recipient),
            DiscardEvent::Configuration { .. } => None,
        }
    }

    /// 获取丢弃原因
    pub fn discard_reason(&self) -> Option<&str> {
        match self {
            DiscardEvent::MessageDiscard { discard_reason, .. } => Some(discard_reason),
            DiscardEvent::Configuration { .. } => None,
        }
    }

    /// 判断是否为邮件丢弃事件
    pub fn is_message_discard(&self) -> bool {
        matches!(self, DiscardEvent::MessageDiscard { .. })
    }

    /// 获取事件严重性级别
    pub fn severity(&self) -> &'static str {
        match self {
            DiscardEvent::MessageDiscard { .. } => "info", // 正常的邮件丢弃
            DiscardEvent::Configuration { .. } => "info",  // 配置信息
        }
    }

    /// 检查是否为成功的丢弃操作
    pub fn is_successful_discard(&self) -> bool {
        match self {
            DiscardEvent::MessageDiscard { status, dsn, .. } => {
                status == "sent" && dsn.starts_with("2.") // 2.x.x DSN表示成功
            }
            DiscardEvent::Configuration { .. } => false,
        }
    }
}

impl DelayBreakdown {
    /// 从Postfix的delays字符串解析 (格式: "queue/conn_setup/conn/transmission")
    pub fn from_delays_string(delays_str: &str) -> Option<Self> {
        let parts: Vec<&str> = delays_str.split('/').collect();
        if parts.len() != 4 {
            return None;
        }

        let queue_wait = parts[0].parse().ok()?;
        let connection_setup = parts[1].parse().ok()?;
        let connection_time = parts[2].parse().ok()?;
        let transmission_time = parts[3].parse().ok()?;

        Some(DelayBreakdown {
            queue_wait,
            connection_setup,
            connection_time,
            transmission_time,
        })
    }

    /// 计算总延迟时间
    pub fn total_delay(&self) -> f64 {
        self.queue_wait + self.connection_setup + self.connection_time + self.transmission_time
    }

    /// 判断是否为快速丢弃(总延迟<0.1秒)
    pub fn is_fast_discard(&self) -> bool {
        self.total_delay() < 0.1
    }
}

impl std::fmt::Display for DiscardConfigType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DiscardConfigType::TransportMapping => write!(f, "transport mapping"),
            DiscardConfigType::DiscardRules => write!(f, "discard rules"),
            DiscardConfigType::ServiceStartup => write!(f, "service startup"),
            DiscardConfigType::Other => write!(f, "other"),
        }
    }
}