fail2ban-log-parser-core 0.1.2

Fast, zero-copy fail2ban log parser built with winnow
Documentation
use std::net::{IpAddr, Ipv4Addr};

use chrono::Datelike;
use fail2ban_log_parser_core::{Fail2BanEvent, Fail2BanHeaderType, Fail2BanLevel, parse};

#[test]
fn parse_standard_found_line() {
    let line = "2024-01-15 14:32:01,847 fail2ban.filter [12345] INFO [sshd] Found 192.168.1.1";
    let result: Vec<_> = parse(line).collect();
    assert_eq!(result.len(), 1);
    let log = result[0].as_ref().unwrap();
    assert!(log.timestamp().is_some());
    assert_eq!(log.header(), Some(&Fail2BanHeaderType::Filter));
    assert_eq!(log.pid(), Some(12345));
    assert_eq!(log.level(), Some(&Fail2BanLevel::Info));
    assert_eq!(log.jail(), Some("sshd"));
    assert_eq!(log.event(), Some(&Fail2BanEvent::Found));
    assert_eq!(log.ip(), Some(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))));
}

#[test]
fn parse_ban_line() {
    let line = "2024-06-20 03:15:42,100 fail2ban.actions [999] NOTICE [sshd] Ban 10.0.0.5";
    let log = parse(line).next().unwrap().unwrap();
    assert_eq!(log.header(), Some(&Fail2BanHeaderType::Actions));
    assert_eq!(log.pid(), Some(999));
    assert_eq!(log.level(), Some(&Fail2BanLevel::Notice));
    assert_eq!(log.jail(), Some("sshd"));
    assert_eq!(log.event(), Some(&Fail2BanEvent::Ban));
    assert_eq!(log.ip(), Some(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5))));
}

#[test]
fn parse_syslog_all_months() {
    let months = [
        ("Jan", 1),
        ("Feb", 2),
        ("Mar", 3),
        ("Apr", 4),
        ("May", 5),
        ("Jun", 6),
        ("Jul", 7),
        ("Aug", 8),
        ("Sep", 9),
        ("Oct", 10),
        ("Nov", 11),
        ("Dec", 12),
    ];
    for (abbr, expected_month) in &months {
        let line = format!(
            "{} 1 2024 12:00:00,000 fail2ban.filter [1] INFO [sshd] Found 1.2.3.4",
            abbr
        );
        let log = parse(&line).next().unwrap().unwrap();
        let ts = log.timestamp().unwrap();
        assert_eq!(
            ts.month(),
            *expected_month,
            "failed for month abbreviation {}",
            abbr
        );
    }
}

#[test]
fn parse_time_with_dot_milliseconds() {
    let line = "2024-01-01 14:32:01.847 fail2ban.filter [1] INFO [sshd] Found 1.2.3.4";
    let log = parse(line).next().unwrap().unwrap();
    let ts = log.timestamp().unwrap();
    assert_eq!(ts.format("%H:%M:%S").to_string(), "14:32:01");
}

#[test]
fn parse_leap_day() {
    let line = "2024-02-29 12:00:00,000 fail2ban.filter [1] INFO [sshd] Found 1.2.3.4";
    let log = parse(line).next().unwrap().unwrap();
    let ts = log.timestamp().unwrap();
    assert_eq!(ts.format("%Y-%m-%d").to_string(), "2024-02-29");
}

#[test]
fn parse_end_of_day_timestamp() {
    let line = "2024-01-01 23:59:59,999 fail2ban.filter [1] INFO [sshd] Found 1.2.3.4";
    let log = parse(line).next().unwrap().unwrap();
    let ts = log.timestamp().unwrap();
    assert_eq!(ts.format("%H:%M:%S").to_string(), "23:59:59");
}

#[test]
fn parse_timestamp_millisecond_precision() {
    let line = "2024-06-15 08:30:45,123 fail2ban.filter [1] INFO [sshd] Found 1.2.3.4";
    let log = parse(line).next().unwrap().unwrap();
    let ts = log.timestamp().unwrap();
    assert_eq!(ts.timestamp_subsec_millis(), 123);
}

#[test]
fn parse_with_extra_whitespace() {
    let line = "2024-01-01 00:00:00,000  fail2ban.filter  [1]  INFO  [sshd]  Found  1.2.3.4";
    let log = parse(line).next().unwrap().unwrap();
    assert_eq!(log.event(), Some(&Fail2BanEvent::Found));
    assert_eq!(log.ip(), Some(&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))));
}