// Package panicrecovery converts unrecovered panics into a structured 500 // response and a single error-level log entry. It is wired exactly once at the // top of the gin middleware chain. package panicrecovery import ( "net/http" "galaxy/backend/internal/server/httperr" "galaxy/backend/internal/server/middleware/requestid" "galaxy/backend/internal/telemetry" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // Middleware returns a gin middleware that recovers from panics, logs the // failure with trace fields, and writes the standard 500 envelope. func Middleware(logger *zap.Logger) gin.HandlerFunc { if logger == nil { logger = zap.NewNop() } return gin.CustomRecovery(func(c *gin.Context, recovered any) { fields := []zap.Field{ zap.String("method", c.Request.Method), zap.String("path", c.FullPath()), zap.Any("panic", recovered), } if requestID, ok := requestid.FromGin(c); ok { fields = append(fields, zap.String("request_id", requestID)) } fields = append(fields, telemetry.TraceFieldsFromContext(c.Request.Context())...) logger.Error("http handler panicked", fields...) httperr.Abort(c, http.StatusInternalServerError, httperr.CodeInternalError, "internal server error") }) }