1use crate::value::Value;
12use std::collections::HashMap;
13use std::fmt;
14use std::sync::{Arc, Mutex};
15use std::time::{SystemTime, UNIX_EPOCH};
16
17#[derive(Debug, Clone, PartialEq)]
19pub enum AuditEventType {
20 Access,
22 Modification,
24 ValidationFailure,
26 Reload,
28 Load,
30 Save,
32}
33
34#[derive(Debug, Clone, PartialEq, PartialOrd)]
36pub enum AuditSeverity {
37 Info = 1,
39 Warning = 2,
41 Error = 3,
43 Critical = 4,
45}
46
47#[derive(Debug, Clone)]
49pub struct AuditEvent {
50 pub id: String,
52 pub timestamp: SystemTime,
54 pub event_type: AuditEventType,
56 pub severity: AuditSeverity,
58 pub key: Option<String>,
60 pub old_value: Option<Value>,
62 pub new_value: Option<Value>,
64 pub user_context: Option<String>,
66 pub metadata: HashMap<String, String>,
68 pub error_message: Option<String>,
70 pub source: Option<String>,
72}
73
74impl AuditEvent {
75 pub fn new(event_type: AuditEventType, severity: AuditSeverity) -> Self {
77 Self {
78 id: generate_event_id(),
79 timestamp: SystemTime::now(),
80 event_type,
81 severity,
82 key: None,
83 old_value: None,
84 new_value: None,
85 user_context: None,
86 metadata: HashMap::new(),
87 error_message: None,
88 source: None,
89 }
90 }
91
92 pub fn with_key(mut self, key: impl Into<String>) -> Self {
94 self.key = Some(key.into());
95 self
96 }
97
98 pub fn with_old_value(mut self, value: Value) -> Self {
100 self.old_value = Some(value);
101 self
102 }
103
104 pub fn with_new_value(mut self, value: Value) -> Self {
106 self.new_value = Some(value);
107 self
108 }
109
110 pub fn with_user_context(mut self, context: impl Into<String>) -> Self {
112 self.user_context = Some(context.into());
113 self
114 }
115
116 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
118 self.metadata.insert(key.into(), value.into());
119 self
120 }
121
122 pub fn with_error(mut self, message: impl Into<String>) -> Self {
124 self.error_message = Some(message.into());
125 self
126 }
127
128 pub fn with_source(mut self, source: impl Into<String>) -> Self {
130 self.source = Some(source.into());
131 self
132 }
133}
134
135impl fmt::Display for AuditEvent {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 let timestamp_millis = self
138 .timestamp
139 .duration_since(UNIX_EPOCH)
140 .unwrap_or_default()
141 .as_millis();
142
143 write!(
144 f,
145 "[{}] {:?}:{:?} id={} key={} user={}",
146 timestamp_millis,
147 self.event_type,
148 self.severity,
149 self.id,
150 self.key.as_deref().unwrap_or("none"),
151 self.user_context.as_deref().unwrap_or("system")
152 )?;
153
154 if let Some(error) = &self.error_message {
155 write!(f, " error=\"{error}\"")?;
156 }
157
158 if let (Some(old), Some(new)) = (&self.old_value, &self.new_value) {
159 write!(f, " change=\"{old:?}\" -> \"{new:?}\"")?;
160 }
161
162 for (key, value) in &self.metadata {
163 write!(f, " {key}=\"{value}\"")?;
164 }
165
166 Ok(())
167 }
168}
169
170pub trait AuditSink: Send + Sync {
172 fn write_event(&self, event: &AuditEvent) -> Result<(), String>;
174
175 fn flush(&self) -> Result<(), String>;
177}
178
179pub struct ConsoleSink {
181 level_filter: AuditSeverity,
182}
183
184impl ConsoleSink {
185 pub fn new(min_level: AuditSeverity) -> Self {
187 Self {
188 level_filter: min_level,
189 }
190 }
191}
192
193impl AuditSink for ConsoleSink {
194 fn write_event(&self, event: &AuditEvent) -> Result<(), String> {
195 if event.severity >= self.level_filter {
196 println!("AUDIT: {event}");
197 }
198 Ok(())
199 }
200
201 fn flush(&self) -> Result<(), String> {
202 Ok(()) }
204}
205
206pub struct FileSink {
208 file_path: String,
209 level_filter: AuditSeverity,
210}
211
212impl FileSink {
213 pub fn new(file_path: impl Into<String>, min_level: AuditSeverity) -> Self {
215 Self {
216 file_path: file_path.into(),
217 level_filter: min_level,
218 }
219 }
220}
221
222impl AuditSink for FileSink {
223 fn write_event(&self, event: &AuditEvent) -> Result<(), String> {
224 if event.severity >= self.level_filter {
225 use std::fs::OpenOptions;
226 use std::io::Write;
227
228 let mut file = OpenOptions::new()
229 .create(true)
230 .append(true)
231 .open(&self.file_path)
232 .map_err(|e| format!("Failed to open audit log file: {e}"))?;
233
234 writeln!(file, "{event}").map_err(|e| format!("Failed to write to audit log: {e}"))?;
235 }
236 Ok(())
237 }
238
239 fn flush(&self) -> Result<(), String> {
240 Ok(())
242 }
243}
244
245pub struct AuditLogger {
247 sinks: Vec<Box<dyn AuditSink>>,
248 enabled: bool,
249}
250
251impl AuditLogger {
252 pub fn new() -> Self {
254 Self {
255 sinks: Vec::new(),
256 enabled: true,
257 }
258 }
259
260 pub fn add_sink(mut self, sink: Box<dyn AuditSink>) -> Self {
262 self.sinks.push(sink);
263 self
264 }
265
266 pub fn set_enabled(mut self, enabled: bool) -> Self {
268 self.enabled = enabled;
269 self
270 }
271
272 pub fn log_event(&self, event: AuditEvent) {
274 if !self.enabled {
275 return;
276 }
277
278 for sink in &self.sinks {
279 if let Err(e) = sink.write_event(&event) {
280 eprintln!("Audit sink error: {e}");
281 }
282 }
283 }
284
285 pub fn log_access(&self, key: &str, user_context: Option<&str>) {
287 let event = AuditEvent::new(AuditEventType::Access, AuditSeverity::Info)
288 .with_key(key)
289 .with_metadata("operation", "get");
290
291 let event = if let Some(user) = user_context {
292 event.with_user_context(user)
293 } else {
294 event
295 };
296
297 self.log_event(event);
298 }
299
300 pub fn log_modification(
302 &self,
303 key: &str,
304 old_value: Option<&Value>,
305 new_value: &Value,
306 user_context: Option<&str>,
307 ) {
308 let mut event = AuditEvent::new(AuditEventType::Modification, AuditSeverity::Warning)
309 .with_key(key)
310 .with_new_value(new_value.clone())
311 .with_metadata("operation", "set");
312
313 if let Some(old) = old_value {
314 event = event.with_old_value(old.clone());
315 }
316
317 if let Some(user) = user_context {
318 event = event.with_user_context(user);
319 }
320
321 self.log_event(event);
322 }
323
324 pub fn log_validation_failure(
326 &self,
327 key: &str,
328 error: &str,
329 value: &Value,
330 user_context: Option<&str>,
331 ) {
332 let event = AuditEvent::new(AuditEventType::ValidationFailure, AuditSeverity::Error)
333 .with_key(key)
334 .with_new_value(value.clone())
335 .with_error(error)
336 .with_metadata("operation", "validate");
337
338 let event = if let Some(user) = user_context {
339 event.with_user_context(user)
340 } else {
341 event
342 };
343
344 self.log_event(event);
345 }
346
347 pub fn log_reload(&self, source: &str, success: bool, error: Option<&str>) {
349 let severity = if success {
350 AuditSeverity::Info
351 } else {
352 AuditSeverity::Error
353 };
354 let mut event = AuditEvent::new(AuditEventType::Reload, severity)
355 .with_source(source)
356 .with_metadata("operation", "reload");
357
358 if let Some(err) = error {
359 event = event.with_error(err);
360 }
361
362 self.log_event(event);
363 }
364
365 pub fn flush(&self) {
367 for sink in &self.sinks {
368 if let Err(e) = sink.flush() {
369 eprintln!("Audit sink flush error: {e}");
370 }
371 }
372 }
373}
374
375impl Default for AuditLogger {
376 fn default() -> Self {
377 Self::new()
378 }
379}
380
381static GLOBAL_AUDIT_LOGGER: Mutex<Option<Arc<AuditLogger>>> = Mutex::new(None);
383
384pub fn init_audit_logger(logger: AuditLogger) {
386 if let Ok(mut global) = GLOBAL_AUDIT_LOGGER.lock() {
387 *global = Some(Arc::new(logger));
388 }
389 }
391
392pub fn get_audit_logger() -> Option<Arc<AuditLogger>> {
394 GLOBAL_AUDIT_LOGGER
395 .lock()
396 .ok()
397 .and_then(|guard| guard.clone())
398}
399
400pub fn audit_log(event: AuditEvent) {
402 if let Some(logger) = get_audit_logger() {
403 logger.log_event(event);
404 }
405}
406
407fn generate_event_id() -> String {
409 use std::sync::atomic::{AtomicU64, Ordering};
410 static COUNTER: AtomicU64 = AtomicU64::new(1);
411
412 let timestamp = SystemTime::now()
413 .duration_since(UNIX_EPOCH)
414 .unwrap_or_default()
415 .as_secs();
416 let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
417
418 format!("{timestamp:x}-{counter:x}")
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use std::sync::{Arc, Mutex};
425
426 struct TestSink {
427 events: Arc<Mutex<Vec<AuditEvent>>>,
428 }
429
430 impl TestSink {
431 fn new() -> (Self, Arc<Mutex<Vec<AuditEvent>>>) {
432 let events = Arc::new(Mutex::new(Vec::new()));
433 (
434 Self {
435 events: Arc::clone(&events),
436 },
437 events,
438 )
439 }
440 }
441
442 impl AuditSink for TestSink {
443 fn write_event(&self, event: &AuditEvent) -> Result<(), String> {
444 self.events.lock().unwrap().push(event.clone());
445 Ok(())
446 }
447
448 fn flush(&self) -> Result<(), String> {
449 Ok(())
450 }
451 }
452
453 #[test]
454 fn test_audit_event_creation() {
455 let event = AuditEvent::new(AuditEventType::Access, AuditSeverity::Info)
456 .with_key("test.key")
457 .with_user_context("test_user")
458 .with_metadata("operation", "get");
459
460 assert_eq!(event.event_type, AuditEventType::Access);
461 assert_eq!(event.severity, AuditSeverity::Info);
462 assert_eq!(event.key, Some("test.key".to_string()));
463 assert_eq!(event.user_context, Some("test_user".to_string()));
464 assert_eq!(event.metadata.get("operation"), Some(&"get".to_string()));
465 }
466
467 #[test]
468 fn test_audit_logger_basic() {
469 let (sink, events) = TestSink::new();
470 let logger = AuditLogger::new().add_sink(Box::new(sink));
471
472 logger.log_access("test.key", Some("test_user"));
473 logger.log_modification(
474 "test.key",
475 None,
476 &Value::String("new_value".to_string()),
477 Some("test_user"),
478 );
479
480 let events = events.lock().unwrap();
481 assert_eq!(events.len(), 2);
482
483 assert_eq!(events[0].event_type, AuditEventType::Access);
484 assert_eq!(events[0].key, Some("test.key".to_string()));
485
486 assert_eq!(events[1].event_type, AuditEventType::Modification);
487 assert_eq!(events[1].key, Some("test.key".to_string()));
488 }
489
490 #[test]
491 fn test_console_sink() {
492 let sink = ConsoleSink::new(AuditSeverity::Info);
493 let event =
494 AuditEvent::new(AuditEventType::Access, AuditSeverity::Info).with_key("test.key");
495
496 assert!(sink.write_event(&event).is_ok());
498 }
499
500 #[test]
501 fn test_event_display() {
502 let event = AuditEvent::new(AuditEventType::Modification, AuditSeverity::Warning)
503 .with_key("test.key")
504 .with_user_context("test_user")
505 .with_old_value(Value::String("old".to_string()))
506 .with_new_value(Value::String("new".to_string()))
507 .with_metadata("operation", "set");
508
509 let display = format!("{event}");
510 assert!(display.contains("Modification"));
511 assert!(display.contains("Warning"));
512 assert!(display.contains("test.key"));
513 assert!(display.contains("test_user"));
514 }
515
516 #[test]
517 fn test_severity_filtering() {
518 let (sink, events) = TestSink::new();
519 let logger = AuditLogger::new().add_sink(Box::new(sink));
520
521 logger.log_event(AuditEvent::new(AuditEventType::Access, AuditSeverity::Info));
523 logger.log_event(AuditEvent::new(
524 AuditEventType::ValidationFailure,
525 AuditSeverity::Error,
526 ));
527
528 let events = events.lock().unwrap();
529 assert_eq!(events.len(), 2);
530 assert_eq!(events[0].severity, AuditSeverity::Info);
531 assert_eq!(events[1].severity, AuditSeverity::Error);
532 }
533}