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)
打印到文件
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如何输出到文件及追加到更多文件日志实现。
本文来自博客园,作者:一朵野生菌,转载请注明原文链接:https://www.cnblogs.com/xmy20051643/p/17152737.html

浙公网安备 33010602011771号