日志中禁止直接打印password或token字段,必须在构造日志前主动脱敏;推荐对结构体实现zapcore.ObjectMarshaler接口或使用SanitizeMap递归处理敏感键值,HTTP请求的query、header和body也需分别脱敏。

日志里直接打 password 或 token 字段?立刻停手
Go 默认的 log、fmt.Printf 或结构化日志库(如 zap、zerolog)不会自动识别敏感字段。你传进去什么,它就原样打出来——哪怕是个明文 api_key: "sk_live_abc123"。这不是配置问题,是设计使然:日志库不负责语义判断。
真正能起作用的,只有你在写日志前主动处理数据。常见错误是试图用正则全局替换日志字符串,结果把 URL 里的 password=xxx 和正常字段名 password_reset_token 一起干掉,或者漏掉嵌套结构里的值。
实操建议:
- 永远在构造日志上下文前做脱敏,而不是对最终字符串“补救”
- 对
map[string]interface{}或 struct 做递归遍历,只替换已知敏感键对应的值(如"password"、"secret"、"token"),不碰键名本身 - 避免用
json.Marshal后再正则替换——性能差,且无法区分键和值
用 zap 的 Marshaler 接口安全透出结构体
如果你用 zap 记录结构体(比如 user := User{Password: "123", Email: "a@b.com"}),直接 logger.Info("user login", zap.Any("user", user)) 会把 Password 明文打出来。正确做法是让结构体实现 zapcore.ObjectMarshaler 接口。
立即学习“go语言免费学习笔记(深入)”;
示例:
func (u User) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("email", u.Email)
enc.AddString("password", "[redacted]") // 固定脱敏值
return nil
}
这样无论你用 zap.Any 还是 zap.Object,都会走这个逻辑。注意:字段顺序不保证,别依赖;如果结构体字段多且敏感项少,比写 Map() 更干净。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
容易踩的坑:
- 忘记导出字段(首字母小写)——
MarshalLogObject不会访问非导出字段 - 在
MarshalLogObject里调用其他可能 panic 的方法,导致日志整个失败 - 误用
zap.Stringer(只影响单个字段 toString),它不适用于结构体整体控制
zerolog 怎么让 password 字段自动变 [REDACTED]
zerolog 没有类似 Marshaler 的接口,但支持通过 zerolog.InterfaceHook 在写入前拦截并修改 interface{}。最稳的方式是封装一个带脱敏逻辑的 Event 包装器。
实操建议:
- 写一个函数
SanitizeMap(m map[string]interface{}) map[string]interface{},递归检查 key 是否在敏感列表("pwd","passwd","auth_token"等),是则把 value 替换为"[REDACTED]" - 记录日志时,不用
e.Interface("req", req),而是e.Interface("req", SanitizeMap(req)) - 如果用
zerolog.Dict()构造嵌套对象,同样需先过一遍SanitizeMap,它不自动递归处理子 map
注意:zerolog 默认开启 Interface 的 JSON 序列化,所以你的 SanitizeMap 必须返回可序列化的值(不能是函数或 channel)。
HTTP 请求体里的敏感字段怎么不进日志?
很多服务会把 io.ReadCloser 直接塞进日志(比如 zap.Binary("body", bodyBytes)),结果整段 JSON 都暴露了。真正的解法不是“截断 body”,而是从源头控制解析逻辑。
推荐做法:
- 用
json.Decoder解析请求体到 struct 后,立刻用脱敏后的 struct 打日志,而不是记原始[]byte - 如果必须记原始 body(审计需求),先用
bytes.ReplaceAll替换已知模式(如"password":"[^"]*"),但仅限于 UTF-8 纯文本且无转义场景;更稳妥的是用json.RawMessage解析后 selectively 取字段 - 别在中间件里对
http.Request.Body做一次读取就完事——它只能读一次,后续业务逻辑会拿不到 body
最容易被忽略的一点:query string 里的 ?token=xxx 和 header 里的 Authorization: Bearer xxx 同样要脱敏,它们不在 request body 里,得单独处理。

















