postfix_log_parser/events/
relay.rs

1//! Relay中继投递模块
2//!
3//! 处理Postfix relay投递代理的事件,包括远程邮件投递、连接管理和投递状态跟踪
4
5use serde::{Deserialize, Serialize};
6use std::net::IpAddr;
7
8use super::base::BaseEvent;
9
10/// RELAY组件事件类型
11/// 基于Postfix源码和真实生产数据分析
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "event_type")]
14pub enum RelayEvent {
15    /// 邮件投递状态 - 占95%+的relay日志
16    DeliveryStatus {
17        #[serde(flatten)]
18        base: BaseEvent,
19        queue_id: String,
20        recipient: String,
21        relay_host: String,
22        relay_ip: Option<IpAddr>,
23        relay_port: Option<u16>,
24        delay: f64,
25        delays: DelayBreakdown,
26        dsn: String,
27        status: DeliveryStatus,
28        status_description: String,
29    },
30    /// 连接问题事件
31    ConnectionIssue {
32        #[serde(flatten)]
33        base: BaseEvent,
34        queue_id: String,
35        recipient: String,
36        relay_host: String,
37        relay_ip: Option<IpAddr>,
38        issue_type: ConnectionIssueType,
39        error_message: String,
40    },
41    /// 中继配置事件
42    RelayConfiguration {
43        #[serde(flatten)]
44        base: BaseEvent,
45        config_type: RelayConfigType,
46        details: String,
47    },
48}
49
50/// 投递状态类型
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[serde(rename_all = "lowercase")]
53pub enum DeliveryStatus {
54    /// 投递成功
55    Sent,
56    /// 投递延迟/推迟
57    Deferred,
58    /// 投递反弹
59    Bounced,
60    /// 投递失败
61    Failed,
62    /// 拒绝投递
63    Rejected,
64}
65
66impl std::fmt::Display for DeliveryStatus {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        match self {
69            DeliveryStatus::Sent => write!(f, "sent"),
70            DeliveryStatus::Deferred => write!(f, "deferred"),
71            DeliveryStatus::Bounced => write!(f, "bounced"),
72            DeliveryStatus::Failed => write!(f, "failed"),
73            DeliveryStatus::Rejected => write!(f, "rejected"),
74        }
75    }
76}
77
78/// 延迟时间细分 - Postfix特有的delays格式
79///
80/// 解析Postfix日志中delays字段的"a/b/c/d"格式,提供详细的延迟时间分析
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct DelayBreakdown {
83    /// 等待在队列中的时间(秒)
84    /// 邮件从接收到开始投递处理的等待时间
85    pub queue_wait: f64,
86
87    /// 连接建立前的延迟(秒)
88    /// 开始连接远程服务器前的准备时间
89    pub connection_setup: f64,
90
91    /// 建立连接的时间(秒)
92    /// TCP连接和可能的TLS握手所花费的时间
93    pub connection_time: f64,
94
95    /// 数据传输时间(秒)
96    /// 实际发送邮件内容所花费的时间
97    pub transmission_time: f64,
98}
99
100/// 连接问题类型
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "snake_case")]
103pub enum ConnectionIssueType {
104    /// 连接丢失
105    LostConnection,
106    /// 连接超时
107    ConnectionTimeout,
108    /// 连接拒绝
109    ConnectionRefused,
110    /// DNS解析失败
111    DnsResolutionFailed,
112    /// TLS握手失败
113    TlsHandshakeFailed,
114    /// 认证失败
115    AuthenticationFailed,
116    /// 其他连接问题
117    Other,
118}
119
120impl std::fmt::Display for ConnectionIssueType {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        match self {
123            ConnectionIssueType::LostConnection => write!(f, "lost connection"),
124            ConnectionIssueType::ConnectionTimeout => write!(f, "connection timeout"),
125            ConnectionIssueType::ConnectionRefused => write!(f, "connection refused"),
126            ConnectionIssueType::DnsResolutionFailed => write!(f, "DNS resolution failed"),
127            ConnectionIssueType::TlsHandshakeFailed => write!(f, "TLS handshake failed"),
128            ConnectionIssueType::AuthenticationFailed => write!(f, "authentication failed"),
129            ConnectionIssueType::Other => write!(f, "other connection issue"),
130        }
131    }
132}
133
134/// 中继配置类型
135#[derive(Debug, Clone, Serialize, Deserialize)]
136#[serde(rename_all = "snake_case")]
137pub enum RelayConfigType {
138    /// 中继主机配置
139    RelayHostConfig,
140    /// 传输映射配置
141    TransportMapping,
142    /// 认证配置
143    AuthConfig,
144    /// TLS配置
145    TlsConfig,
146}
147
148impl RelayEvent {
149    /// 获取队列ID(如果存在)
150    pub fn queue_id(&self) -> Option<&str> {
151        match self {
152            RelayEvent::DeliveryStatus { queue_id, .. } => Some(queue_id),
153            RelayEvent::ConnectionIssue { queue_id, .. } => Some(queue_id),
154            RelayEvent::RelayConfiguration { .. } => None,
155        }
156    }
157
158    /// 获取收件人地址(如果存在)
159    pub fn recipient(&self) -> Option<&str> {
160        match self {
161            RelayEvent::DeliveryStatus { recipient, .. } => Some(recipient),
162            RelayEvent::ConnectionIssue { recipient, .. } => Some(recipient),
163            RelayEvent::RelayConfiguration { .. } => None,
164        }
165    }
166
167    /// 获取中继主机(如果存在)
168    pub fn relay_host(&self) -> Option<&str> {
169        match self {
170            RelayEvent::DeliveryStatus { relay_host, .. } => Some(relay_host),
171            RelayEvent::ConnectionIssue { relay_host, .. } => Some(relay_host),
172            RelayEvent::RelayConfiguration { .. } => None,
173        }
174    }
175
176    /// 判断是否为投递成功事件
177    pub fn is_successful_delivery(&self) -> bool {
178        matches!(
179            self,
180            RelayEvent::DeliveryStatus {
181                status: DeliveryStatus::Sent,
182                ..
183            }
184        )
185    }
186
187    /// 判断是否为连接问题
188    pub fn is_connection_issue(&self) -> bool {
189        matches!(self, RelayEvent::ConnectionIssue { .. })
190    }
191
192    /// 获取事件严重性级别
193    pub fn severity(&self) -> &'static str {
194        match self {
195            RelayEvent::DeliveryStatus { status, .. } => match status {
196                DeliveryStatus::Sent => "info",
197                DeliveryStatus::Deferred => "warning",
198                DeliveryStatus::Bounced => "error",
199                DeliveryStatus::Failed => "error",
200                DeliveryStatus::Rejected => "error",
201            },
202            RelayEvent::ConnectionIssue { .. } => "warning",
203            RelayEvent::RelayConfiguration { .. } => "info",
204        }
205    }
206}
207
208impl DelayBreakdown {
209    /// 从Postfix的delays字符串解析 (格式: "queue/conn_setup/conn/transmission")
210    pub fn from_delays_string(delays_str: &str) -> Option<Self> {
211        let parts: Vec<&str> = delays_str.split('/').collect();
212        if parts.len() != 4 {
213            return None;
214        }
215
216        Some(DelayBreakdown {
217            queue_wait: parts[0].parse().unwrap_or(0.0),
218            connection_setup: parts[1].parse().unwrap_or(0.0),
219            connection_time: parts[2].parse().unwrap_or(0.0),
220            transmission_time: parts[3].parse().unwrap_or(0.0),
221        })
222    }
223
224    /// 获取总延迟时间
225    pub fn total_delay(&self) -> f64 {
226        self.queue_wait + self.connection_setup + self.connection_time + self.transmission_time
227    }
228}