|
|
@@ -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
|
|
|
+}
|