postfix_log_parser/components/
anvil.rs1use crate::components::ComponentParser;
27use crate::events::anvil::{AnvilEvent, StatisticType};
28use crate::events::ComponentEvent;
29use chrono::{DateTime, Datelike, Utc};
30use lazy_static::lazy_static;
31use regex::Regex;
32
33lazy_static! {
34 static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
36 r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
37 ).unwrap();
38
39 static ref STATISTICS_REGEX: Regex = Regex::new(
41 r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
42 ).unwrap();
43
44 static ref CACHE_SIZE_REGEX: Regex = Regex::new(
46 r"^statistics: max cache size (\d+) at (.+)$"
47 ).unwrap();
48}
49
50pub struct AnvilParser;
51
52impl AnvilParser {
53 pub fn new() -> Self {
54 Self
55 }
56
57 fn parse_config_warning(&self, message: &str) -> Option<AnvilEvent> {
59 if let Some(captures) = CONFIG_WARNING_REGEX.captures(message) {
60 let file_path = captures.get(1)?.as_str().to_string();
61 let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
62 let parameter_name = captures.get(3)?.as_str().to_string();
63 let parameter_value = captures.get(4)?.as_str().to_string();
64
65 let warning_message = format!(
66 "overriding earlier entry: {}={}",
67 parameter_name, parameter_value
68 );
69
70 Some(AnvilEvent::config_warning(
71 Utc::now(),
72 None,
73 file_path,
74 line_number,
75 parameter_name,
76 warning_message,
77 ))
78 } else {
79 None
80 }
81 }
82
83 fn parse_statistics(&self, message: &str) -> Option<AnvilEvent> {
85 if let Some(captures) = CACHE_SIZE_REGEX.captures(message) {
87 let value: u32 = captures.get(1)?.as_str().parse().ok()?;
88 let time_str = captures.get(2)?.as_str();
89 let metric_timestamp = self.parse_metric_timestamp(time_str)?;
90
91 return Some(AnvilEvent::statistics(
92 Utc::now(),
93 None,
94 StatisticType::MaxCacheSize,
95 value,
96 None, "system".to_string(), metric_timestamp,
99 ));
100 }
101
102 if let Some(captures) = STATISTICS_REGEX.captures(message) {
104 let metric_str = captures.get(1)?.as_str();
105 let value: u32 = captures.get(2)?.as_str().parse().ok()?;
106 let rate_window = captures.get(3).map(|m| m.as_str().to_string());
107 let service_client = captures.get(4)?.as_str().to_string();
108 let time_str = captures.get(5)?.as_str();
109
110 let metric_type = match metric_str {
112 "connection rate" => StatisticType::MaxConnectionRate,
113 "connection count" => StatisticType::MaxConnectionCount,
114 "message rate" => StatisticType::MaxMessageRate,
115 _ => return None,
116 };
117
118 let metric_timestamp = self.parse_metric_timestamp(time_str)?;
120
121 Some(AnvilEvent::statistics(
122 Utc::now(),
123 None,
124 metric_type,
125 value,
126 rate_window,
127 service_client,
128 metric_timestamp,
129 ))
130 } else {
131 None
132 }
133 }
134
135 fn parse_metric_timestamp(&self, time_str: &str) -> Option<DateTime<Utc>> {
137 use chrono::NaiveDateTime;
138
139 if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S%.f") {
144 return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
145 }
146
147 if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S") {
149 return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
150 }
151
152 let year = Utc::now().year();
154 let datetime_str = format!("{} {}", year, time_str);
155
156 if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S%.f") {
158 return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
159 }
160
161 if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S") {
163 return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
164 }
165
166 None
167 }
168}
169
170impl ComponentParser for AnvilParser {
171 fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
172 let clean_message = message.trim();
173
174 if let Some(event) = self.parse_config_warning(clean_message) {
176 return Ok(ComponentEvent::Anvil(event));
177 }
178
179 if let Some(event) = self.parse_statistics(clean_message) {
181 return Ok(ComponentEvent::Anvil(event));
182 }
183
184 Err(crate::error::ParseError::ComponentParseError {
185 component: "anvil".to_string(),
186 reason: format!("Unable to parse message: {}", message),
187 })
188 }
189
190 fn component_name(&self) -> &'static str {
191 "anvil"
192 }
193}
194
195impl Default for AnvilParser {
196 fn default() -> Self {
197 Self::new()
198 }
199}