必须用包装器捕获状态码,因原生 ResponseWriter 不暴露已写入状态;需重写 WriteHeader 和 Write 方法并导出 statusCode 字段,且 panic 时须在 defer recover 中手动设为500。

为什么直接读 w.WriteHeader() 拿不到真实状态码
原生 http.ResponseWriter 不暴露已写入的状态码,w.Header().Set() 只改 header,不设状态;w.WriteHeader(status) 才真正提交状态,但调用后无法再读取。如果你在 next.ServeHTTP() 后直接查 w,拿到的永远是 0 或未定义值。
根本原因是:标准库没提供 Status() 方法,必须靠包装器在 WriteHeader 被调用时主动捕获。
- 常见错误:用
w.(http.Hijacker)类型断言或反射硬读——无效且不稳定 - 304、204、302 等无 body 响应极易漏统计,因为
Write()根本不会被调用 - 显式调用
w.WriteHeader(404)和隐式触发(如首次Write())行为不一致,必须统一拦截WriteHeader
怎么写一个能捕获状态码的 responseWriter 包装器
核心是定义结构体嵌入 http.ResponseWriter,重写 WriteHeader 记录状态,并透传所有其他方法。别忘了覆盖 Write——某些 handler 会跳过 WriteHeader 直接 Write,此时需隐式补默认 200。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type responseWriter struct {
http.ResponseWriter
statusCode int
wroteHeader bool
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.wroteHeader = true
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
if !rw.wroteHeader {
rw.WriteHeader(http.StatusOK)
}
return rw.ResponseWriter.Write(b)
}
-
statusCode必须是导出字段,否则外部无法读取 - 不要在
WriteHeader里做耗时操作(如打日志),避免阻塞响应流 - 如果要用
sync.Pool复用实例,注意Reset()逻辑要清空statusCode和wroteHeader
中间件里怎么安全读取状态码和耗时
必须等 next.ServeHTTP(rw, r) 完全返回后再读 rw.statusCode,不能 defer 里读——defer 在函数 return 前执行,但 handler 可能 panic 或提前 write,导致状态码未落定。
- 耗时计算用
time.Since(start)即可,但 start 必须在next.ServeHTTP()前获取 - 别依赖
req.Context().Done()或Deadline()算耗时,那是超时控制,不是实际处理时间 - 若 handler panic,
recover()后状态码仍是 0,需额外判断并设为 500(除非你用http.StripPrefix等预处理中间件兜底)
路径归一化和状态码分类影响统计准确性
没做路径标准化的日志,/user/123 和 /user/456 会被当两个接口,导致 QPS、错误率统计失真;状态码不分类(比如把 400、401、422 全算“客户端错误”)会让告警阈值难设置。
- Gin 用户注意:
c.Request.URL.Path是原始路径,c.FullPath()才是路由模板(如/user/:id),统计前务必用后者 - net/http 用户需自己维护路由树映射,或用
chi/gorilla/mux的RouteMatch提取 pattern - 建议状态码分组:2xx → success,3xx → redirect,4xx → client_error,5xx → server_error,其他 → unknown
真实线上最容易被忽略的点:**状态码捕获时机与 panic 恢复顺序必须严格对齐**。如果中间件里 recover 放在 next.ServeHTTP() 外层,panic 时 rw.statusCode 还是初始值;必须在 defer 里 recover,且 recover 后手动设 rw.statusCode = 500,否则 500 错误在指标里直接消失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










