自定义 slog.Handler 必须实现 Enabled、WithAttrs、WithGroup 三个方法,仅实现 Handle 会导致日志静默丢失;slog.New() 第二个参数需为 slog.Handler 接口类型,os.Stdout 需经 slog.NewTextHandler 等包装。

slog 自定义 Handler 不是加个 Handle 方法就能用,漏掉 Enabled、WithAttrs 或 WithGroup 会导致日志静默丢失——而且不报错,只“没打出东西”。
为什么 slog.New() 传 os.Stdout 直接编译失败
因为 slog.New() 第二个参数必须是实现了 slog.Handler 接口的类型,而 os.Stdout 是 *os.File,它只实现了 io.Writer,和 slog.Handler 完全无关。
常见错误写法:slog.New(os.Stdout, nil) → 编译报错:cannot use os.Stdout (type *os.File) as type slog.Handler
- 正确做法:用
slog.NewTextHandler或slog.NewJSONHandler包一层,再传给slog.New() - 开发期可选
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}),方便定位问题 - 上线建议用
slog.NewJSONHandler(os.Stdout, nil),配合 Loki / ELK 等工具解析
自定义 Handler 必须实现的三个方法
只写 Handle(r slog.Record) error 是不够的。Logger 在调用 Info、With、WithGroup 时,会分别触发 Enabled、WithAttrs、WithGroup —— 没实现就等于“跳过”,字段丢掉、group 失效、日志级别过滤失灵。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
立即学习“go语言免费学习笔记(深入)”;
-
Enabled(context.Context, slog.Level) bool:控制是否跳过该条日志(比如线上关 DEBUG) -
WithAttrs([]slog.Attr) slog.Handler:把logger.With("user_id", 123)的属性挂到新 Handler 实例上 -
WithGroup(string) slog.Handler:支持logger.WithGroup("db").Info(...),生成嵌套结构(JSON 下是对象,Text 下是点号键名)
这三个方法返回的新 Handler 实例,必须能继承之前的所有配置(比如 opts.AddSource、时间格式偏好),否则 WithGroup("http").With("trace_id", "abc") 可能根本不出现在最终输出里。
AddSource 性能影响和 JSON 时间格式陷阱
AddSource: true 会让 handler 调用 runtime.Caller 获取文件+行号,实测在高并发写日志场景下,吞吐量可能下降 5–10%。这不是 bug,是设计取舍:显式开启,避免默认拖慢所有服务。
- 开发/测试环境可以开:
&slog.HandlerOptions{AddSource: true, Level: slog.LevelDebug} - 生产环境慎开,尤其对延迟敏感的服务;如需定位,优先靠 trace_id + 日志上下文补全
-
slog.NewJSONHandler默认输出 RFC3339 格式时间(如"2026-05-27T03:48:22.123Z"),不可定制——想改格式?只能自己实现Handler,重写Handle中 time 字段序列化逻辑 - 第三方 handler(如
github.com/samber/slog-multi)行为不一致,务必查清其HandlerOptions支持项,别假设它和标准库一样
最易被忽略的点:Handler 实例不是线程安全的,如果多个 goroutine 共享同一个自定义 Handler 实例且内部有状态(比如缓存、计数器、sync.Mutex 漏加),日志字段会错乱或 panic。要么加锁,要么确保每次 WithAttrs/WithGroup 返回的是新实例(无共享状态)。

















