GoLang使用SQLC及fmt调用时如何记录带参数的SQL查询日志
SQLC 记录带参数查询日志的实现方案
你不需要手动获取SQL常量再手动拼接格式化,SQLC生成代码的抽象层本身就预留了无侵入的日志注入能力,比你目前的思路实现成本低很多,也没有维护负担。
最优方案:包装DBTX层实现通用日志拦截
SQLC生成的所有查询方法,都不会直接硬编码调用底层数据库驱动,而是依赖一个DBTX接口(定义了ExecContext/QueryContext/QueryRowContext三个核心方法)执行SQL。你只需要自己实现一个带日志逻辑的DBTX包装类,初始化SQLC生成的Queries实例时传入这个包装类,就能自动拿到所有SQL语句和对应的参数,不需要改动任何SQLC生成的代码。
示例代码:
import ( "context" "database/sql" "log" "time" ) // LoggingDB 实现SQLC要求的DBTX接口,包装原生数据库连接 type LoggingDB struct { db *sql.DB } func (l *LoggingDB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { start := time.Now() res, err := l.db.ExecContext(ctx, query, args...) log.Printf("[SQLC EXEC] cost=%v err=%v sql=%s args=%v", time.Since(start), err, query, args) return res, err } func (l *LoggingDB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) { start := time.Now() rows, err := l.db.QueryContext(ctx, query, args...) log.Printf("[SQLC QUERY] cost=%v err=%v sql=%s args=%v", time.Since(start), err, query, args) return rows, err } func (l *LoggingDB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row { start := time.Now() row := l.db.QueryRowContext(ctx, query, args...) log.Printf("[SQLC QUERY_ROW] cost=%v sql=%s args=%v", time.Since(start), query, args) return row } // 初始化SQLC生成的Queries时直接传入包装后的实例即可 func InitQueries(realDB *sql.DB) *db.Queries { return db.New(&LoggingDB{db: realDB}) }
这个方案的优势:
- 零侵入SQLC生成的代码,升级SQLC版本不会出现兼容问题
- 自动覆盖所有SQLC生成的查询方法,不需要逐个方法加日志
- 不需要手动维护SQL常量和参数映射关系,不会出现漏记、错配的问题
特定场景简化方案
如果你用pgx驱动连接PostgreSQL,连上面的包装类都不用写,直接利用pgx自带的Tracer能力即可:
import "github.com/jackc/pgx/v5/pgxpool" poolCfg, err := pgxpool.ParseConfig(dsn) // 实现pgx.QueryTracer接口,在TraceQueryStart方法里就能拿到SQL和参数 poolCfg.ConnConfig.Tracer = &yourCustomQueryTracer{} pool, err := pgxpool.NewWithConfig(context.Background(), poolCfg) // 直接把连接池传给SQLC生成的Queries即可,所有查询会自动被Tracer捕获 queries := db.New(pool)
不推荐手动获取SQL常量的实现思路
这种方案维护成本极高:SQLC生成的SQL常量会随你的SQL文件修改、SQLC版本升级发生变化,手动维护常量和查询的映射关系很容易出现遗漏、错配,手动拼接参数和SQL占位符也容易出现转义错误,长期维护成本远高于拦截DBTX层的方案。
如果需要生成参数替换后的完整可执行SQL,不要自己手动拼接,直接用对应驱动提供的安全转义方法,比如pgx的Sanitize方法,避免SQL注入风险和转义错误。
SQLC本身没有内置日志打印功能,它的定位是SQL到Go代码的生成工具,日志、链路追踪这类横切逻辑,官方推荐的实现方式就是通过包装DBTX层注入。
内容的提问来源于stack exchange,提问作者M.K.
相关产品推荐
相关产品推荐

