默认 gin.Recovery() 不适合生产报警,因其仅输出“panic recovered”无堆栈、不告警、不区分环境;应使用 RecoveryWithWriter() 配合自定义 recoveryFunc 实现 panic 捕获、堆栈记录、监控上报与限流响应。

为什么默认的 gin.Recovery() 不适合生产报警
它只往日志里写一句“panic recovered”,不带堆栈、不发告警、不区分环境,线上出问题时你得翻日志再 grep panic,等发现可能已经丢了几十个请求。真正的报警需要:捕获 panic 内容 + 记录完整堆栈 + 上报到监控系统(如 Prometheus Alertmanager、Sentry 或企业微信/钉钉机器人)+ 避免日志刷屏。
如何用 RecoveryWithWriter() 替换默认 Recovery 中间件
别直接调 gin.Recovery(),它没法定制行为;要用 gin.RecoveryWithWriter(),传入自定义 io.Writer,把 panic 输出重定向到你的处理逻辑里:
-
RecoveryWithWriter()第一个参数是io.Writer,可以是os.Stderr、文件句柄,甚至是一个自定义的io.Writer实现(比如把内容塞进 channel 或发 HTTP 请求) - 第二个参数是可选的
recoveryFunc,类型为func(*gin.Context, interface{}),这才是真正做报警的地方 - 必须配合
c.AbortWithStatusJSON()终止后续中间件执行,否则 panic 恢复后还可能继续走业务逻辑,导致二次 panic 或数据错乱
在 recoveryFunc 里做报警的实操要点
这个函数是你唯一能拿到 panic 值和上下文的地方,也是唯一该写报警逻辑的位置:
- 先判断
err类型:用switch e := err.(type)区分*AppError、error、string,避免err.(error).Error()在非 error 类型上 panic - 提取关键信息:请求路径
c.Request.URL.Path、方法c.Request.Method、客户端 IPc.ClientIP()、时间戳 - 记录堆栈:用
debug.Stack()获取完整 panic 堆栈,别只打err.Error() - 触发报警:调用你封装好的告警函数(比如
alert.Panic("prod", path, stack)),注意加限流,防止雪崩式报警 - 响应要明确:对用户返回统一错误结构,比如
c.AbortWithStatusJSON(500, gin.H{"code": 50001, "msg": "服务暂时不可用"})
容易被忽略的坑:panic 发生时机和中间件顺序
Recovery 中间件必须在所有业务中间件之前注册,否则 panic 发生在它前面的中间件里就捕获不到:
- 错误写法:
r.Use(AuthMiddleware()); r.Use(gin.Recovery())→ AuthMiddleware 里 panic 就漏了 - 正确顺序:
r.Use(gin.Recovery()); r.Use(AuthMiddleware()) - 如果用了
gin.Default(),它内部已注册gin.Recovery(),此时再r.Use(CustomRecovery())会导致两个 Recovery 同时生效,可能重复响应或状态码冲突 - 调试阶段建议关掉
gin.ReleaseMode,让错误详情透出;上线前务必确保GIN_MODE=release,否则堆栈可能暴露敏感路径或变量名
recoveryFunc,再花哨的监控平台也收不到第一条消息。


















