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