gorm日志输出到文件

日志原理分析

gorm手册上写着,如果需要自定义logger,则需要实现如下接口:

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)
} 

 从源码分析一下实现过程

源码里面有个接口定义如下:

// Writer log writer interface
type Writer interface {
	Printf(string, ...interface{})
} 

日志类定义如下:

type logger struct {
	Writer
	Config
	infoStr, warnStr, errStr            string
	traceStr, traceErrStr, traceWarnStr string
}

logger使用了Write作为一个匿名字段,同时使得logger结构体调用Printf函数时候实际上调用流程是logger.Writer.Printf()。

以Info方法为例:

// 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...)...)
	}
}

l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)函数的实际调用将会是实现了Writer接口的结构体的调用,

结合gorm日志初始化代码中来看

Logger: logger.Default.LogMode(logger.Info)

Default字段的实现如下:
Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{
		SlowThreshold:             200 * time.Millisecond,
		LogLevel:                  Warn,
		IgnoreRecordNotFoundError: false,
		Colorful:                  true,
	})

log.New(os.Stdout, "\r\n", log.LstdFlags)返回的是一个log.Logger实例,而同时log.Logger也实现了Writer的Printf方法,代码如下:

func (l *Logger) Printf(format string, v ...any)
 因此gorm日志底层是使用golang日志包的logger来进行日志输出,同时把输出目标设定为os.Stdout来实现打印到标准输出的。

打印到文件

Stdout定义:Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")

因此打印到指定文件的一个简单办法代码实现就是替掉log.New方法中的stdout即可,

如下:

logfile, _ := os.Create("run.log")
db.Session(&gorm.Session{ // 自定session并且GORM日志存放到文件
		DryRun: true,
		Logger: logger.New(log.New(logfile, "\r\n", log.LstdFlags),
			logger.Config{
				SlowThreshold: time.Second,
				LogLevel:      logger.Info,
				Colorful:      true,
			}),
}).Select(clause.Associations).Delete(inst) 

上面的代码在进行delete操作时将把日志保存目标设定为指定的文件run.log。

自己实现日志 

实现目标:输出到文件的同时保留输出到os.Stdout的能力,同时允许扩展到更多输出点。

实现源码如下:

package filelogger

import (
	"context"
	"errors"
	"fmt"
	gormlogger "gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
	"log"
	"os"
	"syscall"
	"time"
)

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"
)

const (
	// Silent silent log level
	Silent gormlogger.LogLevel = iota + 1
	// Error error log level
	Error
	// Warn warn log level
	Warn
	// Info info log level
	Info
)

const DEFAULT_LOGFILE string = "run.log"

type LogConfig struct {
	gormlogger.Config
	LogFile         string
	AddtionalLogger Loggers
}

type Loggers []log.Logger

// FileLogger 文件系统日志
type FileLogger struct {
	LogConfig
	Loggers                             Loggers
	infoStr, warnStr, errStr            string
	traceStr, traceErrStr, traceWarnStr string
}

func NewLogger(config LogConfig) *FileLogger {
	if len(config.LogFile) == 0 {
		config.LogFile = DEFAULT_LOGFILE
	}

	loggers := make([]log.Logger, 0)
	stdout := os.NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	consoleLogger := log.New(stdout, "\r\n", log.LstdFlags)
	loggers = append(loggers, *consoleLogger)

	logfile, _ := os.Create(config.LogFile)
	fileLogger := log.New(logfile, "\r\n", log.LstdFlags)
	loggers = append(loggers, *fileLogger)

	loggers = append(loggers, config.AddtionalLogger[:]...)

	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 &FileLogger{
		LogConfig:    config,
		Loggers:      loggers,
		infoStr:      infoStr,
		warnStr:      warnStr,
		errStr:       errStr,
		traceStr:     traceStr,
		traceWarnStr: traceWarnStr,
		traceErrStr:  traceErrStr,
	}
}

func (logger *FileLogger) printf(msg string, data ...interface{}) {
	for _, l := range logger.Loggers {
		l.Printf(msg, data...)
	}
}

func (logger *FileLogger) LogMode(lv gormlogger.LogLevel) gormlogger.Interface {
	logger.LogLevel = lv
	return logger
}

func (logger *FileLogger) Info(ctx context.Context, msg string, data ...interface{}) {
	if logger.LogLevel >= Info {
		logger.printf(logger.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

func (logger *FileLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if logger.LogLevel >= Warn {
		logger.printf(logger.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

func (logger *FileLogger) Error(ctx context.Context, msg string, data ...interface{}) {
	if logger.LogLevel >= Error {
		logger.printf(logger.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Trace 打印sql语句
func (logger *FileLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
	if logger.LogLevel <= Silent {
		return
	}

	elapsed := time.Since(begin)
	switch {
	case err != nil && logger.LogLevel >= Error && (!errors.Is(err, gormlogger.ErrRecordNotFound) || !logger.IgnoreRecordNotFoundError):
		sql, rows := fc()
		if rows == -1 {
			logger.printf(logger.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			logger.printf(logger.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case elapsed > logger.SlowThreshold && logger.SlowThreshold != 0 && logger.LogLevel >= Warn:
		sql, rows := fc()
		slowLog := fmt.Sprintf("SLOW SQL >= %v", logger.SlowThreshold)
		if rows == -1 {
			logger.printf(logger.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			logger.printf(logger.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case logger.LogLevel == Info:
		sql, rows := fc()
		if rows == -1 {
			logger.printf(logger.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			logger.printf(logger.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	}
}

 

调用代码:

secLogfile, _ := os.Create("secLogfile.txt") //定义扩展第二个日志
secLogger := log.New(secLogfile, "\r\n", log.LstdFlags)

anotherLogfile, _ := os.Create("anotherlog.log")
anotherLogger := log.New(anotherLogfile, "\r\n", log.LstdFlags) // 定义扩展第三个日志

mylogger := filelogger.NewLogger(filelogger.LogConfig{
	LogFile:         "test.log",
	AddtionalLogger: append(make(filelogger.Loggers, 0), *anotherLogger, *secLogger), // 附加日志列表
	Config: logger.Config{  // gorm日志原始配置项
		SlowThreshold:             200 * time.Millisecond,
		IgnoreRecordNotFoundError: false,
		Colorful:                  true,
	},
}).LogMode(filelogger.Info)   

运行结果如图:

 

 

 可以看到同时生成了test.log等其他两个日志文件,同时终端输出如下:

2023/02/25 22:28:59 /home/xxx/goworkspace/src/dbapp1/main.go:200
[0.184ms] [rows:2] SELECT * FROM `computers` WHERE `computers`.`UserId` = "31c70e86-3bd6-43d7-ba15-8db25bed48bb" AND `computers`.`del` = 0

2023/02/25 22:28:59 /home/xxx/goworkspace/src/dbapp1/main.go:200
[1.434ms] [rows:1] SELECT * FROM `employeers` WHERE (1=1 and name = '新员工1') AND `employeers`.`del` = 0 LIMIT 1

2023/02/25 22:28:59 /home/xxx/goworkspace/src/dbapp1/main.go:225
[0.035ms] [rows:0] UPDATE `computers` SET `del`=1 WHERE `computers`.`UserId` = "31c70e86-3bd6-43d7-ba15-8db25bed48bb" AND `computers`.`del` = 0
2023/02/25 22:28:59 employeer [31c70e86-3bd6-43d7-ba15-8db25bed48bb] deleted.

2023/02/25 22:28:59 /home/xxx/goworkspace/src/dbapp1/main.go:225
[0.193ms] [rows:0] UPDATE `employeers` SET `del`=1 WHERE `employeers`.`uuid` = "31c70e86-3bd6-43d7-ba15-8db25bed48bb" AND `employeers`.`del` = 0

  

至此,gorm如何输出到文件及追加到更多文件日志实现。

posted @ 2023-02-25 22:31  一朵野生菌  阅读(1679)  评论(0编辑  收藏  举报