postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! 本地投递(local)组件解析器

use crate::error::ParseError;
use crate::events::local::{LocalEvent, ConfigurationWarning, LocalDelivery, ExternalDelivery, WarningType, DeliveryStatus, DeliveryMethod};
use crate::events::base::BaseEvent;
use crate::events::ComponentEvent;
use super::ComponentParser;

use regex::Regex;
use std::collections::HashMap;

/// LOCAL组件解析器
///
/// 基于Postfix LOCAL守护进程的真实日志格式开发
/// LOCAL守护进程的特点:
/// - 处理本地邮件投递
/// - 管理别名和转发
/// - 处理配置警告
/// - 执行外部命令和文件投递
pub struct LocalParser {
    // 配置警告模式
    nis_domain_regex: Regex,
    alias_not_found_regex: Regex,
    
    // 本地投递模式
    local_delivery_regex: Regex,
    
    // 外部投递模式
    external_delivery_regex: Regex,
}

impl LocalParser {
    pub fn new() -> Self {
        Self {
            // 配置警告正则表达式(MasterParser已剥离"warning:"前缀)
            nis_domain_regex: Regex::new(
                r"^dict_nis_init: NIS domain name not set - NIS lookups disabled$"
            ).expect("LOCAL NIS域名警告正则表达式编译失败"),
            
            alias_not_found_regex: Regex::new(
                r"^required alias not found: (.+)$"
            ).expect("LOCAL别名未找到警告正则表达式编译失败"),
            
            // 本地投递正则表达式
            local_delivery_regex: Regex::new(
                r"^([0-9A-F]+): to=<([^>]+)>(?:, orig_to=<([^>]+)>)?, relay=([^,]+), delay=([0-9.]+), delays=([0-9.]+/[0-9.]+/[0-9.]+/[0-9.]+), dsn=([^,]+), status=(\w+)(?: \(([^)]+)\))?$"
            ).expect("LOCAL本地投递正则表达式编译失败"),
            
            // 外部投递正则表达式
            external_delivery_regex: Regex::new(
                r"([0-9A-F]+): to=<([^>]+)>, relay=([^,]+), delay=([0-9.]+), (?:cmd|file)=(.+), status=(\w+)"
            ).expect("LOCAL外部投递正则表达式编译失败"),
        }
    }

    /// 解析LOCAL日志行
    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
        // 尝试解析配置警告
        if let Some(event) = self.parse_configuration_warning(line, base_event.clone()) {
            return Some(event);
        }

        // 尝试解析本地投递
        if let Some(event) = self.parse_local_delivery(line, base_event.clone()) {
            return Some(event);
        }

        // 尝试解析外部投递
        if let Some(event) = self.parse_external_delivery(line, base_event) {
            return Some(event);
        }

        None
    }

    fn parse_configuration_warning(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
        // NIS域名未设置警告
        if self.nis_domain_regex.is_match(line) {
            let warning = ConfigurationWarning {
                timestamp: base_event.timestamp,
                warning_type: WarningType::NisDomainNotSet,
                message: "NIS domain name not set - NIS lookups disabled".to_string(),
                details: HashMap::new(),
            };
            return Some(LocalEvent::ConfigurationWarning(warning));
        }

        // 必需别名未找到警告
        if let Some(caps) = self.alias_not_found_regex.captures(line) {
            let alias_name = caps.get(1).unwrap().as_str();
            let mut details = HashMap::new();
            details.insert("alias_name".to_string(), alias_name.to_string());
            
            let warning = ConfigurationWarning {
                timestamp: base_event.timestamp,
                warning_type: WarningType::RequiredAliasNotFound,
                message: format!("required alias not found: {}", alias_name),
                details,
            };
            return Some(LocalEvent::ConfigurationWarning(warning));
        }

        None
    }

    fn parse_local_delivery(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
        if let Some(caps) = self.local_delivery_regex.captures(line) {
            let queue_id = caps.get(1).unwrap().as_str().to_string();
            let recipient = caps.get(2).unwrap().as_str().to_string();
            let original_recipient = caps.get(3).map(|m| m.as_str().to_string());
            let relay = caps.get(4).unwrap().as_str().to_string();
            let delay = caps.get(5).unwrap().as_str().parse::<f64>().unwrap_or(0.0);
            let delays_str = caps.get(6).unwrap().as_str();
            let dsn = caps.get(7).unwrap().as_str().to_string();
            let status_str = caps.get(8).unwrap().as_str();
            let extra_info = caps.get(9).map(|m| m.as_str());

            // 解析delays
            let delays: Vec<f64> = delays_str
                .split('/')
                .map(|s| s.parse::<f64>().unwrap_or(0.0))
                .collect();

            // 确定投递状态
            let status = match status_str.to_lowercase().as_str() {
                "sent" => DeliveryStatus::Sent,
                "bounced" => DeliveryStatus::Bounced,
                "deferred" => DeliveryStatus::Deferred,
                _ => DeliveryStatus::Sent,
            };

            // 确定投递方法
            let delivery_method = if let Some(info) = extra_info {
                if info.contains("discarded") {
                    DeliveryMethod::Discarded
                } else if info.contains("forwarded") {
                    DeliveryMethod::Forwarded
                } else if info.contains("piped") {
                    DeliveryMethod::Piped
                } else if info.contains("file") {
                    DeliveryMethod::File
                } else {
                    DeliveryMethod::Mailbox
                }
            } else {
                DeliveryMethod::Mailbox
            };

            let delivery = LocalDelivery {
                timestamp: base_event.timestamp,
                queue_id,
                recipient,
                original_recipient,
                relay,
                delay,
                delays,
                dsn,
                status,
                delivery_method,
                size: None,
                nrcpt: None,
            };

            return Some(LocalEvent::LocalDelivery(delivery));
        }

        None
    }

    fn parse_external_delivery(&self, line: &str, base_event: BaseEvent) -> Option<LocalEvent> {
        if let Some(caps) = self.external_delivery_regex.captures(line) {
            let queue_id = caps.get(1).unwrap().as_str().to_string();
            let recipient = caps.get(2).unwrap().as_str().to_string();
            let relay = caps.get(3).unwrap().as_str().to_string();
            let delay = caps.get(4).unwrap().as_str().parse::<f64>().unwrap_or(0.0);
            let external_target = caps.get(5).unwrap().as_str().to_string();
            let status_str = caps.get(6).unwrap().as_str();

            let status = match status_str.to_lowercase().as_str() {
                "sent" => DeliveryStatus::Sent,
                "bounced" => DeliveryStatus::Bounced,
                "deferred" => DeliveryStatus::Deferred,
                _ => DeliveryStatus::Sent,
            };

            let mut details = HashMap::new();
            details.insert("relay".to_string(), relay);

            let (command, file_path) = if external_target.starts_with('/') {
                // 文件路径
                (None, Some(external_target))
            } else {
                // 命令
                (Some(external_target), None)
            };

            let delivery = ExternalDelivery {
                timestamp: base_event.timestamp,
                queue_id,
                recipient,
                original_recipient: None,
                command,
                file_path,
                status,
                delay,
                details,
            };

            return Some(LocalEvent::ExternalDelivery(delivery));
        }

        None
    }
}

impl ComponentParser for LocalParser {
    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
        // 创建一个临时的BaseEvent,用于解析
        // 在实际使用中,这些字段会被MasterParser正确填充
        let base_event = BaseEvent {
            timestamp: chrono::Utc::now(),
            hostname: "temp".to_string(),
            component: "local".to_string(),
            process_id: 0,
            log_level: crate::events::base::PostfixLogLevel::Info,
            raw_message: message.to_string(),
        };

        if let Some(local_event) = self.parse_line(message, base_event) {
            Ok(ComponentEvent::Local(local_event))
        } else {
            Err(ParseError::ComponentParseError {
                component: "local".to_string(),
                reason: "无法识别的local日志格式".to_string(),
        })
        }
    }

    fn component_name(&self) -> &'static str {
        "local"
    }
}

impl Default for LocalParser {
    fn default() -> Self {
        Self::new()
    }
}