postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! # Anvil 连接统计组件解析器
//!
//! Anvil 是 Postfix 的连接统计和速率限制组件,负责:
//! - 监控客户端连接频率和数量
//! - 统计最大连接速率和消息速率  
//! - 提供配置警告和系统统计信息
//! - 支持基于来源的速率限制策略
//!
//! ## 支持的事件类型
//!
//! - **配置警告**: 配置文件中的参数覆盖警告
//! - **连接统计**: 最大连接速率、连接数量、消息速率统计
//! - **缓存统计**: 缓存大小统计信息
//!
//! ## 示例日志格式
//!
//! ```text
//! # 配置警告
//! /etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0
//!
//! # 连接统计  
//! statistics: max connection rate 2/60s for (smtp:192.168.2.127) at Apr 10 11:47:05
//! statistics: max cache size 1 at Apr 10 11:46:30
//! ```

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! {
    // 配置警告:/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0
    static ref CONFIG_WARNING_REGEX: Regex = Regex::new(
        r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
    ).unwrap();

    // 统计信息:statistics: max connection rate 2/60s for (smtp:192.168.2.127) at Apr 10 11:47:05
    static ref STATISTICS_REGEX: Regex = Regex::new(
        r"^statistics: max (.+?) (\d+)(/\d+s)? for \((.+?)\) at (.+)$"
    ).unwrap();

    // 缓存大小统计:statistics: max cache size 1 at Apr 10 11:46:30
    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,
            };

            // 解析时间戳(格式:Apr 10 11:47:05)
            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;

        // 新格式:2025 Apr 10 11:47:05.123456
        // 旧格式:Apr 10 11:47:05

        // 首先尝试解析新格式(包含年份和可选毫秒)
        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()
    }
}