use chrono::Utc; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; use std::sync::Mutex; pub struct AuditLog { log_file: Mutex, log_path: PathBuf, } impl AuditLog { pub fn new(log_path: &str) -> anyhow::Result { let path = PathBuf::from(log_path); // 确保日志目录存在 if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; } // 打开日志文件(追加模式) let file = OpenOptions::new().create(true).append(true).open(&path)?; Ok(Self { log_file: Mutex::new(file), log_path: path, }) } pub fn log_operation(&self, user_id: &str, operation: &str, path: &str, result: &str) { let timestamp = Utc::now().format("%Y-%m-%dT%H:%M:%SZ"); let entry = format!( "[{}] user={} operation={} path=\"{}\" result={}\n", timestamp, user_id, operation, path, result ); // 写入日志文件 if let Ok(mut file) = self.log_file.lock() { if let Err(e) = file.write_all(entry.as_bytes()) { log::error!("Failed to write audit log: {}", e); } } // 同时输出到标准日志 log::info!( "Audit: user={} operation={} path=\"{}\" result={}", user_id, operation, path, result ); } pub fn log_error(&self, user_id: &str, operation: &str, path: &str, error: &str) { self.log_operation(user_id, operation, path, &format!("error: {}", error)); } pub fn log_success(&self, user_id: &str, operation: &str, path: &str) { self.log_operation(user_id, operation, path, "success"); } pub fn get_log_path(&self) -> &PathBuf { &self.log_path } } impl Clone for AuditLog { fn clone(&self) -> Self { // Clone时重新打开文件 Self::new(&self.log_path.to_string_lossy()).unwrap_or_else(|_| { // 如果失败,使用临时路径 Self::new("/tmp/sftp_audit_fallback.log").unwrap() }) } }