postfix_log_parser/events/
bounce.rs

1//! Bounce退信处理模块
2//!
3//! 处理Postfix bounce守护进程的事件,包括邮件退信、延迟通知和递送状态报告
4
5use serde::{Deserialize, Serialize};
6
7use super::base::BaseEvent;
8
9/// BOUNCE事件类型定义
10///
11/// 基于Postfix BOUNCE守护进程的实际功能开发
12/// BOUNCE守护进程负责:
13/// - 生成投递失败通知邮件
14/// - 生成发送者和邮件管理员通知
15/// - 处理格式错误请求
16/// - 管理退信和延迟通知的生成
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18pub enum BounceEvent {
19    /// 发送者投递失败通知事件
20    ///
21    /// 当邮件投递失败时,BOUNCE守护进程为原始发送者生成一个投递失败通知邮件
22    /// 这是BOUNCE组件最主要的功能,占95%+的日志量
23    ///
24    /// 示例日志:
25    /// "5FC392A20996: sender non-delivery notification: 732B92A209A3"
26    SenderNotification {
27        /// 基础事件信息
28        base: BaseEvent,
29        /// 原始邮件的队列ID
30        original_queue_id: String,
31        /// 生成的退信通知邮件的队列ID
32        bounce_queue_id: String,
33    },
34
35    /// 邮件管理员投递失败通知事件
36    ///
37    /// 当邮件投递失败且需要通知邮件管理员时生成
38    /// 通常在复杂的投递失败场景中出现
39    ///
40    /// 示例日志:
41    /// "633F488423: postmaster non-delivery notification: 6DFA788422"
42    PostmasterNotification {
43        /// 基础事件信息
44        base: BaseEvent,
45        /// 原始邮件的队列ID
46        original_queue_id: String,
47        /// 生成的管理员通知邮件的队列ID
48        bounce_queue_id: String,
49    },
50
51    /// BOUNCE守护进程警告事件
52    ///
53    /// 处理BOUNCE服务过程中出现的各种警告
54    /// 包括格式错误、配置问题、资源限制等
55    ///
56    /// 示例日志:
57    /// "warning: malformed request"
58    Warning {
59        /// 基础事件信息
60        base: BaseEvent,
61        /// 警告类型
62        warning_type: BounceWarningType,
63        /// 警告详细信息
64        details: String,
65    },
66}
67
68/// BOUNCE警告类型枚举
69///
70/// 定义BOUNCE守护进程可能产生的各种警告类型
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub enum BounceWarningType {
73    /// 格式错误请求
74    ///
75    /// 当BOUNCE守护进程接收到格式不正确的请求时产生
76    /// 这是在真实日志中发现的最常见警告类型
77    ///
78    /// 示例:
79    /// - "warning: malformed request"
80    MalformedRequest,
81
82    /// 配置相关警告
83    ///
84    /// BOUNCE服务配置文件或配置参数相关的警告
85    ///
86    /// 示例:
87    /// - "warning: configuration file not found"
88    /// - "warning: invalid configuration parameter"
89    Configuration,
90
91    /// 资源限制警告
92    ///
93    /// 内存、磁盘空间、文件描述符等系统资源相关的警告
94    ///
95    /// 示例:
96    /// - "warning: memory allocation failed"
97    /// - "warning: disk space low"
98    ResourceLimit,
99
100    /// 其他类型警告
101    ///
102    /// 不属于上述特定类别的其他警告
103    Other,
104}
105
106impl BounceEvent {
107    /// 获取事件的基础信息
108    pub fn base(&self) -> &BaseEvent {
109        match self {
110            BounceEvent::SenderNotification { base, .. } => base,
111            BounceEvent::PostmasterNotification { base, .. } => base,
112            BounceEvent::Warning { base, .. } => base,
113        }
114    }
115
116    /// 获取事件类型的字符串表示
117    pub fn event_type(&self) -> &'static str {
118        match self {
119            BounceEvent::SenderNotification { .. } => "sender_notification",
120            BounceEvent::PostmasterNotification { .. } => "postmaster_notification",
121            BounceEvent::Warning { .. } => "warning",
122        }
123    }
124
125    /// 检查是否为通知事件(投递失败通知)
126    pub fn is_notification(&self) -> bool {
127        matches!(
128            self,
129            BounceEvent::SenderNotification { .. } | BounceEvent::PostmasterNotification { .. }
130        )
131    }
132
133    /// 检查是否为警告事件
134    pub fn is_warning(&self) -> bool {
135        matches!(self, BounceEvent::Warning { .. })
136    }
137
138    /// 获取通知相关的队列ID(如果是通知事件)
139    pub fn get_queue_ids(&self) -> Option<(&str, &str)> {
140        match self {
141            BounceEvent::SenderNotification {
142                original_queue_id,
143                bounce_queue_id,
144                ..
145            } => Some((original_queue_id, bounce_queue_id)),
146            BounceEvent::PostmasterNotification {
147                original_queue_id,
148                bounce_queue_id,
149                ..
150            } => Some((original_queue_id, bounce_queue_id)),
151            BounceEvent::Warning { .. } => None,
152        }
153    }
154
155    /// 获取警告信息(如果是警告事件)
156    pub fn get_warning_info(&self) -> Option<(&BounceWarningType, &str)> {
157        match self {
158            BounceEvent::Warning {
159                warning_type,
160                details,
161                ..
162            } => Some((warning_type, details)),
163            _ => None,
164        }
165    }
166
167    /// 格式化为人类可读的描述
168    pub fn format_description(&self) -> String {
169        match self {
170            BounceEvent::SenderNotification {
171                original_queue_id,
172                bounce_queue_id,
173                ..
174            } => {
175                format!(
176                    "为原始邮件 {} 生成发送者投递失败通知,通知邮件队列ID: {}",
177                    original_queue_id, bounce_queue_id
178                )
179            }
180            BounceEvent::PostmasterNotification {
181                original_queue_id,
182                bounce_queue_id,
183                ..
184            } => {
185                format!(
186                    "为原始邮件 {} 生成邮件管理员投递失败通知,通知邮件队列ID: {}",
187                    original_queue_id, bounce_queue_id
188                )
189            }
190            BounceEvent::Warning {
191                warning_type,
192                details,
193                ..
194            } => {
195                let type_desc = match warning_type {
196                    BounceWarningType::MalformedRequest => "格式错误请求",
197                    BounceWarningType::Configuration => "配置警告",
198                    BounceWarningType::ResourceLimit => "资源限制警告",
199                    BounceWarningType::Other => "其他警告",
200                };
201                format!("{}: {}", type_desc, details)
202            }
203        }
204    }
205}
206
207impl BounceWarningType {
208    /// 获取警告类型的严重程度(1-5,5为最严重)
209    pub fn severity(&self) -> u8 {
210        match self {
211            BounceWarningType::MalformedRequest => 2, // 中低严重性,通常是客户端问题
212            BounceWarningType::Configuration => 4,    // 高严重性,影响服务正常运行
213            BounceWarningType::ResourceLimit => 5,    // 最高严重性,可能导致服务不可用
214            BounceWarningType::Other => 3,            // 中等严重性,需要关注
215        }
216    }
217
218    /// 获取警告类型的建议处理方式
219    pub fn suggested_action(&self) -> &'static str {
220        match self {
221            BounceWarningType::MalformedRequest => {
222                "检查客户端发送的请求格式,可能需要联系发送方修正"
223            }
224            BounceWarningType::Configuration => "检查并修正BOUNCE服务相关配置,重启服务使配置生效",
225            BounceWarningType::ResourceLimit => "立即检查系统资源使用情况,释放资源或扩容",
226            BounceWarningType::Other => "分析具体警告内容,采取相应的处理措施",
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use crate::events::base::PostfixLogLevel;
235    use chrono::{DateTime, Utc};
236
237    fn create_test_base_event() -> BaseEvent {
238        BaseEvent {
239            timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48+00:00")
240                .unwrap()
241                .with_timezone(&Utc),
242            hostname: "m01".to_string(),
243            component: "bounce".to_string(),
244            process_id: 133,
245            log_level: PostfixLogLevel::Info,
246            raw_message: "test message".to_string(),
247        }
248    }
249
250    #[test]
251    fn test_sender_notification_event() {
252        let base = create_test_base_event();
253        let event = BounceEvent::SenderNotification {
254            base: base.clone(),
255            original_queue_id: "5FC392A20996".to_string(),
256            bounce_queue_id: "732B92A209A3".to_string(),
257        };
258
259        assert_eq!(event.event_type(), "sender_notification");
260        assert!(event.is_notification());
261        assert!(!event.is_warning());
262        assert_eq!(event.base(), &base);
263
264        let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
265        assert_eq!(orig_id, "5FC392A20996");
266        assert_eq!(bounce_id, "732B92A209A3");
267
268        assert!(event.get_warning_info().is_none());
269    }
270
271    #[test]
272    fn test_postmaster_notification_event() {
273        let base = create_test_base_event();
274        let event = BounceEvent::PostmasterNotification {
275            base: base.clone(),
276            original_queue_id: "633F488423".to_string(),
277            bounce_queue_id: "6DFA788422".to_string(),
278        };
279
280        assert_eq!(event.event_type(), "postmaster_notification");
281        assert!(event.is_notification());
282        assert!(!event.is_warning());
283
284        let (orig_id, bounce_id) = event.get_queue_ids().unwrap();
285        assert_eq!(orig_id, "633F488423");
286        assert_eq!(bounce_id, "6DFA788422");
287    }
288
289    #[test]
290    fn test_warning_event() {
291        let base = create_test_base_event();
292        let event = BounceEvent::Warning {
293            base: base.clone(),
294            warning_type: BounceWarningType::MalformedRequest,
295            details: "malformed request".to_string(),
296        };
297
298        assert_eq!(event.event_type(), "warning");
299        assert!(!event.is_notification());
300        assert!(event.is_warning());
301
302        let (warning_type, details) = event.get_warning_info().unwrap();
303        assert_eq!(*warning_type, BounceWarningType::MalformedRequest);
304        assert_eq!(details, "malformed request");
305
306        assert!(event.get_queue_ids().is_none());
307    }
308
309    #[test]
310    fn test_warning_type_severity() {
311        assert_eq!(BounceWarningType::MalformedRequest.severity(), 2);
312        assert_eq!(BounceWarningType::Configuration.severity(), 4);
313        assert_eq!(BounceWarningType::ResourceLimit.severity(), 5);
314        assert_eq!(BounceWarningType::Other.severity(), 3);
315    }
316
317    #[test]
318    fn test_format_description() {
319        let base = create_test_base_event();
320
321        let sender_event = BounceEvent::SenderNotification {
322            base: base.clone(),
323            original_queue_id: "5FC392A20996".to_string(),
324            bounce_queue_id: "732B92A209A3".to_string(),
325        };
326        let desc = sender_event.format_description();
327        assert!(desc.contains("5FC392A20996"));
328        assert!(desc.contains("732B92A209A3"));
329        assert!(desc.contains("发送者投递失败通知"));
330
331        let warning_event = BounceEvent::Warning {
332            base: base.clone(),
333            warning_type: BounceWarningType::MalformedRequest,
334            details: "malformed request".to_string(),
335        };
336        let desc = warning_event.format_description();
337        assert!(desc.contains("格式错误请求"));
338        assert!(desc.contains("malformed request"));
339    }
340
341    #[test]
342    fn test_serialization() {
343        let base = create_test_base_event();
344        let event = BounceEvent::SenderNotification {
345            base,
346            original_queue_id: "5FC392A20996".to_string(),
347            bounce_queue_id: "732B92A209A3".to_string(),
348        };
349
350        // 测试序列化
351        let serialized = serde_json::to_string(&event).unwrap();
352        assert!(
353            serialized.contains("sender_notification") || serialized.contains("SenderNotification")
354        );
355
356        // 测试反序列化
357        let deserialized: BounceEvent = serde_json::from_str(&serialized).unwrap();
358        assert_eq!(event, deserialized);
359    }
360}