gorm_logger.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package logger
  2. import (
  3. "context"
  4. "errors"
  5. "path/filepath"
  6. "runtime"
  7. "strings"
  8. "time"
  9. "github.com/runningwater/gohub/pkg/helpers"
  10. "go.uber.org/zap"
  11. "gorm.io/gorm"
  12. gormlogger "gorm.io/gorm/logger"
  13. )
  14. // GormLogger 操作对象,实现 gormlogger.Interface 接口,用于自定义 GORM 的日志输出
  15. type GormLogger struct {
  16. ZapLogger *zap.Logger
  17. SlowThreshold time.Duration
  18. }
  19. // NewGormLogger 创建一个新的 GormLogger 实例
  20. // 该实例使用全局的 logger.Logger 对象作为日志记录器
  21. // 慢查询阈值为 200ms
  22. // 返回值: GormLogger 实例
  23. // 注意: 该函数不会启动 GORM 的日志记录功能,需要在 GORM 配置中手动启用
  24. // 例如: db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.NewGormLogger()})
  25. func NewGormLogger() GormLogger {
  26. return GormLogger{
  27. ZapLogger: Logger, // 使用全局的 logger.Logger 对象
  28. SlowThreshold: 200 * time.Millisecond, // 慢查询阈值,200ms
  29. }
  30. }
  31. // LogMode 实现 gormlogger.Interface 接口的 LogMode 方法
  32. func (l GormLogger) LogMode(level gormlogger.LogLevel) gormlogger.Interface {
  33. return GormLogger{
  34. ZapLogger: l.ZapLogger,
  35. SlowThreshold: l.SlowThreshold,
  36. }
  37. }
  38. // Info 实现 gormlogger.Interface 接口的 Info 方法
  39. func (l GormLogger) Info(_ context.Context, str string, args ...any) {
  40. l.logger().Sugar().Debugf(str, args...)
  41. }
  42. // Warn 实现 gormlogger.Interface 接口的 Warn 方法
  43. func (l GormLogger) Warn(_ context.Context, str string, args ...any) {
  44. l.logger().Sugar().Warnf(str, args...)
  45. }
  46. // Error 实现 gormlogger.Interface 接口的 Error 方法
  47. func (l GormLogger) Error(_ context.Context, str string, args ...any) {
  48. l.logger().Sugar().Errorf(str, args...)
  49. }
  50. // Trace 实现 gormlogger.Interface 接口的 Trace 方法
  51. func (l GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
  52. // 计算执行时间
  53. elapsed := time.Since(begin)
  54. // 获取 SQL 和影响行数
  55. sql, rows := fc()
  56. // 通用字段
  57. logFields := []zap.Field{
  58. zap.String("sql", sql),
  59. zap.Int64("rows", rows),
  60. zap.String("time", helpers.MicrosecondsStr(elapsed)),
  61. }
  62. // Gorm 错误
  63. if err != nil {
  64. // 记录未找到的错误使用 warning 等级
  65. if errors.Is(err, gorm.ErrRecordNotFound) {
  66. l.logger().Warn("Database ErrRecordNotFound", logFields...)
  67. } else {
  68. // 其他错误使用 error 等级
  69. logFields = append(logFields, zap.Error(err))
  70. l.logger().Error("Database Error", logFields...)
  71. }
  72. }
  73. // 慢查询日志
  74. if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
  75. l.logger().Warn("Database Slow Log", logFields...)
  76. }
  77. // 记录所有 SQL 请求 ,使用 debug 等级
  78. l.logger().Debug("Database Query", logFields...)
  79. }
  80. // logger 内部方法,确保 Zap 内置信息 Caller 的准确性
  81. // 返回值: *zap.Logger 对象
  82. func (l GormLogger) logger() *zap.Logger {
  83. var (
  84. gormPackage = filepath.Join("gorm.io", "gorm")
  85. zapGormPackage = filepath.Join("moul.io", "zapgorm2")
  86. )
  87. // 减去一次封装,以及一次在 logger 初始化中添加 zap.AddCallerSkip(1)
  88. clone := l.ZapLogger.WithOptions(zap.AddCallerSkip(-2))
  89. for i := 2; i < 15; i++ {
  90. _, file, _, ok := runtime.Caller(i)
  91. switch {
  92. case !ok:
  93. case strings.HasSuffix(file, "_test.go"):
  94. case strings.Contains(file, gormPackage):
  95. case strings.Contains(file, zapGormPackage):
  96. default:
  97. // 返回一个附带跳过行号的 zap.Logger
  98. return clone.WithOptions(zap.AddCallerSkip(i))
  99. }
  100. }
  101. return l.ZapLogger
  102. }