package observability import ( "encoding/json" "fmt" "net/http" "os" "path/filepath" "sync" "time" ) // AuditEntry 审计日志条目,记录关键管理操作的详细信息。 type AuditEntry struct { Timestamp string `json:"timestamp"` Actor string `json:"actor"` // 操作者 app_id Action string `json:"action"` // 操作类型 Resource string `json:"resource"` // 操作资源 ResourceID string `json:"resource_id"` // 资源 ID Method string `json:"method"` // HTTP 方法 Path string `json:"path"` // 请求路径 IP string `json:"ip"` // 客户端 IP Status int `json:"status"` // HTTP 响应码 Details map[string]interface{} `json:"details,omitempty"` } // AuditLogger 审计日志记录器,将关键操作写入独立日志文件。 type AuditLogger struct { mu sync.Mutex file *os.File logger *Logger } // NewAuditLogger 创建审计日志记录器,日志写入指定目录下的 audit.log 文件。 func NewAuditLogger(dir string, logger *Logger) *AuditLogger { auditPath := filepath.Join(dir, "audit.log") file, err := os.OpenFile(auditPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { logger.Warn("failed to open audit log file, audit logs will go to stderr", F().Event("audit_log_init_failed").Reason(err.Error())) return &AuditLogger{file: nil, logger: logger} } return &AuditLogger{file: file, logger: logger} } // Record 记录一条审计日志。 func (a *AuditLogger) Record(entry AuditEntry) { if entry.Timestamp == "" { entry.Timestamp = time.Now().UTC().Format(time.RFC3339Nano) } data, err := json.Marshal(entry) if err != nil { a.logger.Error("audit log marshal error", F().Event("audit_marshal_error").Reason(err.Error())) return } a.mu.Lock() defer a.mu.Unlock() if a.file != nil { fmt.Fprintln(a.file, string(data)) } else { fmt.Fprintln(os.Stderr, string(data)) } } // RecordFromRequest 从 HTTP 请求中提取信息并记录审计日志。 func (a *AuditLogger) RecordFromRequest(r *http.Request, actor, action, resource, resourceID string, status int, details map[string]interface{}) { entry := AuditEntry{ Actor: actor, Action: action, Resource: resource, ResourceID: resourceID, Method: r.Method, Path: r.URL.Path, IP: extractIP(r), Status: status, Details: details, } a.Record(entry) } // Close 关闭审计日志文件。 func (a *AuditLogger) Close() { if a.file != nil { a.file.Close() } } // extractIP 从请求中提取客户端 IP 地址。 func extractIP(r *http.Request) string { if xff := r.Header.Get("X-Forwarded-For"); xff != "" { return xff } if xri := r.Header.Get("X-Real-IP"); xri != "" { return xri } return r.RemoteAddr }