feat(middleware): 新建请求日志与 Panic 恢复中间件
- Logger 中间件:生成 request_id(注入 X-Request-ID header),记录 method/path/status/latency/client_ip - Recovery 中间件:自定义 panic 恢复,记录 request 上下文和堆栈
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user