日志脱敏必须在结构体实现zapcore.objectmarshaler接口时完成,分级输出则由levelenabler控制;二者独立,不可混用或通过hook统一处理。

日志脱敏和分级输出在 Zap 中是两件独立的事:脱敏必须发生在构造日志上下文时,分级则由 LevelEnabler 控制是否写入;可观测性不是加个库就自动具备的,它依赖结构化字段、上下文透传和明确的黄金信号设计。
结构体字段脱敏必须实现 MarshalLogObject
直接传结构体给 zap.Any 或 zap.Object,敏感字段(如 Password、Token)会原样输出——Zap 不扫描字段名,也不识别语义。唯一可控路径是让结构体实现 zapcore.ObjectMarshaler 接口。
- 常见错误:在
MarshalLogObject里调用未判空的u.Credentials.Token,导致日志 panic - 字段首字母小写(如
password string)会被反射跳过,必须导出(Password string) - 不要在该方法里调用其他可能 panic 的逻辑,否则整条日志失败
- 字段顺序不保证,别依赖输出顺序;若敏感字段少,比写全量
map更干净
分级输出不能靠 if 判断,得用 LevelEnabler
logger.Warn("msg") 不一定输出,取决于 logger 初始化时绑定的 LevelEnabler。设为 zap.LevelError,则 Warn 被直接丢弃,连格式化都不做。
- 生产环境常用
zap.LevelInfo,开发调试可设为zap.LevelDebug - 动态改级别要用
zap.AtomicLevel,调用atomicLevel.SetLevel(zap.DebugLevel)立即生效 - 别直接赋值全局 level 变量,否则并发下更新丢失
-
DPanic是 debug 模式下的 panic,非 debug 下降级为Error
按级别拆分文件必须手动路由,不能靠 Zap 内置功能
Zap 默认不支持 error.log / info.log 自动分流,因为它的 core 只接受单个 WriteSyncer。要实现分流,得手动构造多个 core,每个绑定独立文件句柄和 LevelEnablerFunc。
- 每个级别必须用独立的
*os.File或lumberjack.Logger实例,共用句柄会导致混写或 panic - 用
zapcore.NewTee(cores)组合多个 core,不用NewMultiCore(不保证写入顺序) - 务必禁用
zap.AddSampler(),否则采样与级别判断脱节,日志错位 -
lumberjack.MaxAge是“文件最老存活天数”,不是“按天归档”;没日志写的那天不会生成新文件
可观测性落地的关键是字段设计,不是日志库选型
结构化日志只是基础,真正决定可观测性效果的是字段命名、上下文注入方式和黄金指标定义。比如 req_id 必须从 HTTP 头或中间件生成并透传到所有子日志,duration_ms 必须统一单位且带标签区分 DB/HTTP/Cache 耗时。
- 避免拼接字符串日志,所有字段名必须明确(如
user_id、status_code) - 高频循环内禁止打
INFO日志,否则日志爆炸、磁盘撑爆 - ERROR 日志必须含堆栈 + 可操作线索(如失败 SQL、下游返回码),不能只写 “failed”
- 敏感字段脱敏不是可选项——哪怕只在 DEBUG 级别输出,也必须脱敏
最容易被忽略的点是:脱敏和分级看似都发生在日志调用链上,但它们的控制点完全不同——一个在数据载体(结构体)上,一个在日志实例(logger)上;混用或试图用 Hook 统一处理,只会让两者都失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











