echo默认recover中间件可捕获panic并返回500,但若提前自定义httperrorhandler会绕过recover导致堆栈丢失;应确保recover注册在httperrorhandler赋值之后,或在自定义处理器中通过c.set/c.get传递debug.stack()获取的堆栈信息。

中间件里 panic 没被捕获?检查 echo.HTTPErrorHandler 是否被覆盖
默认情况下,Echo 的 Recover 中间件确实会捕获 panic 并返回 500,但如果你自定义了 echo.HTTPErrorHandler(比如为了统一错误响应结构),它可能在 panic 被 Recover 处理前就提前介入,导致堆栈丢失或格式混乱。这时候 Recover 实际上没起作用。
实操建议:
- 确保
e.HTTPErrorHandler不在e.Use(middleware.Recover())之前被赋值;更稳妥的做法是:不直接覆盖HTTPErrorHandler,而是在自定义错误处理器里区分error类型——如果是*echo.HTTPError正常处理,如果是其他 panic 衍生的 error(如echo.NewHTTPError(500, ...)包裹后的),再做日志和堆栈提取 - 不要依赖
Recover默认行为输出堆栈——它只记录到 logger,不返回给客户端,也不结构化 - 若需返回结构化 panic 信息(仅开发环境),必须手动接管 recover 流程
自己写 recover 中间件:用 debug.Stack() 拿原始堆栈
官方 middleware.Recover() 内部调用的是 debug.PrintStack(),输出到 stderr,没法拿字符串做后续处理。要格式化并嵌入 JSON 响应,得自己写。
关键点:
- 必须用
recover()捕获,且在 defer 函数中调用debug.Stack()——debug.Stack()只在 panic 发生后调用才有效,且返回[]byte - 别用
runtime.Caller或runtime.StackTrace,它们只给单帧,不是完整 panic 堆栈 - 捕获后立即调用
c.Error()触发错误处理器,或直接c.JSON()返回(注意状态码设为 500)
示例片段:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
func PanicRecover() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
defer func() {
if r := recover(); r != nil {
stack := debug.Stack()
err := fmt.Errorf("panic: %v\n%s", r, stack)
// 开发环境才返回详细堆栈
if echo.IsDebug() {
c.JSON(http.StatusInternalServerError, map[string]interface{}{
"error": "internal server error",
"stack": string(stack),
})
return
}
c.JSON(http.StatusInternalServerError, map[string]string{
"error": "internal server error",
})
}
}()
return next(c)
}
}
}
堆栈太长?用 strings.SplitN(string(stack), "\n", 20) 截断
完整 debug.Stack() 输出可能上千行,尤其带 vendor 路径时。直接返回既不安全(暴露内部路径/变量名),也影响响应体积和可读性。
实操建议:
- 生产环境绝对不要返回完整堆栈,至少过滤掉
vendor/和go/src/行,可用strings.ReplaceAll简单脱敏 - 保留最上面 10–15 行(含 panic 行 + goroutine 调用链顶部),用
strings.SplitN(..., "\n", 20)后取前 N 行更可靠,比正则匹配稳定 - 注意
debug.Stack()结尾有换行,string(stack)直接拼接易多出空行,建议strings.TrimSpace一下
为什么 echo.HTTPErrorHandler 里拿不到 panic 堆栈?
因为 Recover 中间件捕获 panic 后,会构造一个 *echo.HTTPError(状态码 500,message 是 panic 的 fmt.Sprint(r)),然后调用 c.Error(err)。此时原始 panic 已消失,debug.Stack() 再调就是空的。
所以:如果坚持用 HTTPErrorHandler 统一出口,就必须在 Recover 中间件里把堆栈存到 c.Set("panic_stack", stack),再在 HTTPErrorHandler 里用 c.Get("panic_stack") 取出来——这是唯一能跨中间件传递 panic 上下文的方式。
容易被忽略的一点:Echo 的 c.Set 和 c.Get 是 request-scoped 的,不会污染全局,但必须确保 Recover 在 HTTPErrorHandler 触发前执行(即中间件注册顺序不能错)。










