必须同时设置 slowthreshold 和 logmode(logger.warn)才触发慢查询日志;trace()是唯一能精准计时的位置;上线前须检查mysql慢日志配置、gorm日志输出目标及参数脱敏。

必须同时设置 SlowThreshold 和 LogMode(logger.Warn)
只设 SlowThreshold 或只开 LogMode 都不会触发慢查询日志。GORM v2 的慢查询判断逻辑是:只有当 LogMode 处于 logger.Warn(或更低,如 Info)且执行耗时超过 SlowThreshold 时,才走慢查分支。
常见错误配置:
-
LogMode(logger.Info)但没设SlowThreshold→ 所有 SQL 都打,但“慢”的那条不会被标记 -
SlowThreshold = 200 * time.Millisecond但LogMode(logger.Error)→ 慢了也不记录,因为 Warn 级别被跳过 - 用
db.Debug().Find(...)临时看 SQL → 这只是提升单次日志级别,不参与慢查判定逻辑
自定义 Trace() 是唯一能精准计时的位置
Trace() 方法在 SQL 执行完、结果已读取、连接尚未释放时调用,此时才能拿到真实耗时、影响行数和原始 SQL。其他钩子(BeforeQuery、AfterFind)要么太早、要么太晚,无法覆盖预加载、Raw SQL、Session 查询等全路径。
正确做法是嵌套默认 logger 并重写 Trace:
type SlowQueryLogger struct {
gorm.Logger
slowThreshold time.Duration
}
<p>func (l *SlowQueryLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
elapsed := time.Since(begin)
if elapsed > l.slowThreshold {
sql, rows := fc()
log.Printf("[SLOW] %v | SQL: %s | Rows: %d", elapsed, sql, rows)
}
}</p>
注意:fc() 必须延迟调用,避免无谓字符串拼接;err 已含失败信息,无需额外判空。
上线前必须检查三件事:MySQL 配置、日志输出目标、参数脱敏
GORM 日志只是客户端视角,它不替代 MySQL 服务端慢日志。上线后发现“没日志”,大概率不是代码问题:
- MySQL 侧未启用:
SHOW VARIABLES LIKE 'slow_query_log'必须为ON;long_query_time建议开发设0.1,生产设1.0;配置需写入/etc/mysql/my.cnf的[mysqld]段并重启 - GORM 日志默认写
os.Stdout,而你的服务日志可能进了rotatelogs或 Loki;必须把自定义 logger 的 writer 指向项目统一日志器(如zap.SugaredLogger的Write方法) - 生产环境务必保持
ParameterizedQueries: true(默认值),否则logger.Config{ParameterizedQueries: false}会把真实参数值拼进日志,有安全风险且拖慢性能
context.WithValue 是最安全的计时传递方式
不要用全局 map 存 begin time,多 goroutine 并发下极易错配、panic 或内存泄漏。GORM v2.2+ 内部 trace 流程已透传 ctx,你只需在 Begin 阶段存:ctx = context.WithValue(ctx, key, time.Now()),在 End 阶段取即可。
这个细节容易被忽略:如果你没从请求链路(比如 Gin 中间件)把带 traceID 的 ctx 传入 DB 操作,所有慢查日志就只是孤岛,没法关联到具体请求。而靠中间件统一塞进全局 *gorm.DB 实例,反而会污染上下文隔离性。











