博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
gorm中关于sql日志记录 golang
阅读量:3986 次
发布时间:2019-05-24

本文共 3790 字,大约阅读时间需要 12 分钟。

记录sql的慢查询日志和错误日志是很有必要的。

gorm 版本 gorm.io/gorm v1.20.1

gorm提供了默认的logger实现:

if config.Logger == nil {
config.Logger = logger.Default}Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{
SlowThreshold: 100 * time.Millisecond, LogLevel: Warn, Colorful: true,})

可以看出默认logger的特点:

1、基于标准输出的。2、慢sql的标准为 100毫秒。3、彩色输出。4、日期时间的格式为 2020/10/23 11:04:22 。5、前缀为 \r\n 。

显然这种默认的logger是不能满足需求的,因此需要定义自己的Logger。

使用 gorm.io/gorm/logger 下的 New 方法来实例化一个 logger 。

func New(writer Writer, config Config) Interface type Writer interface {
Printf(string, ...interface{
})}

需要一个实现了 Printf(string, ...interface{}) 方法的对象。

正好golang提供的 log 包就可以作为一个 Writer,而且我的日志组件也是对 log 包的封装,

于是可以这样:

newLogger := logger.New(	logging.Logger,	logger.Config{
SlowThreshold: 1 * time.Second, LogLevel: logger.Warn, Colorful: false, },)

创建连接

gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true, // 禁用默认事务 Logger: newLogger, PrepareStmt: true, // 创建并缓存预编译语句})

这样 gorm 所有的日志就都会以日志的形式记录到日志文件。

需要注意的是 gorm 的日志只是使用了日志组件的资源,即:

logging.Logger也就是 logging 包中的var Logger     *log.Logger...F, err = file.MustOpen(fileName, filePath)...Logger = log.New(F, DefaultPrefix, log.LstdFlags)

所以 gorm 写入日志只是调用了 log.Printf ,而饼没有经过 logging 封装的方法,这就导致 gorm 写入的日志的日志等级和前缀没有办法设置,于是就是上一次写入日志时设置的日志等级和前缀,而日志组件是系统启动是就启动的,也就是说所以的goroutine共用的。但是这个关系也不大,因为 gorm 的日志内容里还有自己的错误级别,你可以据此判断错误情况。

所有的sql语句都是由 callbacks.go 下的 Execute 方法来执行的,在这个方法中执行的日志记录

db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
return db.Dialector.Explain(stmt.SQL.String(), stmt.Vars...), db.RowsAffected}, db.Error)

对应的就是 logger 下面的 Trace 方法:

func (l logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if l.LogLevel > 0 {
elapsed := time.Since(begin) switch {
case err != nil && l.LogLevel >= Error: sql, rows := fc() l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql) case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= Warn: sql, rows := fc() l.Printf(l.traceWarnStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) case l.LogLevel >= Info: sql, rows := fc() l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) } }}

在这个方法中你可以看出在哪些情况下会记录日志。

这三个 case 中都会记录sql语句,1和2是异常的情况,3是正常的情况。

对于慢sql日志,需要设置 SlowThreshold 大于0的数,且 LogLevel >= Warn

对于err,需要设置 LogLevel >= Error

注意 gorm 的日志级别递进关系:

Silent 	1Error	2Warn	3Info	4

所以,如果想要所有的sql都会被记录下来,那么 LogLevel 应该设置为 Info 。

如果只是想调试单个操作的sql的话,可以连接上一个 Debug() 操作:

db.Debug().Where("name = ?", "jinzhu").First(&User{
})

我们来看一下 Debug() 方法:

func (db *DB) Debug() (tx *DB) {
return db.Session(&Session{
WithConditions: true, Logger: db.Logger.LogMode(logger.Info), })}

很明显它是一次性的。

指的注意的是,当我们查询单条记录的时候,我们通过 ErrRecordNotFound 来判断是否存在,为什么会这样呢?

我们来看一下 scan.go 文件,它是将查询出的结果放入目标对象,而由于golang变量的零值的存在,所以不好判断是否查询到结果,于是就定义了一个err:

...if db.RowsAffected == 0 && db.Statement.RaiseErrorOnNotFound {
db.AddError(ErrRecordNotFound)}

然后来看 finisher_api.go 文件,着里面定义查询单条记录的方法,First, Take, Last,它们都将 Statement.RaiseErrorOnNotFound 设置为 true 啦:

func (db *DB) First(dest interface{
}, conds ...interface{
}) (tx *DB) {
tx = db.Limit(1).Order(clause.OrderByColumn{
Column: clause.Column{
Table: clause.CurrentTable, Name: clause.PrimaryKey}, }) if len(conds) > 0 {
tx.Statement.AddClause(clause.Where{
Exprs: tx.Statement.BuildCondition(conds[0], conds[1:]...)}) } tx.Statement.RaiseErrorOnNotFound = true tx.Statement.Dest = dest tx.callbacks.Query().Execute(tx) return}

通过以上分析,在开发环境 LogLevel 设置为 Info,线上环境设置为 Warm 。

但是存在一个问题,那就是当查询单条记录不存在时,会将sql记录下来,这个设计的初衷时什么?当然,你可以不查询单条,改成 Find 查询多条,最后判断切片的长度,取第0个。

转载地址:http://njaui.baihongyu.com/

你可能感兴趣的文章
i.mx53开发板挂载NFS
查看>>
驱动调试前期准备工作
查看>>
i.mx53 linux led driver
查看>>
杂七杂八的
查看>>
linux spi
查看>>
linux spi dev test program
查看>>
Overview of Linux kernel SPI support
查看>>
i.mx53 uboot
查看>>
linux 驱动开发 头文件
查看>>
嵌入式linux 开发板 dhcp ip
查看>>
/etc/resolv.conf
查看>>
/etc/hosts
查看>>
container_of()传入结构体中的成员,返回该结构体的首地址
查看>>
linux sfdisk partition
查看>>
ipconfig,ifconfig,iwconfig
查看>>
opensuse12.2 PL2303 minicom
查看>>
电平触发方式和边沿触发的区别
查看>>
中断函数中不能调用ioremap()!!!!!!!
查看>>
网络视频服务器移植
查看>>
Encoding Schemes
查看>>