应将原始请求体读入[]byte后用bytes.NewReader重设r.Body,并同步更新ContentLength和Header;开头需用http.MaxBytesReader限制大小以防OOM。

中间件里怎么拿到原始请求体又不破坏后续读取
Go 的 http.Request.Body 是单次读取的,直接 io.ReadAll(r.Body) 会导致后续 handler 拿不到数据。必须用 io.TeeReader 或缓冲重写 Body。
推荐做法:把原始 body 读进 []byte,再用 bytes.NewReader 包一层塞回 r.Body,同时保留副本用于日志。注意别漏掉 r.ContentLength 和 r.Header.Set("Content-Length", ...),否则某些 handler(比如 gin 的 binding)会出错。
- 务必在中间件开头就做
r.Body = http.MaxBytesReader(nil, r.Body, 10 防止大请求耗尽内存 - 若请求体是
application/x-www-form-urlencoded或multipart/form-data,优先用r.ParseForm()后取r.PostForm,避免手动解析失败 - 不要对
GET请求尝试读 body —— 大多数情况下它为空,但有些客户端会塞数据,需按需判断r.Method == "GET"跳过
哪些字段必须脱敏、怎么判断是否敏感
不能靠关键词黑名单硬匹配(比如“password”),因为字段名可能变形(pwd、auth_token、id_card)。实际应结合 HTTP 方法、路径、Content-Type 和结构化解析结果动态决策。
例如:对 POST /api/v1/users 且 Content-Type: application/json 的请求,先 json.Unmarshal 成 map[string]interface{},再递归检查 key 是否在预设敏感键集合中(["password", "token", "idCard", "phone"]),值统一替换成 "***"。注意保持原始类型(字符串才脱敏,数字/bool 不动)。
立即学习“go语言免费学习笔记(深入)”;
- 敏感键列表建议从配置文件加载,支持正则(如
^.*key$),避免硬编码 - 对 query 参数也需脱敏,用
r.URL.Query()获取后遍历,但注意r.URL.RawQuery要同步更新,否则日志里看到的是未脱敏的原始串 - 不要脱敏整个 JSON 字符串 —— 容易破坏结构或误杀,只处理 value,且仅限字符串类型
gin 框架里如何注入日志中间件并控制输出粒度
gin 的 gin.HandlerFunc 签名固定,中间件必须返回 func(*gin.Context)。关键在于:日志写入时机要选在 c.Next() 之后(拿到响应状态码和耗时),但脱敏逻辑必须在 c.Next() 之前(否则请求体已被消费)。
典型结构:
func RequestLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// ① 读 body + 脱敏(仅 POST/PUT/PATCH)
// ② 记录开始时间、remote IP、method、path、query
c.Next()
// ③ 记录 status code、latency、response size
// ④ 写日志(含脱敏后的请求快照)
}
}
- 用
c.Set("log_data", logMap)在中间件间传递脱敏后数据,避免重复解析 - 通过
c.GetBool("skip_log")支持按路由跳过(比如健康检查接口),在路由注册时加c.Set("skip_log", true) - 日志输出建议用结构化格式(JSON),字段包括:
ts、method、path、status、latency_ms、req_id、client_ip、req_body(脱敏后)、resp_body(谨慎开启)
响应体要不要记录、记录到什么程度
响应体记录成本高、风险大(可能含用户数据、token、分页游标等),生产环境强烈建议默认关闭。真有需要,也只对特定路径(如 /api/debug/)且状态码为 2xx 的响应做浅层记录。
如果启用,同样面临 body 只能读一次的问题:gin 的 c.Writer 是 ResponseWriter 接口,需用自定义 responseWriter 包装,拦截 Write() 和 WriteHeader()。注意响应头里的 Content-Encoding: gzip 会导致正文是压缩流,此时不应解压(性能损耗+风险),直接跳过记录。
- 永远不记录
Set-Cookie、Authorization响应头 - 响应体长度超过 1KB 时截断,末尾加
"... (truncated)" - 若业务返回 error 结构(如
{"code":40001,"msg":"xxx"}),可提取code和msg字段记入日志,比全量 body 更安全有效
lumberjack.Logger 做轮转,并监控日志目录 inodes 使用率。


















