Skip to main content

sh_layer4/audit_logger/
logger.rs

1//! 审计日志记录器
2//!
3//! 主要的审计日志接口。
4
5use chrono::{DateTime, Utc};
6use serde_json::Value;
7use std::sync::Arc;
8
9use super::entry::{AuditAction, AuditEntry, AuditFilter, AuditResult, ExportFormat};
10use super::storage::{AuditStorage, MemoryStorage};
11use anyhow::Result;
12
13/// 审计日志配置
14#[derive(Debug, Clone)]
15pub struct AuditConfig {
16    /// 是否启用
17    pub enabled: bool,
18    /// 是否脱敏敏感数据
19    pub sanitize_sensitive: bool,
20    /// 敏感字段列表
21    pub sensitive_fields: Vec<String>,
22    /// 默认用户 ID (未指定时使用)
23    pub default_user_id: String,
24    /// 异步写入
25    pub async_write: bool,
26    /// 保留天数
27    pub retention_days: u32,
28}
29
30impl Default for AuditConfig {
31    fn default() -> Self {
32        Self {
33            enabled: true,
34            sanitize_sensitive: true,
35            sensitive_fields: vec![
36                "password".to_string(),
37                "token".to_string(),
38                "api_key".to_string(),
39                "secret".to_string(),
40                "credential".to_string(),
41            ],
42            default_user_id: "system".to_string(),
43            async_write: true,
44            retention_days: 90,
45        }
46    }
47}
48
49/// 审计日志记录器
50pub struct AuditLogger {
51    /// 存储后端
52    storage: Arc<dyn AuditStorage>,
53    /// 配置
54    config: AuditConfig,
55}
56
57impl AuditLogger {
58    /// 创建新的审计日志记录器
59    pub fn new(config: AuditConfig) -> Self {
60        Self {
61            storage: Arc::new(MemoryStorage::new(10000)),
62            config,
63        }
64    }
65
66    /// 使用自定义存储后端
67    pub fn with_storage(mut self, storage: Arc<dyn AuditStorage>) -> Self {
68        self.storage = storage;
69        self
70    }
71
72    /// 获取配置
73    pub fn config(&self) -> &AuditConfig {
74        &self.config
75    }
76
77    /// 记录审计日志
78    pub async fn log(&self, entry: AuditEntry) -> Result<()> {
79        if !self.config.enabled {
80            return Ok(());
81        }
82
83        // 脱敏处理
84        let entry = if self.config.sanitize_sensitive {
85            self.sanitize_entry(entry)
86        } else {
87            entry
88        };
89
90        self.storage.save(&entry).await
91    }
92
93    /// 快速记录操作
94    pub async fn log_action(
95        &self,
96        user_id: &str,
97        action: AuditAction,
98        resource_type: &str,
99        resource_id: Option<&str>,
100        result: AuditResult,
101    ) -> Result<()> {
102        let mut entry = AuditEntry::new(user_id, action, resource_type);
103
104        if let Some(id) = resource_id {
105            entry = entry.with_resource_id(id);
106        }
107
108        entry = entry.with_result(result);
109
110        self.log(entry).await
111    }
112
113    /// 记录成功操作
114    pub async fn log_success(
115        &self,
116        user_id: &str,
117        action: AuditAction,
118        resource_type: &str,
119        resource_id: Option<&str>,
120    ) -> Result<()> {
121        self.log_action(
122            user_id,
123            action,
124            resource_type,
125            resource_id,
126            AuditResult::Success,
127        )
128        .await
129    }
130
131    /// 记录失败操作
132    pub async fn log_failure(
133        &self,
134        user_id: &str,
135        action: AuditAction,
136        resource_type: &str,
137        resource_id: Option<&str>,
138        error_code: &str,
139        error_message: &str,
140    ) -> Result<()> {
141        self.log_action(
142            user_id,
143            action,
144            resource_type,
145            resource_id,
146            AuditResult::failure(error_code, error_message),
147        )
148        .await
149    }
150
151    /// 记录拒绝访问
152    pub async fn log_denied(
153        &self,
154        user_id: &str,
155        action: AuditAction,
156        resource_type: &str,
157        resource_id: Option<&str>,
158        reason: &str,
159    ) -> Result<()> {
160        self.log_action(
161            user_id,
162            action,
163            resource_type,
164            resource_id,
165            AuditResult::denied(reason),
166        )
167        .await
168    }
169
170    /// 查询审计日志
171    pub async fn query(&self, filter: AuditFilter) -> Result<Vec<AuditEntry>> {
172        self.storage.query(&filter).await
173    }
174
175    /// 导出审计日志
176    pub async fn export(&self, format: ExportFormat, filter: AuditFilter) -> Result<Vec<u8>> {
177        self.storage.export(format, &filter).await
178    }
179
180    /// 清理过期日志
181    pub async fn cleanup(&self, before: DateTime<Utc>) -> Result<usize> {
182        self.storage.cleanup(before).await
183    }
184
185    /// 获取日志条目数
186    pub async fn count(&self) -> Result<usize> {
187        self.storage.count().await
188    }
189
190    /// 脱敏处理审计条目
191    fn sanitize_entry(&self, mut entry: AuditEntry) -> AuditEntry {
192        if let Some(details) = &entry.details {
193            entry.details = Some(self.sanitize_value(details.clone()));
194        }
195        entry
196    }
197
198    /// 脱敏 JSON 值
199    fn sanitize_value(&self, value: Value) -> Value {
200        match value {
201            Value::Object(mut map) => {
202                for field in &self.config.sensitive_fields {
203                    if let Some(v) = map.get_mut(field) {
204                        *v = Value::String("***REDACTED***".to_string());
205                    }
206                }
207                for (_, v) in map.iter_mut() {
208                    *v = self.sanitize_value(v.clone());
209                }
210                Value::Object(map)
211            }
212            Value::Array(arr) => {
213                Value::Array(arr.into_iter().map(|v| self.sanitize_value(v)).collect())
214            }
215            other => other,
216        }
217    }
218
219    /// 创建审计条目构建器
220    pub fn builder(
221        &self,
222        user_id: &str,
223        action: AuditAction,
224        resource_type: &str,
225    ) -> AuditEntryBuilder<'_> {
226        AuditEntryBuilder {
227            entry: AuditEntry::new(user_id, action, resource_type),
228            logger: self,
229        }
230    }
231}
232
233/// 审计条目构建器
234pub struct AuditEntryBuilder<'a> {
235    entry: AuditEntry,
236    logger: &'a AuditLogger,
237}
238
239impl<'a> AuditEntryBuilder<'a> {
240    pub fn with_session(mut self, session_id: &str) -> Self {
241        self.entry = self.entry.with_session(session_id);
242        self
243    }
244
245    pub fn with_resource_id(mut self, resource_id: &str) -> Self {
246        self.entry = self.entry.with_resource_id(resource_id);
247        self
248    }
249
250    pub fn with_details(mut self, details: Value) -> Self {
251        self.entry = self.entry.with_details(details);
252        self
253    }
254
255    pub fn with_ip(mut self, ip_address: &str) -> Self {
256        self.entry = self.entry.with_ip(ip_address);
257        self
258    }
259
260    pub fn with_result(mut self, result: AuditResult) -> Self {
261        self.entry = self.entry.with_result(result);
262        self
263    }
264
265    pub fn with_duration(mut self, duration_ms: u64) -> Self {
266        self.entry = self.entry.with_duration(duration_ms);
267        self
268    }
269
270    pub async fn log(self) -> Result<()> {
271        self.logger.log(self.entry).await
272    }
273}
274
275impl Default for AuditLogger {
276    fn default() -> Self {
277        Self::new(AuditConfig::default())
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[tokio::test]
286    async fn test_audit_logger_creation() {
287        let logger = AuditLogger::default();
288        assert!(logger.config().enabled);
289    }
290
291    #[tokio::test]
292    async fn test_log_action() {
293        let logger = AuditLogger::default();
294
295        logger
296            .log_success("user1", AuditAction::Read, "document", Some("doc123"))
297            .await
298            .unwrap();
299
300        let count = logger.count().await.unwrap();
301        assert_eq!(count, 1);
302    }
303
304    #[tokio::test]
305    async fn test_query() {
306        let logger = AuditLogger::default();
307
308        logger
309            .log_success("user1", AuditAction::Read, "doc", None)
310            .await
311            .unwrap();
312        logger
313            .log_success("user2", AuditAction::Read, "doc", None)
314            .await
315            .unwrap();
316
317        let filter = AuditFilter {
318            user_id: Some("user1".to_string()),
319            ..Default::default()
320        };
321
322        let entries = logger.query(filter).await.unwrap();
323        assert_eq!(entries.len(), 1);
324        assert_eq!(entries[0].user_id, "user1");
325    }
326
327    #[tokio::test]
328    async fn test_sanitize_sensitive_data() {
329        let config = AuditConfig {
330            sanitize_sensitive: true,
331            ..Default::default()
332        };
333        let logger = AuditLogger::new(config);
334
335        let entry =
336            AuditEntry::new("user1", AuditAction::Create, "user").with_details(serde_json::json!({
337                "username": "test",
338                "password": "secret123"
339            }));
340
341        logger.log(entry).await.unwrap();
342
343        let entries = logger.query(AuditFilter::default()).await.unwrap();
344        let details = entries[0].details.as_ref().unwrap();
345
346        assert_eq!(details.get("password").unwrap(), "***REDACTED***");
347    }
348
349    #[tokio::test]
350    async fn test_builder_pattern() {
351        let logger = AuditLogger::default();
352
353        logger
354            .builder("user1", AuditAction::Login, "session")
355            .with_ip("192.168.1.1")
356            .with_duration(100)
357            .log()
358            .await
359            .unwrap();
360
361        let entries = logger.query(AuditFilter::default()).await.unwrap();
362        assert_eq!(entries.len(), 1);
363        assert_eq!(entries[0].ip_address, Some("192.168.1.1".to_string()));
364    }
365
366    #[tokio::test]
367    async fn test_disabled_logger() {
368        let config = AuditConfig {
369            enabled: false,
370            ..Default::default()
371        };
372        let logger = AuditLogger::new(config);
373
374        logger
375            .log_success("user1", AuditAction::Read, "doc", None)
376            .await
377            .unwrap();
378
379        let count = logger.count().await.unwrap();
380        assert_eq!(count, 0);
381    }
382}