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 }