Files
postfix-log-viewer/src/parser.rs
Emmanuel BERNAT 18da265098 Nettoyage du code généré par IA.
Remise à plat des états de l'application (saisie, affichage...)
2025-05-19 22:32:03 +02:00

78 lines
3.2 KiB
Rust

use crate::models::{LogEntry, SMTPSession};
use chrono::{Datelike, NaiveDateTime};
use regex::Regex;
use std::{collections::HashMap};
lazy_static::lazy_static! {
static ref LOG_LINE_RE: Regex = Regex::new(
r"^(?P<month>\w+)\s+(?P<day>\d+)\s+(?P<time>\d+:\d+:\d+)\s+(?P<hostname>\S+)\s+(?P<process>\S+):\s+(?P<message>.*)$"
).unwrap();
}
fn get_log_year(file_path: Option<&str>) -> Option<i32> {
file_path.and_then(|path| {
let metadata = std::fs::metadata(path).ok()?;
let modified = metadata.modified().ok()?;
let datetime: chrono::DateTime<chrono::Local> = modified.into();
Some(datetime.year())
})
}
pub fn parse_log_file_with_path(contents: &str, file_path: Option<&str>) -> anyhow::Result<Vec<SMTPSession>> {
let mut sessions: HashMap<String, SMTPSession> = HashMap::new();
let log_year = get_log_year(file_path).unwrap_or_else(|| chrono::Local::now().year());
for line in contents.lines() {
if let Some(captures) = LOG_LINE_RE.captures(line) {
let month = &captures["month"];
let day = &captures["day"];
let time = &captures["time"];
let message = captures["message"].to_string();
// Create a timestamp string in the format that chrono can parse
let timestamp_str = format!("{} {} {} {}", log_year, month, day, time);
let mut timestamp = match NaiveDateTime::parse_from_str(&timestamp_str, "%Y %b %d %H:%M:%S") {
Ok(ts) => ts,
Err(_) => continue, // Skip lines with invalid timestamps
};
// Handle year rollover (if log entry is from next year but file is from previous year)
if timestamp.month() == 1 && log_year != chrono::Local::now().year() {
if let Some(prev_year_timestamp) = timestamp.with_year(log_year + 1) {
timestamp = prev_year_timestamp;
}
}
let entry = LogEntry {
timestamp,
message: message.clone(),
};
// Extract session ID from the message
if let Some(id_caps) = crate::models::SESSION_ID_RE.captures(&message) {
let session_id = id_caps[1].to_string();
// Get or create the session
let session = sessions.entry(session_id.clone())
.or_insert_with(|| SMTPSession::new(&session_id));
// Add the entry to the session
session.add_entry(entry);
// Update session information from the message
session.update_from_message(&message);
}
}
}
// Convert the HashMap to a Vec and sort by start time (most recent first)
let mut sessions: Vec<SMTPSession> = sessions.into_iter().map(|(_, v)| v).collect();
sessions.sort_by(|a, b| {
let a_time = a.start_time.unwrap_or_else(|| chrono::DateTime::<chrono::Utc>::MIN_UTC.naive_local());
let b_time = b.start_time.unwrap_or_else(|| chrono::DateTime::<chrono::Utc>::MIN_UTC.naive_local());
b_time.cmp(&a_time) // Sort in descending order (newest first)
});
Ok(sessions)
}