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) }