Go日志中间件必须包装http.ResponseWriter以捕获真实状态码、响应字节数和准确耗时,仅用log.Printf记录method/path无法反映处理结果、延迟和返回量,导致排障失效。

Go 日志中间件不能只记请求开始时间就完事——状态码、响应体大小、耗时都得在 response 写完后才能确定,而 http.ResponseWriter 不暴露这些信息,必须包装。
为什么直接用 log.Printf 打 r.Method 和 r.URL.Path 不够
这类日志只能反映“请求进来了”,但无法回答关键问题:处理成功了吗?花了多久?返回了多少字节?客户端到底收到了什么?尤其在线上排障时,缺失状态码和耗时,等于盲人摸象。
常见错误现象:log.Printf("%s %s", r.Method, r.URL.Path) 看起来正常,但日志里永远没有 200 或 500,也无法判断是 handler 慢还是网络慢。
- 状态码只在
WriteHeader或首次Write时隐式确定(默认200) - 响应体大小只有在
Write实际发生后才能累加 - 耗时必须用
time.Since(start),且要放在next.ServeHTTP之后
必须实现 ResponseWriter 包装器来捕获状态码和字节数
标准 http.ResponseWriter 接口不提供 Status 或 Size 字段,你得自己造一个 wrapper 类型,重写 WriteHeader 和 Write 方法。
关键逻辑:
-
WriteHeader(code):立即记录statusCode = code,并标记已写头 -
Write(b):若尚未写头,先调wrapped.WriteHeader(http.StatusOK),再记录statusCode = 200 - 所有
Write调用都应统计写入字节数(len(b)),别依赖io.MultiWriter—— 它可能绕过你的计数逻辑
示例结构体定义:
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
size int
}
func (w *loggingResponseWriter) WriteHeader(code int) {
w.statusCode = code
w.ResponseWriter.WriteHeader(code)
}
func (w *loggingResponseWriter) Write(b []byte) (int, error) {
if w.statusCode == 0 {
w.statusCode = http.StatusOK
}
n, err := w.ResponseWriter.Write(b)
w.size += n
return n, err
}
想记录请求体?先防 EOF,再防 OOM
直接 io.ReadAll(r.Body) 会导致后续 handler 读到空 body,典型报错:json: cannot unmarshal object into Go value of type xxx。
安全做法分三步:
- 开头就用
http.MaxBytesReader限制最大读取长度(如10 ),防止恶意大请求打爆内存 - 用
io.ReadAll读取原始 body 到[]byte,然后用bytes.NewReader(buf)构造新io.ReadCloser - 手动重设
r.Body,并同步更新r.ContentLength和r.Header.Set("Content-Length", strconv.Itoa(len(buf))),否则某些框架(如 Gin binding)会解析失败
注意:GET 请求的 r.Body 大概率为 nil 或空,务必先判 r.Method != "GET" 再操作。
脱敏不是简单字符串替换,得看上下文
把所有含 "password" 的字段替换成 "***" 是危险的——字段名可能是 pwd、auth_token、idCardNo,甚至嵌套在 JSON 深层。
实际该怎么做:
- 对
application/json请求,先json.Unmarshal成map[string]interface{},再递归遍历 key,匹配预设敏感键集合(支持正则,如^.*token$) - 只对
string类型的 value 脱敏,数字、布尔值保持原样 - query 参数同样需脱敏:用
r.URL.Query()解析后遍历,但别忘了同步更新r.URL.RawQuery,否则日志里看到的还是明文 - 敏感键列表建议从配置文件加载,避免硬编码
最易被忽略的一点:脱敏必须在 next.ServeHTTP 之前完成。一旦 handler 开始解析 body,你就没机会改了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











