浏览代码

feat: 数据库请求日志

runningwater 1 年之前
父节点
当前提交
dd71598523
共有 3 个文件被更改,包括 124 次插入4 次删除
  1. 2 2
      bootstrap/database.go
  2. 5 2
      main.go
  3. 117 0
      pkg/logger/gorm_logger.go

+ 2 - 2
bootstrap/database.go

@@ -8,11 +8,11 @@ import (
 	"github.com/runningwater/gohub/app/models/user"
 	"github.com/runningwater/gohub/pkg/config"
 	"github.com/runningwater/gohub/pkg/database"
+	"github.com/runningwater/gohub/pkg/logger"
 
 	"gorm.io/driver/mysql"
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
-	"gorm.io/gorm/logger"
 )
 
 // SetupDB 初始化数据库和 ORM
@@ -40,7 +40,7 @@ func SetupDB() {
 	}
 
 	// 连接数据库, 并设置 GORM 日志模式
-	database.Connect(dbConfig, logger.Default.LogMode(logger.Info))
+	database.Connect(dbConfig, logger.NewGormLogger())
 	// 设置最大连接数
 	database.SQLDB.SetMaxOpenConns(config.GetInt("database.mysql.max_open_connections"))
 	// 设置最大空闲连接数

+ 5 - 2
main.go

@@ -22,11 +22,14 @@ func main() {
 	flag.Parse()
 	config.InitConfig(env)
 
-	// 初始化 DB
-	bootstrap.SetupDB()
 	// 初始化 Logger
 	bootstrap.SetupLogger()
 
+	// 初始化 DB
+	// 注意: 初始化 DB 前应该先初始化 logger
+	bootstrap.SetupDB()
+
+
 	// gin 框架设置为发布模式,线上环境需要设置
 	gin.SetMode(gin.ReleaseMode)
 

+ 117 - 0
pkg/logger/gorm_logger.go

@@ -0,0 +1,117 @@
+package logger
+
+import (
+	"context"
+	"errors"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"time"
+
+	"github.com/runningwater/gohub/pkg/helpers"
+	"go.uber.org/zap"
+	"gorm.io/gorm"
+	gormlogger "gorm.io/gorm/logger"
+)
+
+// GormLogger 操作对象,实现 gormlogger.Interface 接口,用于自定义 GORM 的日志输出
+type GormLogger struct {
+	ZapLogger *zap.Logger
+	SlowThreshold time.Duration
+}
+
+// NewGormLogger 创建一个新的 GormLogger 实例
+// 该实例使用全局的 logger.Logger 对象作为日志记录器
+// 慢查询阈值为 200ms
+// 返回值: GormLogger 实例
+// 注意: 该函数不会启动 GORM 的日志记录功能,需要在 GORM 配置中手动启用
+// 例如: db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.NewGormLogger()})
+func NewGormLogger() GormLogger {
+
+	return GormLogger{
+		ZapLogger:     Logger, // 使用全局的 logger.Logger 对象
+		SlowThreshold: 200 * time.Millisecond,  // 慢查询阈值,200ms
+	}
+}
+
+// LogMode 实现 gormlogger.Interface 接口的 LogMode 方法
+func (l GormLogger) LogMode(level gormlogger.LogLevel) gormlogger.Interface {
+	return GormLogger{
+		ZapLogger:     l.ZapLogger,
+		SlowThreshold: l.SlowThreshold,
+	}
+}
+// Info 实现 gormlogger.Interface 接口的 Info 方法
+func (l GormLogger) Info(_ context.Context, str string, args ...any) {
+	l.logger().Sugar().Debugf(str, args...)
+}
+// Warn 实现 gormlogger.Interface 接口的 Warn 方法
+func (l GormLogger) Warn(_ context.Context, str string, args ...any) {
+	l.logger().Sugar().Warnf(str, args...)
+}
+// Error 实现 gormlogger.Interface 接口的 Error 方法
+func (l GormLogger) Error(_ context.Context, str string, args ...any) {
+	l.logger().Sugar().Errorf(str, args...)
+}
+// Trace 实现 gormlogger.Interface 接口的 Trace 方法
+func (l GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
+
+	// 计算执行时间
+	elapsed := time.Since(begin)
+	// 获取 SQL 和影响行数
+	sql, rows := fc()
+
+	// 通用字段
+	logFields := []zap.Field{
+		zap.String("sql", sql),
+		zap.Int64("rows", rows),
+		zap.String("time", helpers.MicrosecondsStr(elapsed)),
+	}
+
+	// Gorm 错误
+	if err != nil {
+		// 记录未找到的错误使用 warning 等级
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			l.logger().Warn("Database ErrRecordNotFound", logFields...)
+		} else {
+			// 其他错误使用 error 等级
+			logFields = append(logFields, zap.Error(err))
+			l.logger().Error("Database Error", logFields...)
+		}
+	}
+
+	// 慢查询日志
+	if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
+		l.logger().Warn("Database Slow Log", logFields...)
+	}
+
+	// 记录所有 SQL 请求 ,使用 debug 等级
+	l.logger().Debug("Database Query", logFields...)
+}
+
+
+// logger 内部方法,确保 Zap 内置信息 Caller 的准确性
+// 返回值: *zap.Logger 对象
+func (l GormLogger) logger() *zap.Logger {
+	var (
+		gormPackage    =  filepath.Join("gorm.io", "gorm")
+		zapGormPackage =  filepath.Join("moul.io", "zapgorm2")
+	)
+
+	// 减去一次封装,以及一次在 logger 初始化中添加 zap.AddCallerSkip(1)
+	clone := l.ZapLogger.WithOptions(zap.AddCallerSkip(-2))
+
+	for i := 2; i < 15; i++ {
+		_, file, _, ok := runtime.Caller(i)
+		switch {
+		case !ok:
+		case strings.HasSuffix(file, "_test.go"):
+		case strings.Contains(file, gormPackage):
+		case strings.Contains(file, zapGormPackage):
+		default:
+			// 返回一个附带跳过行号的 zap.Logger 
+			return clone.WithOptions(zap.AddCallerSkip(i))
+		}
+	}
+	return l.ZapLogger
+}