78 lines
3.2 KiB
Rust
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(×tamp_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)
|
|
}
|