postfix_log_parser/components/
master.rs1use regex::Regex;
7use crate::components::ComponentParser;
8use crate::events::ComponentEvent;
9use crate::events::base::{BaseEvent, PostfixLogLevel};
10use crate::events::master::{
11 MasterEvent, MasterLifecycleEvent, MasterProcessWarning,
12 MasterServiceLimit, MasterConfigWarning
13};
14use crate::error::ParseError;
15use chrono::{DateTime, Utc};
16
17pub struct MasterParser {
18 daemon_started_regex: Regex,
20 daemon_reload_regex: Regex,
21
22 process_killed_regex: Regex,
24 bad_command_startup_regex: Regex,
25
26 service_limit_regex: Regex,
28
29 process_count_suggestion_regex: Regex,
31 stress_config_reference_regex: Regex,
32}
33
34impl MasterParser {
35 pub fn new() -> Self {
36 MasterParser {
37 daemon_started_regex: Regex::new(r"^daemon started -- version ([^,]+), configuration (.+)$").unwrap(),
39 daemon_reload_regex: Regex::new(r"^reload -- version ([^,]+), configuration (.+)$").unwrap(),
40
41 process_killed_regex: Regex::new(r"^warning: process (/[^/]+/[^/]+/[^/]+/([^/\s]+)) pid (\d+) killed by signal (\d+)$").unwrap(),
43 bad_command_startup_regex: Regex::new(r"^warning: (/[^/]+/[^/]+/[^/]+/[^:]+): bad command startup -- throttling$").unwrap(),
44
45 service_limit_regex: Regex::new(r#"^warning: service "([^"]+)" \(([^)]+)\) has reached its process limit "(\d+)": new clients may experience noticeable delays$"#).unwrap(),
47
48 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(),
50 stress_config_reference_regex: Regex::new(r"^warning: see (https?://[^\s]+) for examples of stress-adapting configuration settings$").unwrap(),
51 }
52 }
53
54 fn create_base_event(&self, message: &str) -> BaseEvent {
55 BaseEvent {
56 timestamp: DateTime::parse_from_rfc3339("2024-04-27T16:20:48Z")
57 .unwrap()
58 .with_timezone(&Utc),
59 hostname: "unknown".to_string(),
60 component: "master".to_string(),
61 process_id: 0,
62 log_level: PostfixLogLevel::Info,
63 raw_message: message.to_string(),
64 }
65 }
66
67 fn parse_daemon_lifecycle(&self, message: &str) -> Option<MasterEvent> {
68 let base = self.create_base_event(message);
69
70 if let Some(caps) = self.daemon_started_regex.captures(message) {
71 let version = caps.get(1).map(|m| m.as_str().to_string())
72 .unwrap_or_else(|| "unknown".to_string());
73 let configuration = caps.get(2).map(|m| m.as_str().to_string())
74 .unwrap_or_else(|| "unknown".to_string());
75 return Some(MasterEvent::DaemonLifecycle {
76 base,
77 lifecycle: MasterLifecycleEvent::Started { version, configuration },
78 });
79 }
80
81 if let Some(caps) = self.daemon_reload_regex.captures(message) {
82 let version = caps.get(1).map(|m| m.as_str().to_string())
83 .unwrap_or_else(|| "unknown".to_string());
84 let configuration = caps.get(2).map(|m| m.as_str().to_string())
85 .unwrap_or_else(|| "unknown".to_string());
86 return Some(MasterEvent::DaemonLifecycle {
87 base,
88 lifecycle: MasterLifecycleEvent::Reload { version, configuration },
89 });
90 }
91
92 None
93 }
94
95 fn parse_process_warning(&self, message: &str) -> Option<MasterEvent> {
96 let mut base = self.create_base_event(message);
97 base.log_level = PostfixLogLevel::Warning;
98
99 if let Some(caps) = self.process_killed_regex.captures(message) {
100 let process_path = caps.get(1).map(|m| m.as_str().to_string())
101 .unwrap_or_else(|| "unknown".to_string());
102 let service = caps.get(2).map(|m| m.as_str().to_string())
103 .unwrap_or_else(|| "unknown".to_string());
104 let pid = caps.get(3).and_then(|m| m.as_str().parse::<u32>().ok())
105 .unwrap_or(0);
106 let signal = caps.get(4).and_then(|m| m.as_str().parse::<u8>().ok())
107 .unwrap_or(0);
108 return Some(MasterEvent::ProcessWarning {
109 base,
110 warning: MasterProcessWarning::ProcessKilled {
111 service,
112 process_path,
113 pid,
114 signal,
115 },
116 });
117 }
118
119 if let Some(caps) = self.bad_command_startup_regex.captures(message) {
120 let full_path = caps.get(1).map(|m| m.as_str().to_string())
121 .unwrap_or_else(|| "unknown".to_string());
122 let service = full_path.split('/').last().unwrap_or("unknown").to_string();
124 return Some(MasterEvent::ProcessWarning {
125 base,
126 warning: MasterProcessWarning::BadCommandStartup { service },
127 });
128 }
129
130 None
131 }
132
133 fn parse_service_limit(&self, message: &str) -> Option<MasterEvent> {
134 let mut base = self.create_base_event(message);
135 base.log_level = PostfixLogLevel::Warning;
136
137 if let Some(caps) = self.service_limit_regex.captures(message) {
138 let service = caps.get(1).map(|m| m.as_str().to_string())
139 .unwrap_or_else(|| "unknown".to_string());
140 let service_address = caps.get(2).map(|m| m.as_str().to_string())
141 .unwrap_or_else(|| "unknown".to_string());
142 let limit = caps.get(3).and_then(|m| m.as_str().parse::<u32>().ok())
143 .unwrap_or(0);
144 return Some(MasterEvent::ServiceLimit {
145 base,
146 limit: MasterServiceLimit::ProcessLimitReached {
147 service,
148 service_address,
149 limit,
150 },
151 });
152 }
153
154 None
155 }
156
157 fn parse_configuration_warning(&self, message: &str) -> Option<MasterEvent> {
158 let mut base = self.create_base_event(message);
159 base.log_level = PostfixLogLevel::Warning;
160
161 if self.process_count_suggestion_regex.is_match(message) {
162 return Some(MasterEvent::ConfigurationWarning {
163 base,
164 warning: MasterConfigWarning::ProcessCountSuggestion,
165 });
166 }
167
168 if let Some(caps) = self.stress_config_reference_regex.captures(message) {
169 let url = caps.get(1).map(|m| m.as_str().to_string())
170 .unwrap_or_else(|| "unknown".to_string());
171 return Some(MasterEvent::ConfigurationWarning {
172 base,
173 warning: MasterConfigWarning::StressConfigReference { url },
174 });
175 }
176
177 None
178 }
179}
180
181impl ComponentParser for MasterParser {
182 fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
183 if let Some(event) = self.parse_daemon_lifecycle(message) {
185 return Ok(ComponentEvent::Master(event));
186 }
187
188 if message.contains("warning:") {
190 if let Some(event) = self.parse_process_warning(message) {
191 return Ok(ComponentEvent::Master(event));
192 }
193
194 if let Some(event) = self.parse_service_limit(message) {
195 return Ok(ComponentEvent::Master(event));
196 }
197
198 if let Some(event) = self.parse_configuration_warning(message) {
199 return Ok(ComponentEvent::Master(event));
200 }
201 }
202
203 Err(ParseError::ComponentParseError {
204 component: "master".to_string(),
205 reason: format!("Unsupported message format: {}", message),
206 })
207 }
208
209 fn component_name(&self) -> &'static str {
210 "master"
211 }
212
213 fn can_parse(&self, component: &str) -> bool {
214 component == "master"
215 }
216}