package middleware import ( "bytes" "encoding/json" httpx "kra/internal/server/httpx" "log/slog" "strings" "github.com/gin-gonic/gin" ) // ErrorAudit emits a structured Error-level log for unexpected HTTP failures. // The logging core is the single persistence path for sys_error, matching the // reference behavior and avoiding duplicate rows for HTTP failures. func ErrorAudit(logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { c.Next() privateErrors := strings.TrimSpace(c.Errors.ByType(gin.ErrorTypePrivate).String()) auditPersistFailed, _ := c.Get(ctxOperationAuditPersistFailedKey) // sysError writes must never audit themselves. Log-viewer failures are // already recorded by the handler with the underlying filesystem error; // emitting again from the response envelope would duplicate both the // classified error file and the sys_error row. if auditPersistFailed != true && (strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/") || c.Writer.Status() >= 300 && c.Writer.Status() < 500) { return } var response httpx.Response var body []byte if value, ok := c.Get(ctxRespBufferKey); ok { if buffer, valid := value.(*bytes.Buffer); valid { body = buffer.Bytes() } } if json.Unmarshal(body, &response) != nil && privateErrors == "" { return } if response.Code == httpx.CodeSuccess && privateErrors == "" { return } if privateErrors == "" && expectedClientFailure(response.Msg) { return } errorMessage := response.Msg if privateErrors != "" { errorMessage = privateErrors } requestID, _ := c.Get("request_id") if logger != nil { logger.ErrorContext(c.Request.Context(), "请求处理失败", "mod", failureLogModule(c.Request.URL.Path), "path", c.Request.URL.Path, "method", c.Request.Method, "status", c.Writer.Status(), "error", errorMessage, "request_id", stringValue(requestID), "trace_id", stringValueFromContext(c, "trace_id")) } } } func failureLogModule(path string) string { for _, marker := range []string{"/fileUploadAndDownload/", "/mediaUpload/", "/attachmentCategory/"} { if strings.Contains(path, marker) { return "upload" } } if strings.Contains(path, "/timedTask/") { return "timedTask" } if strings.Contains(path, "/logViewer/") { return "log-viewer" } return "biz" } func expectedClientFailure(message string) bool { for _, value := range []string{"参数错误", "请输入用户名和密码", "验证码错误", "用户名不存在或者密码错误", "用户被禁止登录", "账号已锁定", "请求太过频繁", "权限不足", "密码已过期", "未登录", "token", "令牌失效"} { if strings.Contains(message, value) { return true } } return false }