必须脱敏的字段包括password、id_card、phone、email、bank_account等明确敏感信息;应于日志构造阶段(而非输出阶段)通过zap.field或zerolog.interface按需脱敏,避免正则误杀和结构破坏。

日志中哪些字段必须脱敏?先明确敏感字段清单
不是所有字段都要脱敏,盲目替换反而破坏日志可读性。真正需要处理的是明确属于敏感信息的字段,比如 password、id_card、phone、email、bank_account 等。Go 标准库或主流日志库(如 log、zap、zerolog)本身不提供字段级脱敏能力,得靠你在结构化日志写入前主动过滤或替换。
常见错误是直接对日志字符串做正则替换——这会误杀正常文本(比如日志里出现 “password123” 作为普通单词),也绕过了结构化上下文,不可靠。
- 优先在构造日志上下文(
ctx)或日志对象(zap.Fields、zerolog.Dict)时就完成脱敏 - 避免在
fmt.Sprintf或log.Printf的字符串拼接阶段处理 - 敏感字段名建议统一约定(如加
_sensitive后缀),便于自动化识别
用 zap.Field 实现字段级脱敏(推荐方案)
zap 是 Go 生态最常用的高性能结构化日志库,它的 Field 类型支持自定义编码逻辑。你可以封装一个 RedactString 函数,返回带脱敏逻辑的 zap.Field:
func RedactString(key string, value string) zap.Field {
if value == "" {
return zap.String(key, value)
}
// 手机号:138****1234
if len(value) == 11 && regexp.MustCompile(`^1[3-9]\d{9}$`).MatchString(value) {
return zap.String(key, value[:3]+"****"+value[7:])
}
// 身份证:110***********1234(只保留前3后4)
if len(value) == 18 && regexp.MustCompile(`^\d{17}[\dxX]$`).MatchString(value) {
return zap.String(key, value[:3]+"***********"+value[14:])
}
return zap.String(key, "***")
}
使用时直接传入:logger.Info("user login", RedactString("phone", user.Phone), RedactString("id_card", user.IDCard))。这种方式不侵入业务逻辑,也不影响日志性能(脱敏只在必要字段上触发)。
- 不要把脱敏逻辑塞进
zap.Stringer接口实现里——那会导致所有fmt.String()调用都被污染 - 若字段类型是
int64(如身份证号误存为数字),先转成字符串再判断,否则长度校验失效 - 生产环境建议把正则编译提到包变量,避免每次调用都重复编译
zerolog 中如何安全注入脱敏逻辑?
zerolog 默认不支持自定义字段编码器,但可通过 zerolog.Interface + 匿名结构体绕过限制。关键点是:不能直接用 zerolog.Str,而要让字段值实现 MarshalZerologObject 方法:
type RedactedString struct{ V string }
func (r RedactedString) MarshalZerologObject(e *zerolog.Event) {
if r.V == "" {
e.Str("", "")
return
}
switch {
case isPhone(r.V):
e.Str("", r.V[:3]+"****"+r.V[7:])
case isIDCard(r.V):
e.Str("", r.V[:3]+"***********"+r.V[14:])
default:
e.Str("", "***")
}
}
// 使用:logger.Info().Fields(map[string]interface{}{"phone": RedactedString{V: u.Phone}}).Msg("login")
注意 zerolog 的 Fields 方法接收 map[string]interface{},所以必须包装成结构体传入。如果直接传 string,脱敏逻辑不会触发。
- 别用
zerolog.Arr()处理敏感数组(如 token 列表)——需逐项包装为RedactedString -
zerolog默认输出 JSON,确保脱敏后的值仍是合法 JSON 字符串(不能含控制字符) - 该方案比
zap多一层反射开销,QPS 极高场景建议压测对比
全局拦截 vs. 按需脱敏:别掉进“一刀切”陷阱
有人想用 log.SetOutput 或 zap.Core 拦截全部日志输出再正则清洗——这在实际项目中基本不可行。原因很实在:
- JSON 日志一旦序列化成字节流,字段边界丢失,无法精准定位哪个
"phone"是敏感值 - HTTP 请求体、SQL 日志等非结构化内容,正则容易漏匹配或过度匹配
- 日志库底层 buffer 可能复用,字符串替换可能引发内存越界或并发 panic
- 脱敏后无法还原原始值,审计溯源困难(应保留原始值到加密存储,而非日志)
真正可靠的路径只有一条:在业务代码决定「记录什么」的那一刻,就完成脱敏。不是日志输出时,而是日志构造时。
复杂点在于不同模块对“敏感”的定义可能不一致,比如支付模块认为订单号敏感,而用户模块不认为。所以脱敏逻辑最好下沉到领域模型层,由结构体方法统一暴露 Redacted(),而不是散落在各个 logger.Info 调用里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











