postfix_log_parser/components/
master.rs1use 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_started_regex: Regex,
51 daemon_reload_regex: Regex,
52
53 process_killed_regex: Regex,
55 bad_command_startup_regex: Regex,
56
57 service_limit_regex: Regex,
59
60 process_count_suggestion_regex: Regex,
62 stress_config_reference_regex: Regex,
63}
64
65impl MasterParser {
66 pub fn new() -> Self {
67 MasterParser {
68 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_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_limit_regex: Regex::new(r#"^warning: service "([^"]+)" \(([^)]+)\) has reached its process limit "(\d+)": new clients may experience noticeable delays$"#).unwrap(),
78
79 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 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 if let Some(event) = self.parse_daemon_lifecycle(message) {
216 return Ok(ComponentEvent::Master(event));
217 }
218
219 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}