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 中开启和配置日志的常用方法。
- 开启日志记录:
当创建 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")
}
// 其他数据库操作…
}
- 调整日志级别: GORM 支持以下日志级别:
Silent
: 静默模式,不打印任何日志Error
: 只打印错误信息Warn
: 打印警告信息和错误信息Info
: 打印所有的信息
你可以通过修改 LogLevel
的值来设置这些日志级别。
3. 打印慢查询日志:
GORM 允许你设置一个阈值 SlowThreshold
,用于标识慢查询,当查询执行时间超过这个阈值时,会以日志形式打印出来。
4. 自定义日志输出:
如果你想将日志发送到其他地方,而不是标准输出(如文件、数据库或远程日志服务),可以实现 gorm.logger.Writer
接口,并将自定义的 writer 设置给 logger。
请注意,上述代码片段使用的是 GORM v2 的配置方式,如果你使用的是 GORM v1,配置方式会有所不同。日志的配置和实现可能会因库的版本更新而略
3.重写gorm
重写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.代码解读
修改slowThreshold字段默认值,可达到自定义慢sql打印目的
该方法打印文件名
trace方法实现自定义日志输出格式
标签:err,自定义,ctx,gorm,func,sql,日志,logger From: https://blog.51cto.com/u_12040959/9487061