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 #[allow(clippy::print_stdout)]
201 {
202 println!("AUDIT: {event}");
203 }
204 }
205 Ok(())
206 }
207
208 fn flush(&self) -> Result<(), String> {
209 Ok(()) }
211}
212
213pub struct FileSink {
215 file_path: String,
216 level_filter: AuditSeverity,
217}
218
219impl FileSink {
220 pub fn new(file_path: impl Into<String>, min_level: AuditSeverity) -> Self {
222 Self {
223 file_path: file_path.into(),
224 level_filter: min_level,
225 }
226 }
227}
228
229impl AuditSink for FileSink {
230 fn write_event(&self, event: &AuditEvent) -> Result<(), String> {
231 if event.severity >= self.level_filter {
232 use std::fs::OpenOptions;
233 use std::io::Write;
234
235 let mut file = OpenOptions::new()
236 .create(true)
237 .append(true)
238 .open(&self.file_path)
239 .map_err(|e| format!("Failed to open audit log file: {e}"))?;
240
241 writeln!(file, "{event}").map_err(|e| format!("Failed to write to audit log: {e}"))?;
242 }
243 Ok(())
244 }
245
246 fn flush(&self) -> Result<(), String> {
247 Ok(())
249 }
250}
251
252pub struct AuditLogger {
254 sinks: Vec<Box<dyn AuditSink>>,
255 enabled: bool,
256}
257
258impl AuditLogger {
259 pub fn new() -> Self {
261 Self {
262 sinks: Vec::new(),
263 enabled: true,
264 }
265 }
266
267 pub fn add_sink(mut self, sink: Box<dyn AuditSink>) -> Self {
269 self.sinks.push(sink);
270 self
271 }
272
273 pub fn set_enabled(mut self, enabled: bool) -> Self {
275 self.enabled = enabled;
276 self
277 }
278
279 pub fn log_event(&self, event: AuditEvent) {
281 if !self.enabled {
282 return;
283 }
284
285 for sink in &self.sinks {
286 if let Err(e) = sink.write_event(&event) {
287 #[allow(clippy::print_stderr)]
293 {
294 eprintln!("Audit sink error: {e}");
295 }
296 }
297 }
298 }
299
300 pub fn log_access(&self, key: &str, user_context: Option<&str>) {
302 let event = AuditEvent::new(AuditEventType::Access, AuditSeverity::Info)
303 .with_key(key)
304 .with_metadata("operation", "get");
305
306 let event = if let Some(user) = user_context {
307 event.with_user_context(user)
308 } else {
309 event
310 };
311
312 self.log_event(event);
313 }
314
315 pub fn log_modification(
317 &self,
318 key: &str,
319 old_value: Option<&Value>,
320 new_value: &Value,
321 user_context: Option<&str>,
322 ) {
323 let mut event = AuditEvent::new(AuditEventType::Modification, AuditSeverity::Warning)
324 .with_key(key)
325 .with_new_value(new_value.clone())
326 .with_metadata("operation", "set");
327
328 if let Some(old) = old_value {
329 event = event.with_old_value(old.clone());
330 }
331
332 if let Some(user) = user_context {
333 event = event.with_user_context(user);
334 }
335
336 self.log_event(event);
337 }
338
339 pub fn log_validation_failure(
341 &self,
342 key: &str,
343 error: &str,
344 value: &Value,
345 user_context: Option<&str>,
346 ) {
347 let event = AuditEvent::new(AuditEventType::ValidationFailure, AuditSeverity::Error)
348 .with_key(key)
349 .with_new_value(value.clone())
350 .with_error(error)
351 .with_metadata("operation", "validate");
352
353 let event = if let Some(user) = user_context {
354 event.with_user_context(user)
355 } else {
356 event
357 };
358
359 self.log_event(event);
360 }
361
362 pub fn log_reload(&self, source: &str, success: bool, error: Option<&str>) {
364 let severity = if success {
365 AuditSeverity::Info
366 } else {
367 AuditSeverity::Error
368 };
369 let mut event = AuditEvent::new(AuditEventType::Reload, severity)
370 .with_source(source)
371 .with_metadata("operation", "reload");
372
373 if let Some(err) = error {
374 event = event.with_error(err);
375 }
376
377 self.log_event(event);
378 }
379
380 pub fn flush(&self) {
382 for sink in &self.sinks {
383 if let Err(e) = sink.flush() {
384 #[allow(clippy::print_stderr)]
389 {
390 eprintln!("Audit sink flush error: {e}");
391 }
392 }
393 }
394 }
395}
396
397impl Default for AuditLogger {
398 fn default() -> Self {
399 Self::new()
400 }
401}
402
403static GLOBAL_AUDIT_LOGGER: Mutex<Option<Arc<AuditLogger>>> = Mutex::new(None);
405
406pub fn init_audit_logger(logger: AuditLogger) {
408 if let Ok(mut global) = GLOBAL_AUDIT_LOGGER.lock() {
409 *global = Some(Arc::new(logger));
410 }
411 }
413
414pub fn get_audit_logger() -> Option<Arc<AuditLogger>> {
416 GLOBAL_AUDIT_LOGGER
417 .lock()
418 .ok()
419 .and_then(|guard| guard.clone())
420}
421
422pub fn audit_log(event: AuditEvent) {
424 if let Some(logger) = get_audit_logger() {
425 logger.log_event(event);
426 }
427}
428
429fn generate_event_id() -> String {
431 use std::sync::atomic::{AtomicU64, Ordering};
432 static COUNTER: AtomicU64 = AtomicU64::new(1);
433
434 let timestamp = SystemTime::now()
435 .duration_since(UNIX_EPOCH)
436 .unwrap_or_default()
437 .as_secs();
438 let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
439
440 format!("{timestamp:x}-{counter:x}")
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use std::sync::{Arc, Mutex};
447
448 struct TestSink {
449 events: Arc<Mutex<Vec<AuditEvent>>>,
450 }
451
452 impl TestSink {
453 fn new() -> (Self, Arc<Mutex<Vec<AuditEvent>>>) {
454 let events = Arc::new(Mutex::new(Vec::new()));
455 (
456 Self {
457 events: Arc::clone(&events),
458 },
459 events,
460 )
461 }
462 }
463
464 impl AuditSink for TestSink {
465 fn write_event(&self, event: &AuditEvent) -> Result<(), String> {
466 self.events.lock().unwrap().push(event.clone());
467 Ok(())
468 }
469
470 fn flush(&self) -> Result<(), String> {
471 Ok(())
472 }
473 }
474
475 #[test]
476 fn test_audit_event_creation() {
477 let event = AuditEvent::new(AuditEventType::Access, AuditSeverity::Info)
478 .with_key("test.key")
479 .with_user_context("test_user")
480 .with_metadata("operation", "get");
481
482 assert_eq!(event.event_type, AuditEventType::Access);
483 assert_eq!(event.severity, AuditSeverity::Info);
484 assert_eq!(event.key, Some("test.key".to_string()));
485 assert_eq!(event.user_context, Some("test_user".to_string()));
486 assert_eq!(event.metadata.get("operation"), Some(&"get".to_string()));
487 }
488
489 #[test]
490 fn test_audit_logger_basic() {
491 let (sink, events) = TestSink::new();
492 let logger = AuditLogger::new().add_sink(Box::new(sink));
493
494 logger.log_access("test.key", Some("test_user"));
495 logger.log_modification(
496 "test.key",
497 None,
498 &Value::String("new_value".to_string()),
499 Some("test_user"),
500 );
501
502 let events = events.lock().unwrap();
503 assert_eq!(events.len(), 2);
504
505 assert_eq!(events[0].event_type, AuditEventType::Access);
506 assert_eq!(events[0].key, Some("test.key".to_string()));
507
508 assert_eq!(events[1].event_type, AuditEventType::Modification);
509 assert_eq!(events[1].key, Some("test.key".to_string()));
510 }
511
512 #[test]
513 fn test_console_sink() {
514 let sink = ConsoleSink::new(AuditSeverity::Info);
515 let event =
516 AuditEvent::new(AuditEventType::Access, AuditSeverity::Info).with_key("test.key");
517
518 assert!(sink.write_event(&event).is_ok());
520 }
521
522 #[test]
523 fn test_event_display() {
524 let event = AuditEvent::new(AuditEventType::Modification, AuditSeverity::Warning)
525 .with_key("test.key")
526 .with_user_context("test_user")
527 .with_old_value(Value::String("old".to_string()))
528 .with_new_value(Value::String("new".to_string()))
529 .with_metadata("operation", "set");
530
531 let display = format!("{event}");
532 assert!(display.contains("Modification"));
533 assert!(display.contains("Warning"));
534 assert!(display.contains("test.key"));
535 assert!(display.contains("test_user"));
536 }
537
538 #[test]
539 fn test_severity_filtering() {
540 let (sink, events) = TestSink::new();
541 let logger = AuditLogger::new().add_sink(Box::new(sink));
542
543 logger.log_event(AuditEvent::new(AuditEventType::Access, AuditSeverity::Info));
545 logger.log_event(AuditEvent::new(
546 AuditEventType::ValidationFailure,
547 AuditSeverity::Error,
548 ));
549
550 let events = events.lock().unwrap();
551 assert_eq!(events.len(), 2);
552 assert_eq!(events[0].severity, AuditSeverity::Info);
553 assert_eq!(events[1].severity, AuditSeverity::Error);
554 }
555}