Skip to main content

cortexai_audit/
lib.rs

1//! Audit logging for AI agents.
2//!
3//! This crate provides comprehensive audit logging for all tool calls,
4//! LLM requests, and agent lifecycle events. It supports multiple backends
5//! and is designed for compliance and debugging in production environments.
6//!
7//! # Features
8//!
9//! - **Structured Events**: Rich event types for tool calls, LLM requests, errors, and more
10//! - **Multiple Backends**: File, JSON, and async logging with rotation support
11//! - **Configurable Levels**: Filter events by severity (Debug, Info, Warn, Error, Critical)
12//! - **Log Rotation**: Size-based, daily, or hourly rotation with cleanup
13//! - **Non-blocking**: Async wrapper for high-throughput scenarios
14//! - **Compliance Ready**: Designed for GDPR, SOC2, and enterprise requirements
15//!
16//! # Quick Start
17//!
18//! ```rust,no_run
19//! use cortexai_audit::{
20//!     AuditEvent, AuditContext, JsonFileLogger, RotationConfig, RotationPolicy, AuditLogger
21//! };
22//!
23//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
24//! // Create a JSON file logger with daily rotation
25//! let rotation = RotationConfig::new(RotationPolicy::Daily)
26//!     .with_max_files(30);
27//!
28//! let logger = JsonFileLogger::new(
29//!     "/var/log/agents/audit.jsonl",
30//!     Default::default(),
31//!     rotation,
32//! ).await?;
33//!
34//! // Log a tool call
35//! let event = AuditEvent::tool_call("read_file", serde_json::json!({"path": "/tmp/data.txt"}), true)
36//!     .with_context(
37//!         AuditContext::new()
38//!             .with_trace_id("req-123")
39//!             .with_agent_id("research-agent")
40//!     );
41//!
42//! logger.log(event).await?;
43//! logger.flush().await?;
44//! # Ok(())
45//! # }
46//! ```
47//!
48//! # Event Types
49//!
50//! The crate supports various event types through `EventKind`:
51//!
52//! - `ToolCall`: Tool invocations with parameters and results
53//! - `LlmRequest`: Outgoing LLM API requests
54//! - `LlmResponse`: Incoming LLM responses
55//! - `AgentLifecycle`: Agent start, stop, pause, resume events
56//! - `ApprovalDecision`: Human-in-the-loop approval decisions
57//! - `Error`: Error events with stack traces
58//! - `Security`: Security-related events (auth, rate limits)
59//! - `Custom`: Extensible custom events
60//!
61//! # Backends
62//!
63//! ## FileLogger
64//! Simple human-readable text format, good for development.
65//!
66//! ## JsonFileLogger
67//! Structured JSON Lines format with rotation, ideal for production.
68//!
69//! ## AsyncLogger
70//! Non-blocking wrapper around any logger for high-throughput scenarios.
71//!
72//! ## MemoryLogger
73//! In-memory storage for testing.
74//!
75//! ## CompositeLogger
76//! Writes to multiple backends simultaneously.
77//!
78//! # Configuration
79//!
80//! ```rust
81//! use cortexai_audit::{AuditConfig, AuditLevel};
82//!
83//! // Development config - verbose logging
84//! let dev_config = AuditConfig::development();
85//!
86//! // Production config - info level with redaction
87//! let prod_config = AuditConfig::production();
88//!
89//! // Custom config
90//! let custom_config = AuditConfig::new()
91//!     .with_min_level(AuditLevel::Warn)
92//!     .with_redaction(true)
93//!     .with_max_payload_size(5 * 1024);
94//! ```
95
96pub mod backends;
97pub mod error;
98pub mod traits;
99pub mod types;
100
101// Re-export main types
102pub use error::AuditError;
103pub use traits::{AuditConfig, AuditLogger, AuditStats, CompositeLogger, MemoryLogger, NoOpLogger};
104pub use types::{
105    AuditContext, AuditEvent, AuditEventBuilder, AuditLevel, EventKind, LifecycleAction,
106    SecurityEventType,
107};
108
109// Re-export backends
110pub use backends::{
111    AsyncLogger, AsyncLoggerBuilder, FileLogger, JsonFileLogger, RotationConfig, RotationPolicy,
112};
113
114/// Convenience function to create a production-ready logger.
115///
116/// Creates a JSON file logger with:
117/// - Daily rotation
118/// - 30 days retention
119/// - Production-level configuration
120pub async fn create_production_logger(
121    path: impl Into<std::path::PathBuf>,
122) -> Result<JsonFileLogger, AuditError> {
123    let rotation = RotationConfig::new(RotationPolicy::Daily).with_max_files(30);
124    JsonFileLogger::new(path, AuditConfig::production(), rotation).await
125}
126
127/// Convenience function to create a development logger.
128///
129/// Creates a file logger with debug-level logging.
130pub async fn create_dev_logger(
131    path: impl Into<std::path::PathBuf>,
132) -> Result<FileLogger, AuditError> {
133    FileLogger::new(path, AuditConfig::development()).await
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[tokio::test]
141    async fn test_memory_logger_integration() {
142        let logger = MemoryLogger::new();
143
144        // Log various events
145        logger
146            .log(AuditEvent::tool_call("tool1", serde_json::json!({}), true))
147            .await
148            .unwrap();
149        logger
150            .log(AuditEvent::llm_request("anthropic", "claude-3", false))
151            .await
152            .unwrap();
153        logger
154            .log(AuditEvent::error_event(
155                "RuntimeError",
156                "Something went wrong",
157            ))
158            .await
159            .unwrap();
160
161        assert_eq!(logger.count().await, 3);
162
163        let events = logger.events().await;
164        assert!(matches!(events[0].kind, EventKind::ToolCall { .. }));
165        assert!(matches!(events[1].kind, EventKind::LlmRequest { .. }));
166        assert!(matches!(events[2].kind, EventKind::Error { .. }));
167    }
168
169    #[test]
170    fn test_event_builder() {
171        let event = AuditEventBuilder::new()
172            .level(AuditLevel::Info)
173            .kind(EventKind::Custom {
174                name: "test".to_string(),
175                payload: serde_json::json!({"key": "value"}),
176            })
177            .trace_id("trace-1")
178            .session_id("session-1")
179            .agent_id("agent-1")
180            .build();
181
182        assert_eq!(event.level, AuditLevel::Info);
183        assert_eq!(event.context.trace_id, Some("trace-1".to_string()));
184        assert_eq!(event.context.session_id, Some("session-1".to_string()));
185        assert_eq!(event.context.agent_id, Some("agent-1".to_string()));
186    }
187
188    #[test]
189    fn test_config_presets() {
190        let dev = AuditConfig::development();
191        assert_eq!(dev.min_level, AuditLevel::Debug);
192        assert!(dev.include_debug);
193        assert!(!dev.redact_sensitive);
194
195        let prod = AuditConfig::production();
196        assert_eq!(prod.min_level, AuditLevel::Info);
197        assert!(!prod.include_debug);
198        assert!(prod.redact_sensitive);
199    }
200}