Skip to main content

px_auth/infrastructure/
audit_sink.rs

1use crate::domain::audit_event::AuditEvent;
2use crate::domain::audit_sink::AuditSink;
3use async_trait::async_trait;
4use px_errors::AppError;
5use std::path::PathBuf;
6use tokio::io::AsyncWriteExt;
7use tokio::sync::Mutex;
8
9pub struct FileAuditSink {
10    path: PathBuf,
11    lock: Mutex<()>,
12}
13
14impl FileAuditSink {
15    pub fn new(path: impl Into<PathBuf>) -> Self {
16        Self {
17            path: path.into(),
18            lock: Mutex::new(()),
19        }
20    }
21}
22
23#[async_trait]
24impl AuditSink for FileAuditSink {
25    async fn record(&self, event: &AuditEvent) -> Result<(), AppError> {
26        let line = event
27            .redacted_json()
28            .map_err(|e| AppError::InternalError(format!("audit json: {e}")))?;
29        let _guard = self.lock.lock().await;
30        let mut file = tokio::fs::OpenOptions::new()
31            .create(true)
32            .append(true)
33            .open(&self.path)
34            .await
35            .map_err(|e| AppError::InternalError(format!("audit open: {e}")))?;
36        file.write_all(line.as_bytes())
37            .await
38            .map_err(|e| AppError::InternalError(format!("audit write: {e}")))?;
39        file.write_all(b"\n")
40            .await
41            .map_err(|e| AppError::InternalError(format!("audit newline: {e}")))?;
42        Ok(())
43    }
44}
45
46pub struct StdoutAuditSink;
47
48impl StdoutAuditSink {
49    pub fn new() -> Self {
50        Self
51    }
52}
53
54impl Default for StdoutAuditSink {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60#[async_trait]
61impl AuditSink for StdoutAuditSink {
62    async fn record(&self, event: &AuditEvent) -> Result<(), AppError> {
63        let line = event
64            .redacted_json()
65            .map_err(|e| AppError::InternalError(format!("audit json: {e}")))?;
66        tracing::info!(target: "audit", "{line}");
67        Ok(())
68    }
69}