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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Recovery middleware turns a panic anywhere in the handler chain into a controlled HTTP response instead of a crashed server.
- 2Broken-pipe errors mean the client already left, so there's no point returning a status — logging a warning is enough.
- 3Attaching request context like method, path, and request ID to every log entry makes production panics traceable.
Related explainers
rust
use axum::{ http::StatusCode, response::IntoResponse, routing::get,
Serving an SPA and API with Axum
routing
static-files
spa-fallback
Intermediate
7 steps
javascript
const STORAGE_KEY = "app_state"; const CURRENT_VERSION = 3; const migrations = {
Versioned state migrations in localStorage
migrations
persistence
versioning
Intermediate
9 steps
go
package server import ( "context"
Composing HTTP middleware in Go
middleware
http-handlers
closures
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Verifying Stripe webhooks in Laravel
webhooks
signature-verification
event-dispatch
Intermediate
8 steps
javascript
const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); };
Async error handling in Express routes
async-await
error-handling
middleware
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, http::StatusCode, routing::get,
Building a JSON user API in Axum
routing
shared-state
json-serialization
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-panic-recovery-middleware-in-gin-explained-go-bdca/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.