Skip to main content

aurora_db/
audit.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fs::OpenOptions;
4use std::io::Write;
5use std::path::PathBuf;
6
7/// Type of operation being audited
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum AuditOperation {
10    Query,
11    Mutation,
12    Schema,
13    Subscription,
14}
15
16/// Audit log entry
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct AuditEntry {
19    pub timestamp: DateTime<Utc>,
20    pub operation: AuditOperation,
21    pub collection: Option<String>,
22    pub query: Option<String>,
23    pub affected_rows: Option<usize>,
24    pub duration_ms: Option<f64>,
25    pub user_id: Option<String>,
26    pub success: bool,
27    pub error: Option<String>,
28}
29
30/// Audit logger trait
31pub trait AuditLogger: Send + Sync {
32    fn log(&self, entry: AuditEntry);
33}
34
35/// File-based audit logger
36pub struct FileAuditLogger {
37    log_path: PathBuf,
38}
39
40impl FileAuditLogger {
41    pub fn new(log_path: PathBuf) -> Self {
42        Self { log_path }
43    }
44}
45
46impl AuditLogger for FileAuditLogger {
47    fn log(&self, entry: AuditEntry) {
48        let json = match serde_json::to_string(&entry) {
49            Ok(j) => j,
50            Err(e) => {
51                eprintln!("Failed to serialize audit entry: {}", e);
52                return;
53            }
54        };
55
56        let mut file = match OpenOptions::new()
57            .create(true)
58            .append(true)
59            .open(&self.log_path)
60        {
61            Ok(f) => f,
62            Err(e) => {
63                eprintln!("Failed to open audit log file: {}", e);
64                return;
65            }
66        };
67
68        if let Err(e) = writeln!(file, "{}", json) {
69            eprintln!("Failed to write audit log: {}", e);
70        }
71    }
72}
73
74/// Console audit logger (for development)
75pub struct ConsoleAuditLogger;
76
77impl AuditLogger for ConsoleAuditLogger {
78    fn log(&self, entry: AuditEntry) {
79        let status = if entry.success { "✓" } else { "✗" };
80        let duration = entry
81            .duration_ms
82            .map(|d| format!(" ({}ms)", d))
83            .unwrap_or_default();
84
85        println!(
86            "[AUDIT] {} {:?} collection={} user={} rows={} {}{}",
87            status,
88            entry.operation,
89            entry.collection.as_deref().unwrap_or("N/A"),
90            entry.user_id.as_deref().unwrap_or("system"),
91            entry.affected_rows.unwrap_or(0),
92            entry.timestamp.format("%Y-%m-%d %H:%M:%S"),
93            duration
94        );
95
96        if let Some(err) = entry.error {
97            println!("       Error: {}", err);
98        }
99    }
100}
101
102/// No-op audit logger (default)
103pub struct NoOpAuditLogger;
104
105impl AuditLogger for NoOpAuditLogger {
106    fn log(&self, _entry: AuditEntry) {
107        // Do nothing
108    }
109}