package logger
import (
"context"
"errors"
"fmt"
"io"
"log"
"os"
"time"
"gorm.io/gorm/utils"
)
var ErrRecordNotFound = errors .New ("record not found" )
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"
)
type LogLevel int
const (
Silent LogLevel = iota + 1
Error
Warn
Info
)
type Writer interface {
Printf (string , ...interface {})
}
type Config struct {
SlowThreshold time .Duration
Colorful bool
IgnoreRecordNotFoundError bool
ParameterizedQueries bool
LogLevel LogLevel
}
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 = New (log .New (io .Discard , "" , log .LstdFlags ), Config {})
Default = New (log .New (os .Stdout , "\r\n" , log .LstdFlags ), Config {
SlowThreshold : 200 * time .Millisecond ,
LogLevel : Warn ,
IgnoreRecordNotFoundError : false ,
Colorful : true ,
})
Recorder = traceRecorder {Interface : Default , BeginAt : time .Now ()}
)
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
}
func (l *logger ) LogMode (level LogLevel ) Interface {
newlogger := *l
newlogger .LogLevel = level
return &newlogger
}
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 ...)...)
}
}
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 ...)...)
}
}
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 ...)...)
}
}
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 )
}
}
}
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
}
func (l traceRecorder ) New () *traceRecorder {
return &traceRecorder {Interface : l .Interface , BeginAt : time .Now ()}
}
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
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .