首页 > 数据库 >如何重写gorm日志(实现自定义慢sql打印)

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

时间:2024-01-30 14:31:37浏览次数:33  
标签:err 自定义 ctx gorm func sql 日志 logger

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方法实现自定义日志输出格式

标签:err,自定义,ctx,gorm,func,sql,日志,logger
From: https://blog.51cto.com/u_12040959/9487061

相关文章

  • [转帖]Oracle获取被锁的SQL源头
    https://blog.csdn.net/weixin_42233789转载:https://blog.csdn.net/robinson1988/article/details/106204387各位DBA,看到这篇文章是不是很开心,解决了你一个大麻烦,赶紧把它部署到实时监控程序吧(咳咳,转载,抄袭不注明文章出处的人可耻哈)session1:updateemp_baksetename=......
  • MySQL建索引报错:BLOB/TEXT column used in key specification without a key length
    MySQL建索引报错:BLOB/TEXTcolumnusedinkeyspecificationwithoutakeylength因为text类型的字段值太长,没办法为全部内容建立索引,只能指定前多少位字符建立索引;就像这样createindex`索引名`on表名(字段名(600));所以能用varchar能放下的尽量使用varchar吧......
  • 在 Windows 平台下安装与配置 MySQL 5.7.36
    Windows一般使用两种MySQL安装方式,即MySQL二进制分发版(.msi安装文件)和免安装版(.zip压缩文件)。一般来讲,应当使用二进制分发版,因为该版本比其他的分发版使用起来要简单,不再需要其他工具来启动就可以运行MySQL。本次实验是在Windows10平台上选用图形化的二进制安装方式,其他W......
  • html 自定义 checkbox样式
    input[type=checkbox]{appearance:none;-webkit-appearance:none;-moz-appearance:none;width:20px;height:20px;border:1pxsolid#ff6a00;background-color:white;line-height:20px;......
  • [转帖]SQL SERVER--- 排序规则、数据类型
    https://zhuanlan.zhihu.com/p/162933497 一、排序规则有时候我们向数据库插入文本时,会出现乱码“?”,这时有可能是我们创建数据库没有设置好排序规则以Chinese_PRC_CI_AS为例前半部分Chinese_PRC指的是针对大陆简体字unicode的排序规则后半部分的含义为:_BIN二进......
  • 必备SQL和表关系
    必备SQL和表关系1.必备SQL创建数据createtabledepart( idint(4)notnullauto_incrementprimarykey,titlevarchar(20)notnull);createtableinfo( idint(4)notnullauto_incrementprimarykey,namevarchar(20)notnull,emallvarcha......
  • python操作mysql
    python操作mysql1.数据库连接池在操作数据库时需要使用数据库连接池。pip3.9installpymysql#安装pymysqlpip3.9installdbutils#安装dbutilsimportthreadingimportpymysqlfromdbutils.pooled_dbimportPooledDBMYSQL_DB_POOL=PooledDB(creator=pym......
  • SQL变量数据加工在Java规则引擎中的应用案例分析
    SQL变量加工SQL加工背景,在决策配置过程中,一些复杂的逻辑或模型可通过自定义SQL脚本编写创建数据变量,通过SQL脚本可以便捷的从数据库中取数,并且自定义SQL支持传参,可满足更复杂多变的数据加工处理。注意,SQL变量加工和算子编排加工的方式不同,SQL变量加工依赖于对应数据源的服务器的性......
  • MySQL查看bin_log日志
    有这样一段业务逻辑,首先保存业务数据,然后发送报文,最后确认报文回来以后更新业务数据。伪代码大概是这样的:/***保存数据,并调用发送报文方法*/publicvoidsave(){//0.保存数据//调用send()方法send();}/***发送报文*/publicvoidsend(){/......
  • PLSQL Developer汉语设置
    PLSQLQDeveloper是由Oracle公司推出的数据库开发工具,具有很好的移植性和适应性.但是当我们安装完成Oracle11gPLSQLDeveloper工具后发现状态栏的显示是英文,对于大多数人来说都是看不懂,如果长期使用的话或许还能接受,对于小新真的十分困难,下面就是如何将PLSQLDeveloper工具设置为......