日志脱敏必须在结构化阶段介入,使用slog.handler安全修改record.attrs和record.message,通过logvalue接口、递归清洗、分层脱敏及深度限制实现可控脱敏。

日志脱敏不是“加个正则就能搞定”的事,而是必须在结构化阶段就介入——等日志变成字符串再处理,已经晚了,且大概率破坏 JSON、漏嵌套字段、误杀合法词。
slog.Handler 是 Go 1.21+ 最轻量可靠的脱敏入口
它让你在日志写入前拿到 slog.Record,可安全修改 record.Attrs 和 record.Message,无需第三方依赖。
- 必须用
append([]slog.Attr{}, ...)构造新 slice 处理record.Attrs:原 slice 是只读的,直接改会引发并发 panic -
record.Message可能是拼接字符串(如fmt.Sprintf("token=%s", t)),需尝试json.Unmarshal后递归清洗,不能直接strings.ReplaceAll - 敏感 key 判定别用
==:用strings.Contains(strings.ToLower(key), "token")覆盖X-Auth-Token、access_token等变体 - error 类型要递归检查:
err.Unwrap()链里可能藏敏感 token,得遍历每层Error()返回值
结构体实现 LogValue 或 MarshalLogObject 才真正可控
靠 slog.Any("user", u) 打结构体?默认反射输出,不走任何脱敏逻辑。必须让类型自己决定怎么暴露值。
- 字段类型(如
type APIKey string)实现LogValue() slog.Value,返回slog.StringValue("[REDACTED]") - struct 实现
LogValue时,注意slog.Group或嵌套slog.Any会跳过外层,每层都得覆盖 - 用指针接收器:
func (u *User) LogValue() slog.Value,否则 nil 字段或嵌套时无法安全访问 - 非导出字段(小写首字母)不会被访问,脱敏无效;别在
LogValue里调用可能 panic 的操作(如未判空 map 取值)
HTTP 请求体、Header、Query 参数必须分层脱敏
只脱 body 不够,Authorization、Cookie、URL 中的 ?token=xxx 同样高危,且不能靠字符串替换。
- body 脱敏前先用
http.MaxBytesReader包装r.Body,防 OOM;读完后用bytes.NewReader重设,并同步更新r.ContentLength和r.Header.Set("Content-Length", ...) - query 用
r.URL.Query()获取 map,遍历 key 匹配敏感词后替换 value,再调r.URL.RawQuery = query.Encode()同步 - header 脱敏遍历
r.Header,key 判定用strings.Contains(strings.ToLower(hk), "auth"),覆盖Authorization、X-API-Key等 - 敏感 key 列表建议从配置加载,支持正则(如
^.*key$),避免硬编码;掩码统一用"***",不保留原始长度
最易被忽略的是嵌套深度和副作用:递归脱敏 map 或 struct 时没设 depth 限制,可能栈溢出;直接修改原 map 或 slice,会影响业务逻辑;还有就是 slog.Group 和 slog.Any 的穿透行为,会让你以为脱敏了,其实根本没走到逻辑里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











