必须包装 http.ResponseWriter 拦截 WriteHeader 调用以捕获真实状态码,因 echo.Context 不暴露原始状态码且 c.Response().Status 始终为 0;需重写 WriteHeader 和 Write 方法,并处理 panic、error 及 http.Error 绕过问题。

中间件里拿不到http.ResponseWriter原始状态码
Echo 的 echo.Context 不暴露底层响应状态码,直接调用 c.Response().Status 返回的是 0(因为写入尚未发生)。真正写入发生在 handler 执行完、中间件返回后。所以必须包装 http.ResponseWriter,拦截 WriteHeader 调用。
用echo.WrapResponseWriter包装响应器
Echo 提供了标准方式:实现 echo.ResponseWriter 接口并代理原始响应器。关键是在 WriteHeader 被调用时捕获状态码,并存到 c.Response().Header() 或上下文值中供后续使用。
常见错误是只监听 Write(不触发状态码写入)或漏掉未显式调用 WriteHeader 的情况(此时默认 200,但需在 Write 前补捕获)。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 必须重写
WriteHeader(int)方法,并保存状态码到c.Set("status_code", code) - 还要重写
Write([]byte)方法:若WriteHeader未被调用过,则此时隐式写入 200,应同步记录 - 避免在中间件里直接读
c.Response().Status—— 它始终为 0
func StatusCodeLogger() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
rw := &statusWriter{ResponseWriter: c.Response(), statusCode: 0}
c.SetResponse(echo.NewResponse(rw, c.Echo()))
err := next(c)
// 此时 rw.statusCode 已确定
log.Printf("status=%d path=%s", rw.statusCode, c.Request().URL.Path)
return err
}
}
}
type statusWriter struct {
echo.ResponseWriter
statusCode int
}
func (w *statusWriter) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
func (w *statusWriter) Write(b []byte) (int, error) {
if w.statusCode == 0 {
w.statusCode = http.StatusOK
}
return w.ResponseWriter.Write(b)
}
注意panic和error导致的状态码丢失
如果 handler panic 或返回非 nil error(且没被全局 HTTPErrorHandler 拦截),Echo 默认返回 500,但这个写入发生在中间件链之外,你的包装器可能收不到 WriteHeader 调用。
- 确保注册了自定义
HTTPErrorHandler,并在其中手动设置状态码并记录 - 在中间件末尾加
defer检查:若rw.statusCode == 0,说明出错了,按实际响应头推断(如Content-Type: application/json+ body 含"error",大概率是 500) - 不要依赖
recover()在中间件里捕获 panic —— Echo 的 panic 处理机制在中间件外层
日志字段要区分“预期”和“实际”状态码
有些业务逻辑会提前设置 c.Response().Status = 400,但这只是标记,不等于已写出。真实状态码只取决于最终 WriteHeader 调用值。记录时务必以包装器捕获的为准。
容易忽略的一点:当使用 c.String()、c.JSON() 等快捷方法时,它们内部会调用 WriteHeader,你的包装器能正常捕获;但若 handler 直接调用 http.Error(),它绕过 Echo 封装,会直接写原始 http.ResponseWriter,导致你的包装器失效 —— 这种情况必须避免,统一用 c.Logger() 或 c.Error()。

















