postfix_log_parser/components/
anvil.rs1use crate::components::ComponentParser;
2use crate::events::anvil::{AnvilEvent, StatisticType};
3use crate::events::ComponentEvent;
4use chrono::{DateTime, Datelike, TimeZone, Utc};
5use lazy_static::lazy_static;
6use regex::Regex;
7use std::collections::HashMap;
8
9lazy_static! {
10 static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
12 r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
13 ).unwrap();
14
15 static ref STATISTICS_REGEX: Regex = Regex::new(
17 r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
18 ).unwrap();
19
20 static ref CACHE_SIZE_REGEX: Regex = Regex::new(
22 r"^statistics: max cache size (\d+) at (.+)$"
23 ).unwrap();
24}
25
26pub struct AnvilParser;
27
28impl AnvilParser {
29 pub fn new() -> Self {
30 Self
31 }
32
33 fn parse_config_warning(&self, message: &str) -> Option<AnvilEvent> {
35 if let Some(captures) = CONFIG_WARNING_REGEX.captures(message) {
36 let file_path = captures.get(1)?.as_str().to_string();
37 let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
38 let parameter_name = captures.get(3)?.as_str().to_string();
39 let parameter_value = captures.get(4)?.as_str().to_string();
40
41 let warning_message = format!(
42 "overriding earlier entry: {}={}",
43 parameter_name, parameter_value
44 );
45
46 Some(AnvilEvent::config_warning(
47 Utc::now(),
48 None,
49 file_path,
50 line_number,
51 parameter_name,
52 warning_message,
53 ))
54 } else {
55 None
56 }
57 }
58
59 fn parse_statistics(&self, message: &str) -> Option<AnvilEvent> {
61 if let Some(captures) = CACHE_SIZE_REGEX.captures(message) {
63 let value: u32 = captures.get(1)?.as_str().parse().ok()?;
64 let time_str = captures.get(2)?.as_str();
65 let metric_timestamp = self.parse_metric_timestamp(time_str)?;
66
67 return Some(AnvilEvent::statistics(
68 Utc::now(),
69 None,
70 StatisticType::MaxCacheSize,
71 value,
72 None, "system".to_string(), metric_timestamp,
75 ));
76 }
77
78 if let Some(captures) = STATISTICS_REGEX.captures(message) {
80 let metric_str = captures.get(1)?.as_str();
81 let value: u32 = captures.get(2)?.as_str().parse().ok()?;
82 let rate_window = captures.get(3).map(|m| m.as_str().to_string());
83 let service_client = captures.get(4)?.as_str().to_string();
84 let time_str = captures.get(5)?.as_str();
85
86 let metric_type = match metric_str {
88 "connection rate" => StatisticType::MaxConnectionRate,
89 "connection count" => StatisticType::MaxConnectionCount,
90 "message rate" => StatisticType::MaxMessageRate,
91 _ => return None,
92 };
93
94 let metric_timestamp = self.parse_metric_timestamp(time_str)?;
96
97 Some(AnvilEvent::statistics(
98 Utc::now(),
99 None,
100 metric_type,
101 value,
102 rate_window,
103 service_client,
104 metric_timestamp,
105 ))
106 } else {
107 None
108 }
109 }
110
111 fn parse_metric_timestamp(&self, time_str: &str) -> Option<DateTime<Utc>> {
113 let year = Utc::now().year();
116
117 let month_map: HashMap<&str, u32> = [
119 ("Jan", 1),
120 ("Feb", 2),
121 ("Mar", 3),
122 ("Apr", 4),
123 ("May", 5),
124 ("Jun", 6),
125 ("Jul", 7),
126 ("Aug", 8),
127 ("Sep", 9),
128 ("Oct", 10),
129 ("Nov", 11),
130 ("Dec", 12),
131 ]
132 .iter()
133 .cloned()
134 .collect();
135
136 let parts: Vec<&str> = time_str.split_whitespace().collect();
137 if parts.len() != 3 {
138 return None;
139 }
140
141 let month_str = parts[0];
142 let day: u32 = parts[1].parse().ok()?;
143 let time_parts: Vec<&str> = parts[2].split(':').collect();
144
145 if time_parts.len() != 3 {
146 return None;
147 }
148
149 let hour: u32 = time_parts[0].parse().ok()?;
150 let minute: u32 = time_parts[1].parse().ok()?;
151 let second: u32 = time_parts[2].parse().ok()?;
152
153 let month = month_map.get(month_str)?;
154
155 Utc.with_ymd_and_hms(year, *month, day, hour, minute, second)
156 .single()
157 }
158}
159
160impl ComponentParser for AnvilParser {
161 fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
162 let clean_message = message.trim();
163
164 if let Some(event) = self.parse_config_warning(clean_message) {
166 return Ok(ComponentEvent::Anvil(event));
167 }
168
169 if let Some(event) = self.parse_statistics(clean_message) {
171 return Ok(ComponentEvent::Anvil(event));
172 }
173
174 Err(crate::error::ParseError::ComponentParseError {
175 component: "anvil".to_string(),
176 reason: format!("Unable to parse message: {}", message),
177 })
178 }
179
180 fn component_name(&self) -> &'static str {
181 "anvil"
182 }
183}