Go slog.NewJSONHandler 怎样输出结构化 JSON 日志
来源:17golang原创
时间:2026-10-29 09:43:16 440浏览 收藏
如果 Go 服务的日志要交给采集系统按字段检索,关键不是把字符串拼成“看起来像 JSON”,而是让日志记录由标准库统一编码。slog.NewJSONHandler 会把每次日志调用写成一行 JSON 对象,再交给指定的 io.Writer。这样 level、msg 和业务属性都能保持字段边界。
官方文档:https://pkg.go.dev/log/slog
小型服务可以从os.Stdout加默认选项开始;进入生产采集后,再按日志量、来源定位和敏感字段要求配置Level、AddSource与ReplaceAttr。
先把 JSON 日志的输入与输出边界定下来
NewJSONHandler(w, opts) 接收一个输出目标和可选配置,返回实现了 slog.Handler 的 JSON 处理器。opts 传 nil 时使用默认选项;处理器负责把记录编码成逐行 JSON,而不是让业务代码自己拼接引号、逗号和转义符。
下面的初始化把应用日志绑定到标准输出。标准输出只是一个 Writer 选择,容器运行时、日志代理或本地进程可以在边界外继续接收它:
package main
import (
"log/slog"
"os"
)
func newLogger() *slog.Logger {
// nil 使用默认 HandlerOptions,适合先建立最小 JSON 输出边界。
handler := slog.NewJSONHandler(os.Stdout, nil)
return slog.New(handler)
}
func main() {
logger := newLogger()
// 键值属性会成为 JSON 字段,调用方不需要拼接 JSON 字符串。
logger.Info("请求完成", "request_id", "req-42", "status", 200)
}
架构上可以把它拆成三层:业务调用方产生 Record,JSONHandler 负责编码,io.Writer 负责承接结果。这样将来把输出从标准输出换成文件或自定义 Writer 时,业务日志调用不必跟着改。

按采集负载配置 HandlerOptions
默认配置能输出 JSON,但生产环境通常需要先做三项决策。Level 决定最低记录级别;低于它的记录会被处理器忽略。AddSource 会增加日志调用位置,适合排查问题但会带来额外字段和定位开销。ReplaceAttr 则可以重命名、改写或丢弃属性,包括内置的 time、level、source 和 msg。
package main
import (
"log/slog"
"os"
"strings"
)
func newProductionLogger() *slog.Logger {
options := &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true,
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
// 只示意脱敏边界:不要把令牌、密码等原始值写入日志。
if strings.EqualFold(attr.Key, "token") || strings.EqualFold(attr.Key, "password") {
return slog.String(attr.Key, "[REDACTED]")
}
return attr
},
}
// Writer 决定日志交给哪里,选项决定写出哪些字段。
return slog.New(slog.NewJSONHandler(os.Stdout, options))
}
脱敏函数要按你的字段命名规范扩展,并考虑分组属性;不能只依赖“调用方自觉不打印”。如果开启 AddSource,先确认采集端允许并索引该字段;如果日志量很大,可以让 LevelVar 参与动态调节,而不是把所有调试信息长期写入生产出口。

把结构化字段设计成稳定契约
JSON 只是编码格式,真正方便检索的是稳定的字段名和类型。建议把 request_id、service、operation、status 这类跨模块都会使用的字段固定下来;数值就保持数值,不要一会儿写成 200、一会儿写成 "200"。
同时要区分三类内容:msg 描述人类可读的事件,业务属性承担机器检索,错误对象只记录必要的错误信息。不要把一整段已经序列化的 JSON 再作为字符串传入,否则采集端看到的会是嵌套转义文本,失去字段级查询价值。
| 设计项 | 推荐做法 | 常见问题 |
|---|---|---|
| 事件名称 | 短而稳定,放在 msg | 把动态参数拼进长句 |
| 业务字段 | 使用固定键名和稳定类型 | 同一字段在字符串和数字间切换 |
| 敏感信息 | 在 HandlerOptions 统一改写或删除 | 只在少数调用点手工脱敏 |
| 错误信息 | 保留可定位的摘要和分类 | 直接输出凭据、请求体或完整隐私数据 |
根据运行场景选择落地方案
在容器或进程管理环境里,标准输出通常是最简单的出口:应用只负责写 JSON,采集层负责转发、索引和保留策略。写本地文件时,要额外面对文件轮转、磁盘容量和进程权限;把 Writer 换成自定义实现时,则要明确并发安全、短写入和错误处理契约。
可以按下面的决策收口:
- 服务交给平台采集:优先
os.Stdout,保持单条日志一行,避免混入非 JSON 文本。 - 需要源码定位:开启
AddSource,但把它当成排障字段,不要让业务查询依赖路径字符串。 - 日志字段受合规约束:集中使用
ReplaceAttr做删减或替换,并把新增敏感字段纳入评审清单。 - 日志量不可控:先用级别和采集端过滤控制体量;不要为了“完整”永久保留所有调试日志。
落地前至少确认输出 Writer、最低级别、字段命名、脱敏策略和采集端的单行约束。这样 slog.NewJSONHandler 才是一个清晰的日志架构边界,而不是把格式转换责任散落在每个业务函数里。
常见问题
NewJSONHandler 会输出一整个 JSON 数组吗?
不会。它按日志记录逐行写出 JSON 对象,采集端应按行读取,而不是等待一个完整数组结束。
为什么已经使用 JSONHandler,还要固定字段类型?
JSONHandler 只负责编码;如果同一个键在不同事件里使用不同类型,检索、聚合和告警规则仍然会变得不稳定。
AddSource 要不要一直开启?
它适合需要源码位置的排障场景。是否长期开启应结合日志量、采集字段和定位收益决定,不要把它当成业务字段的替代品。
-
213 收藏
-
433 收藏
-
380 收藏
-
475 收藏
-
280 收藏
-
316 收藏
-
375 收藏
-
138 收藏
-
452 收藏
-
272 收藏
-
284 收藏
-
413 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习