log_parser_kma 0.1.3

Rust-based log file parser, helping extract datetime, log levels and messages
Documentation
#![doc = include_str!("../docs.md")]

use chrono::NaiveDateTime;
pub use pest::Parser;
use pest_derive::Parser;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Parser)]
#[grammar = "./grammar.pest"]
pub struct LogParser;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum LogLevel {
    Info,
    Warning,
    Error,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct LogLine {
    pub datetime: NaiveDateTime,
    pub level: LogLevel,
    pub message: String,
}

#[derive(Debug, Error)]
pub enum LogParseError {
    #[error("Pest grammar error")]
    ParseError(Box<pest::error::Error<Rule>>),
    #[error("Invalid log level")]
    InvalidLogLevel,
    #[error("Invalid date time provided")]
    InvalidDateTime,
    #[error("Error reading file")]
    FileError(std::io::Error),
}

impl LogParser {
    pub fn parse_file(path: &str) -> Result<Vec<LogLine>, LogParseError> {
        let content = std::fs::read_to_string(path).map_err(LogParseError::FileError)?;

        let mut parsed_logs: Vec<LogLine> = Vec::new();

        for line in content.lines() {
            match LogLine::parse(line) {
                Ok(log_line) => parsed_logs.push(log_line),
                Err(e) => eprintln!("Failed to parse line: {:?}, err: {:?}", line, e),
            }
        }

        Ok(parsed_logs)
    }
}

impl LogLine {
    pub fn parse(line: &str) -> Result<LogLine, LogParseError> {
        let mut parse_result = LogParser::parse(Rule::logline, line)
            .map_err(|e| LogParseError::ParseError(Box::new(e)))?;
        let pair = parse_result.next().unwrap();

        let datetime_str = pair.clone().into_inner().next().unwrap().as_str();
        let datetime = NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S")
            .map_err(|_| LogParseError::InvalidDateTime)?;

        let level_str = pair.clone().into_inner().nth(1).unwrap().as_str();
        let level = match level_str {
            "INFO" => LogLevel::Info,
            "WARNING" => LogLevel::Warning,
            "ERROR" => LogLevel::Error,
            _ => return Err(LogParseError::InvalidLogLevel),
        };

        let message = pair.into_inner().last().unwrap().as_str().to_string();
        Ok(LogLine {
            datetime,
            level,
            message,
        })
    }
}