Files
gen2d/backend/internal/mildware/logger.go
T

82 lines
2.0 KiB
Go
Raw Normal View History

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