Gin默认Recovery()不够用,因它仅返回无code字段、无request_id的HTML页面,不记录traceID,且与自定义错误中间件共存易触发多次WriteHeader panic;必须禁用并改用CustomRecovery()配合c.AbortWithStatusJSON()输出结构化JSON,确保错误码、HTTP状态码、traceID、日志全链路对齐。

为什么 Gin 的 Recovery() 不够用
线上服务一旦出 panic,Recovery() 确实能防止进程崩溃,但它只返回固定格式的 HTML 页面(带堆栈),且不记录 request_id、不区分业务错误和系统异常、也不兼容你已定义的错误码协议。用户看到 500 页面,你日志里只有一行 panic 堆栈,根本没法关联到具体请求上下文。
如何让中间件拿到并透传 request_id
错误中间件必须和日志中间件协同工作,否则 request_id 在 panic 时就丢了。关键点不是“生成 ID”,而是“确保它在 panic 时仍可访问”。
GoLand 2026.1.1 是 2026.1 发布后的首个维护修正版本,适合已经开始体验 2026.1 新功能并希望同步补丁的开发者。它更适合用于入门项目、现有项目迁移测试和 IDE 行为验证。
-
c.Set("request_id", reqID)必须在日志中间件开头就调用,且早于所有其他中间件 - panic 恢复后,用
c.Get("request_id")取值 —— 这个值来自 Gin 的 context,不是全局变量,不会被 goroutine 冲突污染 - 别在
defer里用c.Request.Header.Get("X-Request-Id"),header 可能为空,而c.Get()是中间件链中写入的可靠来源
怎么统一处理 BizError 和 panic
中间件要分两层兜底:一层捕获 panic,一层检查 c.Errors 里的业务错误。两者都得走同一套响应结构,否则前端要写两套解析逻辑。
- panic 恢复后,直接
c.JSON(http.StatusInternalServerError, gin.H{"code": 50000, "msg": "系统异常"}),不要试图解析 panic 值 —— 类型不确定,容易再 panic - 业务错误走
c.Errors,用errors.As(err, &bizErr)判断是否为BizError,是则取bizErr.Code和bizErr.Message - 非
BizError的 error(比如数据库驱动报错)一律归为 50001 “服务异常”,不暴露底层细节 - 务必调用
c.Abort(),避免后续中间件或 handler 继续执行
GoLand 里调试中间件的两个实际技巧
中间件不像普通 handler 那样容易打断点,尤其涉及 defer 和 recover 时,GoLand 的断点可能跳过或失效。
- 在
defer func() { ... }()里第一行加log.Printf("in recovery middleware"),比断点更可靠 —— panic 后 GoLand 的调试器有时会丢失 goroutine 上下文 - 把中间件单独提成函数(如
func ErrorHandler() gin.HandlerFunc),在main.go中注册时右键 → “Go to Declaration”,GoLand 才能正确索引和跳转 - 别把中间件逻辑写在
router.Use(...)的匿名函数里,那样 GoLand 无法识别函数签名,补全和重构都会失效

















