github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/gorm.go (about) 1 package xlog 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 "sync/atomic" 9 "time" 10 11 "go.uber.org/zap" 12 "go.uber.org/zap/zapcore" 13 glogger "gorm.io/gorm/logger" 14 gutils "gorm.io/gorm/utils" 15 ) 16 17 var _ glogger.Interface = (*GormXLogger)(nil) 18 19 type GormXLogger struct { 20 logger XLogger 21 cfg *glogger.Config 22 dynamicLevelEnabler zap.AtomicLevel 23 gormLevel int32 24 } 25 26 func (l *GormXLogger) LogMode(lvl glogger.LogLevel) glogger.Interface { 27 atomic.StoreInt32(&l.gormLevel, int32(lvl)) 28 l.dynamicLevelEnabler.SetLevel(getLogLevelOrDefaultForGorm(lvl)) 29 return l 30 } 31 32 func (l *GormXLogger) Info(ctx context.Context, msg string, data ...any) { 33 if glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) >= glogger.Info { 34 l.logger.InfoContext(ctx, fmt.Sprintf(msg, data...), zap.String("fileAndLine", gutils.FileWithLineNum())) 35 } 36 } 37 38 func (l *GormXLogger) Warn(ctx context.Context, msg string, data ...any) { 39 if glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) >= glogger.Warn { 40 l.logger.WarnContext(ctx, fmt.Sprintf(msg, data...), zap.String("fileAndLine", gutils.FileWithLineNum())) 41 } 42 } 43 44 func (l *GormXLogger) Error(ctx context.Context, msg string, data ...any) { 45 if glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) >= glogger.Error { 46 l.logger.ErrorContext(ctx, nil, fmt.Sprintf(msg, data...), zap.String("fileAndLine", gutils.FileWithLineNum())) 47 } 48 } 49 50 func (l *GormXLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { 51 if glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) <= glogger.Silent { 52 return 53 } 54 55 elapsed := time.Since(begin) 56 switch { 57 case err != nil && glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) >= glogger.Error && (!errors.Is(err, glogger.ErrRecordNotFound) || l.cfg.IgnoreRecordNotFoundError): 58 sql, rows := fc() 59 if rows <= -1 { 60 l.logger.ErrorContext(ctx, err, "error trace", 61 zap.String("fileAndLine", gutils.FileWithLineNum()), 62 zap.String("rows", "-"), 63 zap.Int64("elapsedMs", elapsed.Milliseconds()), 64 zap.String("sql", sql), 65 ) 66 } else { 67 l.logger.ErrorContext(ctx, err, "error trace", 68 zap.String("fileAndLine", gutils.FileWithLineNum()), 69 zap.String("rows", strconv.FormatInt(rows, 10)), 70 zap.Int64("elapsedMs", elapsed.Milliseconds()), 71 zap.String("sql", sql), 72 ) 73 } 74 case elapsed > l.cfg.SlowThreshold && l.cfg.SlowThreshold != 0 && glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) >= glogger.Warn: 75 sql, rows := fc() 76 if rows <= -1 { 77 l.logger.WarnContext(ctx, "slow sql", 78 zap.Int64("thresholdMs", l.cfg.SlowThreshold.Milliseconds()), 79 zap.String("fileAndLine", gutils.FileWithLineNum()), 80 zap.String("rows", "-"), 81 zap.Int64("elapsedMs", elapsed.Milliseconds()), 82 zap.String("sql", sql), 83 ) 84 } else { 85 l.logger.WarnContext(ctx, "slow sql", 86 zap.Int64("thresholdMs", l.cfg.SlowThreshold.Milliseconds()), 87 zap.String("fileAndLine", gutils.FileWithLineNum()), 88 zap.String("rows", strconv.FormatInt(rows, 10)), 89 zap.Int64("elapsedMs", elapsed.Milliseconds()), 90 zap.String("sql", sql), 91 ) 92 } 93 case glogger.LogLevel(atomic.LoadInt32(&l.gormLevel)) == glogger.Info: 94 sql, rows := fc() 95 if rows <= -1 { 96 l.logger.InfoContext(ctx, "common sql info", 97 zap.String("fileAndLine", gutils.FileWithLineNum()), 98 zap.String("rows", "-"), 99 zap.Int64("elapsedMs", elapsed.Milliseconds()), 100 zap.String("sql", sql), 101 ) 102 } else { 103 l.logger.InfoContext(ctx, "common sql info", 104 zap.String("fileAndLine", gutils.FileWithLineNum()), 105 zap.String("rows", strconv.FormatInt(rows, 10)), 106 zap.Int64("elapsedMs", elapsed.Milliseconds()), 107 zap.String("sql", sql), 108 ) 109 } 110 } 111 } 112 113 func NewGormXLogger(logger XLogger, opts ...GormXLoggerOption) *GormXLogger { 114 gl := &GormXLogger{ 115 cfg: &glogger.Config{}, 116 } 117 for _, o := range opts { 118 o(gl.cfg) 119 } 120 if gl.cfg.SlowThreshold <= 0 { 121 gl.cfg.SlowThreshold = 500 * time.Millisecond 122 } 123 gl.gormLevel = int32(gl.cfg.LogLevel) 124 gl.dynamicLevelEnabler = zap.NewAtomicLevelAt(getLogLevelOrDefaultForGorm(gl.cfg.LogLevel)) 125 126 l := &xLogger{} 127 l.logger.Store(logger. 128 zap(). 129 Named("Gorm"). 130 WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { 131 if core == nil { 132 panic("[XLogger] core is nil") 133 } 134 cc, ok := core.(xLogCore) 135 if !ok { 136 panic("[XLogger] core is not XLogCore") 137 } 138 var err error 139 if mc, ok := cc.(xLogMultiCore); ok && mc != nil { 140 if cc, err = WrapCoresNewLevelEnabler(mc, 141 gl.dynamicLevelEnabler, 142 componentCoreEncoderCfg(), 143 ); err != nil { 144 panic(err) 145 } 146 } else { 147 if cc, err = WrapCoreNewLevelEnabler(cc, 148 gl.dynamicLevelEnabler, 149 componentCoreEncoderCfg(), 150 ); err != nil { 151 panic(err) 152 } 153 } 154 return cc 155 })), 156 ) 157 gl.logger = l 158 return gl 159 } 160 161 func getLogLevelOrDefaultForGorm(lvl glogger.LogLevel) zapcore.Level { 162 switch lvl { 163 case glogger.Info: 164 return zapcore.InfoLevel 165 case glogger.Warn: 166 return zapcore.WarnLevel 167 case glogger.Error: 168 return zapcore.ErrorLevel 169 case glogger.Silent: 170 fallthrough 171 default: 172 return zapcore.DebugLevel 173 } 174 } 175 176 type GormXLoggerOption func(*glogger.Config) 177 178 func WithGormXLoggerSlowThreshold(threshold time.Duration) GormXLoggerOption { 179 return func(cfg *glogger.Config) { 180 cfg.SlowThreshold = threshold 181 } 182 } 183 184 func WithGormXLoggerLogLevel(lvl glogger.LogLevel) GormXLoggerOption { 185 return func(cfg *glogger.Config) { 186 cfg.LogLevel = lvl 187 } 188 } 189 190 func WithGormXLoggerIgnoreRecord404Err() GormXLoggerOption { 191 return func(cfg *glogger.Config) { 192 cfg.IgnoreRecordNotFoundError = true 193 } 194 } 195 196 func WithGormXLoggerParameterizedQueries() GormXLoggerOption { 197 return func(cfg *glogger.Config) { 198 cfg.ParameterizedQueries = true 199 } 200 }