必须在日志写入前完成脱敏,Go标准库及zap、zerolog、slog均不支持自动识别敏感语义,需通过结构体实现MarshalLogObject或LogValue接口、自定义Handler等方式显式控制敏感字段输出,避免字符串替换等破坏结构化语义的错误做法。

日志里直接打印 password、token、api_key 字段?这不是配置漏了,是根本没做脱敏——Go 的日志库(log、zap、zerolog、slog)压根不会自动识别敏感语义,你传什么它就打什么。
zap.Any 为啥还打出明文 password?
因为 zap.Any 默认对结构体走反射序列化,不调用任何脱敏逻辑。哪怕字段名是 Password,只要没显式干预,值就原样透出。
- 正确做法:让结构体实现
zapcore.ObjectMarshaler接口,只在MarshalLogObject里手动控制字段输出 - 必须导出字段(首字母大写),否则
enc.AddString拿不到值 - 别在
MarshalLogObject里调用可能 panic 的方法(如未判空的 map 访问),否则整条日志会静默丢弃 - 如果结构体嵌套深、敏感字段少,比写全量
Map()更轻量且不易漏
slog 日志中 password 总是泄露?检查是否实现了 LogValue
slog(Go 1.21+)绕过 fmt.Stringer 和 fmt.Formatter,直接取值。光给字段加 String() 方法没用。
- 敏感字段类型(如
type APIKey string)必须实现LogValue() slog.Value - 返回
slog.StringValue("[REDACTED]"),不是普通字符串 - 如果字段是 struct 内嵌的,得让整个 struct 实现
LogValue,不能只靠字段类型 - 注意:
slog.Group或嵌套slog.Any会跳过外层LogValue,需确保每层都覆盖
用正则替换日志字符串?90% 场景下会翻车
全局 strings.ReplaceAll(logStr, "password=", "password=[REDACTED]") 看似简单,实际踩坑密集:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 把 URL 中的
password=xxx和正常字段名password_reset_token一起干掉 - JSON 字符串里引号、转义混乱,导致日志格式损坏(比如
"{\"password\":\"123\"}"被错切) - 无法处理嵌套结构:
user: { profile: { token: "abc" } }完全漏脱 - 性能差:每次日志都要跑完整字符串扫描,高并发下 CPU 毛刺明显
map[string]interface{} 怎么安全递归脱敏?
这是最通用、也最容易失控的场景。关键不是“怎么遍历”,而是“怎么判断该不该脱”:
- 敏感 key 列表要明确:只匹配
"password"、"secret"、"token"、"auth"等,不模糊匹配(避免误杀"password_hint") - 只替换 value,不动 key 名;value 是 map 或 slice 时递归进一层,但 depth > 5 就停,防栈溢出
- 原始 map 不可变:返回新 map,避免副作用影响业务逻辑
- 别用
json.Marshal+ 正则:序列化开销大,且无法区分键和值中的相同字符串
真正难的不是写一个脱敏函数,而是在 HTTP handler、gRPC interceptor、DB query hook、panic recover 等不同上下文中,保持脱敏逻辑一致且不漏——这些地方的数据来源、生命周期、错误容忍度全都不一样,硬塞同一套递归逻辑反而更容易崩。

















