From 681bc8f47287d3d7edd57063bf1474f002f53b4f Mon Sep 17 00:00:00 2001 From: wonder Date: Mon, 25 May 2026 14:30:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(middleware):=20=E6=96=B0=E5=BB=BA=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=97=A5=E5=BF=97=E4=B8=8E=20Panic=20=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E4=B8=AD=E9=97=B4=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Logger 中间件:生成 request_id(注入 X-Request-ID header),记录 method/path/status/latency/client_ip - Recovery 中间件:自定义 panic 恢复,记录 request 上下文和堆栈 --- backend/internal/mildware/logger.go | 81 +++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 backend/internal/mildware/logger.go diff --git a/backend/internal/mildware/logger.go b/backend/internal/mildware/logger.go new file mode 100644 index 0000000..d6e3c65 --- /dev/null +++ b/backend/internal/mildware/logger.go @@ -0,0 +1,81 @@ +package mildware + +import ( + "crypto/rand" + "fmt" + "log/slog" + "net/http" + "runtime/debug" + "time" + + "gen2d/internal/logger" + + "github.com/gin-gonic/gin" +) + +// Logger 返回 HTTP 请求日志中间件。 +// 为每个请求生成 request_id,记录 method、path、status、latency、client_ip。 +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + requestID := generateRequestID() + + c.Set("request_id", requestID) + ctx, _ := logger.WithRequestID(c.Request.Context(), requestID) + c.Request = c.Request.WithContext(ctx) + + c.Header("X-Request-ID", requestID) + + c.Next() + + latency := time.Since(start) + status := c.Writer.Status() + + l := logger.FromCtx(ctx) + attrs := []slog.Attr{ + slog.String("method", c.Request.Method), + slog.String("path", c.Request.URL.Path), + slog.Int("status", status), + slog.Duration("latency", latency), + slog.String("client_ip", c.ClientIP()), + } + + if status >= 500 { + l.LogAttrs(ctx, slog.LevelError, "request completed", attrs...) + } else if status >= 400 { + l.LogAttrs(ctx, slog.LevelWarn, "request completed", attrs...) + } else { + l.LogAttrs(ctx, slog.LevelInfo, "request completed", attrs...) + } + } +} + +// Recovery 返回自定义 panic 恢复中间件。 +// panic 时记录完整的 request 上下文和堆栈,返回 500。 +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + ctx := c.Request.Context() + l := logger.FromCtx(ctx) + l.Error("panic recovered", + "error", fmt.Sprintf("%v", r), + "method", c.Request.Method, + "path", c.Request.URL.Path, + "request_id", c.GetString("request_id"), + "stack", string(debug.Stack()), + ) + + c.AbortWithStatusJSON(http.StatusInternalServerError, + gin.H{"code": http.StatusInternalServerError, "message": "服务器内部错误"}) + } + }() + c.Next() + } +} + +func generateRequestID() string { + b := make([]byte, 8) + _, _ = rand.Read(b) + return fmt.Sprintf("%d-%x", time.Now().UnixMilli(), b) +}