use chrono::Utc;
use serde::Serialize;
use std::path::Path;
use tokio::fs::{File, OpenOptions};
use tokio::io::{AsyncWriteExt, BufWriter};
use tokio::sync::Mutex;
use super::{Hook, HookEvent};
#[derive(Serialize)]
struct AuditRecord<'a> {
timestamp: String,
#[serde(flatten)]
event: &'a HookEvent<'a>,
}
pub struct AuditFileLoggerHook {
writer: Mutex<BufWriter<File>>,
}
impl AuditFileLoggerHook {
pub async fn new(path: impl AsRef<Path>) -> std::io::Result<Self> {
let file = OpenOptions::new()
.create(true) .append(true) .open(path)
.await?;
let writer = BufWriter::new(file);
Ok(Self {
writer: Mutex::new(writer),
})
}
}
#[async_trait::async_trait]
impl Hook for AuditFileLoggerHook {
type Error = std::io::Error;
async fn on_event(&self, event: &HookEvent<'_>) -> Result<(), Self::Error> {
let record = AuditRecord {
timestamp: Utc::now().to_rfc3339(),
event,
};
let mut json_line = serde_json::to_string(&record).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to serialize event: {}", e),
)
})?;
json_line.push('\n');
let mut writer = self.writer.lock().await;
writer.write_all(json_line.as_bytes()).await?;
writer.flush().await?;
Ok(())
}
}