必须实现logger.interface接口的trace等四个方法,其中trace是自定义sql日志格式的核心入口,需通过fc()获取sql和行数,结合context实现线程安全与请求追踪。

如何替换GORM默认日志器为自定义格式
GORM v2(gorm.io/gorm)不直接提供“格式字符串”配置,必须通过实现 logger.Interface 接口来接管日志输出逻辑。官方默认的 logger.Default 是一个结构体实例,你不能只改它的字段——得自己写一个满足接口的类型。
关键点是:必须实现四个方法:Trace、Info、Warn、Error,其中 Trace 最常用(含SQL、参数、耗时、行数),也是自定义格式的主要入口。
示例中常见错误是只重写了 Info 却忽略 Trace,结果SQL日志仍按默认格式打印。
- 不要试图修改
logger.Config中的SlowThreshold或LogLevel来改变格式——它们只控制开关和阈值 - 若用
gorm.Open(..., &gorm.Config{Logger: yourLogger}),确保传入的是实现了logger.Interface的指针(如&myLogger),否则方法调用会失败 - v1(
github.com/jinzhu/gorm)的SetLogger已废弃,新项目务必用 v2 接口
自定义Trace日志:包含SQL、参数、耗时、错误的最小可行结构
Trace 方法签名是:func(ctx context.Context, begin time.Time, fc func() (string, int64), err error)。其中 fc() 返回 SQL 字符串和影响行数,这是你唯一能拿到原始SQL和参数的地方(参数已由GORM预处理为占位符+值,不会暴露明文敏感字段)。
注意:fc() 是延迟执行的,仅当 LogLevel 允许输出时才调用——所以别在外面提前调它。
type MyLogger struct {
logger.Interface
}
func (l MyLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
elapsed := time.Since(begin)
sql, rows := fc()
if err != nil {
fmt.Printf("[ERROR] %s | %s | rows:%d | err:%v\n", elapsed, sql, rows, err)
return
}
fmt.Printf("[SQL] %s | %s | rows:%d\n", elapsed, sql, rows)
}
这里没做参数脱敏或换行美化,但已具备可读性基础。若需高亮或结构化(如JSON),可在该函数内用 fmt.Sprintf 拼接或调用第三方格式化库。
为什么不能直接用log.Printf或zap.Sugar().Infof?
因为GORM日志器要求线程安全且上下文感知:Trace 等方法接收 context.Context,常用于注入请求ID、用户ID等追踪字段。如果直接用全局 log.Printf,这些信息就丢失了。
正确做法是在自定义日志器中缓存或提取 ctx.Value:
func (l MyLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
reqID := ctx.Value("req_id")
if reqID == nil {
reqID = "unknown"
}
// ... 然后在printf里插入 [req_id:%v]
}
另一个坑是:某些日志库(如 zerolog)的 Logger.With().XXX() 返回新实例,必须确保每次 Trace 都基于当前 ctx 构造,而不是复用一个静态 logger 实例。
兼容性与性能注意事项
自定义日志器会成为SQL执行路径上的同步阻塞点。如果在 Trace 里做了耗时操作(比如网络请求、磁盘写入、复杂JSON序列化),会拖慢所有DB操作。生产环境务必避免。
- 格式化尽量用
fmt.Sprintf或strings.Builder,别用正则或反射 - 日志级别控制交给GORM自身(通过
logger.Config.LogLevel),你的实现里不要再做if level > Info {...}判断 - 若需对接ELK或Loki,建议只在
Trace中输出结构化字符串(如time=... sql=... rows=... err=...),由采集端解析,而非在Go层拼JSON
最易被忽略的是:GORM在事务、预编译、钩子中可能多次调用 Trace,同一SQL可能打多条日志——要靠 ctx 中的唯一标识或SQL哈希去重,而不是假设每条SQL只执行一次。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











