Files
weichen f3f23a3018 feat(errcode): 用稳定错误码记录 HTTP 与异步失败
失败响应此前不写日志,request_id 无法对照原因。注册表不覆盖处理器已经给出的 HTTP 状态。
2026-09-28 02:27:16 +00:00

110 lines
2.7 KiB
Go

package server
import (
"context"
"encoding/json"
"net/http"
"strings"
"chat-agent-system/internal/errcode"
"chat-agent-system/internal/httpx"
)
func writeJSONResponse(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(data)
}
func writeSafeError(w http.ResponseWriter, r *http.Request, status int, legacy string) {
emitError(w, r, status, errcode.FromLegacy(status, legacy), legacy)
}
func writeDomainError(w http.ResponseWriter, r *http.Request, status int, err error) {
if err == nil {
writeSafeError(w, r, status, "")
return
}
if st, ok := domainHTTPStatus(err); ok {
def := errcode.FromStatus(st)
if d, found := errcode.FromError(err); found {
def = d
}
emitError(w, r, st, def, err.Error())
return
}
if def, found := errcode.FromError(err); found {
// 处理器传入的状态是既有 HTTP 契约。注册表状态只在调用方未给出时使用。
st := status
if st == 0 {
st = def.Status
}
if st == 0 {
st = http.StatusInternalServerError
}
emitError(w, r, st, def, err.Error())
return
}
emitError(w, r, status, errcode.FromLegacy(status, err.Error()), err.Error())
}
func emitError(w http.ResponseWriter, r *http.Request, status int, def errcode.Def, legacy string) {
if def.ID == "" {
def = errcode.FromStatus(status)
}
details := map[string]any{}
if def.Guide != "" {
details["guide"] = def.Guide
}
if def.Module != "" {
details["module"] = def.Module
}
rid := incomingRequestID(w, r)
httpx.WriteHTTP(w, r, status, def.ID, legacy, rid, def.Message, details)
if rid == "" && w != nil {
rid = w.Header().Get(httpx.HeaderRequestID)
}
reportHTTPError(r, status, def, rid, legacy)
}
func incomingRequestID(w http.ResponseWriter, r *http.Request) string {
if r != nil {
if v := strings.TrimSpace(r.Header.Get(httpx.HeaderRequestID)); v != "" {
return v
}
}
if w != nil {
if v := strings.TrimSpace(w.Header().Get(httpx.HeaderRequestID)); v != "" {
return v
}
}
return ""
}
func reportHTTPError(r *http.Request, status int, def errcode.Def, requestID, legacy string) {
ev := errcode.Event{
Code: def.ID,
Class: def.Class,
HTTPStatus: status,
RequestID: requestID,
Module: def.Module,
Source: "http",
Retryable: def.Retryable,
UserMessage: def.Message,
Upstream: legacy,
}
ctx := context.Background()
if r != nil {
ev.Method = r.Method
ev.Path = r.URL.Path
if sid := strings.TrimSpace(r.URL.Query().Get("session_id")); sid != "" {
ev.SessionID = sid
}
if user := principalFromContext(r.Context()); user != nil {
ev.ActorUserID = user.ID
}
ctx = r.Context()
}
errcode.Report(ctx, ev)
}