必须包装 responsewriter 捕获真实 status 和 size:定义 loggingresponsewriter 结构体内嵌 responsewriter 并添加 status、size 字段,重写 writeheader 和 write 方法;中间件中为每个请求新建实例并置于压缩中间件外层。

Go 标准库的 http.ResponseWriter 不暴露状态码和写入字节数,直接在 handler 里用 log.Printf 打日志,拿到的永远是请求刚进来时的信息——status 写死为 200,size 根本为 0,latency 也不准。必须包装它。
怎么包装 ResponseWriter 捕获真实 status 和 size
核心是定义一个结构体,内嵌原始 ResponseWriter,加两个字段:status(初始设为 0)和 size(初始设为 0),再重写 WriteHeader 和 Write 方法:
type loggingResponseWriter struct {
http.ResponseWriter
status int
size int
}
func (lrw *loggingResponseWriter) WriteHeader(status int) {
lrw.status = status
lrw.ResponseWriter.WriteHeader(status)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
if lrw.status == 0 {
lrw.status = http.StatusOK
}
n, err := lrw.ResponseWriter.Write(b)
lrw.size += n
return n, err
}
注意两点:WriteHeader 必须赋值 lrw.status,否则 Write 里 fallback 到 200 的判断就失效;Write 里绝不能调 WriteHeader,HTTP 规范禁止多次调用。
中间件函数里怎么套进去才不崩
别改原 handler 签名,也别在 next.ServeHTTP 前后手动写响应。标准写法是用 http.HandlerFunc 包一层,创建新 loggingResponseWriter 实例传给 next:
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lrw := &loggingResponseWriter{
ResponseWriter: w,
status: 0,
size: 0,
}
next.ServeHTTP(lrw, r)
latency := time.Since(start)
log.Printf("%s %s %d %d %v", r.Method, r.RequestURI, lrw.status, lrw.size, latency)
})
}
关键点:
- 每个请求新建一个
loggingResponseWriter,不复用、不共享字段,天然并发安全 - 用
r.RequestURI而非r.URL.Path,它保留原始路径+查询参数,不受反向代理或路由重写影响 - 日志打在
next.ServeHTTP之后,确保status和size已被真实写入 - 如果用了
gogz或其他压缩中间件,你的日志中间件必须包在它外层,否则拿不到最终响应体长度
生产环境最容易翻车的三个细节
日志中间件上线后出问题,90% 都卡在这三处:
-
采样没做:高频接口(如健康检查)全量打日志,磁盘/IO/ES 直接打爆。建议对
GET /health这类路径跳过记录,或按百分比采样 -
上下文丢失:没把
r.Context()透传进日志字段,查问题时找不到 traceID 或用户 ID。应在中间件开头提取r.Context().Value("trace_id")并塞进日志结构体 -
敏感信息裸奔:
r.RequestURI含查询参数,r.Header含Authorization,不脱敏就上日志平台等于公开密钥。必须正则替换掉token=.*?&、password=.*?&等模式
最常被忽略的是:gzip 中间件会替换 ResponseWriter,如果你的日志中间件放在它里面,lrw.size 就是压缩前字节数,不是客户端实际收到的大小——而监控系统通常只认后者。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











