当前位置: 首页 > article >正文

GORM慢查询、SQL日志与Go项目日志的整合与串联

上节内容我们详细讲解了Go项目中GORM的下载安装、初始化、读写分离和多数据源方面的内容。仅就功能来说做完这些工作后项目对Gorm的集成已经完成了,实际上有些项目确实也是这样做的。上篇文章在末尾我们提了句如果仅集成到这个程度,功能开发完全没有问题,但如果你还要长期维护项目的话,那么问题可大了去了。

原因是,GORM产生的SQL记录、慢查询、以及数据库错误都是通过GORM自身的GormLogger写到日志的,而且GormLogger默认是写到标准输出的。。。而不是项目本身的日志文件里,这样一来如果真的是SQL错误、慢查询等导致的Bug,你只能知道发生Error了,但是在项目日志里却找不到Error的明细,那你解决BUG就只能靠猜和试啦。

eaa23651989f9534a6606e0fae7e4e51.png

所以这节内容我们介绍怎么把GORM日志和项目日志整合到一起,让你拥有下面这样的SQL详细信息

{
    "level": "debug",
    "ts": "2024-10-09T17:09:07.603+0800",
    "msg": "SQL DEBUG",
    "sql": "INSERT INTO `orders` (`user_id`,`bill_money`,`order_no`,`state`,`is_del`,`created_at`,`updated_at`) VALUES (123453453,20,'20240627596615375920904456',1,0,'2024-10-09 17:09:07.586','2024-10-09 17:09:07.586')",
    "rows": 1,
    "dur(ms)": 53,
    "traceid": "19d822280c64c5ed",
    "spanid": "450ccc402492ed45",
    "pspanid": "",
    "file": "gormlog.go",
    "line": 49
}

支持慢查询、SQL错误的详细记录,在开发环境还会增加SQL DEBUG输出把执行的SQL语句输出到日志文件和控制台,方便在开发阶段进行调试和验证。

我们同样会为GORM日志注入追踪ID,把它们归因到关联的请求上下文中去。这样一旦发生数据库相关的错误,我们可以通过监控主动发现、使用错误日志中的追踪ID还能把整个请求相关的日志都检索出来,查问题也会轻松很多。

本节讲述的一些技能能极大提高项目的可观测性,有助于我们更高效地定位项目Bug的原因,有兴趣的赶快扫码订阅起来吧。

ecd949b2d6f0826f78f68c4882e770be.png

加入项目后访问https://github.com/go-study-lab/go-mall/compare/c6...c7 能查看我们在项目中定制化GORM的整个过程

f6cc1e4ab24da87f256a1231b1297265.png

Gorm Logger Interface

GORM 允许我们自定义Logger把它执行的数据库记录、产生的错误等都记录到项目自身的日志文件中去,

V1版本的GROM的 logger 接口长这个样子,仅仅提供了一个Print方法,Print方法的参数都是 create、updates、query 这些方法的回调中传递过去的,我们并没有办法传递Context。

type logger interface {
 Print(v ...interface{})
}

所以前几年的项目流行使用GLS -- Goroutine Local Storage,因为官方不推荐也不认可GLS这个概念,GLS的实现都是第三方库,前几年我之前公司项目自己封装的Logger里就大量使用 jtolio/gls 这个库实现的GLS。

后来做新项目时我曾经还想偷懒直接使用之前公司的Logger,结果发现Go 1.19 版本以上 jtolio/gls 这个库就不能用了。

jtolio/gls 仓库地址: https://github.com/jtolio/gls

好在GORM在V2 版本的Logger 中增加了Context 回调的能力,这样就能把执行SQL时的上下文信息也记录到日志中来,这点在V1 版本是做不到的。

在GORM V2 中它新增了以下Logger 接口 logger.Interface:

type Interface interface {  
   LogMode(LogLevel) Interface  
   Info(context.Context, string, ...interface{})  
   Warn(context.Context, string, ...interface{})  
   Error(context.Context, string, ...interface{})  
   Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)  
}

每个方法都有应用的上下文Context参数传递进来,还专门提供了Trace方法让我们实现,供我们实现查询的SQL语句和消耗时间的记录。

接下来我们开始自定义GORM Logger,其中使用上我们前面已经封装好的项目的日志门面,通过日志门面实现把GORM日志和项目日志记录到同一个地方。

实现GormLogger 把GORM日志整合到项目日志

现在我们在项目的 dal/dao 目录下新建gormlog.go 文件,并添加以下代码。

type GormLogger struct {
 SlowThreshold time.Duration
}

func NewGormLogger() *GormLogger {
 return &GormLogger{
  SlowThreshold: 500 * time.Millisecond,
}

新建GormLogger类型,在类型中的字段 SlowThreshold 用于设置慢查询的门槛,我们在这里设置超过500ms 就是慢查询,如果需要按环境定制化, 就把这个做成配置项放到配置文件中去。

接下来用GormLogger 实现上面GORM的logger.Interface 中定义的所有方法:

func (l *GormLogger) LogMode(lev gormLogger.LogLevel) gormLogger.Interface {
 return &GormLogger{}
}
func (l *GormLogger) Info(ctx context.Context, msg string, data ...interface{}) {
 logger.New(ctx).Info(msg, "data", data)
}
func (l *GormLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
 logger.New(ctx).Error(msg, "data", data)
}
func (l *GormLogger) Error(ctx context.Context, msg string, data ...interface{}) {
 logger.New(ctx).Error(msg, "data", data)
}
func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
 // 获取运行时间
 duration := time.Since(begin).Milliseconds()
 // 获取 SQL 语句和返回条数
 sql, rows := fc()
 // Gorm 错误时记录错误日志
 if err != nil {
  logger.New(ctx).Error("SQL ERROR", "sql", sql, "rows", rows, "dur(ms)", duration)
 }
 // 慢查询日志
 if duration > l.SlowThreshold.Milliseconds() {
  logger.New(ctx).Warn("SQL SLOW", "sql", sql, "rows", rows, "dur(ms)", duration)
 } else {
  logger.New(ctx).Debug("SQL DEBUG", "sql", sql, "rows", rows, "dur(ms)", duration)
 }
}

注意因为GORM的logger包跟我们自己的日志门面 logger 包名称冲突了,所以在导入包的时候要给它设置gormLogger这个别名。

上面的实现主要的逻辑是对Trace方法的重写,GORM在执行SQL回调Trace方法时,会把执行开始的时间、执行的SQL、返回的函数以及执行中的错误告诉我们。如果执行中发生错误就记录错误日志,如果消耗时间超过我们约定的500ms就记一条Warn级别的日志。

为 GormLogger 注入追踪信息

为GORM日志注入traceId是关键操作,能让我们把它产生的日志归因到对应的请求上下文产生的日志中去,这部分请扫码订阅后阅读完整版。

517f2ba7248b7fbf2756c46231f6fc79.png

因为代码量较大,有些测试用的代码没有放上来,大家加入项目后可以从测试方法的路由开始往下走一遍代码。

同时加入项目后访问https://github.com/go-study-lab/go-mall/compare/c6...c7 也能查看我们在项目中定制化GORM的整个过程

c93180d91f3a149dcd997d62ec148c8d.png

专栏分为五大部分,目前已经更新30多节教程,主要内容架构如下:

ae248758a1ff91cc5a95b363f7183d9e.png
  • 第一部分介绍让框架变得好用的诸多实战技巧,比如通过自定义日志门面让项目日志更简单易用、支持自动记录请求的追踪信息和程序位置信息、通过自定义Error在实现Go error接口的同时支持给给错误添加错误链,方便追溯错误源头。

  • 第二部分:讲解项目分层架构的设计和划分业务模块的方法和标准,让你以后无论遇到什么项目都能按这套标准自己划分出模块和逻辑分层。后面几个部分均是该部分所讲内容的实践。

  • 第三部分:设计实现一个套支持多平台登录,Token泄露检测、同平台多设备登录互踢功能的用户认证体系,这套用户认证体系既可以在你未来开发产品时直接应用

  • 第四部分:商城app C端接口功能的实现,强化分层架构实现的讲解,这里还会讲解用责任链、策略和模版等设计模式去解决订单结算促销、支付方式支付场景等多种多样的实际问题。

  • 第五部分:单元测试、项目Docker镜像、K8s部署和服务保障相关的一些基础内容和注意事项

扫描上方的海报二维码或者访问 https://xiaobot.net/p/golang 即刻订阅。


http://www.kler.cn/a/415910.html

相关文章:

  • #Js篇: 链式判断运算符 ?.和Null判断运算符 ??和逻辑赋值运算符||= = ??=
  • 领养我的宠物:SpringBoot开发指南
  • Cesium K-means自动聚合点的原理
  • 史陶比尔机器人维修-接口总结
  • Mac启动服务慢问题解决,InetAddress.getLocalHost().getHostAddress()慢问题。
  • 3.24MayBeSomeComputeC
  • 如何通过PHP爬虫模拟表单提交,抓取隐藏数据
  • USB Type-C一线通扩展屏:多场景应用,重塑高效办公与极致娱乐体验
  • 26页PDF | 数据中台能力框架及评估体系解读(限免下载)
  • linux-信号
  • springboot339javaweb的新能源充电系统pf(论文+源码)_kaic
  • nginx同一域名下部署多个项目
  • 【动态规划】股票市场交易策略优化
  • hhdb数据库介绍(10-17)
  • Kylin Server V10 下 Nacos 集群部署
  • KST-3D01型胎儿超声仿真体模、吸声材料以及超声骨密度仪用定量试件介绍
  • 总结UiPath Studio的介绍与安装步骤
  • DETR:End-to-End Object Detection with Transformers
  • Android触摸事件setOnTouchListener用法
  • 各个排序算法基础速通万字介绍