go http日志中间件需用http.handler包装,缓存r.body防下游读空,自定义responsewriter捕获status/size,结构化记录method/path/ip/耗时等字段并脱敏敏感信息。

Go HTTP 中间件里怎么加请求日志
直接用 http.Handler 包一层就行,核心是读取 r.Body 前先用 io.TeeReader 或 bytes.Buffer 缓存请求体,否则后续 handler 读不到内容。别直接 log.Printf 就完事——没记录方法、路径、状态码、耗时,等于白打。
常见错误:在中间件里调用 r.Body.Read() 后没重置 r.Body,导致下游 handler 报 http: read on closed response body 或收不到 JSON 数据。
- 用
io.MultiReader+bytes.NewReader(buf.Bytes())重建r.Body - 记录字段至少包含:
r.Method、r.URL.Path、r.RemoteAddr、开始时间、结束时间、w.Header().Get("Content-Type") - 敏感字段(如
Authorization、password)必须脱敏,不能原样打到日志里
为什么不能直接用 log.Println 写中间件日志
log.Println 是同步阻塞的,高并发下会拖慢整个请求链路;它也不支持结构化输出,查日志得靠 grep,没法对接 ELK 或 Loki。生产环境必须用 zerolog、zap 这类结构化日志库。
示例中如果用 zerolog,要提前配置 zerolog.TimeFieldFormat = zerolog.TimeFormatUnix,避免默认 RFC3339 格式写入大量字符串开销。
- 避免在中间件里初始化新 logger 实例,应复用全局
*zerolog.Logger - 用
.With().Str("path", r.URL.Path).Int("status", statusCode)打结构字段,别拼字符串 - 日志级别按场景选:
Info()记常规请求,Warn()记 4xx,Error()记 5xx 和 panic
如何安全地记录 request body 和 response body
记录请求体容易,但 response body 更麻烦——http.ResponseWriter 是接口,没暴露底层 buffer。必须用自定义 wrapper 类型实现 ResponseWriter,劫持 Write()、WriteHeader()、Flush() 方法,把数据先写进 bytes.Buffer,再透传给原始 writer。
注意:如果 handler 调用了 hijack(比如 WebSocket 升级),wrapper 会失效,此时应跳过 body 记录,只记 header 级信息。
- 响应体大小要限制,比如只记录前 1024 字节,防 OOM,用
io.LimitReader(buf, 1024) - 判断
r.ContentLength > 0 && r.ContentLength 再读请求体,超长就只记长度不记内容 - JSON 类型才尝试
json.Compact()格式化,其他类型(如image/jpeg)直接跳过 body 日志
gin / echo 框架里怎么插日志中间件
框架封装了 http.Handler,但中间件签名不同:gin 用 func(*gin.Context),echo 用 echo.MiddlewareFunc。不能直接套标准 net/http 中间件,得适配。
gin 示例里别用 c.Request.Body = ioutil.NopCloser(...),go 1.16+ 已弃用 ioutil,改用 io.NopCloser;echo 则要注意 c.Response().Before(func() {}) 不可靠,应在 c.Response().Writer 被替换前完成包装。
- gin:用
c.Request.Body缓存后,调用c.Next(),再从c.Writer取响应数据 - echo:实现
echo.HTTPErrorHandler时,要确保 panic 日志也走同一套结构化通道 - 所有框架中间件都需加
defer捕获 panic,并记录堆栈,否则 500 错误可能无声丢失
真正难的不是写日志,而是控制日志粒度:哪些路径该记全量 body,哪些只记 header;什么时候该关掉响应体记录;怎么让日志采样率可动态调整。这些逻辑一旦硬编码进中间件,后面就很难改。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











