use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use log::{debug, warn};
use regex::Regex;
use std::collections::HashMap;
use crate::components::{master::MasterParser as MasterComponentParser, ComponentParser};
use crate::error::{ParseError, ParseResult};
use crate::events::base::ComponentEvent;
use crate::events::base::{PostfixLogEvent, UnknownEvent};
pub struct MasterParser {
component_parsers: HashMap<String, Box<dyn ComponentParser>>,
base_log_regex: Regex,
}
impl MasterParser {
pub fn new() -> Self {
let mut parser = Self {
component_parsers: HashMap::new(),
base_log_regex: Regex::new(
r"^((?:\d{4}\s+)?\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(\S+)\s+postfix/([^\[\]]+)\[(\d+)\]:\s+(.+)$",
)
.expect("基础日志正则表达式编译失败"),
};
parser.register_default_parsers();
parser
}
fn register_default_parsers(&mut self) {
use crate::components::*;
self.register_parser("smtpd", Box::new(smtpd::SmtpdParser::new()));
self.register_parser("qmgr", Box::new(qmgr::QmgrParser::new()));
self.register_parser("smtp", Box::new(smtp::SmtpParser::new()));
self.register_parser("cleanup", Box::new(cleanup::CleanupParser::new()));
self.register_parser("error", Box::new(error::ErrorParser::new()));
self.register_parser("relay", Box::new(relay::RelayParser::new()));
self.register_parser("relay/smtp", Box::new(relay::RelayParser::new()));
self.register_parser("discard", Box::new(discard::DiscardParser::new()));
self.register_parser("bounce", Box::new(bounce::BounceParser::new()));
self.register_parser(
"postfix-script",
Box::new(postfix_script::PostfixScriptParser::new()),
);
self.register_parser("master", Box::new(MasterComponentParser::new()));
self.register_parser("local", Box::new(LocalParser::new()));
self.register_parser("postmap", Box::new(PostmapParser::new()));
self.register_parser("postsuper", Box::new(PostsuperParser::new().unwrap()));
self.register_parser("anvil", Box::new(anvil::AnvilParser::new()));
self.register_parser("pickup", Box::new(pickup::PickupParser::new()));
self.register_parser(
"trivial-rewrite",
Box::new(trivial_rewrite::TrivialRewriteParser::new()),
);
self.register_parser("postlogd", Box::new(postlogd::PostlogdParser::new()));
self.register_parser("proxymap", Box::new(proxymap::ProxymapParser::new()));
self.register_parser("sendmail", Box::new(sendmail::SendmailParser::new()));
self.register_parser("virtual", Box::new(virtual_parser::VirtualParser::new()));
}
pub fn register_parser(&mut self, component: &str, parser: Box<dyn ComponentParser>) {
debug!("注册组件解析器: {}", component);
self.component_parsers.insert(component.to_string(), parser);
}
pub fn parse(&self, log_line: &str) -> ParseResult {
debug!("开始解析日志行: {}", log_line);
let base_info = match self.parse_base_format(log_line) {
Ok(info) => info,
Err(e) => return ParseResult::failure(e),
};
let parser = match self.component_parsers.get(&base_info.component) {
Some(parser) => parser,
None => {
warn!("未找到组件解析器: {}", base_info.component);
return self.create_unknown_event(log_line, base_info);
}
};
match parser.parse(&base_info.message) {
Ok(event) => {
let postfix_event = PostfixLogEvent::new(
log_line.to_string(),
base_info.timestamp,
base_info.hostname,
base_info.component,
base_info.process_id,
base_info.log_level,
event,
None, );
ParseResult::success(postfix_event, 1.0)
}
Err(e) => {
warn!(
"组件解析失败: {} - {}, 创建Unknown事件",
base_info.component, e
);
self.create_unknown_event(log_line, base_info)
}
}
}
fn parse_base_format(&self, log_line: &str) -> Result<BaseLogInfo, ParseError> {
let captures =
self.base_log_regex
.captures(log_line)
.ok_or_else(|| ParseError::InvalidLogFormat {
reason: "不匹配标准Postfix日志格式".to_string(),
})?;
let timestamp_str = captures.get(1).unwrap().as_str();
let hostname = captures.get(2).unwrap().as_str().to_string();
let component = captures.get(3).unwrap().as_str().to_string();
let process_id = captures
.get(4)
.unwrap()
.as_str()
.parse::<u32>()
.map_err(|_| ParseError::InvalidLogFormat {
reason: "无效的进程ID".to_string(),
})?;
let raw_message = captures.get(5).unwrap().as_str().to_string();
let timestamp = self.parse_timestamp(timestamp_str)?;
let (log_level, message) = self.extract_log_level_and_message(&raw_message);
Ok(BaseLogInfo {
timestamp,
hostname,
component,
process_id,
log_level,
message,
raw_message,
})
}
fn parse_timestamp(&self, timestamp_str: &str) -> Result<DateTime<Utc>, ParseError> {
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(timestamp_str, "%Y %b %d %H:%M:%S%.f") {
return Ok(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(timestamp_str, "%Y %b %d %H:%M:%S") {
return Ok(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
let current_year = chrono::Utc::now().year();
let datetime_str = format!("{} {}", current_year, timestamp_str);
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S%.f") {
return Ok(DateTime::from_naive_utc_and_offset(naive_dt, Utc));
}
let naive_dt =
NaiveDateTime::parse_from_str(&datetime_str, "%Y %b %d %H:%M:%S").map_err(|_e| {
ParseError::InvalidTimestamp {
timestamp: timestamp_str.to_string(),
}
})?;
Ok(DateTime::from_naive_utc_and_offset(naive_dt, Utc))
}
fn extract_log_level_and_message(
&self,
raw_message: &str,
) -> (crate::events::base::PostfixLogLevel, String) {
if let Some(first_word_end) = raw_message.find(' ') {
let first_word = &raw_message[..first_word_end + 1]; if let Some(level) =
crate::events::base::PostfixLogLevel::from_prefix(first_word.trim())
{
let message = raw_message[first_word_end + 1..].to_string();
return (level, message);
}
}
if raw_message.starts_with("warning:") {
let message = raw_message
.strip_prefix("warning:")
.unwrap_or(raw_message)
.trim()
.to_string();
return (crate::events::base::PostfixLogLevel::Warning, message);
}
(
crate::events::base::PostfixLogLevel::Info,
raw_message.to_string(),
)
}
fn create_unknown_event(&self, log_line: &str, base_info: BaseLogInfo) -> ParseResult {
let unknown_event = UnknownEvent {
component: base_info.component.clone(),
message: base_info.message.clone(),
};
let component_name = base_info.component.clone();
let postfix_event = PostfixLogEvent::new(
log_line.to_string(),
base_info.timestamp,
base_info.hostname,
base_info.component,
base_info.process_id,
base_info.log_level,
ComponentEvent::Unknown(unknown_event),
None,
);
ParseResult::partial(
postfix_event,
0.3,
vec![format!("未识别的组件: {}", component_name)],
)
}
pub fn registered_components(&self) -> Vec<&String> {
self.component_parsers.keys().collect()
}
pub fn parse_base_info(&self, log_line: &str) -> Result<BaseLogInfo, ParseError> {
self.parse_base_format(log_line)
}
}
impl Default for MasterParser {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BaseLogInfo {
pub timestamp: DateTime<Utc>,
pub hostname: String,
pub component: String,
pub process_id: u32,
pub log_level: crate::events::base::PostfixLogLevel,
pub message: String,
pub raw_message: String,
}