go使用zap + lumberjack重构项目的日志系统

【工作随笔】重构项目的日志系统

原先公司的go项目是采用之前一个大哥自己开发的一个log包,来实现各种日志的打印监控,对于和很多的场景支持和日志分割都做的不太好,所以在近期有空的时候对老的日志系统进行了重写。

1 明确优化需求

  • 性能强劲(至少要比老的好)
  • 兼容老的日志代码,减少改动(不然就得改成百上千行代码)
  • 支持日志按天分割(别的C++的服务都是这么分割,要方便运维开发监控脚本)

2 前期准备

在网络上看了很多的大牛写的介绍决定采用【zap + lumberjack】两个包来实现我们的新日志系统,这也是现在go语言常见的一种日志解决方案

  • Zap go.uber.org/zap
    现在最强大的go logger包之一,官方文档附在超连接里,性能强大调用精简。并且zap提供了两种类型的 logger
SugaredLogger

Logger
省流介绍:Logger只支持强类型,当是性能更强,我们的项目为了兼容老代码所以选择SugaredLogger
  • lumberjack github.com/natefinch/lumberjack

是一个支持日志分割,日志过期删除的小包,非常适配Zap

3 撸代码

首先是创建一个可以打印日志的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()

以上,代码都是精简省流版本,更加环保,更加便于阅读,希望对大家有所帮助

你可能感兴趣的:(Go,工作随笔,go,golang)