slog.Info("msg", "key", value) 输出 !BADKEY 是因为 key 必须为字符串字面量或常量,否则静态校验失败即替换为 !BADKEY;正确写法是用 slog.String、slog.Int 等构造器,并启用 sloglint attr-only 检查。

为什么 slog.Info("msg", "key", value) 会输出 !BADKEY?
因为 slog.Info 的可变参数必须成对出现(key, value),且 key 必须是字符串字面量或常量;一旦传入变量、表达式或非字符串类型,slog 就无法静态校验,会默默把非法 key 替换为 !BADKEY —— 它不报错,但日志字段全废了。
- 常见错误现象:
slog.Info("user login", userID, 123)→ 输出{"msg":"user login","!BADKEY":123} - 正确写法必须用
slog.Attr构造器:slog.String("user_id", "123")、slog.Int("attempts", 3) - 编译器能直接捕获拼写错误(如
slog.Str("user_id", "...")会报错)、类型不匹配(slog.Int("id", "123")编译失败) - 强烈建议启用
sloglint:在.golangci.yml中配置attr-only: true,全局杜绝裸 key/value
怎么让 slog 输出真正的 JSON 而不是文本?
默认的 slog.New 不管你传什么 handler,只要没显式包装,它就 fallback 到标准库 log 的文本格式——哪怕你传了 slog.NewJSONHandler(os.Stdout, nil),也得确保它被真正用上。
- 错误姿势:
slog.New(slog.NewJSONHandler(os.Stdout, nil))✅ 正确;但若漏掉这层slog.New(...),直接调slog.Info,仍是文本 - 必须显式设置默认 logger:
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))) - 字段名强制小驼峰(
user_id),不支持自定义 key 名;想用userId?得自己实现Handler或用中间层封装 -
slog.Any("req", req)不会展开结构体,只会调fmt.Sprint转成字符串;要真 JSON 对象,得手动展开:slog.String("req_path", req.URL.Path)等
HTTP 请求怎么自动带上 trace_id?
靠 slog.With + 上下文传播,而不是每次 log 都手写 "trace_id", id。核心是把 trace ID 注入 context.Context,再让 logger 继承它。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 中间件中生成并注入:
ctx := context.WithValue(r.Context(), traceKey{}, cid),然后logger := slog.With("trace_id", cid) - 更推荐用社区库
slog-context:slogctx.With(r.Context(), slog.String("trace_id", cid)),后续所有slog.InfoContext(ctx, ...)自动携带 - 别用
slog.WithGroup模拟上下文——它只影响当前 logger 实例,不随 context 传递,跨 goroutine 就断了 - 注意:handler 的
AddSource: true会加source字段,但不会帮你传 trace_id;这个必须你自己带进去
struct 嵌套字段怎么打日志才不丢信息?
slog.Any 是个陷阱:它把整个 struct 当黑盒 toString,JSON 里只存一个字符串,搜索、过滤、聚合全失效。
立即学习“go语言免费学习笔记(深入)”;
- 错误示范:
slog.Any("user", user)→"user":"{Name:alice ID:123}"(纯字符串,不可解析) - 正确做法是手动展开关键字段:
slog.String("user_name", user.Name), slog.Int("user_id", user.ID) - 如果字段多且稳定,可为 struct 实现
LogValue() slog.Value方法,返回slog.Group,这样slog.Any("user", user)才真输出 object - 敏感字段(如 password、token)务必脱敏:不要
slog.String("password", u.Password),而应slog.String("password", "[redacted]")
最易忽略的点是:slog 的 Handler 是不可变的,WithAttrs 返回新实例,旧 logger 不受影响;调试时看到日志没带预期字段,八成是 logger 实例没传对,或者中间某处又用了默认 logger。

















