use std::io::Write;
use std::sync::{Arc, Mutex, RwLock};
use super::event::AuditEvent;
#[derive(Debug, thiserror::Error)]
pub enum AuditLogError {
#[error("logger capacity exceeded ({0} events)")]
CapacityExceeded(usize),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("serialization error: {0}")]
Serialization(String),
}
pub trait AuditLogger: Send + Sync {
fn log(&self, event: AuditEvent) -> Result<(), AuditLogError>;
fn flush(&self) -> Result<(), AuditLogError> {
Ok(())
}
}
pub struct InMemoryAuditLogger {
events: Arc<RwLock<Vec<AuditEvent>>>,
max_capacity: usize,
}
impl Default for InMemoryAuditLogger {
fn default() -> Self {
Self::new()
}
}
impl InMemoryAuditLogger {
pub fn new() -> Self {
Self::with_capacity(10_000)
}
pub fn with_capacity(max_capacity: usize) -> Self {
Self {
events: Arc::new(RwLock::new(Vec::new())),
max_capacity,
}
}
pub fn events(&self) -> Vec<AuditEvent> {
self.events
.read()
.unwrap_or_else(|p| p.into_inner())
.clone()
}
pub fn len(&self) -> usize {
self.events.read().unwrap_or_else(|p| p.into_inner()).len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
self.events
.write()
.unwrap_or_else(|p| p.into_inner())
.clear();
}
}
impl AuditLogger for InMemoryAuditLogger {
fn log(&self, event: AuditEvent) -> Result<(), AuditLogError> {
let mut guard = self.events.write().unwrap_or_else(|p| p.into_inner());
if guard.len() >= self.max_capacity {
return Err(AuditLogError::CapacityExceeded(self.max_capacity));
}
guard.push(event);
Ok(())
}
}
pub struct JsonLineAuditLogger {
writer: Arc<Mutex<Box<dyn Write + Send>>>,
}
impl JsonLineAuditLogger {
pub fn new(writer: impl Write + Send + 'static) -> Self {
Self {
writer: Arc::new(Mutex::new(Box::new(writer))),
}
}
pub fn to_file(path: &std::path::Path) -> Result<Self, AuditLogError> {
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
Ok(Self::new(file))
}
pub fn to_stderr() -> Self {
Self::new(std::io::stderr())
}
}
impl AuditLogger for JsonLineAuditLogger {
fn log(&self, event: AuditEvent) -> Result<(), AuditLogError> {
let line = serde_json::to_string(&event)
.map_err(|e| AuditLogError::Serialization(e.to_string()))?;
let mut guard = self.writer.lock().unwrap_or_else(|p| p.into_inner());
guard.write_all(line.as_bytes())?;
guard.write_all(b"\n")?;
Ok(())
}
fn flush(&self) -> Result<(), AuditLogError> {
let mut guard = self.writer.lock().unwrap_or_else(|p| p.into_inner());
guard.flush()?;
Ok(())
}
}
pub struct CompositeAuditLogger {
loggers: Vec<Arc<dyn AuditLogger>>,
}
impl CompositeAuditLogger {
pub fn new(loggers: Vec<Arc<dyn AuditLogger>>) -> Self {
Self { loggers }
}
}
impl AuditLogger for CompositeAuditLogger {
fn log(&self, event: AuditEvent) -> Result<(), AuditLogError> {
let mut first_err: Option<AuditLogError> = None;
for logger in &self.loggers {
if let Err(e) = logger.log(event.clone()) {
if first_err.is_none() {
first_err = Some(e);
}
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
fn flush(&self) -> Result<(), AuditLogError> {
let mut first_err: Option<AuditLogError> = None;
for logger in &self.loggers {
if let Err(e) = logger.flush() {
if first_err.is_none() {
first_err = Some(e);
}
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
}