Skip to main content

cortexai_audit/
traits.rs

1//! Audit logger traits and configuration.
2//!
3//! This module defines the core `AuditLogger` trait that all backends implement,
4//! along with configuration types and utilities.
5
6use crate::error::AuditError;
7use crate::types::{AuditEvent, AuditLevel};
8use async_trait::async_trait;
9use std::sync::Arc;
10
11/// Configuration for audit logging.
12#[derive(Debug, Clone)]
13pub struct AuditConfig {
14    /// Minimum level to log (events below this are filtered)
15    pub min_level: AuditLevel,
16    /// Whether to include debug information
17    pub include_debug: bool,
18    /// Maximum size for parameter/result payloads (bytes)
19    pub max_payload_size: usize,
20    /// Whether to redact sensitive fields
21    pub redact_sensitive: bool,
22    /// Fields to redact
23    pub redact_fields: Vec<String>,
24    /// Whether audit logging is enabled
25    pub enabled: bool,
26    /// Buffer size for async logging
27    pub buffer_size: usize,
28    /// Flush interval in seconds
29    pub flush_interval_secs: u64,
30}
31
32impl Default for AuditConfig {
33    fn default() -> Self {
34        Self {
35            min_level: AuditLevel::Info,
36            include_debug: false,
37            max_payload_size: 10 * 1024, // 10KB
38            redact_sensitive: true,
39            redact_fields: vec![
40                "password".to_string(),
41                "secret".to_string(),
42                "token".to_string(),
43                "api_key".to_string(),
44                "apiKey".to_string(),
45                "authorization".to_string(),
46            ],
47            enabled: true,
48            buffer_size: 1000,
49            flush_interval_secs: 5,
50        }
51    }
52}
53
54impl AuditConfig {
55    /// Create a new configuration with defaults.
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Create a development configuration with debug enabled.
61    pub fn development() -> Self {
62        Self {
63            min_level: AuditLevel::Debug,
64            include_debug: true,
65            redact_sensitive: false,
66            ..Default::default()
67        }
68    }
69
70    /// Create a production configuration with stricter settings.
71    pub fn production() -> Self {
72        Self {
73            min_level: AuditLevel::Info,
74            include_debug: false,
75            redact_sensitive: true,
76            max_payload_size: 5 * 1024, // 5KB in production
77            ..Default::default()
78        }
79    }
80
81    /// Set the minimum log level.
82    pub fn with_min_level(mut self, level: AuditLevel) -> Self {
83        self.min_level = level;
84        self
85    }
86
87    /// Enable or disable debug information.
88    pub fn with_debug(mut self, include: bool) -> Self {
89        self.include_debug = include;
90        self
91    }
92
93    /// Set maximum payload size.
94    pub fn with_max_payload_size(mut self, size: usize) -> Self {
95        self.max_payload_size = size;
96        self
97    }
98
99    /// Enable or disable sensitive field redaction.
100    pub fn with_redaction(mut self, enabled: bool) -> Self {
101        self.redact_sensitive = enabled;
102        self
103    }
104
105    /// Add fields to redact.
106    pub fn with_redact_fields(mut self, fields: Vec<String>) -> Self {
107        self.redact_fields.extend(fields);
108        self
109    }
110
111    /// Enable or disable audit logging.
112    pub fn enabled(mut self, enabled: bool) -> Self {
113        self.enabled = enabled;
114        self
115    }
116
117    /// Set buffer size.
118    pub fn with_buffer_size(mut self, size: usize) -> Self {
119        self.buffer_size = size;
120        self
121    }
122
123    /// Check if an event should be logged based on level.
124    pub fn should_log(&self, level: AuditLevel) -> bool {
125        self.enabled && level >= self.min_level
126    }
127}
128
129/// The core audit logger trait.
130///
131/// All audit backends implement this trait to provide consistent logging behavior.
132#[async_trait]
133pub trait AuditLogger: Send + Sync {
134    /// Log an audit event.
135    async fn log(&self, event: AuditEvent) -> Result<(), AuditError>;
136
137    /// Log multiple events in a batch.
138    async fn log_batch(&self, events: Vec<AuditEvent>) -> Result<(), AuditError> {
139        for event in events {
140            self.log(event).await?;
141        }
142        Ok(())
143    }
144
145    /// Flush any buffered events.
146    async fn flush(&self) -> Result<(), AuditError>;
147
148    /// Get the logger name for identification.
149    fn name(&self) -> &str;
150
151    /// Check if the logger is healthy.
152    async fn health_check(&self) -> Result<(), AuditError> {
153        Ok(())
154    }
155
156    /// Get statistics about logged events.
157    async fn stats(&self) -> AuditStats {
158        AuditStats::default()
159    }
160}
161
162/// Statistics about audit logging.
163#[derive(Debug, Clone, Default)]
164pub struct AuditStats {
165    /// Total events logged
166    pub total_events: u64,
167    /// Events by level
168    pub events_by_level: std::collections::HashMap<AuditLevel, u64>,
169    /// Failed log attempts
170    pub failed_events: u64,
171    /// Bytes written
172    pub bytes_written: u64,
173    /// Last event timestamp
174    pub last_event_time: Option<chrono::DateTime<chrono::Utc>>,
175}
176
177/// A composite logger that writes to multiple backends.
178pub struct CompositeLogger {
179    loggers: Vec<Arc<dyn AuditLogger>>,
180    config: AuditConfig,
181}
182
183impl CompositeLogger {
184    /// Create a new composite logger.
185    pub fn new(config: AuditConfig) -> Self {
186        Self {
187            loggers: Vec::new(),
188            config,
189        }
190    }
191
192    /// Add a logger backend.
193    pub fn add_logger(mut self, logger: Arc<dyn AuditLogger>) -> Self {
194        self.loggers.push(logger);
195        self
196    }
197
198    /// Add a logger backend by reference.
199    pub fn with_logger(&mut self, logger: Arc<dyn AuditLogger>) {
200        self.loggers.push(logger);
201    }
202}
203
204#[async_trait]
205impl AuditLogger for CompositeLogger {
206    async fn log(&self, event: AuditEvent) -> Result<(), AuditError> {
207        if !self.config.should_log(event.level) {
208            return Ok(());
209        }
210
211        let mut errors = Vec::new();
212        for logger in &self.loggers {
213            if let Err(e) = logger.log(event.clone()).await {
214                errors.push(format!("{}: {}", logger.name(), e));
215            }
216        }
217
218        if errors.is_empty() {
219            Ok(())
220        } else {
221            Err(AuditError::Multiple(errors))
222        }
223    }
224
225    async fn flush(&self) -> Result<(), AuditError> {
226        let mut errors = Vec::new();
227        for logger in &self.loggers {
228            if let Err(e) = logger.flush().await {
229                errors.push(format!("{}: {}", logger.name(), e));
230            }
231        }
232
233        if errors.is_empty() {
234            Ok(())
235        } else {
236            Err(AuditError::Multiple(errors))
237        }
238    }
239
240    fn name(&self) -> &str {
241        "composite"
242    }
243
244    async fn health_check(&self) -> Result<(), AuditError> {
245        for logger in &self.loggers {
246            logger.health_check().await?;
247        }
248        Ok(())
249    }
250}
251
252/// A no-op logger for testing or when audit is disabled.
253pub struct NoOpLogger;
254
255#[async_trait]
256impl AuditLogger for NoOpLogger {
257    async fn log(&self, _event: AuditEvent) -> Result<(), AuditError> {
258        Ok(())
259    }
260
261    async fn flush(&self) -> Result<(), AuditError> {
262        Ok(())
263    }
264
265    fn name(&self) -> &str {
266        "noop"
267    }
268}
269
270/// A logger that collects events in memory for testing.
271#[derive(Default)]
272pub struct MemoryLogger {
273    events: tokio::sync::RwLock<Vec<AuditEvent>>,
274}
275
276impl MemoryLogger {
277    /// Create a new memory logger.
278    pub fn new() -> Self {
279        Self::default()
280    }
281
282    /// Get all logged events.
283    pub async fn events(&self) -> Vec<AuditEvent> {
284        self.events.read().await.clone()
285    }
286
287    /// Get event count.
288    pub async fn count(&self) -> usize {
289        self.events.read().await.len()
290    }
291
292    /// Clear all events.
293    pub async fn clear(&self) {
294        self.events.write().await.clear();
295    }
296}
297
298#[async_trait]
299impl AuditLogger for MemoryLogger {
300    async fn log(&self, event: AuditEvent) -> Result<(), AuditError> {
301        self.events.write().await.push(event);
302        Ok(())
303    }
304
305    async fn flush(&self) -> Result<(), AuditError> {
306        Ok(())
307    }
308
309    fn name(&self) -> &str {
310        "memory"
311    }
312
313    async fn stats(&self) -> AuditStats {
314        let events = self.events.read().await;
315        let mut stats = AuditStats {
316            total_events: events.len() as u64,
317            ..Default::default()
318        };
319
320        for event in events.iter() {
321            *stats.events_by_level.entry(event.level).or_insert(0) += 1;
322        }
323
324        if let Some(last) = events.last() {
325            stats.last_event_time = Some(last.timestamp);
326        }
327
328        stats
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335    use crate::types::EventKind;
336
337    #[test]
338    fn test_audit_config_defaults() {
339        let config = AuditConfig::default();
340        assert_eq!(config.min_level, AuditLevel::Info);
341        assert!(config.enabled);
342        assert!(config.redact_sensitive);
343    }
344
345    #[test]
346    fn test_audit_config_should_log() {
347        let config = AuditConfig::new().with_min_level(AuditLevel::Warn);
348
349        assert!(!config.should_log(AuditLevel::Debug));
350        assert!(!config.should_log(AuditLevel::Info));
351        assert!(config.should_log(AuditLevel::Warn));
352        assert!(config.should_log(AuditLevel::Error));
353        assert!(config.should_log(AuditLevel::Critical));
354    }
355
356    #[test]
357    fn test_audit_config_disabled() {
358        let config = AuditConfig::new().enabled(false);
359        assert!(!config.should_log(AuditLevel::Critical));
360    }
361
362    #[tokio::test]
363    async fn test_memory_logger() {
364        let logger = MemoryLogger::new();
365
366        let event = AuditEvent::info(EventKind::Custom {
367            name: "test".to_string(),
368            payload: serde_json::json!({}),
369        });
370
371        logger.log(event).await.unwrap();
372        assert_eq!(logger.count().await, 1);
373
374        let events = logger.events().await;
375        assert_eq!(events.len(), 1);
376    }
377
378    #[tokio::test]
379    async fn test_noop_logger() {
380        let logger = NoOpLogger;
381        let event = AuditEvent::info(EventKind::Custom {
382            name: "test".to_string(),
383            payload: serde_json::json!({}),
384        });
385
386        assert!(logger.log(event).await.is_ok());
387        assert!(logger.flush().await.is_ok());
388    }
389
390    #[tokio::test]
391    async fn test_composite_logger() {
392        let memory1 = Arc::new(MemoryLogger::new());
393        let memory2 = Arc::new(MemoryLogger::new());
394
395        let composite = CompositeLogger::new(AuditConfig::default())
396            .add_logger(memory1.clone())
397            .add_logger(memory2.clone());
398
399        let event = AuditEvent::info(EventKind::Custom {
400            name: "test".to_string(),
401            payload: serde_json::json!({}),
402        });
403
404        composite.log(event).await.unwrap();
405
406        assert_eq!(memory1.count().await, 1);
407        assert_eq!(memory2.count().await, 1);
408    }
409}