slog默认不输出时间戳和文件行号是设计选择而非bug,因遵循“显式优于隐式”原则;需手动配置HandlerOptions{AddSource: true}启用源码位置,time字段虽自动添加但格式不可定制。

slog 默认不输出时间戳和文件行号,不是 bug,是设计选择;想加就得手动配 HandlerOptions。
为什么 slog.NewTextHandler 或 slog.NewJSONHandler 默认没时间戳和源码位置
因为 slog 坚持「显式优于隐式」:time 字段虽然默认写入,但 AddSource(含文件名、行号)必须显式开启,否则完全不出现;这不是遗漏,是避免默认开销影响高并发场景的性能。Text 和 JSON handler 都自动加 time 字段,但格式固定、不可定制——想改成 timestamp 或 ISO8601 精简版?只能自己实现 Handler 或用 ReplaceAttr 重命名。
-
AddSource: true会调用runtime.Caller,压测时可观测到微秒级延迟上升,生产环境建议关闭或仅 DEBUG 级别启用 - 日志里看到
source字段是???:0?说明 handler 没传&slog.HandlerOptions{AddSource: true},只设Level不够 - JSON 输出中
time是纳秒精度字符串,无法直接被 Logstash 的datefilter 解析为 timestamp——得靠采集层做转换,别指望 slog 自己适配
slog.With 和 slog.WithGroup 在 JSON 输出里的结构差异
With 是扁平追加,WithGroup 是嵌套建域;同一个 "user_id" 放在 group 里,JSON 就变成 {"user": {"id": 123}},而 With 是 {"user_id": 123}。后端解析器若只认一级 key,WithGroup 会导致字段丢失或解析失败。
- HTTP 请求上下文适合用
WithGroup("http"):后续所有Info/Error自动带http.method、http.status_code前缀,Text 输出显示为http.method=GET,方便 grep - 全局服务标识(如
"service"、"env")用With更稳妥:避免下游系统因不支持嵌套 JSON 而丢弃整个 log entry - 误用
WithGroup后发现 JSON 多了一层{"net": {"ip": "192.168.1.1"}},但监控平台只认ip字段——这时删掉WithGroup,改用With("ip", "192.168.1.1")
自定义 slog.Handler 最容易漏掉的三个方法
只实现 Handle 方法,Enabled、WithAttrs、WithGroup 全部不写,结果就是日志静默消失、属性丢失、group 失效——而且不报错,查起来像玄学问题。
立即学习“go语言免费学习笔记(深入)”;
-
Enabled必须判断level <= opts.Level,否则slog.Debug在 prod 环境仍会执行构造参数逻辑(哪怕最终不输出),造成 CPU 和内存浪费 -
WithAttrs必须返回新 Handler 实例,不能原地修改字段;否则多个 goroutine 并发调用With会 panic(Go map 写并发) -
WithGroup若返回原 handler,嵌套属性就进不了Handle的Record.Attrs——你传了slog.Group("db", slog.String("query", "SELECT...")),但 JSON 里根本看不到db.query
slog.Attr 构造函数比裸 key/value 安全得多
写 logger.Info("msg", "user_id", 123) 看似省事,但一旦 "user_id" 是变量名拼错、或值为 nil,slog 就默默输出 !BADKEY 字段,线上排查时你根本不知道哪个 key 挂了。
- 用
slog.Int("user_id", 123)、slog.String("ip", ip)、slog.Bool("success", ok),编译期就能捕获类型错误和未定义变量 - 配合
sloglintlinter:settings: sloglint: attr-only: true,CI 直接拒收裸 key/value 写法 -
slog.Any("error", err)是安全兜底,但别滥用——它会触发反射,性能比slog.String("error", err.Error())差 3–5 倍
最易被忽略的一点:slog 的 HandlerOptions.ReplaceAttr 能过滤、重命名、甚至删除 time 字段,但无法新增字段;想加 trace_id,必须靠 With 或中间件注入,而不是指望 ReplaceAttr 补全。


















