原先公司的go项目是采用之前一个大哥自己开发的一个log包,来实现各种日志的打印监控,对于和很多的场景支持和日志分割都做的不太好,所以在近期有空的时候对老的日志系统进行了重写。
在网络上看了很多的大牛写的介绍决定采用【zap + lumberjack】两个包来实现我们的新日志系统,这也是现在go语言常见的一种日志解决方案
SugaredLogger
Logger
省流介绍:Logger只支持强类型,当是性能更强,我们的项目为了兼容老代码所以选择SugaredLogger
是一个支持日志分割,日志过期删除的小包,非常适配Zap
首先是创建一个可以打印日志的SugaredLogger
package logger
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var Svrlogger Logger
// 为了兼容老代码,选择自己封装一层
type Logger struct {
logger *zap.SugaredLogger
}
// 创建一个lumberjack的Write
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./logPath",
MaxSize: 10, // 日志大小(塞满了才创建新的)
// 备份相关的参数
MaxBackups: 10,
MaxAge: 10,
Compress: false,
}
return zapcore.AddSync(lumberJackLogger)
}
func InitLogger() {
// 设置一些基本日志格式
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
// 略~
})
// 创建一个core
core := zapcore.NewCore(encoder, getLogWriter(), zapcore.DebugLevel)
// 给logger赋值
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
Demo写完了,测试了一下没有问题,接下里我们思考下一个问题:
我们期望的是日志随日期的变化来打印,即换天之后创建新的日志,然后往新的日志中打印,而lumberjack的日志打印模式是先给日志文件设置最大的大小,如果日志内容超过这个大小才会创建新的日志文件,这显然是不符合我们要求的,所以没办法手撕lumberjack,还好就百来行代码重写一下就行了。
// 关键的函数改Write就行了,别的函数的改动我就不细写了
func (l *LoggerWriter) Write(p []byte) (n int, err error) {
l.mu.Lock()
defer l.mu.Unlock()
/* 代码略...*/
t := currentTime()
// 判断是不是换天了,如果换天了就要重新调用rotate()
if l.update.Day() != t.Day() || l.update.Month() != t.Month() || l.update.Year() != t.Year() {
if err := l.rotate(); err != nil {
return 0, err
}
}
/* 代码略...*/
n, err = l.file.Write(p)
return n, err
}
解决了lumberjack的问题之后,我又发现了新的问题:
首先我们期望debug,info 这类信息的日志信息更适合用于保活信息的打印,这类信息应该交由运维同学监控,而我们开发成员更应该关注Warn,Error,Fatal,Painc;所以更好的实现方式应该是一个go服务同时打印两份文件,所以我们一个logger实例就需要创建多个zapcore!
func InitLogger() {
/*不重要的代码...*/
savelog := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl <= zapcore.InfoLevel
})
errorlog := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.WarnLevel
})
// 使用了core的NewTee
core := zapcore.NewTee(
// 保活日志
zapcore.NewCore(encoder, zapcore.AddSync(getLogWriter("./save/log")), savelog ),
// 错误日志
zapcore.NewCore(encoder, zapcore.AddSync(getLogWriter("./error/log")), errorlog ),
)
/*不重要的代码...*/
log := zap.New(core, zap.AddCaller())
Svrlogger.logger := log.Sugar()
}
打印两个日志的问题解决之后,我又了个新的鬼点子,我希望打印的错误日志也可以在程序终端进行输出!所以还得要改改!使用io包的io.MultiWriter方法,让zap的日志往终端和文件都能打印
func InitLogger() {
/*不重要的代码...*/
core := zapcore.NewTee(
// 保活日志
zapcore.NewCore(encoder, zapcore.AddSync(getLogWriter("./save/log")), savelog ),
// 错误日志
zapcore.NewCore(encoder, zapcore.AddSync(io.MultiWriter(os.Stdout,getLogWriter("./error/log"))), errorlog ),
)
/*不重要的代码...*/
}
最后新的日志系统提交测试之后,发现我开发的时候忘记了自己是在zap的外面包了一层,所以打印日志堆栈信息的时候,打印的调用函数都是logger.go,我就回到go.uber.org/zap的文档里一阵翻阅找到了这个方法:
// AddCallerSkip increases the number of callers skipped by caller annotation
// (as enabled by the AddCaller option). When building wrappers around the
// Logger and SugaredLogger, supplying this Option prevents zap from always
// reporting the wrapper code as the caller.
func AddCallerSkip(skip int) Option {
return optionFunc(func(log *Logger) {
log.callerSkip += skip
})
}
他允许我们打印调用堆栈信息的时候向上skip,所以我们最后的代码变成了这样子
log := zap.New(core, zap.AddCaller(),zap.AddCallerSkip(1))
Svrlogger.logger := log.Sugar()
以上,代码都是精简省流版本,更加环保,更加便于阅读,希望对大家有所帮助