postfix_log_parser/events/
bounce.rs

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