use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use async_trait::async_trait;
use gradatum_core::audit::http::{AuditSink, HttpAuditEvent};
use tokio::io::AsyncWriteExt as _;
use tokio::sync::Mutex;
#[allow(dead_code)]
struct Inner {
current_date: String,
file: tokio::fs::File,
}
#[allow(dead_code)]
pub struct JsonlFileSink {
base_dir: PathBuf,
current: Arc<Mutex<Option<Inner>>>,
dropped_total: Arc<AtomicU64>,
}
#[allow(dead_code)]
impl JsonlFileSink {
pub fn new(base_dir: PathBuf) -> Self {
Self {
base_dir,
current: Arc::new(Mutex::new(None)),
dropped_total: Arc::new(AtomicU64::new(0)),
}
}
pub fn dropped_total(&self) -> u64 {
self.dropped_total.load(Ordering::Relaxed)
}
async fn record_inner(&self, event: HttpAuditEvent) -> Result<(), std::io::Error> {
let today = event.ts.format("%Y-%m-%d").to_string();
let mut guard = self.current.lock().await;
let needs_rotate = guard
.as_ref()
.is_none_or(|inner| inner.current_date != today);
if needs_rotate {
let file = self.open_file_for_date(&today).await?;
*guard = Some(Inner {
current_date: today.clone(),
file,
});
}
let inner = guard
.as_mut()
.expect("Inner est Some — initialisé juste au-dessus");
let line = serde_json::to_string(&event)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
inner.file.write_all(line.as_bytes()).await?;
inner.file.write_all(b"\n").await?;
inner.file.flush().await?;
Ok(())
}
async fn open_file_for_date(&self, date: &str) -> Result<tokio::fs::File, std::io::Error> {
tokio::fs::create_dir_all(&self.base_dir).await?;
let path = self.base_dir.join(format!("audit.{date}.jsonl"));
tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.mode(0o640)
.open(&path)
.await
}
}
#[async_trait]
impl AuditSink for JsonlFileSink {
async fn record(&self, event: HttpAuditEvent) -> Result<(), std::io::Error> {
let result = self.record_inner(event).await;
if result.is_err() {
self.dropped_total.fetch_add(1, Ordering::Relaxed);
tracing::warn!("audit sink : événement non persisté — dropped_total incrémenté");
}
result
}
}