package database import ( "context" "errors" "fmt" "time" gormlogger "gorm.io/gorm/logger" "qoobing.com/gomod/log" ) // ErrRecordNotFound record not found error var ErrRecordNotFound = errors.New("record not found") // Colors const ( Reset = "\033[0m" Red = "\033[31m" Green = "\033[32m" Yellow = "\033[33m" Blue = "\033[34m" Magenta = "\033[35m" Cyan = "\033[36m" White = "\033[37m" BlueBold = "\033[34;1m" MagentaBold = "\033[35;1m" RedBold = "\033[31;1m" YellowBold = "\033[33;1m" ) // LogLevel log level type LogLevel = gormlogger.LogLevel const ( // Silent silent log level Silent LogLevel = iota + 1 // Error error log level Error // Warn warn log level Warn // Info info log level Info ) // LogConfig logger config type LogConfig struct { SlowThreshold time.Duration Colorful bool IgnoreRecordNotFoundError bool ParameterizedQueries bool LogLevel LogLevel } var ( // // Discard logger will print any log to io.Discard // Discard = New(log.New(io.Discard, "", log.LstdFlags), Config{}) // // defaultLogger defaultLogger logger // defaultLogger = New(LogConfig{ SlowThreshold: 200 * time.Millisecond, LogLevel: Info, IgnoreRecordNotFoundError: false, Colorful: true, }) // defautRecorder logger records running SQL into a recorder instance defautRecorder = traceRecorder{Interface: defaultLogger, BeginAt: time.Now()} ) // New initialize logger func New(config LogConfig) gormlogger.Interface { var ( infoStr = "%s\n[info] " warnStr = "%s\n[warn] " errStr = "%s\n[error] " traceStr = "%s\n[%.3fms] [rows:%v] %s" traceWarnStr = "%s\n[%.3fms] [rows:%v] %s" traceErrStr = "%s\n[%.3fms] [rows:%v] %s" ) if config.Colorful { infoStr = Green + "%s\n" + Reset + Green + "[info] " + Reset warnStr = BlueBold + "%s\n" + Reset + Magenta + "[warn] " + Reset errStr = Magenta + "%s\n" + Reset + Red + "[error] " + Reset traceStr = Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s" traceWarnStr = Yellow + "%s\n" + Reset + RedBold + "[%.3fms] " + Yellow + "[rows:%v]" + Magenta + " %s" + Reset traceErrStr = MagentaBold + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s" } baseLogger := *log.New("gorm") baseLogger.SetCalldepth(3) return &logger{ baseLogger: baseLogger, LogConfig: config, infoStr: infoStr, warnStr: warnStr, errStr: errStr, traceStr: traceStr, traceWarnStr: traceWarnStr, traceErrStr: traceErrStr, } } type logger struct { LogConfig baseLogger log.Logger infoStr, warnStr, errStr string traceStr, traceErrStr, traceWarnStr string } // LogMode log mode func (l *logger) LogMode(level LogLevel) gormlogger.Interface { newlogger := *l newlogger.LogLevel = level return &newlogger } // Info print info func (l *logger) Info(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Info { l.baseLogger.Infof(l.infoStr+msg, data) } } // Warn print warn messages func (l *logger) Warn(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Warn { l.baseLogger.Warningf(l.warnStr+msg, data) } } // Error print error messages func (l *logger) Error(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= Error { l.baseLogger.Errorf(l.errStr+msg, data) } } // Trace print sql message // //nolint:cyclop func (l *logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { if l.LogLevel <= Silent { return } elapsed := time.Since(begin) switch { case err != nil && l.LogLevel >= Error && (!errors.Is(err, ErrRecordNotFound) || !l.IgnoreRecordNotFoundError): sql, rows := fc() if rows == -1 { l.baseLogger.Debugf(l.traceErrStr, err, float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.baseLogger.Debugf(l.traceErrStr, err, float64(elapsed.Nanoseconds())/1e6, rows, sql) } case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= Warn: sql, rows := fc() slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold) if rows == -1 { l.baseLogger.Debugf(l.traceWarnStr, slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.baseLogger.Debugf(l.traceWarnStr, slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql) } case l.LogLevel == Info: sql, rows := fc() if rows == -1 { l.baseLogger.Debugf(l.traceStr, float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.baseLogger.Debugf(l.traceStr, float64(elapsed.Nanoseconds())/1e6, rows, sql) } } } // ParamsFilter filter params func (l *logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) { if l.LogConfig.ParameterizedQueries { return sql, nil } return sql, params } type traceRecorder struct { gormlogger.Interface BeginAt time.Time SQL string RowsAffected int64 Err error } // New trace recorder func (l *traceRecorder) New() *traceRecorder { return &traceRecorder{Interface: l.Interface, BeginAt: time.Now()} } // Trace implement logger interface func (l *traceRecorder) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { l.BeginAt = begin l.SQL, l.RowsAffected = fc() l.Err = err }