应显式配置 slowthreshold(如200ms)并设loglevel为warn/error,避免仅用logmode;日志需通过自定义writer接入统一日志系统,并在查询前用withcontext透传上下文。

直接配 SlowThreshold,别只开 LogMode
默认 logger.Default 的 SlowThreshold 是 100ms,但很多人只调用 .LogMode(logger.Warn),结果所有 Warn 级日志(比如连接失败、事务冲突)都打出来,却压根没触发慢查询记录——因为 Warn 级本身不自动判断耗时,必须显式设阈值。
正确做法是用 WithConfig 显式传入 SlowThreshold:
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
},
)
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger})
-
SlowThreshold必须设,且单位是time.Duration(不是毫秒整数) -
LogLevel设为Warn或Error,避免 Info 级全量 SQL 刷屏 - 不要依赖
logger.Default.LogMode(logger.Warn),它不会继承你想要的SlowThreshold
想看真实参数值?得重写 Writer,但线上慎用
默认日志里的 SQL 是带 ? 占位符的,比如 SELECT * FROM users WHERE id = ?。要看到实际绑定的值(如 id = 52),必须自定义 logger.Writer 实现 Printf,并在其中解析 fc() 返回的 SQL 和参数。
但要注意:
- 反射取参数值会显著拖慢性能,压测时延迟可能翻倍
- 生产环境建议关掉,开发/预发用即可
- 如果用了
PrepareStmt: true(推荐),日志里天然就是预编译形式,参数脱敏更安全
Trace 方法才是耗时判断的唯一可靠入口
GORM 的 Trace 回调在 SQL 执行完毕、结果集已读取、rowsAffected 可获取之后才触发——这是唯一能拿到真实耗时 + 行数 + 错误的时机。别用 BeforeQuery/AfterQuery 钩子替代,它们不保证执行完成,rowsAffected 常为 -1。
自定义 Trace 的关键点:
- 必须调用
fc()获取 SQL 和行数,不能跳过 -
elapsed是从begin到Trace被调用的时间,代表端到端耗时(含网络、反序列化) - 日志内容至少包含:
sql、elapsed.Milliseconds()、rows、err - 避免在
Trace里做 I/O(如写文件),改用缓冲队列或异步 logger
日志没进你的统一日志系统?Writer 没接对
GORM 日志默认输出到 os.Stdout,和项目主日志(比如 zap、zerolog 写到文件或 Loki)完全隔离。查问题时只能翻两个地方,根本串不起来。
解决方法是把项目日志实例包装成 logger.Writer:
type LogWriter struct{ logger *zap.Logger }
func (w LogWriter) Printf(format string, v ...interface{}) {
w.logger.Debug("gorm-sql", zap.String("msg", fmt.Sprintf(format, v...)))
}
再传给 logger.New。这样所有 GORM 日志就带上了 traceID、请求路径等上下文字段,和业务日志同源可检索。
最容易被忽略的是:GORM 在预加载(Preload)、事务嵌套等场景会新建 session,原 context 可能丢失。务必在每次查询前用 db.WithContext(ctx) 显式透传。











