GORM v2 的 logger.Info 不显示带参 SQL 是因参数被驱动层隔离以防止敏感数据泄露和减少开销;需同时设 LogLevel=Debug 且 ParameterizedQueries=true 才生效,且 SQLite 参数支持不可靠。

为什么 logger.Info 不显示带参 SQL
GORM v2 的 logger.Info 默认只输出占位符 SQL(如 SELECT * FROM users WHERE id = ?),参数被驱动层单独处理,日志层看不到真实值。这不是遗漏,而是为防敏感数据泄露和减少字符串拼接开销做的主动隔离。
-
ParameterizedQueries: true仅在LogLevel: logger.Debug下生效;设为Info或Warn时该配置被忽略 - MySQL/PostgreSQL 驱动对参数还原更稳定;SQLite 日志中参数常为空或不可靠
- 别用
db.Debug().First(...)临时开启——它只影响单次调用,且不触发ParameterizedQueries逻辑
如何安全启用带参 Debug 日志
必须同时满足两个硬性条件:日志级别为 Debug,且显式开启 ParameterizedQueries。否则看到的仍是问号占位符。
- 初始化时构建新 logger:
logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{LogLevel: logger.Debug, ParameterizedQueries: true}) -
Colorful: true能让 SQL 关键字高亮,在终端里一眼识别语句结构 - 生产环境禁用:Debug 模式不仅打 SQL,还记录事务、钩子、反射过程等,且参数明文暴露(密码、token、手机号都可能出现在日志里)
- 用环境变量控制实例创建,例如:
if os.Getenv("APP_ENV") == "dev" { newLogger = ... },而不是运行时动态切换
怎样监控慢 SQL 而不拖慢服务
靠全局 time.Now() 或在 Info() 里计时是错的——并发下会串上下文,且执行完才打日志,无法精准捕获“超时瞬间”。
- 正确做法是实现自定义 logger 并覆盖
Trace()方法,在begin时刻存入context.WithValue(ctx, key, time.Now()),在end时取出计算耗时 -
fc()是延迟执行闭包,只在真正需要日志时才解析 SQL 字符串,避免无谓开销 -
SlowThreshold建议设为100 * time.Millisecond(OLTP 场景)或500 * time.Millisecond(分析查询),避免日志刷屏 - 不要重写整个
Interface,只需嵌套默认gorm.Logger并覆盖Trace(),兼容性最好
日志输出到文件或对接 zap 等结构化库
GORM 的 logger 只要求实现 Writer 接口(含 Printf(string, ...interface{})),所以任何支持该接口的对象都能作为输出目标。
- 写入文件:传入
os.File实例,或用io.MultiWriter(os.Stdout, file)同时打屏+落盘 - 对接 zap:封装一个
zapWriter结构体,实现Printf,内部调用logger.Info()并把参数转成字段(注意v[4]是参数 slice,v[5]是影响行数) - 别直接用
log.Printf替代——它不带 context,无法透传 trace ID;GORM 的Info()等方法第一个参数就是context.Context - 第三方 logger 如 zap 必须手动从 context 提取
trace_id并注入日志字段,GORM 不自动做这件事
实际部署时最容易被忽略的是:Debug 日志开关和 ParameterizedQueries 的耦合关系,以及 Trace() 中必须用 context 透传起始时间——这两点一旦漏掉,要么看不到参数,要么慢查询告警不准。











