0
点赞
收藏
分享

微信扫一扫

如何重写gorm日志(实现自定义慢sql打印)

龙毓七七 2024-01-30 阅读 17

1.什么是gorm

GORM 是一个流行的 Go 语言 ORM(对象关系映射)库,用于处理数据库的 CRUD(创建、读取、更新、删除)操作。它是一个开源的库,通过简洁的 API 使得对数据库的操作就像操作对象一样自然,无需编写大量的 SQL 代码。GORM 支持主流的关系数据库,如 MySQL、PostgreSQL、SQLite 以及 Microsoft SQL Server。 GORM 提供了许多功能和特性,包括:

  • 自动迁移:能够帮助开发者自动在数据库中创建或更新表结构。
  • CRUD 接口:GORM 封装了数据库的增删改查操作,开发者可以像操作对象一样操作数据库。
  • 关系处理:支持一对一、一对多、多对多关系的映射和操作。
  • 钩子(Hooks):允许在进行数据库操作之前或之后执行自定义逻辑。
  • 事务支持:支持数据库事务,可以保证数据的一致性。
  • 查询链式操作:支持链式操作,让查询语句的构建变得更加灵活和便捷。
  • 预加载(Eager loading):提供预加载功能,可以在查询主对象时预先加载相关的关联对象。 例如,通过 GORM,您可以方便地将一个 Go 结构体映射到数据库表中,并通过结构体实例直接操作对应的表记录。使用 GORM 可以大大提升数据库操作的开发效率,并有助于减少 SQL 注入等安全问题。


2.gorm日志

在 GORM 中,打印日志可以帮助你追踪生成的 SQL 语句以及发生的数据库操作,这对于调试和性能监测都十分有用。GORM 提供了内置的日志接口和一个简单的日志记录器。 以下是在 GORM 中开启和配置日志的常用方法。

  1. 开启日志记录:
    当创建 GORM 数据库连接时,你可以通过调用 Logger 方法来设置日志级别。例如,你可以启用详细的日志输出,这样每次对数据库的操作都会以日志的形式打印出来。

import (
    "gorm.io/gorm"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm/logger"
)
func main() {
    // 设置新的 logger, 其中 'logger.Info' 表示打印所有日志(包括 SQL 语句)
    newLogger := logger.New(
        log.New(os.Stdout, "\r", log.LstdFlags), // io writer(日志输出到标准输出)
        logger.Config{
        SlowThreshold: time.Second,   // 慢 SQL 阈值
        LogLevel:      logger.Info,   // 日志级别
        Colorful:      true,          // 着色打印
        },
        )
        // 以新的 logger 作为参数连接数据库
        db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
        Logger: newLogger,
        })
        if err != nil {
        panic("failed to connect database")
        }
        // 其他数据库操作…
        }

  1. 调整日志级别: GORM 支持以下日志级别:
  • Silent: 静默模式,不打印任何日志
  • Error: 只打印错误信息
  • Warn: 打印警告信息和错误信息
  • Info: 打印所有的信息

你可以通过修改 LogLevel 的值来设置这些日志级别。 3. 打印慢查询日志: GORM 允许你设置一个阈值 SlowThreshold,用于标识慢查询,当查询执行时间超过这个阈值时,会以日志形式打印出来。 4. 自定义日志输出: 如果你想将日志发送到其他地方,而不是标准输出(如文件、数据库或远程日志服务),可以实现 gorm.logger.Writer 接口,并将自定义的 writer 设置给 logger。 请注意,上述代码片段使用的是 GORM v2 的配置方式,如果你使用的是 GORM v1,配置方式会有所不同。日志的配置和实现可能会因库的版本更新而略

3.重写gorm

如何重写gorm日志(实现自定义慢sql打印)_sql

重写gorm可以重新上述interface做到自定义日志打印

gorm_logger.go

package db

import (
	"context"
	"fmt"
	"gorm.io/gorm/logger"
	"runtime"
	"strconv"
	"strings"
	logger2 "support/logger"
	"support/util"
	"support/web/app"
	"time"
)

type GormLogger struct {
	logger.Config
}

func NewGormLogger(config logger.Config) *GormLogger {
	return &GormLogger{Config: config}
}

func (l *GormLogger) LogMode(level logger.LogLevel) logger.Interface {
	newLogger := *l
	newLogger.LogLevel = level
	return &newLogger
}

func (l *GormLogger) Info(ctx context.Context, s string, i ...any) {
	if l.LogLevel >= logger.Info {
		l.log(getLogger(ctx).Info, s, i)
	}
}

func (l *GormLogger) Warn(ctx context.Context, s string, i ...any) {
	if l.LogLevel >= logger.Warn {
		l.log(getLogger(ctx).Warn, s, i)
	}
}

func (l *GormLogger) Error(ctx context.Context, s string, i ...any) {
	if l.LogLevel >= logger.Error {
		l.log(getLogger(ctx).Error, s, i)
	}
}

func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
	if l.LogLevel <= logger.Silent {
		return
	}

	elapsed := time.Since(begin)
	sql, rows := fc()
	rowStr := util.If(rows == -1, "-", strconv.FormatInt(rows, 10))
	switch {
	case err != nil && l.LogLevel >= logger.Error && (!isGormErr(err, logger.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
		l.Error(ctx, "%s [%.3fms] [rows:%v] %s", err, float64(elapsed.Nanoseconds())/1e6, rowStr, sql)
	case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn:
		slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
		l.Error(ctx, "%s [%.3fms] [rows:%v] %s", slowLog, float64(elapsed.Nanoseconds())/1e6, rowStr, sql)
	case l.LogLevel == logger.Info:
		l.Info(ctx, "[%.3fms] [rows:%s] %s", float64(elapsed.Nanoseconds())/1e6, rowStr, sql)
	default:
		// do nothing
	}
}

type logFunc func(format string, v ...any)

func (l *GormLogger) log(lf logFunc, format string, v []any) {
	format = "[GORM] " + FileWithLineNum() + " " + format
	lf(format, v...)
}

// 返回文件路径和调用行号
func FileWithLineNum() string {
	// 通常从第4层开始到gorm库代码
	for i := 4; i < 15; i++ {
		_, file, line, ok := runtime.Caller(i)
		if ok && !strings.Contains(file, "gorm.io") {
			return file + ":" + strconv.Itoa(line)
		}
	}

	return ""
}

func isGormErr(err, target error) bool {
	if target == nil {
		return err == target
	}
	return err.Error() == target.Error()

}

func getLogger(ctx context.Context) logger2.ILog {
	v := ctx.Value(app.KeyLogger)
	if v == nil {
		return logger2.LogPrefix("")
	} else {
		return v.(logger2.ILog)
	}
}

mysql.go

package db

import (
	"context"
	"errors"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	mysql2 "support/database/mysql"
	logger2 "support/logger"
	"support/web/app"
	"sync"
	"time"
)

var conn *gorm.DB
var l sync.RWMutex

func getConn() *gorm.DB {
	l.RLock()
	v := conn
	l.RUnlock()
	return v
}
func setConn(v *gorm.DB) {
	l.Lock()
	conn = v
	l.Unlock()
}

var gormLogger = NewGormLogger(logger.Config{
	SlowThreshold:             time.Millisecond * 1000,
	Colorful:                  false,
	IgnoreRecordNotFoundError: true,
	LogLevel:                  logger.Info,
})

var gormConfig = &gorm.Config{
	SkipDefaultTransaction: true, // 关闭默认事务
	CreateBatchSize:        1000, // 批量插入1000
	Logger:                 gormLogger,
	NamingStrategy: schema.NamingStrategy{
		SingularTable: true, // 表名单数映射
	},
}

func Init(c *mysql2.Config) {
	if c.SlowThreshold != 0 {
		gormLogger.SlowThreshold = time.Duration(c.SlowThreshold) * time.Millisecond
	}
	// open
	db, err := gorm.Open(mysql.Open(c.MysqlUrl), gormConfig)
	if err != nil {
		panic(err)
	}
	// 设置连接池
	sqlDB, err := db.DB()
	if err != nil {
		panic(err)
	}
	sqlDB.SetMaxOpenConns(c.MaxConn)

	setConn(db)
	logger2.Info("init mysql db finished")
}

func Model(ctx context.Context, model any) *gorm.DB {
	return getConn().WithContext(ctx).Model(model)
}

func Db(ctx context.Context) *gorm.DB {
	return getConn().WithContext(ctx)
}

func Table(ctx context.Context, tbl string) *gorm.DB {
	return getConn().WithContext(ctx).Table(tbl)
}

func Ping() error {
	ch := make(chan error, 1)
	go func() {
		ch <- getConn().Exec("select 1").Error
	}()
	select {
	case err := <-ch:
		return err
	case <-time.After(15 * time.Second):
		return errors.New("ping timeout 15s")
	}
}

func WithLog(log logger2.ILog) *gorm.DB {
	ctx := context.WithValue(context.TODO(), app.KeyLogger, log)
	return getConn().WithContext(ctx)
}

4.代码解读

如何重写gorm日志(实现自定义慢sql打印)_sql_02

修改slowThreshold字段默认值,可达到自定义慢sql打印目的

如何重写gorm日志(实现自定义慢sql打印)_sql_03

该方法打印文件名

如何重写gorm日志(实现自定义慢sql打印)_sql_04

trace方法实现自定义日志输出格式

举报

相关推荐

0 条评论