package server import ( "log/slog" "net/http" "runtime/debug" ) // RecoveryOption configures the Recovery middleware. type RecoveryOption func(*recoveryOptions) type recoveryOptions struct { logger *slog.Logger } // WithRecoveryLogger sets the logger for the Recovery middleware. // If not set, panics are recovered silently (500 is still returned). func WithRecoveryLogger(l *slog.Logger) RecoveryOption { return func(o *recoveryOptions) { o.logger = l } } // Recovery returns a middleware that recovers from panics in downstream // handlers. A recovered panic results in a 500 Internal Server Error // response and is logged (if a logger is configured) with the stack trace. func Recovery(opts ...RecoveryOption) Middleware { o := &recoveryOptions{} for _, opt := range opts { opt(o) } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if v := recover(); v != nil { if o.logger != nil { o.logger.LogAttrs(r.Context(), slog.LevelError, "panic recovered", slog.Any("panic", v), slog.String("stack", string(debug.Stack())), slog.String("method", r.Method), slog.String("path", r.URL.Path), slog.String("request_id", RequestIDFromContext(r.Context())), ) } http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) } }