golang中实现一个log包-2

上一篇blog记录了实现一个基本日志包的过程,这里自己尝试实现一个带有rolling功能的日志包

其实从性能和功能单一的角度来说,很多日志库是不支持rolling相关的功能的,比如uber推出的zap日志包,就推荐用户自己通过logrotate等方式自己去处理日志rolling的问题。 不过有些场景下,我们希望日志文件能不需要外界干预的情况下正常运行。 可以通过实现一个支持rolling的io.Writer来解决这个问题。

基本功能

  • 支持多种rolling类型, 不切割,按日期切割,按文件大小切割
  • 能设置备份文件大小和个数,支持多余日志自动清理

在github上看到一个已经有人实现了一个这样的工具包lumberjack,从包中的Logger对象定义看,可以指定备份文件的大小/个数/保存日期,会按照时间来命令备份文件,并自动清除过期或者超过备份文件总数的日志,同时提供备份日志压缩功能。

所以犯懒,直接集成这个包来做日志切割了,如下是lumberjack包中Logger struct的定义

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
type Logger struct {
// Filename is the file to write logs to. Backup log files will be retained
// in the same directory. It uses <processname>-lumberjack.log in
// os.TempDir() if empty.
Filename string `json:"filename" yaml:"filename"`

// MaxSize is the maximum size in megabytes of the log file before it gets
// rotated. It defaults to 100 megabytes.
MaxSize int `json:"maxsize" yaml:"maxsize"`

// MaxAge is the maximum number of days to retain old log files based on the
// timestamp encoded in their filename. Note that a day is defined as 24
// hours and may not exactly correspond to calendar days due to daylight
// savings, leap seconds, etc. The default is not to remove old log files
// based on age.
MaxAge int `json:"maxage" yaml:"maxage"`

// MaxBackups is the maximum number of old log files to retain. The default
// is to retain all old log files (though MaxAge may still cause them to get
// deleted.)
MaxBackups int `json:"maxbackups" yaml:"maxbackups"`

// LocalTime determines if the time used for formatting the timestamps in
// backup files is the computer's local time. The default is to use UTC
// time.
LocalTime bool `json:"localtime" yaml:"localtime"`

// Compress determines if the rotated log files should be compressed
// using gzip. The default is not to perform compression.
Compress bool `json:"compress" yaml:"compress"`

size int64
file *os.File
mu sync.Mutex

millCh chan bool
startMill sync.Once
}

v2版本日志包设计思路

这里在上篇博文的v1版本基础上,设计v2版本的日记包。
在包中声明一个RollingConf对象,来指明日志滚动切割配置,RollingType=0 则不切割日志,RollingType=1则按照文件大小来切割,这里暂时不支持按天进行日志文件滚动切割。

1
2
3
4
5
6
7
8
type RollingConf struct {
RollingType int // 0不切割,1按文件大小切割
MaxSize int // 单个文件最大容量,单位megabytes, 如果传0 ,则使用默认值100进行处理
MaxBackups int // 最大文件备份文件个数,如果为0,则不限制备份文件个数
MaxAge int //备份文件最大保留日期,单位为天,依据日志文件中的时间后缀做判断,如果为0,则不进行过期判断
CompressFlag bool // 备份日志是否压缩,true开启压缩
LocaltimeFlag bool //是否使用本地时间格式标注备份文件,true表示使用
}

在这个v2版本的初始化日志对象函数NewLogger进行修改,传参增加一个RollingConf参数,完整的日志对象初始化函数如下。基本思路就是,如果不开启rolling,则使用os.OpenFile去实现io.Writer接口,如果开启rolling,则调用lumberjack包,创建一个支持rolling的对象,这个对象同样实现了io.Writer接口。v2版本其他部分和v1保持一致,使用的时候,对v1,v2两个版本的变化是无感知的。

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
#v2/log.go
func NewLogger(level int, logPath string, logFile string, r *RollingConf) *ELog {
mkLogDir(logPath)
file := logPath + "/" + logFile
// writer, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777)
// if err != nil {
// panic("open log file error")
// }

var writer io.Writer

switch r.RollingType {
case 0: //不进行日志切割
var err error
if writer, err = os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777); err != nil {
panic("open log file error")
}
case 1: //按文件大小进行切割,并指定
writer = &lumberjack.Logger{
Filename: file,
MaxSize: r.MaxSize, // 单个文件最大容量,单位megabytes, 如果传0 ,则使用默认值100进行处理
MaxBackups: r.MaxBackups, // 最大文件备份文件个数,如果为0,则不限制备份文件个数
MaxAge: r.MaxAge, //备份文件最大保留日期,单位为天,依据日志文件中的时间后缀做判断,如果为0,则不进行过期判断
Compress: r.CompressFlag, // 是否进行备份文件压缩,默认不压缩
LocalTime: r.LocaltimeFlag, // 默认为false 代表使用utc输出
}

}

infoLog := log.New(writer, "[info]", log.Ldate|log.Ltime)
warnLog := log.New(writer, "[warn]", log.Ldate|log.Ltime|log.Llongfile)
errLog := log.New(writer, "[err]", log.Ldate|log.Ltime|log.Lshortfile)

now := time.Now()

infoLogFile := &fileLogger{
logger: infoLog,
curOpenFile: file,
writer: writer,
openTime: now,
}

warnLogFile := &fileLogger{
logger: warnLog,
curOpenFile: file,
writer: writer,
openTime: now,
}

errLogFile := &fileLogger{
logger: errLog,
curOpenFile: file,
writer: writer,
openTime: now,
}

l := &ELog{
infoLogger: infoLogFile,
warnLogger: warnLogFile,
errLogger: errLogFile,
minLevel: level,
}
return l
}

v2版本使用测试

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
#main.go

package main

import (
log "elog/log/v2"
"time"
)

var logger *log.ELog

func initLogger() {
logPath := "./logs"
logFile := "1.log"
logLevel := log.InfoLevel

rollingConf := &log.RollingConf{
RollingType: 1, // 0不切割,1按文件大小切割
MaxSize: 10, // 单个文件最大容量,单位megabytes, 如果传0 ,则使用默认值100进行处理
MaxBackups: 5, // 最大文件备份文件个数,如果为0,则不限制备份文件个数
MaxAge: 30, //备份文件最大保留日期,单位为天,依据日志文件中的时间后缀做判断,如果为0,则不进行过期判断
CompressFlag: true, // 备份日志是否压缩,true开启压缩
LocaltimeFlag: true, //是否使用本地时间格式标注备份文件,true表示使用
}

logger = log.NewLogger(logLevel, logPath, logFile, rollingConf)
}

func main() {

initLogger()

logger.Info("hello info")

for n := 0; n < 10000; n++ {
test()
time.Sleep(time.Millisecond * 50)
}
}

func test() {
logger.Info("hello info ")
logger.Warn("hello waring", "warn1")
logger.Err("hello err", "err1", "kkkkkkk")
}

参考:
rolling writer
lumberjack
pingcap-log