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)
 因此gorm日志底层是使用golang日志包的logger来进行日志输出,同时把输出目标设定为os.Stdout来实现打印到标准输出的。

打印到文件

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如何输出到文件及追加到更多文件日志实现。

posted @   一朵野生菌  阅读(1796)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示