Go 结构化日志实践指南
Go 结构化日志实践指南
日志不是把字符串打印出来就完事。真实服务里,日志要能搜索、聚合、告警、追踪请求链路。结构化日志的价值,就是让日志从“人眼阅读”变成“系统可分析”。
Go 1.21 之后标准库提供了 log/slog,很多项目已经不需要额外引入日志库。
一、使用 slog 输出 JSON
1 | logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) |
输出会是 JSON 格式,适合被日志平台采集。
二、错误日志要带上下文
不要只写:
1 | logger.Error("request failed") |
更好的写法:
1 | logger.Error("request failed", |
线上排查时,日志缺上下文就等于没写。
三、请求级字段可以提前绑定
同一个请求里的日志通常都需要 trace id、用户 id、路径等字段:
1 | reqLogger := logger.With( |
这样每条日志都会自动带上这些字段。
四、日志级别要有边界
常见级别可以这样理解:
Debug:本地调试或临时排查。Info:关键生命周期事件。Warn:有异常但还能继续。Error:当前操作失败,需要关注。
不要把所有东西都打成 Error。告警系统看到大量假错误,很快就会失去意义。
五、不要记录敏感信息
日志里不要出现密码、token、身份证、银行卡、完整手机号等敏感数据。
如果必须记录用户标识,优先使用内部用户 ID。对邮箱、手机号这类信息,要脱敏。
六、在 HTTP 中间件里记录请求日志
1 | func accessLog(logger *slog.Logger, next http.Handler) http.Handler { |
真实项目还应该记录状态码、请求大小、响应大小和 trace id。
七、实践建议
Go 项目如果没有特殊需求,可以优先使用 log/slog。它足够简单,也能满足大多数结构化日志场景。
日志的关键不是多,而是可定位。每条关键日志都应该回答三个问题:发生了什么,影响了谁,能不能顺着字段继续查下去。