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