直接用 slog.New 输出空日志是因为默认 Handler 在无 Attr 或空消息时静默丢弃;需显式传 io.Writer、设 Level 和 AddSource 控制 caller,With 实现扁平上下文,WithGroup 生成嵌套结构,对接 Loki/Datadog 需自定义 Handler 并处理 timestamp 与 labels。

为什么直接用 slog.New 会输出空日志?
因为默认的 slog.Handler(比如 slog.NewTextHandler 或 slog.NewJSONHandler)只在有 Attr 或非空消息时才真正写入;如果只调用 slog.Info("hello") 且没配 Level 或没设 ReplaceAttr,可能被静默丢弃或输出为空行。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 始终显式传入一个非 nil 的
io.Writer,例如os.Stderr,避免用nil导致 handler 退化为 noop - 启用调试级日志看底层行为:
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))) - 注意:Go 1.21+ 默认 level 是
LevelInfo,但若环境变量GODEBUG=slog=1开启,会额外打印 handler 内部决策过程
如何让 slog 输出带文件名和行号?
slog 不像 logrus 那样默认带 caller,必须手动开启。关键不是改 logger,而是配置 handler 的 HandlerOptions.AddSource。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 设置
AddSource: true,否则slog.With("key", "val").Info("msg")永远显示 caller 为 slog 包内部函数 - 文本输出下,caller 格式由
ReplaceAttr控制;若想精简路径,可自定义过滤:func replaceAttr(_ []string, a slog.Attr) slog.Attr { if a.Key == slog.SourceKey { if src, ok := a.Value.Any().(*slog.Source); ok { src.File = filepath.Base(src.File) // 只留文件名 } } return a } - 注意:开启
AddSource有约 5%–10% 性能损耗(栈帧解析),生产高频日志场景慎用
slog.With 和 slog.WithGroup 的实际区别在哪?
两者都返回新 Logger,但语义和序列化行为完全不同:With 是扁平键值对,WithGroup 生成嵌套结构,尤其在 JSON 输出中明显。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
With记录请求 ID、用户 ID 等全局上下文:slog.With("req_id", reqID).Info("start processing") - 用
WithGroup划分逻辑域,比如数据库操作块:dbLog := slog.WithGroup("db") dbLog.With("table", "users").Info("query executed") // 输出: {"db":{"table":"users","msg":"query executed"}} - JSON handler 下,group 名会作为顶层 key;Text handler 下 group 名仅作前缀(如
db.table="users"),不改变扁平结构 - 嵌套 group 允许,但超过 2 层易导致字段名过长(如
a.b.c.d.e.field),不利于日志检索
怎么把 slog 日志对接到 Loki 或 Datadog?
原生 slog 不支持 HTTP 上报,必须用自定义 Handler。核心是实现 slog.Handler.Handle 方法,把 slog.Record 转成目标格式并发送。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 不要重写整个 handler,优先 wrap 现有 handler:用
slog.NewJSONHandler序列化 record,再用http.Post发送(注意批量、重试、背压) - Loki 要求 log line 必须含
timestamp和labels字段;slog.Record.Time是纳秒时间戳,需转 RFC3339:r.Time.Format(time.RFC3339Nano) - Datadog 接受 JSON,但要求顶层有
service、host等字段 —— 这些不能靠With注入,得在 handler 的Handle里硬编码或从 env 注入 - 线上服务务必加 buffer channel + worker goroutine,避免日志 IO 阻塞主逻辑;别让 handler 同步调用
http.Do
slog.With 就完事,真正麻烦的是 handler 生命周期管理、caller 提取开销权衡、以及对接外部系统时 timestamp/label/schema 的隐式约定。这些地方不细看文档,上线后查不到日志才是最痛的。


















