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


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

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

    1 明确优化需求

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

    2 前期准备

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

    • Zap go.uber.org/zap
      现在最强大的go logger包之一,官方文档附在超连接里,性能强大调用精简。并且zap提供了两种类型的 logger
    SugaredLogger
    
    Logger
    
    • 1
    • 2
    • 3
    省流介绍:Logger只支持强类型,当是性能更强,我们的项目为了兼容老代码所以选择SugaredLogger
    
    • 1

    是一个支持日志分割,日志过期删除的小包,非常适配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()
    }
    
    
    • 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

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    解决了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()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    打印两个日志的问题解决之后,我又了个新的鬼点子,我希望打印的错误日志也可以在程序终端进行输出!所以还得要改改!使用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 ),
    	)
    	/*不重要的代码...*/
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最后新的日志系统提交测试之后,发现我开发的时候忘记了自己是在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
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    他允许我们打印调用堆栈信息的时候向上skip,所以我们最后的代码变成了这样子

    	log := zap.New(core, zap.AddCaller(),zap.AddCallerSkip(1))
    	Svrlogger.logger := log.Sugar()
    
    • 1
    • 2

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

  • 相关阅读:
    【C++智能指针】(面试)使用my_weak_ptr解决循环引用问题
    lsblk 硬盘属性查看
    数字化转型具体包含哪些内容?
    ROS下控制无人机任任意方向下往机头方向飞行
    MySQL 连接驱动器包 下载教程
    分布式事务
    人工客服平台
    【echart 】legend.icon传递svg图标,图标不显示的原因。
    Hadoop完全分布式环境搭建
    第二十二 查询、检索、搜索
  • 原文地址:https://blog.csdn.net/J1nAB1n9/article/details/127837001