日志实践:log/slog 接入 Gin
本章解决什么问题:Go 1.21 起标准库有了结构化日志 log/slog,多数新项目不再需要 zap/logrus。本章给出“slog 全局初始化 + 中间件记录请求日志 + 业务代码取用”的推荐做法,让日志统一为结构化键值对。
初始化 logger
创建 handler 决定输出形态:JSON 方便采集进日志系统,Text 适合本地人读。用 slog.SetDefault 设为全局默认,业务代码就能直接调 slog.Info:
package main
import (
"log/slog"
"os"
"time"
"github.com/gin-gonic/gin"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
r := gin.New()
r.Use(requestLog(logger), gin.Recovery()) // 用自建日志中间件替换 gin.Logger
r.GET("/hello", func(c *gin.Context) {
slog.Info("handle hello", "user", c.Query("user"))
c.String(200, "hi")
})
r.Run(":8080")
}
func requestLog(l *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
l.Info("http_request",
slog.String("method", c.Request.Method),
slog.String("path", path),
slog.Int("status", c.Writer.Status()),
slog.Duration("latency", time.Since(start)),
slog.String("ip", c.ClientIP()),
)
}
}
要点:gin.Default() 自带 gin.Logger,会与这里的请求日志重复,所以用 gin.New() + requestLog + Recovery 自己装配。
业务日志与请求关联
请求日志与业务日志要能对上,常见手法是给每条请求分配 ID(第 13 章),再用 slog.With 生成带固定字段的派生 logger。例如 requestLog 里 c.Set("req_id", id),业务代码取回后写日志:
id := c.GetString("req_id")
slog.Info("order created", "order_id", 42, "req_id", id)
更进一步的封装是“从 context 取 req_id 的 helper”,把取值的样板收敛到一个函数里,避免散落。当请求要透传多个公共字段时,可在初始化处用 slog.With 生成派生 logger(如带 service、version 字段),再让中间件把它存入 context,业务代码取出后直接写日志,公共字段自动带上。
Handler 内打日志统一走 slog。字段较多时可用 slog.Group 归组,或直接写成键值对参数(如上面示例的写法);自定义类型的字段建议实现 slog.LogValuer 接口,把内部结构折叠成一行可读值再输出。
几个工程细节
- 日志级别:开发用 Debug 起步,生产一般 Info;HandlerOptions.Level 可在初始化处控制;
- 输出目标:JSONHandler 指向 os.Stdout,由部署层的日志收集器统一采集,程序内不要自己写文件轮转;
- 字段命名保持统一风格(method/path/status/latency 等),方便日志系统建索引与聚合;
- 不要 log.Printf 与 slog 混用:两类输出格式不同,混着打会让时间线难以对齐;
- 避免记录密码、token 等敏感字段,确需排查时先脱敏再落日志;
- Recovery 的堆栈默认走 gin.DefaultErrorWriter,与 slog 不在一条线上,如需统一可换成第 13 章的自写恢复中间件并用 slog 记录堆栈;
- c.ClientIP 依赖代理信任配置,部署在反向代理之后要按官方文档 gin-gonic.com 设置可信代理,否则拿到的可能是代理地址。
关键点
- slog 是标准库,JSONHandler + slog.SetDefault 是最小可用组合。
- 用自建请求日志中间件替换 gin.Logger,避免一套请求打两遍。
- 通过请求 ID 把请求日志与业务日志串成一条链路,排障效率高一个量级。
小结
slog 接入 Gin 的要点只有三个:初始化、中间件记请求、业务代码记事件。最后一章让服务“体面退场”:优雅停机与超时控制。