slog.info默认不带时间戳和文件行号是设计使然,需通过newtexthandler显式配置addsource:true和addtime:true;生产禁用addsource防泄露与性能损耗,时间戳由handler自动添加不可定制格式。

Go 1.21+ 项目直接用 slog,别碰标准库 log 包做结构化日志——它没级别控制、没字段注入、不支持 Handler 抽象,硬改只会越配越乱。
怎么让 slog.Info 输出带时间戳和文件行号
默认的 slog.Info 不输出时间、不显示源码位置,不是 bug,是设计:格式由 Handler 决定,不是日志函数决定。
- 开发环境必须显式构造
slog.NewTextHandler并设AddSource: true和AddTime: true,例如:handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ AddSource: true, AddTime: true, TimeFormat: time.DateTime, }) - 生产环境禁用
AddSource:暴露路径有安全风险,且获取调用栈有性能开销 - 别在每条日志里手动拼时间:
slog.Info("msg", "time", time.Now().String())——结构崩解,time变成字符串字段,Loki/Grafana 无法当时间轴解析
为什么 slog.Debug 在生产没输出
不是代码漏写,是 HandlerOptions.Level 没设对。默认级别是 slog.LevelInfo,slog.Debug 被静默丢弃。
- 启动时必须明确设
Level:&slog.HandlerOptions{Level: slog.LevelDebug}(开发)或Level: slog.LevelInfo(生产) - 不要在业务逻辑里加
if debug { slog.Debug(...) }——破坏日志统一入口,也违背“配置驱动行为”原则 - 环境变量切换级别?用
os.Getenv("LOG_LEVEL")解析后转成slog.Level常量,别传字符串
全局字段(如 service_name、env)怎么注入才不漏
靠每次 slog.With("service_name", "api").Info(...) 手动加?漏一处就断链,排查时 trace_id 对不上。
- 服务启动时一次性创建根 logger:
logger := slog.With( slog.String("service_name", "user-api"), slog.String("env", os.Getenv("ENV")), slog.String("version", "v1.2.0"), ) -
slog.With返回新 logger,原 logger 不变;所有后续logger.Info、logger.Error自动携带这些字段 - 动态字段(如 HTTP 请求中的
trace_id)不能塞进根 logger,应在中间件中提取后调用logger.With("trace_id", tid)创建子 logger 透传
JSON 格式怎么配才被 ELK/Filebeat 正确采集
只调用 slog.NewJSONHandler 不够,缺关键字段会导致日志被 Loki 当作非法 JSON 丢弃,或 Kibana 里查不到 @timestamp。
- 必须设
TimeFieldFormat:用time.RFC3339,否则时间字段是纳秒整数,ELK 解析失败 - 必须显式加
"level"字段:slog.LevelFieldName = "level",再配slog.LevelFieldMarshalFunc = strings.ToLower让值小写 - 确保一行一 JSON:Handler 默认就是单行输出,但别在
.Msg()里拼换行符,也别用fmt.Printf混入日志流
最易被忽略的一点:所有配置(Handler、Level、LevelFieldName)必须在第一条日志输出前完成,slog.SetDefault() 后再改 handler 无效——日志已按旧配置发出去了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











