gorm日志输出到文件
日志原理分析
gorm手册上写着,如果需要自定义logger,则需要实现如下接口:
1 2 3 4 5 6 7 | 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) } |
从源码分析一下实现过程
源码里面有个接口定义如下:
1 2 3 4 | // Writer log writer interface type Writer interface { Printf(string, ... interface {}) } |
日志类定义如下:
1 2 3 4 5 6 | type logger struct { Writer Config infoStr, warnStr, errStr string traceStr, traceErrStr, traceWarnStr string } |
logger使用了Write作为一个匿名字段,同时使得logger结构体调用Printf函数时候实际上调用流程是logger.Writer.Printf()。
以Info方法为例:
1 2 3 4 5 6 | // 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字段的实现如下:
1 2 3 4 5 6 | 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方法,代码如下:
1 | func (l *Logger) Printf(format string, v ...any) |
打印到文件
Stdout定义:Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
因此打印到指定文件的一个简单办法代码实现就是替掉log.New方法中的stdout即可,
如下:
1 2 3 4 5 6 7 8 9 10 | 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的能力,同时允许扩展到更多输出点。
实现源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 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) } } } |
调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具