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

1 回复

更多关于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支持多种日志库适配器:

  1. Zap适配器 (推荐)

    import "github.com/simukti/sqldb-logger/logadapter/zapadapter"
    
    logger := zapadapter.New(zapLogger)
    
  2. Logrus适配器

    import "github.com/simukti/sqldb-logger/logadapter/logrusadapter"
    
    logger := logrusadapter.New(logrusLogger)
    
  3. 标准库log适配器

    import "github.com/simukti/sqldb-logger/logadapter/stdlogadapter"
    
    logger := stdlogadapter.New(stdLogger)
    
  4. 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接口上添加了一个薄薄的包装层。但在生产环境中,你可能需要考虑:

  1. 只在调试时启用详细的SQL日志记录
  2. 使用异步日志记录减少I/O阻塞
  3. 避免在生产环境中记录敏感数据

完整示例

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查询日志,而无需修改现有的数据库代码或标准库。

回到顶部