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