use crate::components::ComponentParser;
use crate::events::anvil::{AnvilEvent, StatisticType};
use crate::events::ComponentEvent;
use chrono::{DateTime, Datelike, Utc};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
).unwrap();
static ref STATISTICS_REGEX: Regex = Regex::new(
r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
).unwrap();
static ref CACHE_SIZE_REGEX: Regex = Regex::new(
r"^statistics: max cache size (\d+) at (.+)$"
).unwrap();
}
pub struct AnvilParser;
impl AnvilParser {
pub fn new() -> Self {
Self
}
fn parse_config_warning(&self, message: &str) -> Option<AnvilEvent> {
if let Some(captures) = CONFIG_WARNING_REGEX.captures(message) {
let file_path = captures.get(1)?.as_str().to_string();
let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
let parameter_name = captures.get(3)?.as_str().to_string();
let parameter_value = captures.get(4)?.as_str().to_string();
let warning_message = format!(
"overriding earlier entry: {}={}",
parameter_name, parameter_value
);
Some(AnvilEvent::config_warning(
Utc::now(),
None,
file_path,
line_number,
parameter_name,
warning_message,
))
} else {
None
}
}
fn parse_statistics(&self, message: &str) -> Option<AnvilEvent> {
if let Some(captures) = CACHE_SIZE_REGEX.captures(message) {
let value: u32 = captures.get(1)?.as_str().parse().ok()?;
let time_str = captures.get(2)?.as_str();
let metric_timestamp = self.parse_metric_timestamp(time_str)?;
return Some(AnvilEvent::statistics(
Utc::now(),
None,
StatisticType::MaxCacheSize,
value,
None, "system".to_string(), metric_timestamp,
));
}
if let Some(captures) = STATISTICS_REGEX.captures(message) {
let metric_str = captures.get(1)?.as_str();
let value: u32 = captures.get(2)?.as_str().parse().ok()?;
let rate_window = captures.get(3).map(|m| m.as_str().to_string());
let service_client = captures.get(4)?.as_str().to_string();
let time_str = captures.get(5)?.as_str();
let metric_type = match metric_str {
"connection rate" => StatisticType::MaxConnectionRate,
"connection count" => StatisticType::MaxConnectionCount,
"message rate" => StatisticType::MaxMessageRate,
_ => return None,
};
let metric_timestamp = self.parse_metric_timestamp(time_str)?;
Some(AnvilEvent::statistics(
Utc::now(),
None,
metric_type,
value,
rate_window,
service_client,
metric_timestamp,
))
} else {
None
}
}
fn parse_metric_timestamp(&self, time_str: &str) -> Option<DateTime<Utc>> {
use chrono::NaiveDateTime;
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S%.f") {
return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(time_str, "%Y %b %d %H:%M:%S") {
return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
let year = Utc::now().year();
let datetime_str = format!("{} {}", year, time_str);
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S%.f") {
return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S") {
return Some(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
None
}
}
impl ComponentParser for AnvilParser {
fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
let clean_message = message.trim();
if let Some(event) = self.parse_config_warning(clean_message) {
return Ok(ComponentEvent::Anvil(event));
}
if let Some(event) = self.parse_statistics(clean_message) {
return Ok(ComponentEvent::Anvil(event));
}
Err(crate::error::ParseError::ComponentParseError {
component: "anvil".to_string(),
reason: format!("Unable to parse message: {}", message),
})
}
fn component_name(&self) -> &'static str {
"anvil"
}
}
impl Default for AnvilParser {
fn default() -> Self {
Self::new()
}
}