1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fs::OpenOptions;
4use std::io::Write;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum AuditOperation {
10 Query,
11 Mutation,
12 Schema,
13 Subscription,
14}
15
16#[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
30pub trait AuditLogger: Send + Sync {
32 fn log(&self, entry: AuditEntry);
33}
34
35pub 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
74pub 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
102pub struct NoOpAuditLogger;
104
105impl AuditLogger for NoOpAuditLogger {
106 fn log(&self, _entry: AuditEntry) {
107 }
109}