应将recover中间件注册为第一个中间件,捕获panic后调用c.error()交由全局httperrorhandler处理,避免直接写响应;恢复时需检查c.response().committed,并用debug.stack()记录完整堆栈。

中间件里用recover()捕获panic但拿不到HTTP错误状态
Echo默认的echo.HTTPErrorHandler只管处理已知错误(比如c.JSON(500, err)),而panic会直接中断请求流。中间件里recover()能抓到panic,但如果不主动写响应,客户端只会收到空响应或连接关闭——因为c.Response().Writer可能已被写过头或处于不可写状态。
实操建议:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 在中间件开头调用
defer func() { if r := recover(); r != nil { ... } }(),确保覆盖整个next(c)执行过程 - 恢复后必须检查
c.Response().Committed:为true说明响应头已发,不能再调c.JSON(),只能记录日志或忽略 - 推荐统一返回
c.String(500, "Internal Server Error"),避免因结构体序列化再panic
把错误注入c.Error()再交由全局错误处理器处理
直接在中间件里写响应容易和后续中间件/路由逻辑冲突(比如日志中间件还没执行、CORS头没加)。更干净的做法是把panic转成error对象,调用c.Error(),让Echo走标准错误流程。
实操建议:
- 在
recover()块里构造自定义错误:err := fmt.Errorf("panic recovered: %v", r) - 调用
c.Error(err),而不是c.JSON(500, ...),这样echo.HTTPErrorHandler会被触发 - 确保你已设置全局错误处理器:
e.HTTPErrorHandler = customHTTPErrorHandler,否则仍会fallback到默认行为
注意中间件注册顺序影响错误捕获范围
如果错误发生在某个中间件内部(比如JWT验证中间件里解析token失败panic),而你的recover中间件注册在它之后,就捕不到这个panic。顺序决定作用域。
实操建议:
- 把recover中间件放在
e.Use(...)最前面,即第一个注册 - 避免在
Group里单独注册recover中间件,除非你明确只监控该分组——它对根路由无效 - 若使用
e.Pre(),它在所有中间件前执行,但不参与请求上下文流转,不适合做recover
日志中丢失原始panic堆栈怎么办
直接fmt.Printf("%v", r)只打印panic值,没有文件行号和调用链。线上排障时很难定位。
实操建议:
- 用
debug.PrintStack()输出完整堆栈到日志(注意:它写到os.Stderr,需重定向或改用debug.Stack()) - 更稳妥的是
buf := debug.Stack(); log.Error(string(buf)),配合结构化日志库(如Zap)打字段stack - 不要在recover中间件里直接
panic(r)二次panic,这会让进程崩溃










