默认 recovery 中间件不够用,因其仅捕获 panic、打印堆栈并返回空白 500,不支持自定义日志格式、错误上报、状态码分级或 json 响应;需自定义中间件实现 panic 拦截、脱敏、结构化返回与告警。

为什么默认的 recovery 中间件不够用
Gin 自带的 recovery 中间件只做两件事:捕获 panic、打印堆栈、返回 500。它不支持自定义错误日志格式,不支持上报 Sentry 或 Prometheus,也不支持根据 panic 类型返回不同状态码或响应体。一旦业务里需要记录 trace ID、区分系统 panic 和业务 panic,或者想把 panic 转成 JSON 错误响应,就必须重写。
如何注册自定义 recovery 中间件替代默认版本
直接调用 r.Use() 替换掉 Gin 默认的 recovery.Recovery(),注意顺序:必须在路由注册前、且在其他可能触发 panic 的中间件(比如鉴权、参数绑定)之后注册,否则会漏捕获。
关键点:
- 必须调用
recover()获取 panic 值,否则无法拦截 - 必须在 defer 函数中调用,且 defer 要在 handler 执行前注册(即 middleware 函数体内)
- 恢复后要显式设置
c.Abort(),否则后续中间件仍会执行 - 建议保留原始 panic 堆栈,用
debug.Stack()而非fmt.Sprintf("%v", err)
func CustomRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
stack := debug.Stack()
log.Printf("[PANIC] %v\n%s", err, stack)
c.AbortWithStatusJSON(500, gin.H{"error": "internal server error"})
}
}()
c.Next()
}
}
// 使用方式(替换默认 recovery)
r := gin.New()
r.Use(CustomRecovery()) // 不要再用 r.Use(gin.Recovery())
怎么让 panic 携带可识别的错误类型和 HTTP 状态码
硬编码 500 不够灵活。常见做法是定义一个接口,比如 type Error interface { Error() string; StatusCode() int },然后在 panic 时抛出实现该接口的 struct。自定义 recovery 中检查 panic 值是否实现了该接口,再据此返回对应状态码和消息。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
示例场景:
- 业务主动 panic
&AppError{Msg: "not found", Code: 404} - 中间件 panic
&ValidationError{Field: "email"}→ 映射为 400 - 其他未识别 panic 一律按 500 处理
type AppError struct {
Msg string
Code int
}
func (e *AppError) Error() string { return e.Msg }
func (e *AppError) StatusCode() int { return e.Code }
// 在 CustomRecovery 的 defer 里:
if err := recover(); err != nil {
if appErr, ok := err.(interface{ StatusCode() int }); ok {
c.AbortWithStatusJSON(appErr.StatusCode(), gin.H{"error": fmt.Sprint(err)})
} else {
c.AbortWithStatusJSON(500, gin.H{"error": "internal error"})
}
}
容易被忽略的细节和线上踩坑点
真实部署中这几个问题最常导致 recovery 失效或掩盖真正问题:
- 没调用
c.Abort():panic 恢复后仍继续执行后续 handler,可能引发二次 panic 或脏数据写入 - 日志没打全:只打
err字符串,丢失 goroutine ID、请求路径、query 参数,排查困难 - panic 发生在 goroutine 里(如异步任务、定时器):HTTP 请求的 recovery 中间件完全捕获不到,必须单独处理
- recover 后继续调用
c.Next():这是典型错误,c.Next()应只在 defer 外、无 panic 时执行 - 使用
log.Fatal或os.Exit:它们会终止整个进程,绕过所有 recover 机制
复杂点在于:panic 可能来自任意深度的调用栈,也可能跨 goroutine;而 recovery 只能兜住当前 HTTP 请求 goroutine 的顶层 panic。真要覆盖全面,得配合全局 signal.Notify 和 runtime.SetFinalizer 做兜底,但那已超出 HTTP 层面了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










