postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! Relay中继投递模块
//!
//! 处理Postfix relay投递代理的事件,包括远程邮件投递、连接管理和投递状态跟踪

use serde::{Deserialize, Serialize};
use std::net::IpAddr;

use super::base::BaseEvent;

/// RELAY组件事件类型
/// 基于Postfix源码和真实生产数据分析
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event_type")]
pub enum RelayEvent {
    /// 邮件投递状态 - 占95%+的relay日志
    DeliveryStatus {
        #[serde(flatten)]
        base: BaseEvent,
        queue_id: String,
        recipient: String,
        relay_host: String,
        relay_ip: Option<IpAddr>,
        relay_port: Option<u16>,
        delay: f64,
        delays: DelayBreakdown,
        dsn: String,
        status: DeliveryStatus,
        status_description: String,
    },
    /// 连接问题事件
    ConnectionIssue {
        #[serde(flatten)]
        base: BaseEvent,
        queue_id: String,
        recipient: String,
        relay_host: String,
        relay_ip: Option<IpAddr>,
        issue_type: ConnectionIssueType,
        error_message: String,
    },
    /// 中继配置事件
    RelayConfiguration {
        #[serde(flatten)]
        base: BaseEvent,
        config_type: RelayConfigType,
        details: String,
    },
}

/// 投递状态类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DeliveryStatus {
    /// 投递成功
    Sent,
    /// 投递延迟/推迟
    Deferred,
    /// 投递反弹
    Bounced,
    /// 投递失败
    Failed,
    /// 拒绝投递
    Rejected,
}

impl std::fmt::Display for DeliveryStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            DeliveryStatus::Sent => write!(f, "sent"),
            DeliveryStatus::Deferred => write!(f, "deferred"),
            DeliveryStatus::Bounced => write!(f, "bounced"),
            DeliveryStatus::Failed => write!(f, "failed"),
            DeliveryStatus::Rejected => write!(f, "rejected"),
        }
    }
}

/// 延迟时间细分 - Postfix特有的delays格式
///
/// 解析Postfix日志中delays字段的"a/b/c/d"格式,提供详细的延迟时间分析
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DelayBreakdown {
    /// 等待在队列中的时间(秒)
    /// 邮件从接收到开始投递处理的等待时间
    pub queue_wait: f64,

    /// 连接建立前的延迟(秒)
    /// 开始连接远程服务器前的准备时间
    pub connection_setup: f64,

    /// 建立连接的时间(秒)
    /// TCP连接和可能的TLS握手所花费的时间
    pub connection_time: f64,

    /// 数据传输时间(秒)
    /// 实际发送邮件内容所花费的时间
    pub transmission_time: f64,
}

/// 连接问题类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConnectionIssueType {
    /// 连接丢失
    LostConnection,
    /// 连接超时
    ConnectionTimeout,
    /// 连接拒绝
    ConnectionRefused,
    /// DNS解析失败
    DnsResolutionFailed,
    /// TLS握手失败
    TlsHandshakeFailed,
    /// 认证失败
    AuthenticationFailed,
    /// 其他连接问题
    Other,
}

impl std::fmt::Display for ConnectionIssueType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConnectionIssueType::LostConnection => write!(f, "lost connection"),
            ConnectionIssueType::ConnectionTimeout => write!(f, "connection timeout"),
            ConnectionIssueType::ConnectionRefused => write!(f, "connection refused"),
            ConnectionIssueType::DnsResolutionFailed => write!(f, "DNS resolution failed"),
            ConnectionIssueType::TlsHandshakeFailed => write!(f, "TLS handshake failed"),
            ConnectionIssueType::AuthenticationFailed => write!(f, "authentication failed"),
            ConnectionIssueType::Other => write!(f, "other connection issue"),
        }
    }
}

/// 中继配置类型
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RelayConfigType {
    /// 中继主机配置
    RelayHostConfig,
    /// 传输映射配置
    TransportMapping,
    /// 认证配置
    AuthConfig,
    /// TLS配置
    TlsConfig,
}

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

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

    /// 获取中继主机(如果存在)
    pub fn relay_host(&self) -> Option<&str> {
        match self {
            RelayEvent::DeliveryStatus { relay_host, .. } => Some(relay_host),
            RelayEvent::ConnectionIssue { relay_host, .. } => Some(relay_host),
            RelayEvent::RelayConfiguration { .. } => None,
        }
    }

    /// 判断是否为投递成功事件
    pub fn is_successful_delivery(&self) -> bool {
        matches!(
            self,
            RelayEvent::DeliveryStatus {
                status: DeliveryStatus::Sent,
                ..
            }
        )
    }

    /// 判断是否为连接问题
    pub fn is_connection_issue(&self) -> bool {
        matches!(self, RelayEvent::ConnectionIssue { .. })
    }

    /// 获取事件严重性级别
    pub fn severity(&self) -> &'static str {
        match self {
            RelayEvent::DeliveryStatus { status, .. } => match status {
                DeliveryStatus::Sent => "info",
                DeliveryStatus::Deferred => "warning",
                DeliveryStatus::Bounced => "error",
                DeliveryStatus::Failed => "error",
                DeliveryStatus::Rejected => "error",
            },
            RelayEvent::ConnectionIssue { .. } => "warning",
            RelayEvent::RelayConfiguration { .. } => "info",
        }
    }
}

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;
        }

        Some(DelayBreakdown {
            queue_wait: parts[0].parse().unwrap_or(0.0),
            connection_setup: parts[1].parse().unwrap_or(0.0),
            connection_time: parts[2].parse().unwrap_or(0.0),
            transmission_time: parts[3].parse().unwrap_or(0.0),
        })
    }

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