Gin 本身不记录 SQL 慢查询日志,因其仅为 HTTP 路由框架,不接触数据库层;SQL 执行由 sql.DB 或 GORM 等驱动/ORM 完成,慢查询日志必须在数据库驱动或 ORM 层(如 GORM v2 的 Logger 或 MySQL 驱动 interceptor)中拦截耗时并判断阈值,Gin 中间件无法可靠捕获 SQL 执行上下文。

为什么 Gin 本身不记录 SQL 慢查询日志
Gin 是 HTTP 路由框架,不接触数据库层;sql.DB 或 gorm.DB 才真正执行 SQL。慢查询日志必须在数据库驱动或 ORM 层介入,不能靠 Gin 中间件自动捕获 SQL 语句和耗时。
用 gorm.Config.Logger 拦截并过滤慢查询(GORM v2)
GORM 自带日志器,但默认不区分快慢。需自定义 logger.Interface,在 LogMode 启用后,于 Trace 方法中判断耗时是否超阈值。
- 慢阈值建议设为
100 * time.Millisecond,太低会刷屏,太高漏报 - 务必检查
ctx.Value(gorm.SessionKey)是否含有效*gorm.Session,避免 panic -
sql字段已参数化(如SELECT * FROM users WHERE id = ?),不带真实值,符合安全要求 - 别直接用
fmt.Printf,应走 Zap / Zerolog 等结构化日志器,打标level=warn和duration_ms
logger := &slowQueryLogger{
threshold: 100 * time.Millisecond,
log: zap.L().Named("gorm-slow"),
}
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger,
})
用 sqlmock 或 go-sql-driver/mysql 的 interceptor(原生 database/sql)
若不用 GORM,而是直连 sql.DB,就得在 driver.Conn 层做钩子。MySQL 驱动从 v1.7+ 支持 interceptor.Query,PostgreSQL 驱动(pgx)也提供类似机制。
- 拦截点必须在
conn.QueryContext或conn.ExecContext内部,而非外层db.QueryRow—— 后者可能复用连接、绕过拦截 - 注意并发安全:
context.Context是唯一可靠传参方式,不要依赖 goroutine 局部变量存开始时间 - 某些驱动(如 sqlite3)不支持 interceptor,此时只能包装
sql.DB的公开方法,但会漏掉Stmt.Exec等调用
别把慢查询日志塞进 Gin 的 gin.Context 日志字段
有人试图在 Gin 中间件里用 c.Set("sql_duration", d),再等 SQL 执行完统一打日志——这完全不可行。HTTP 请求生命周期和 SQL 执行周期无必然顺序,协程可能早已结束,c 已失效。
- SQL 日志必须在数据库操作完成的**同一 goroutine、同一栈帧内**写入
- 跨层传递耗时信息(如从 DB 层回传到 Gin handler)既难保证,又破坏关注点分离
- 如果真要关联请求 ID,应在 SQL 日志里显式注入
req_id := c.GetString("X-Request-ID"),而不是反过来
慢查询不是加个中间件就能解决的事,关键在选对钩子位置:GORM 就用它的 Logger,原生 database/sql 就看驱动是否支持 interceptor,别在 Gin 层硬凑。阈值设太低或日志格式没结构化,很快就会被淹没。


















