go 36 lines · 6 steps

A panic-recovery middleware in Gin

A Gin middleware that recovers from panics, logs them with structured context, and tells broken connections apart from real server errors.

Explained by highlit
1func RecoveryHandler(logger *zap.Logger) gin.HandlerFunc {
2 return gin.CustomRecovery(func(c *gin.Context, recovered any) {
3 var brokenPipe bool
4 if ne, ok := recovered.(*net.OpError); ok {
5 var se *os.SyscallError
6 if errors.As(ne, &se) {
7 msg := strings.ToLower(se.Error())
8 if strings.Contains(msg, "broken pipe") || strings.Contains(msg, "connection reset by peer") {
9 brokenPipe = true
10 }
11 }
12 }
13 
14 stack := string(debug.Stack())
15 fields := []zap.Field{
16 zap.Any("error", recovered),
17 zap.String("method", c.Request.Method),
18 zap.String("path", c.Request.URL.Path),
19 zap.String("client_ip", c.ClientIP()),
20 zap.String("request_id", requestid.Get(c)),
21 }
22 
23 if brokenPipe {
24 logger.Warn("connection interrupted", fields...)
25 c.Error(recovered.(error)) //nolint:errcheck
26 c.Abort()
27 return
28 }
29 
30 logger.Error("panic recovered", append(fields, zap.String("stack", stack))...)
31 c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
32 "error": "internal server error",
33 "request_id": requestid.Get(c),
34 })
35 })
36}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Recovery middleware turns a panic anywhere in the handler chain into a controlled HTTP response instead of a crashed server.
  2. 2Broken-pipe errors mean the client already left, so there's no point returning a status — logging a warning is enough.
  3. 3Attaching request context like method, path, and request ID to every log entry makes production panics traceable.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A panic-recovery middleware in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code