px_auth/infrastructure/
audit_sink.rs1use 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}