214 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package logger
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"time"
 | |
| 
 | |
| 	"gorm.io/gorm/utils"
 | |
| )
 | |
| 
 | |
| // 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 int
 | |
| 
 | |
| const (
 | |
| 	// Silent silent log level
 | |
| 	Silent LogLevel = iota + 1
 | |
| 	// Error error log level
 | |
| 	Error
 | |
| 	// Warn warn log level
 | |
| 	Warn
 | |
| 	// Info info log level
 | |
| 	Info
 | |
| )
 | |
| 
 | |
| // Writer log writer interface
 | |
| type Writer interface {
 | |
| 	Printf(string, ...interface{})
 | |
| }
 | |
| 
 | |
| // Config logger config
 | |
| type Config struct {
 | |
| 	SlowThreshold             time.Duration
 | |
| 	Colorful                  bool
 | |
| 	IgnoreRecordNotFoundError bool
 | |
| 	ParameterizedQueries      bool
 | |
| 	LogLevel                  LogLevel
 | |
| }
 | |
| 
 | |
| // Interface logger interface
 | |
| type Interface interface {
 | |
| 	LogMode(LogLevel) Interface
 | |
| 	Info(context.Context, string, ...interface{})
 | |
| 	Warn(context.Context, string, ...interface{})
 | |
| 	Error(context.Context, string, ...interface{})
 | |
| 	Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// Discard logger will print any log to io.Discard
 | |
| 	Discard = New(log.New(io.Discard, "", log.LstdFlags), Config{})
 | |
| 	// Default Default logger
 | |
| 	Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{
 | |
| 		SlowThreshold:             200 * time.Millisecond,
 | |
| 		LogLevel:                  Warn,
 | |
| 		IgnoreRecordNotFoundError: false,
 | |
| 		Colorful:                  true,
 | |
| 	})
 | |
| 	// Recorder logger records running SQL into a recorder instance
 | |
| 	Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()}
 | |
| )
 | |
| 
 | |
| // New initialize logger
 | |
| func New(writer Writer, config Config) Interface {
 | |
| 	var (
 | |
| 		infoStr      = "%s\n[info] "
 | |
| 		warnStr      = "%s\n[warn] "
 | |
| 		errStr       = "%s\n[error] "
 | |
| 		traceStr     = "%s\n[%.3fms] [rows:%v] %s"
 | |
| 		traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
 | |
| 		traceErrStr  = "%s %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 = Green + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
 | |
| 		traceWarnStr = Green + "%s " + Yellow + "%s\n" + Reset + RedBold + "[%.3fms] " + Yellow + "[rows:%v]" + Magenta + " %s" + Reset
 | |
| 		traceErrStr = RedBold + "%s " + MagentaBold + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
 | |
| 	}
 | |
| 
 | |
| 	return &logger{
 | |
| 		Writer:       writer,
 | |
| 		Config:       config,
 | |
| 		infoStr:      infoStr,
 | |
| 		warnStr:      warnStr,
 | |
| 		errStr:       errStr,
 | |
| 		traceStr:     traceStr,
 | |
| 		traceWarnStr: traceWarnStr,
 | |
| 		traceErrStr:  traceErrStr,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type logger struct {
 | |
| 	Writer
 | |
| 	Config
 | |
| 	infoStr, warnStr, errStr            string
 | |
| 	traceStr, traceErrStr, traceWarnStr string
 | |
| }
 | |
| 
 | |
| // LogMode log mode
 | |
| func (l *logger) LogMode(level LogLevel) 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.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Warn print warn messages
 | |
| func (l *logger) Warn(ctx context.Context, msg string, data ...interface{}) {
 | |
| 	if l.LogLevel >= Warn {
 | |
| 		l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Error print error messages
 | |
| func (l *logger) Error(ctx context.Context, msg string, data ...interface{}) {
 | |
| 	if l.LogLevel >= Error {
 | |
| 		l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, 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.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
 | |
| 		} else {
 | |
| 			l.Printf(l.traceErrStr, utils.FileWithLineNum(), 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.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
 | |
| 		} else {
 | |
| 			l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
 | |
| 		}
 | |
| 	case l.LogLevel == Info:
 | |
| 		sql, rows := fc()
 | |
| 		if rows == -1 {
 | |
| 			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
 | |
| 		} else {
 | |
| 			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ParamsFilter filter params
 | |
| func (l *logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
 | |
| 	if l.Config.ParameterizedQueries {
 | |
| 		return sql, nil
 | |
| 	}
 | |
| 	return sql, params
 | |
| }
 | |
| 
 | |
| type traceRecorder struct {
 | |
| 	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
 | |
| }
 |