Gin 中无法通过单一中间件自动脱敏请求和响应日志,必须分离处理:请求体脱敏在 c.Next() 前完成并重置 r.Body,响应体脱敏需包装 c.Writer 在 c.Next() 后操作,且两者逻辑完全隔离。

直接说结论:Gin 里没法靠中间件“自动”脱敏请求日志,必须拆开做——请求体脱敏在 c.Next() 前,响应体脱敏在 c.Next() 后但需包装 c.Writer,且两者逻辑完全隔离。
为什么不能只写一个中间件统一脱敏
因为请求体和响应体的读取时机、方式、生命周期完全不同:
- 请求体
r.Body是单次可读流,io.ReadAll(r.Body)后后续 handler(比如c.ShouldBindJSON())就拿不到数据了,必须重设r.Body = bytes.NewReader(buf)并同步更新r.ContentLength和Content-Lengthheader - 响应体默认不缓存,
c.Writer一旦调用WriteHeader()或首次Write(),数据就直发 TCP,中间件里根本拿不到原始 JSON 字节 - 中间件无结构体语义,
password字段可能叫pwd、auth_token、userSecret,仅靠字符串匹配会误杀或漏杀
请求体脱敏必须在 c.Next() 前完成
否则绑定逻辑已消费 body,你再读就是空的。关键点不是“能不能读”,而是“读完怎么还回去”:
- 开头加
r.Body = http.MaxBytesReader(c.Writer, r.Body, 1024*1024)防 OOM,上限按业务定(如 1MB) - 跳过
GET请求:if c.Request.Method == "GET" { goto skipBody },避免读空 body 报错 - 对
application/json:用json.Unmarshal解析成map[string]interface{},递归遍历 key,命中敏感键(如"password"、"token"、"idCard")且 value 类型为string时替换为"***" - 对
application/x-www-form-urlencoded或multipart/form-data:优先调c.Request.ParseForm(),再遍历c.Request.PostForm,别手动解析 - 脱敏后要同步更新
c.Request.URL.RawQuery(query 参数)和c.Request.Header(如Authorization),否则日志里看到的是未脱敏原始值
响应体脱敏必须包装 c.Writer
不能等 c.Next() 结束再去读 c.Writer——它没暴露缓冲区。正确做法是提前替换:
- 定义结构体实现
http.ResponseWriter接口,内嵌原c.Writer,并持有一个bytes.Buffer - 重写
Write([]byte):把数据先写进 buffer,不直接透传;重写WriteHeader(int):缓存状态码 - 在
c.Next()后检查c.Request.Header.Get("Content-Type")是否含"application/json",是则从 buffer 取出字节,json.Unmarshal→ 脱敏 →json.Marshal→ 再调原Write() - 非 JSON 类型(如
text/html、image/png)直接透传,避免破坏静态资源或文件下载 - 注意:脱敏后的响应体长度可能变化,需重置
Content-Lengthheader,否则客户端收不到完整数据
敏感字段判定不能硬编码关键词
字段名变形太常见(pwd、api_key、user_secret),靠 strings.Contains(key, "pass") 必踩坑:
- 敏感键列表从配置文件加载,支持正则,例如
^.*(?:pwd|pass|token|key|secret|card|phone|id).* - 结合路径和 method 做白名单控制:只对
POST /api/v1/login、PUT /api/v1/profile等接口脱敏,GET /api/v1/users不处理响应体中的phone - query 参数脱敏也需路径判断:
/api/v1/orders?token=xxx要脱,但/health?check=db不脱 - 结构体字段脱敏不要指望中间件——该用
json:",omitempty"+ 自定义MarshalJSON方法,或 zap 的MarshalLogObject接口
真正难的不是代码怎么写,而是脱敏策略得覆盖所有出口:HTTP 日志、zap 结构化日志、gRPC 响应、甚至内存里的临时 map —— 中间件只管得了 HTTP 流,其他地方得各扫门前雪。


















