consul 配置中心与 gin 全局异常捕获完全独立:consul 仅动态提供错误处理相关配置(如告警开关、sentry dsn),异常捕获必须由自定义 recovery 中间件实现;需禁用 gin.default() 的默认 recovery,改用 gin.new() 手动注册中间件,并确保 customrecovery 置于 r.use() 链最前端,调用 c.abortwithstatusjson() 统一响应,且 noroute/nomethod 需单独复用相同错误格式。

Consul 配置中心和全局异常捕获是两件事,别混着配
Consul 本身不参与 Gin 的 panic 捕获或错误通知逻辑。它只负责拉取配置(比如日志等级、告警开关、Sentry DSN),而异常捕获必须由 Gin 中间件完成。想让 Consul “影响”错误处理,唯一靠谱的方式是:把错误上报开关、告警阈值、监控端点等参数从 Consul 动态读取,再注入到 recovery 中间件里。
常见错误是试图在 consul.NewClient() 初始化阶段就注册 recovery——这时 Gin router 还没建,中间件根本挂不上。正确顺序是:先建 gin.Engine,再初始化 Consul client 并拉配置,最后用配置参数构造 recovery 中间件并 r.Use() 注册。
- Consul 配置项建议用前缀隔离,例如
config/error/enable_alert、config/error/sentry_dsn - 拉取失败时要有 fallback,默认关闭告警,避免启动卡死
- 不要在 recovery 中间件里实时查 Consul —— 每次 panic 都去网络请求,会放大故障
自定义 Recovery 中间件必须禁用 gin.Default() 自带的 recovery
如果你调用了 gin.Default(),它内部已经注册了 gin.Recovery(),这个默认中间件会在你的自定义 recovery 之前执行并吞掉 panic,导致你写的 defer+recover 完全不触发。生产环境务必改用 gin.New(),然后手动注册中间件链。
- 中间件顺序很重要:
r.Use(CustomRecovery(conf))必须放在最前面,早于gin.Logger()和业务中间件 -
CustomRecovery内部必须调用c.AbortWithStatusJSON(),不能只写c.JSON(500, ...)+c.Abort(),否则后续中间件可能再次尝试写响应体,引发http: multiple response.WriteHeader calls - panic 后不能再调用
c.Next(),recover 块里必须立即响应,否则请求上下文已损坏
错误通知不是靠中间件自动发,得显式集成 Sentry / Prometheus / 邮件
Gin 的 recovery 只负责“捕获并返回”,通知是另一层能力。Consul 可以提供通知渠道的配置,但发送动作得你自己写。比如读到 config/alert/channel 是 "sentry",就在 recovery 里初始化 sentry.CurrentHub().CaptureException(err);如果是 "email",就走 SMTP 发送摘要。
- 敏感信息必须脱敏:原始 panic 堆栈绝不能返回给前端,也别直接打到 Sentry 的 message 字段里,应提取
err.Error()作 title,debug.Stack()存为 extra 字段 - 告警要限流:同一错误类型 1 分钟内最多上报 3 次,避免雪崩式告警刷屏
- Consul 配置变更不会自动热重载 recovery 行为,需要监听
watch.KVPair或定期轮询,然后重建中间件实例(实际中更推荐服务重启生效)
别忘了 NoRoute / NoMethod 不走 Recovery,它们需要单独处理
r.NoRoute() 和 r.NoMethod() 是路由匹配失败时的兜底处理器,发生在中间件链之前,所以 CustomRecovery 完全不生效。如果你希望 404 也走统一错误结构,必须在 NoRoute 函数里手动调用相同响应逻辑,而不是 panic 或裸写 c.String()。
- 不要在
NoRoute里调用panic()—— Recovery 不捕获它,错误会透出空白页且无日志 - 建议封装一个复用函数,比如
writeError(c *gin.Context, code, httpCode int, msg string),在 recovery 和 NoRoute 里都调用它 - Consul 可以控制是否开启 404 日志记录,但不能改变 NoRoute 的执行时机
真正容易被忽略的是 panic 类型的不可靠性:直接 panic("xxx") 得到的是 string 类型,panic(errors.New("xxx")) 是 *errors.errorString,而你想在 recovery 里精准识别业务错误,必须用自定义结构体如 *AppError 并做类型断言。Consul 配置再灵活,也救不了类型混乱的 panic。











