scouty 0.3.2

Log parsing, filtering, and analysis library
Documentation
#[cfg(test)]
mod tests {
    use crate::filter::eval::eval;
    use crate::filter::expr::parse;
    use crate::record::{LogLevel, LogRecord};
    use chrono::Utc;
    use std::collections::HashMap;

    fn make_record(level: LogLevel, message: &str, component: Option<&str>) -> LogRecord {
        let mut metadata = HashMap::new();
        metadata.insert("env".to_string(), "prod".to_string());
        LogRecord {
            id: 42,
            timestamp: Utc::now(),
            level: Some(level),
            source: "test-source".into(),
            pid: Some(1234),
            tid: None,
            component_name: component.map(|s| s.to_string()),
            process_name: None,
            hostname: None,
            container: None,
            context: None,
            function: None,
            message: message.into(),
            raw: message.into(),
            metadata: Some(metadata),
            loader_id: "loader-1".into(),
            expanded: None,
        }
    }

    #[test]
    fn eval_eq_level() {
        let expr = parse(r#"level == "ERROR""#).unwrap();
        let r = make_record(LogLevel::Error, "boom", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_eq_level_no_match() {
        let expr = parse(r#"level == "ERROR""#).unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(!eval(&expr, &r));
    }

    #[test]
    fn eval_ne() {
        let expr = parse(r#"level != "DEBUG""#).unwrap();
        let r = make_record(LogLevel::Error, "boom", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_contains() {
        let expr = parse(r#"message contains "error""#).unwrap();
        let r = make_record(LogLevel::Error, "an error occurred", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_starts_with() {
        let expr = parse(r#"message starts_with "an""#).unwrap();
        let r = make_record(LogLevel::Info, "an event", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_ends_with() {
        let expr = parse(r#"message ends_with "occurred""#).unwrap();
        let r = make_record(LogLevel::Error, "error occurred", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_regex() {
        let expr = parse(r#"message regex "err(or)?\s+\d+""#).unwrap();
        let r = make_record(LogLevel::Error, "error 404", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_numeric_comparison() {
        let expr = parse("id >= 10").unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(eval(&expr, &r)); // id=42 >= 10
    }

    #[test]
    fn eval_numeric_lt() {
        let expr = parse("id < 10").unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(!eval(&expr, &r)); // id=42 < 10 is false
    }

    #[test]
    fn eval_and() {
        let expr = parse(r#"level == "ERROR" AND component == "auth""#).unwrap();
        let r = make_record(LogLevel::Error, "fail", Some("auth"));
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_and_one_fails() {
        let expr = parse(r#"level == "ERROR" AND component == "db""#).unwrap();
        let r = make_record(LogLevel::Error, "fail", Some("auth"));
        assert!(!eval(&expr, &r));
    }

    #[test]
    fn eval_or() {
        let expr = parse(r#"level == "ERROR" OR level == "FATAL""#).unwrap();
        let r = make_record(LogLevel::Error, "fail", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_not() {
        let expr = parse(r#"NOT level == "DEBUG""#).unwrap();
        let r = make_record(LogLevel::Error, "fail", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_complex_nested() {
        let expr =
            parse(r#"(level == "ERROR" OR level == "FATAL") AND component == "auth""#).unwrap();
        let r = make_record(LogLevel::Error, "fail", Some("auth"));
        assert!(eval(&expr, &r));

        let r2 = make_record(LogLevel::Info, "ok", Some("auth"));
        assert!(!eval(&expr, &r2));
    }

    #[test]
    fn eval_metadata_field() {
        let expr = parse(r#"metadata.env == "prod""#).unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_missing_field_returns_false() {
        let expr = parse(r#"component == "auth""#).unwrap();
        let r = make_record(LogLevel::Info, "ok", None); // component is None
        assert!(!eval(&expr, &r));
    }

    #[test]
    fn eval_pid() {
        let expr = parse("pid == 1234").unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_source() {
        let expr = parse(r#"source == "test-source""#).unwrap();
        let r = make_record(LogLevel::Info, "ok", None);
        assert!(eval(&expr, &r));
    }

    #[test]
    fn eval_context() {
        let mut r = make_record(LogLevel::Info, "ok", None);
        r.context = Some("Ethernet248".to_string());
        let expr = parse(r#"context == "Ethernet248""#).unwrap();
        assert!(eval(&expr, &r));
        let expr2 = parse(r#"context contains "Ethernet""#).unwrap();
        assert!(eval(&expr2, &r));
    }

    #[test]
    fn eval_function() {
        let mut r = make_record(LogLevel::Info, "ok", None);
        r.function = Some("SET".to_string());
        let expr = parse(r#"function == "SET""#).unwrap();
        assert!(eval(&expr, &r));
        let expr2 = parse(r#"function == "DEL""#).unwrap();
        assert!(!eval(&expr2, &r));
    }
}