要输出 JSON 日志,必须替换底层 Logger 实现而非仅改 Helper 调用;需手动构建支持 Valuer 和 Filter 的 zap.JSON Logger,并注意脱敏应统一通过 Filter 实现。

直接用 log.NewHelper 不行,必须先换底层 Logger 实现
Kratos 的 Helper 本身不决定格式,它只是把 keyvals(比如 "msg", "ok", "uid", 123)原样传给底层 Logger.Log()。所以想输出 JSON,关键不是改 Helper 调用方式,而是让 Logger 的实现把接收到的键值对序列化成 JSON 字符串再写出去。
常见错误是以为调用 h.Info("msg", "uid", 123) 就能自动变 JSON —— 实际上默认的 log.NewStdLogger(os.Stdout) 输出的是纯文本,字段间用空格分隔,根本不是 JSON。
- 必须自己构造一个返回 JSON 字符串的
Logger实现,或基于 zap/logrus 等库封装 -
Helper可以完全不变,业务代码无需修改 - 如果项目已用
log.With()绑定了Valuer(如trace_id),这些字段会自动进入 JSON,无需重复加
用 zap 构建 JSON Logger 时别踩 NewProduction() 这个坑
zap.NewProduction() 看似“生产就绪”,但它输出的是预设结构的 JSON(固定字段名如 "level"、"ts"、"caller"),且不支持动态注入 Valuer 提供的全局字段(比如你注册的 service_name 或 instance_id)。真要灵活控制 JSON 结构,得手动组装 zapcore.Core:
- 用
zapcore.NewJSONEncoder(zapcore.EncoderConfig{...})定制字段名和时间格式 - 把
zapcore.AddSync(os.Stdout)作为WriteSyncer传入zapcore.NewCore() - 最后用
zap.New(core)得到*zap.Logger,再通过log.NewZapLogger()转成 Kratos 的log.Logger接口 - 注意:不要漏掉
defer logger.Sync(),否则日志可能丢失
log.With() 注入的 Valuer 字段在 JSON 中是否生效,取决于你用的 Logger 实现
Kratos 的 log.With(logger, "trace_id", tracing.TraceID()) 是把 Valuer 函数挂载到 logger 链上,但最终是否被序列化进 JSON,要看你的 Logger.Log() 方法里有没有显式调用这些 Valuer 并把结果塞进 keyvals。
- zap 封装的
log.NewZapLogger()已内置支持,只要你在log.With()里注册了Valuer,它们就会出现在 JSON 输出中 - 如果你手写了一个
Logger实现,必须在Log()方法开头手动遍历所有Valuer,执行并追加到keyvals切片末尾,否则这些字段就丢了 - 常见遗漏:
log.DefaultTimestamp和log.DefaultCaller也是Valuer,不显式挂载就不会有"ts"或"caller"字段
敏感字段脱敏必须用 Filter,别在业务层 Infow("password", "***")
业务代码里手动把 "password" 替换成 "***" 是不可靠的:漏写一次、拼错 key 名、或者日志里带嵌套结构(如 "user", User{Password: "123"}),脱敏就失效。Kratos 的 log.NewFilter() 在 Log() 调用末尾介入,能统一处理:
- 用
log.FilterKey("password", "auth_token", "id_card")匹配 key 名并替换 value - 用
log.FilterValueRegexp(regexp.MustCompile(`\d{17}[\dXx]`))匹配身份证号 pattern - 多个
Filter可链式组合,但顺序重要:比如先FilterKey再FilterValueRegexp,避免脱敏后又触发正则匹配 - 注意:Filter 对
keyvals做的是原地修改,不能新增字段,只能改或删
真正难的是字段语义识别 —— 比如 "data" 里可能嵌套敏感信息,这时光靠 key 名匹配不够,得配合自定义 Filter 解析 JSON 字符串再递归脱敏,这部分逻辑容易被忽略,也最难测试覆盖。











