go 49 lines · 8 steps

How a panic-recovery middleware works in Gin

A Gin middleware that catches panics, reports them with request context, and returns a clean 500.

Explained by highlit
1package middleware
2 
3import (
4 "fmt"
5 "net/http"
6 "runtime"
7 
8 "github.com/gin-gonic/gin"
9)
10 
11type ErrorReporter interface {
12 CaptureException(err error, ctx map[string]interface{})
13}
14 
15func Recovery(reporter ErrorReporter) gin.HandlerFunc {
16 return func(c *gin.Context) {
17 defer func() {
18 rec := recover()
19 if rec == nil {
20 return
21 }
22 
23 err, ok := rec.(error)
24 if !ok {
25 err = fmt.Errorf("%v", rec)
26 }
27 
28 stack := make([]byte, 8<<10)
29 stack = stack[:runtime.Stack(stack, false)]
30 
31 reporter.CaptureException(err, map[string]interface{}{
32 "method": c.Request.Method,
33 "path": c.FullPath(),
34 "query": c.Request.URL.RawQuery,
35 "client_ip": c.ClientIP(),
36 "user_agent": c.Request.UserAgent(),
37 "request_id": c.GetString("request_id"),
38 "stack": string(stack),
39 })
40 
41 c.Error(err) //nolint:errcheck
42 c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
43 "error": "internal server error",
44 })
45 }()
46 
47 c.Next()
48 }
49}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A deferred function paired with recover() converts a panic into a normal error you can handle instead of crashing.
  2. 2Capturing request metadata and a stack trace at panic time makes production failures diagnosable after the fact.
  3. 3Aborting with a generic JSON 500 keeps internal details out of the client response while still logging the real cause.

Related explainers

Share this explainer

Here's the card — post it anywhere.

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