package lxlog import ( "context" "errors" "fmt" "log" "os" "runtime" "strconv" "strings" "time" "gorm.io/gorm/logger" "gorm.io/gorm/utils" ) var ( Default = NewDevLogger(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: 200 * time.Millisecond, LogLevel: logger.Warn, IgnoreRecordNotFoundError: false, Colorful: true, }) ) // NewDevLogger initialize logger func NewDevLogger(writer logger.Writer, config logger.Config) logger.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 = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + logger.Reset errStr = logger.Magenta + "%s\n" + logger.Reset + logger.Red + "[error] " + logger.Reset traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" } return &DevLogger{ Writer: writer, Config: config, infoStr: infoStr, warnStr: warnStr, errStr: errStr, traceStr: traceStr, traceWarnStr: traceWarnStr, traceErrStr: traceErrStr, } } type DevLogger struct { logger.Writer logger.Config infoStr, warnStr, errStr string traceStr, traceErrStr, traceWarnStr string } // LogMode log mode func (l *DevLogger) LogMode(level logger.LogLevel) logger.Interface { newlogger := *l newlogger.LogLevel = level return &newlogger } // Info print info func (l *DevLogger) Info(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= logger.Info { l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Warn print warn messages func (l *DevLogger) Warn(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= logger.Warn { l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Error print error messages func (l *DevLogger) Error(ctx context.Context, msg string, data ...interface{}) { if l.LogLevel >= logger.Error { l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...) } } // Trace print sql message // //nolint:cyclop func (l *DevLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { if l.LogLevel <= logger.Silent { return } elapsed := time.Since(begin) switch { case err != nil && l.LogLevel >= logger.Error && (!errors.Is(err, logger.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError): sql, rows := fc() if rows == -1 { l.Printf(l.traceErrStr, l.getCaller(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.Printf(l.traceErrStr, l.getCaller(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql) } case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn: sql, rows := fc() slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold) if rows == -1 { l.Printf(l.traceWarnStr, l.getCaller(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.Printf(l.traceWarnStr, l.getCaller(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql) } case l.LogLevel == logger.Info: sql, rows := fc() if rows == -1 { l.Printf(l.traceStr, l.getCaller(), float64(elapsed.Nanoseconds())/1e6, "-", sql) } else { l.Printf(l.traceStr, l.getCaller(), float64(elapsed.Nanoseconds())/1e6, rows, sql) } } } // 获取调用者信息 func (l *DevLogger) getCaller() string { pcs := [15]uintptr{} // the third caller usually from gorm internal len := runtime.Callers(3, pcs[:]) frames := runtime.CallersFrames(pcs[:len]) for i := 0; i < len; i++ { // second return value is "more", not "ok" frame, _ := frames.Next() // 排序 gorm.io tool/lxutils 这个三种类型的 数据库调用 if ((!strings.Contains(frame.File, "gorm.io/") && !strings.Contains(frame.File, "tool/lxutils")) || strings.HasSuffix(frame.File, "_test.go")) && !strings.HasSuffix(frame.File, ".gen.go") { return string(strconv.AppendInt(append([]byte(frame.File), ':'), int64(frame.Line), 10)) } } return "unknown" }