postfix_log_parser/components/
master.rs

1//! # Master 主控进程组件解析器
2//!
3//! Master 是 Postfix 的主控进程组件,负责:
4//! - 管理和监控所有 Postfix 服务进程
5//! - 处理进程生命周期和重启策略
6//! - 监控服务限制和性能瓶颈
7//! - 提供系统配置警告和优化建议
8//!
9//! ## 核心功能
10//!
11//! - **守护进程管理**: 启动、重载、停止 Postfix 服务
12//! - **进程监控**: 跟踪子进程状态和异常
13//! - **服务限制**: 监控并发连接和进程数限制
14//! - **配置优化**: 提供性能调优建议
15//!
16//! ## 支持的事件类型
17//!
18//! - **守护进程生命周期**: 启动、重载配置事件
19//! - **进程警告**: 进程崩溃、启动失败警告
20//! - **服务限制**: 进程数量限制达到警告
21//! - **配置警告**: 性能优化和配置建议
22//!
23//! ## 示例日志格式
24//!
25//! ```text
26//! # 守护进程启动
27//! daemon started -- version 3.6.4, configuration /etc/postfix
28//!
29//! # 进程警告
30//! warning: process /usr/libexec/postfix/smtpd pid 12345 killed by signal 9
31//! warning: service "smtpd" (unix:/private/smtpd) has reached its process limit "100": new clients may experience noticeable delays
32//!
33//! # 配置建议
34//! warning: to avoid this condition, increase the process count in master.cf or reduce the service time per client
35//! ```
36
37use regex::Regex;
38use crate::components::ComponentParser;
39use crate::events::ComponentEvent;
40use crate::events::base::{BaseEvent, PostfixLogLevel};
41use crate::events::master::{
42    MasterEvent, MasterLifecycleEvent, MasterProcessWarning, 
43    MasterServiceLimit, MasterConfigWarning
44};
45use crate::error::ParseError;
46use chrono::{DateTime, Utc};
47
48pub struct MasterParser {
49    // Daemon lifecycle
50    daemon_started_regex: Regex,
51    daemon_reload_regex: Regex,
52    
53    // Process warnings
54    process_killed_regex: Regex,
55    bad_command_startup_regex: Regex,
56    
57    // Service limits
58    service_limit_regex: Regex,
59    
60    // Configuration warnings
61    process_count_suggestion_regex: Regex,
62    stress_config_reference_regex: Regex,
63}
64
65impl MasterParser {
66    pub fn new() -> Self {
67        MasterParser {
68            // Daemon lifecycle
69            daemon_started_regex: Regex::new(r"^daemon started -- version ([^,]+), configuration (.+)$").unwrap(),
70            daemon_reload_regex: Regex::new(r"^reload -- version ([^,]+), configuration (.+)$").unwrap(),
71            
72            // Process warnings
73            process_killed_regex: Regex::new(r"^warning: process (/[^/]+/[^/]+/[^/]+/([^/\s]+)) pid (\d+) killed by signal (\d+)$").unwrap(),
74            bad_command_startup_regex: Regex::new(r"^warning: (/[^/]+/[^/]+/[^/]+/[^:]+): bad command startup -- throttling$").unwrap(),
75            
76            // Service limits
77            service_limit_regex: Regex::new(r#"^warning: service "([^"]+)" \(([^)]+)\) has reached its process limit "(\d+)": new clients may experience noticeable delays$"#).unwrap(),
78            
79            // Configuration warnings
80            process_count_suggestion_regex: Regex::new(r"^warning: to avoid this condition, increase the process count in master\.cf or reduce the service time per client$").unwrap(),
81            stress_config_reference_regex: Regex::new(r"^warning: see (https?://[^\s]+) for examples of stress-adapting configuration settings$").unwrap(),
82        }
83    }
84
85    fn create_base_event(&self, message: &str) -> BaseEvent {
86        BaseEvent {
87            timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48Z")
88                .unwrap()
89                .with_timezone(&Utc),
90            hostname: "unknown".to_string(),
91            component: "master".to_string(),
92            process_id: 0,
93            log_level: PostfixLogLevel::Info,
94            raw_message: message.to_string(),
95        }
96    }
97
98    fn parse_daemon_lifecycle(&self, message: &str) -> Option<MasterEvent> {
99        let base = self.create_base_event(message);
100        
101        if let Some(caps) = self.daemon_started_regex.captures(message) {
102            let version = caps.get(1).map(|m| m.as_str().to_string())
103                .unwrap_or_else(|| "unknown".to_string());
104            let configuration = caps.get(2).map(|m| m.as_str().to_string())
105                .unwrap_or_else(|| "unknown".to_string());
106            return Some(MasterEvent::DaemonLifecycle {
107                base,
108                lifecycle: MasterLifecycleEvent::Started { version, configuration },
109            });
110        }
111
112        if let Some(caps) = self.daemon_reload_regex.captures(message) {
113            let version = caps.get(1).map(|m| m.as_str().to_string())
114                .unwrap_or_else(|| "unknown".to_string());
115            let configuration = caps.get(2).map(|m| m.as_str().to_string())
116                .unwrap_or_else(|| "unknown".to_string());
117            return Some(MasterEvent::DaemonLifecycle {
118                base,
119                lifecycle: MasterLifecycleEvent::Reload { version, configuration },
120            });
121        }
122
123        None
124    }
125
126    fn parse_process_warning(&self, message: &str) -> Option<MasterEvent> {
127        let mut base = self.create_base_event(message);
128        base.log_level = PostfixLogLevel::Warning;
129        
130        if let Some(caps) = self.process_killed_regex.captures(message) {
131            let process_path = caps.get(1).map(|m| m.as_str().to_string())
132                .unwrap_or_else(|| "unknown".to_string());
133            let service = caps.get(2).map(|m| m.as_str().to_string())
134                .unwrap_or_else(|| "unknown".to_string());
135            let pid = caps.get(3).and_then(|m| m.as_str().parse::<u32>().ok())
136                .unwrap_or(0);
137            let signal = caps.get(4).and_then(|m| m.as_str().parse::<u8>().ok())
138                .unwrap_or(0);
139            return Some(MasterEvent::ProcessWarning {
140                base,
141                warning: MasterProcessWarning::ProcessKilled {
142                    service,
143                    process_path,
144                    pid,
145                    signal,
146                },
147            });
148        }
149
150        if let Some(caps) = self.bad_command_startup_regex.captures(message) {
151            let full_path = caps.get(1).map(|m| m.as_str().to_string())
152                .unwrap_or_else(|| "unknown".to_string());
153            // Extract service name from path like "/usr/libexec/postfix/smtpd"
154            let service = full_path.split('/').last().unwrap_or("unknown").to_string();
155            return Some(MasterEvent::ProcessWarning {
156                base,
157                warning: MasterProcessWarning::BadCommandStartup { service },
158            });
159        }
160
161        None
162    }
163
164    fn parse_service_limit(&self, message: &str) -> Option<MasterEvent> {
165        let mut base = self.create_base_event(message);
166        base.log_level = PostfixLogLevel::Warning;
167        
168        if let Some(caps) = self.service_limit_regex.captures(message) {
169            let service = caps.get(1).map(|m| m.as_str().to_string())
170                .unwrap_or_else(|| "unknown".to_string());
171            let service_address = caps.get(2).map(|m| m.as_str().to_string())
172                .unwrap_or_else(|| "unknown".to_string());
173            let limit = caps.get(3).and_then(|m| m.as_str().parse::<u32>().ok())
174                .unwrap_or(0);
175            return Some(MasterEvent::ServiceLimit {
176                base,
177                limit: MasterServiceLimit::ProcessLimitReached {
178                    service,
179                    service_address,
180                    limit,
181                },
182            });
183        }
184
185        None
186    }
187
188    fn parse_configuration_warning(&self, message: &str) -> Option<MasterEvent> {
189        let mut base = self.create_base_event(message);
190        base.log_level = PostfixLogLevel::Warning;
191        
192        if self.process_count_suggestion_regex.is_match(message) {
193            return Some(MasterEvent::ConfigurationWarning {
194                base,
195                warning: MasterConfigWarning::ProcessCountSuggestion,
196            });
197        }
198
199        if let Some(caps) = self.stress_config_reference_regex.captures(message) {
200            let url = caps.get(1).map(|m| m.as_str().to_string())
201                .unwrap_or_else(|| "unknown".to_string());
202            return Some(MasterEvent::ConfigurationWarning {
203                base,
204                warning: MasterConfigWarning::StressConfigReference { url },
205            });
206        }
207
208        None
209    }
210}
211
212impl ComponentParser for MasterParser {
213    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
214        // Try daemon lifecycle events first
215        if let Some(event) = self.parse_daemon_lifecycle(message) {
216            return Ok(ComponentEvent::Master(event));
217        }
218        
219        // Try warnings (most common)
220        if message.contains("warning:") {
221            if let Some(event) = self.parse_process_warning(message) {
222                return Ok(ComponentEvent::Master(event));
223            }
224            
225            if let Some(event) = self.parse_service_limit(message) {
226                return Ok(ComponentEvent::Master(event));
227            }
228            
229            if let Some(event) = self.parse_configuration_warning(message) {
230                return Ok(ComponentEvent::Master(event));
231            }
232        }
233
234        Err(ParseError::ComponentParseError {
235            component: "master".to_string(),
236            reason: format!("Unsupported message format: {}", message),
237        })
238    }
239
240    fn component_name(&self) -> &'static str {
241        "master"
242    }
243
244    fn can_parse(&self, component: &str) -> bool {
245        component == "master"
246    }
247}
248
249impl Default for MasterParser {
250    fn default() -> Self {
251        Self::new()
252    }
253}