golang无需修改标准库即可记录SQL查询日志的插件库sqldb-logger的使用
Golang无需修改标准库即可记录SQL查询日志的插件库sqldb-logger的使用
SQLDB-Logger是一个无需修改现有*sql.DB
标准库用法即可记录Go SQL数据库驱动日志的库。
上面彩色控制台输出仅作为开发示例
特性
- 可配置的级别化、详细日志记录
- 保持使用(或重用现有)
*sql.DB
不变 - 通过简单日志接口使用自己的日志后端
- 可追踪的日志输出:
- 每个调用都有唯一ID
- 预处理语句和执行会有相同ID
- 执行/结果错误时,会包含查询、参数和相关ID
安装
go get -u -v github.com/simukti/sqldb-logger
建议使用依赖管理工具如Mod或Dep进行版本锁定
使用
Logger
是一个简单接口:
type Logger interface {
Log(ctx context.Context, level Level, msg string, data map[string]interface{})
}
包含4个基本实现,使用知名JSON结构化日志库快速开始:
- Zerolog适配器:使用rs/zerolog作为日志记录器
- Onelog适配器:使用francoispqt/onelog作为日志记录器
- Zap适配器:使用uber-go/zap作为日志记录器
- Logrus适配器:使用sirupsen/logrus作为日志记录器
注意:这些适配器不使用给定的context
,您需要根据需求修改它
与现有SQL DB驱动集成
重用现有*sql.DB
驱动,这是最简单的方式:
例如从:
dsn := "username:passwd@tcp(mysqlserver:3306)/dbname?parseTime=true"
db, err := sql.Open("mysql", dsn) // db是*sql.DB
db.Ping() // 检查连接和DSN正确性
改为:
// import sqldblogger "github.com/simukti/sqldb-logger"
// import "github.com/simukti/sqldb-logger/logadapter/zerologadapter"
dsn := "username:passwd@tcp(mysqlserver:3306)/dbname?parseTime=true"
db, err := sql.Open("mysql", dsn) // db是*sql.DB
// 处理err
loggerAdapter := zerologadapter.New(zerolog.New(os.Stdout))
db = sqldblogger.OpenDriver(dsn, db.Driver(), loggerAdapter/*, 使用默认选项*/) // db仍然是*sql.DB
db.Ping() // 检查连接和DSN正确性
这样就完成了,所有*sql.DB
交互现在都会被记录。
与SQL驱动结构集成
也可以直接与以下公共空结构驱动集成:
MySQL (go-sql-driver/mysql)
db := sqldblogger.OpenDriver(dsn, &mysql.MySQLDriver{}, loggerAdapter /*, ...选项 */)
PostgreSQL (lib/pq)
db := sqldblogger.OpenDriver(dsn, &pq.Driver{}, loggerAdapter /*, ...选项 */)
SQLite3 (mattn/go-sqlite3)
db := sqldblogger.OpenDriver(dsn, &sqlite3.SQLiteDriver{}, loggerAdapter /*, ...选项 */)
以下结构驱动可能兼容:
SQL Server (denisenkom/go-mssqldb)
db := sqldblogger.OpenDriver(dsn, &mssql.Driver{}, loggerAdapter /*, ...选项 */)
Oracle (mattn/go-oci8)
db := sqldblogger.OpenDriver(dsn, oci8.OCI8Driver, loggerAdapter /*, ...选项 */)
日志选项
当使用sqldblogger.OpenDriver(dsn, driver, logger, opt...)
不带第4个可变参数时,将使用默认选项。
以下是使用所有可用选项和非默认值的OpenDriver()
示例:
db = sqldblogger.OpenDriver(
dsn,
db.Driver(),
loggerAdapter,
// 可用选项
sqldblogger.WithErrorFieldname("sql_error"), // 默认: error
sqldblogger.WithDurationFieldname("query_duration"), // 默认: duration
sqldblogger.WithTimeFieldname("log_time"), // 默认: time
sqldblogger.WithSQLQueryFieldname("sql_query"), // 默认: query
sqldblogger.WithSQLArgsFieldname("sql_args"), // 默认: args
sqldblogger.WithMinimumLevel(sqldblogger.LevelTrace), // 默认: LevelDebug
sqldblogger.WithLogArguments(false), // 默认: true
sqldblogger.WithDurationUnit(sqldblogger.DurationNanosecond), // 默认: DurationMillisecond
sqldblogger.WithTimeFormat(sqldblogger.TimeFormatRFC3339), // 默认: TimeFormatUnix
sqldblogger.WithLogDriverErrorSkip(true), // 默认: false
sqldblogger.WithSQLQueryAsMessage(true), // 默认: false
sqldblogger.WithUIDGenerator(sqldblogger.UIDGenerator), // 默认: *defaultUID
sqldblogger.WithConnectionIDFieldname("con_id"), // 默认: conn_id
sqldblogger.WithStatementIDFieldname("stm_id"), // 默认: stmt_id
sqldblogger.WithTransactionIDFieldname("trx_id"), // 默认: tx_id
sqldblogger.WithWrapResult(false), // 默认: true
sqldblogger.WithIncludeStartTime(true), // 默认: false
sqldblogger.WithStartTimeFieldname("start_time"), // 默认: start
sqldblogger.WithPreparerLevel(sqldblogger.LevelDebug), // 默认: LevelInfo
sqldblogger.WithQueryerLevel(sqldblogger.LevelDebug), // 默认: LevelInfo
sqldblogger.WithExecerLevel(sqldblogger.LevelDebug), // 默认: LevelInfo
)
动机
我希望:
- 保持使用
*sql.DB
- 有可配置的输出字段
- 利用结构化日志
- 获取并记录
context.Context
值(如果需要) - 重用pgx日志接口
没找到具有这些特性的Go *sql.DB
日志记录器,所以自己创建了一个。
贡献
如果您发现错误、拼写错误、错误的测试、想法、帮助解决现有问题或任何建设性内容。
请随时创建问题或拉取请求。
许可证
MIT
更多关于golang无需修改标准库即可记录SQL查询日志的插件库sqldb-logger的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang无需修改标准库即可记录SQL查询日志的插件库sqldb-logger的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
使用sqldb-logger记录Golang SQL查询日志
sqldb-logger是一个无需修改标准库即可记录SQL查询日志的Golang库,它通过包装标准库的database/sql
接口实现日志记录功能。
基本用法
首先安装sqldb-logger:
go get github.com/simukti/sqldb-logger
简单示例
package main
import (
"database/sql"
"log"
"os"
"github.com/simukti/sqldb-logger"
"github.com/simukti/sqldb-logger/logadapter/zapadapter"
"go.uber.org/zap"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 创建zap日志记录器
zapLogger, err := zap.NewDevelopment()
if err != nil {
log.Fatalf("failed to create zap logger: %v", err)
}
defer zapLogger.Sync()
// 打开数据库连接
db, err := sql.Open("mysql", "user:password@/dbname")
if err != nil {
zapLogger.Fatal("failed to open db", zap.Error(err))
}
defer db.Close()
// 包装原始db连接以添加日志记录
loggedDB := sqldb_logger.New(
db,
zapadapter.New(zapLogger),
sqldb_logger.WithLogArguments(true),
sqldb_logger.WithLogDriverErrorSkip(true),
)
// 使用loggedDB执行查询
var id int
err = loggedDB.QueryRow("SELECT id FROM users WHERE email = ?", "test@example.com").Scan(&id)
if err != nil {
zapLogger.Error("query failed", zap.Error(err))
}
}
配置选项
sqldb-logger提供了多个配置选项:
loggedDB := sqldb_logger.New(
db,
zapadapter.New(zapLogger),
// 记录SQL参数
sqldb_logger.WithLogArguments(true),
// 记录执行时间
sqldb_logger.WithTimeFormat(sqldb_logger.TimeFormatRFC3339Nano),
// 记录错误跳过
sqldb_logger.WithLogDriverErrorSkip(true),
// 最小日志级别
sqldb_logger.WithMinimumLevel(sqldb_logger.LevelDebug),
// 查询执行前记录
sqldb_logger.WithLogQuery(true),
// 查询执行后记录
sqldb_logger.WithLogResult(true),
// 记录影响的行数
sqldb_logger.WithLogRowsAffected(true),
// 记录最后插入ID
sqldb_logger.WithLogLastInsertID(true),
// 记录错误
sqldb_logger.WithLogError(true),
)
支持的日志适配器
sqldb-logger支持多种日志库适配器:
-
Zap适配器 (推荐)
import "github.com/simukti/sqldb-logger/logadapter/zapadapter" logger := zapadapter.New(zapLogger)
-
Logrus适配器
import "github.com/simukti/sqldb-logger/logadapter/logrusadapter" logger := logrusadapter.New(logrusLogger)
-
标准库log适配器
import "github.com/simukti/sqldb-logger/logadapter/stdlogadapter" logger := stdlogadapter.New(stdLogger)
-
ZeroLog适配器
import "github.com/simukti/sqldb-logger/logadapter/zerologadapter" logger := zerologadapter.New(zerologLogger)
自定义日志格式
你可以实现自己的日志适配器:
type MyLogger struct{}
func (l *MyLogger) Log(ctx context.Context, level sqldb_logger.LogLevel, msg string, data map[string]interface{}) {
// 自定义日志处理逻辑
fmt.Printf("[%s] %s: %+v\n", level, msg, data)
}
// 使用自定义日志器
loggedDB := sqldb_logger.New(db, &MyLogger{})
性能考虑
sqldb-logger对性能的影响很小,因为它只是在标准库的database/sql
接口上添加了一个薄薄的包装层。但在生产环境中,你可能需要考虑:
- 只在调试时启用详细的SQL日志记录
- 使用异步日志记录减少I/O阻塞
- 避免在生产环境中记录敏感数据
完整示例
package main
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/simukti/sqldb-logger"
"github.com/simukti/sqldb-logger/logadapter/zapadapter"
"go.uber.org/zap"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 初始化zap日志
logger, _ := zap.NewProduction()
defer logger.Sync()
// 打开数据库连接
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
logger.Fatal("failed to open db", zap.Error(err))
}
defer db.Close()
// 配置日志记录
loggedDB := sqldb_logger.New(
db,
zapadapter.New(logger),
sqldb_logger.WithLogArguments(true),
sqldb_logger.WithTimeFormat(sqldb_logger.TimeFormatUnix),
sqldb_logger.WithMinimumLevel(sqldb_logger.LevelDebug),
)
// 创建表
_, err = loggedDB.Exec(`CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
)`)
if err != nil {
logger.Error("create table failed", zap.Error(err))
}
// 插入数据
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := loggedDB.ExecContext(ctx,
"INSERT INTO users (name, email) VALUES (?, ?)",
"John Doe", "john@example.com")
if err != nil {
logger.Error("insert failed", zap.Error(err))
}
id, _ := result.LastInsertId()
logger.Info("inserted user", zap.Int64("id", id))
// 查询数据
var name string
err = loggedDB.QueryRow("SELECT name FROM users WHERE id = ?", id).Scan(&name)
if err != nil {
logger.Error("query failed", zap.Error(err))
}
fmt.Printf("User name: %s\n", name)
}
sqldb-logger是一个简单而强大的工具,可以帮助你轻松地记录SQL查询日志,而无需修改现有的数据库代码或标准库。