go 20 lines · 1 tab

Panic recovery middleware that fails closed and logs context

Leah Thompson Jan 2026
1 tab
package middleware

import (
  "log"
  "net/http"
  "runtime/debug"
)

func Recover(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    defer func() {
      if p := recover(); p != nil {
        log.Printf("panic: %v
%s", p, debug.Stack())
        http.Error(w, "internal server error", http.StatusInternalServerError)
      }
    }()
    next.ServeHTTP(w, r)
  })
}
1 file · go Explain with highlit

Even well-tested services panic occasionally: a nil pointer from an unexpected edge case, a slice bounds bug, or a library doing something surprising. If you don’t recover, one request can crash the entire process and turn a small bug into an outage. I wrap my HTTP stack with a recovery middleware that catches panics, logs the panic value and stack trace, and returns a generic 500 to the client. The key is to avoid leaking internals: clients should see “internal server error” while logs contain the details. In production, I also include request identifiers (like X-Request-Id) and route labels so incidents are debuggable. This middleware doesn’t replace tests, but it’s a practical guardrail. When combined with alerting on panic count, it gives you early warning before a rare crash becomes a repeating failure pattern.


Related snips

Share this code

Here's the card — post it anywhere.

Panic recovery middleware that fails closed and logs context — share card
Link copied