中间件函数签名必须是func(*gin.context),漏指针或未调用闭包会导致panic;recover必须置于defer中且自定义恢复中间件须注册为首个use;响应状态不可靠,应以recover非nil、c.errors含critical级apperror及500+状态为告警依据;recover后禁用c.json等写响应操作。

中间件函数签名必须是 func(*gin.Context)
写错签名会导致注册时直接 panic,错误信息类似 panic: interface conversion: interface {} is func(), not gin.HandlerFunc。常见错误是漏掉指针符号,写成 func(gin.Context);或传入带参数的闭包但没执行,比如写了 r.Use(AuthMiddleware("admin")) 却忘了加括号调用,实际传的是函数地址而非 gin.HandlerFunc。
正确写法只有两种:
-
func(c *gin.Context) { ... }(匿名函数) -
func() gin.HandlerFunc { return func(c *gin.Context) { ... } }(带配置的闭包)
recover 必须放在 defer 里,且中间件要注册在最外层
gin.Recovery() 默认只打印日志不触发报警,要自定义行为,必须自己写中间件,并确保它排在所有 r.Use() 调用的第一个位置。否则 panic 发生在它之前,就逃逸了。
典型错误顺序:r.Use(loggerMW, authMW, myRecovery) → myRecovery 永远收不到 panic。
正确顺序示例:
router := gin.New() router.Use(myRecovery()) // 必须第一 router.Use(loggerMW) router.Use(authMW)
中间件内部结构必须是:
defer func() { if err := recover(); err != nil { /* 处理 */ } }()- 然后调用
c.Next()
不能只看 c.Writer.Status() 判断是否出错
很多 panic 发生在响应头已写出之后(比如 c.JSON(200, data) 后再 panic),此时 c.Writer.Status() 返回 200,但客户端实际收不到完整响应。只依赖 status 会漏报。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
真正可靠的依据是:
-
recover()捕获到非 nil 值 → 必须告警 -
c.Errors中存在*AppError且Level == "critical"→ 告警 -
c.Writer.Status() >= 500且c.Errors非空 → 补充告警
注意:用 c.AbortWithError() 抛出的 error 不进 recover,但会存进 c.Errors,所以得统一用 AppError 类型做判断,别用字符串匹配 message。
recover 后别再调用 c.JSON() 或 c.Status()
panic 恢复后,HTTP response 可能已部分写出(header 已发、body 写了一半)。此时再调用 c.JSON() 会 panic,错误如 http: multiple response.WriteHeader calls。
安全做法只有两个:
- 调用
c.Error()记录错误(填入c.Errors,供后续中间件读取) - 异步发报警(如 go func() { sendAlert(...) }()),绝不阻塞当前请求流
别试图在 recover 块里重写响应体——Gin 的 ResponseWriter 不支持重置,强行操作会崩溃或返回乱码。
真实线上环境里,最难绷的是 panic 和业务 error 的混合判定逻辑。很多人把 c.Error(&AppError{Level: "error"}) 和 panic("xxx") 统一当成 critical 处理,结果告警风暴。区分来源、分级响应,才是报警中间件不沦为噪音源的关键。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










