Go日志:LumberJack + Zap

1,181 阅读3分钟

本文正在参加金石计划

你需要先知道的

  1. 什么是日志?

    日志可以理解为事件,它是某一个程序行为的描述。比如吃饭对应的日志描述可以理解为——我在2022年3月10日14点50分吃饭。

  2. 日志滚动

    如果所有日志都保存在一个文件里,首先这个文件会非常大,并且在日常的日志查询中会非常的繁琐,因此我们会按照某一种规则进行日志的拆分,其通常分为两个维度——一是按照时间拆分;一是按照文件大小拆分。因此所谓的日志滚动,就是通过某种滚动的规则进行拆分(或进行压缩)


zap —— 日志生成(需要go1.19以上)

为什么不用log?简单来说zap更快!!!

  1. 获取 —— go get -u go.uber.org/zap

  2. 设置配置 —— Encoder

    encoder := zapcore.NewxxxEncoder(zapcore.NewxxxEncoderConfig)

    1. Encoder —— 日志输出的样式 内置两种样式,ConsoleEncoder & JSONEncoder logger.Info("info 日志", zap.Int("line", 1))

      • ConsoleEncoder 1.6785843672904398e+09 info info 日志 {"line": 1}
      • JSONEncoder {"level":"info","ts":1678584703.859508,"msg":"info 日志","line":1}
    2. EncoderConfig —— 样式的配置文件 zap内置两种默认配置,production、development

      • EncoderConfig结构

        zapcore.EncoderConfig{
         TimeKey:        "ts",
         LevelKey:       "level",
         NameKey:        "logger",
         CallerKey:      "caller",
         FunctionKey:    zapcore.OmitKey,
         MessageKey:     "msg",
         StacktraceKey:  "stacktrace",
         LineEnding:     zapcore.DefaultLineEnding,
         EncodeLevel:    zapcore.LowercaseLevelEncoder,
         EncodeTime:     zapcore.EpochTimeEncoder,
         EncodeDuration: zapcore.SecondsDurationEncoder,
         EncodeCaller:   zapcore.ShortCallerEncoder,
        }
        
      • zap.NewProductionEncoderConfig()

        {"level":"info","ts":1678584703.859508,"msg":"info 日志","line":1}

        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.EpochTimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        
      • zap.NewDevelopmentEncoderConfig()

        {"L":"INFO","T":"2023-03-12T09:37:33.573+0800","M":"info 日志","line":1}

        EncodeLevel:    zapcore.CapitalLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.StringDurationEncoder,
        
      • 总结⚠️ 两个样式的最主要变化就是日志等级、日志时间与Duration的解析方式

  3. 设置日志输出目的地 —— WriterSyncer zapcore支持输出到多个地方,比如说输出到控制台的同时也输出到文件中

    • 单个目的地 consoleSyncer := zapcore.AddSync(os.Stderr)

    • 多个目的地

      logSyncer := zapcore.AddSync(os.Stdout)
      consoleSyncer := zapcore.AddSync(os.Stderr)
      ​
      // 通过NewMultiWriteSyncer方法组合多个WriteSyncer
      // 将会multiWriteSyncer的自定义类型,其底层类型其实是WriteSyncer数组
      // 而multiWriteSyncer实现了Sync,又成为了WriteSyncer
      syncer := zapcore.NewMultiWriteSyncer(logSyncer, consoleSyncer)
      
  4. 获取core

    core := zapcore.NewCore(encoder, syncer, zapcore.InfoLevel)

    • LevelEnabler zap只会输出大于或等于某一个日志等级的日志,其中LevelEnabler就是判断此项的关键。LevelEnabler是一个接口,包含Enabled(Level) bool,当传入的Level大于或等于自己本身的时候将会返回true,反之则返回false
  5. 获取logger

    logger := zap.new(core)

  6. 总结⚠️

    • zap的核心是zapcore

    • zap在日志输出系列的函数,会通过Write函数进入到内部函数。其中会执行一个for循环,它的里面会执行所有WriteSyncer的Write函数

    • zapcore.EncoderConfig

      zapcore.EncoderConfig{
       TimeKey:        "ts",
       LevelKey:       "level",
       NameKey:        "logger",
       CallerKey:      "caller",
       FunctionKey:    zapcore.OmitKey,
       MessageKey:     "msg",
       StacktraceKey:  "stacktrace",
       LineEnding:     zapcore.DefaultLineEnding,
       EncodeLevel:    zapcore.LowercaseLevelEncoder,
       EncodeTime:     zapcore.EpochTimeEncoder,
       EncodeDuration: zapcore.SecondsDurationEncoder,
       EncodeCaller:   zapcore.ShortCallerEncoder,
      }
      
    • zap中有一个_globalL变量,可以通过L()获取,并且可以被ReplaceGlobals(*Logger)替换

      猜测是用于互斥访问的单例,因为注释说他是线程安全的


lumberjack —— 日志保存

  1. 获取 —— import "gopkg.in/natefinch/lumberjack.v2"

  2. 添加到logio.Writer

    log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log",
    MaxSize:    500, // megabytes
    MaxBackups: 3,
    MaxAge:     28,   // days
    Compress:   true, // disabled by default
    })
    
    • Filename : 文件路径
    • MaxSize : 单个文件的最大大小
    • MaxBackups : 老文件最多能保留多少个
    • MaxAge : 老文件最多保留多少天
    • Compress: 决定了回滚的文件是否需要压缩
  1. 总结⚠️

    • lumberjack是什么做到只需要一行就能够将日志输出到文件里的?

lumberjack + zap

zap通过为io.Writer增加Sync继承了WriteSyncer,在每次的日志输出,如Info()的时候,都会调用Write()。并且zapcore能将多个io.Writer同时注册到zap内部,同时使每个都生效。而lumberjack就符合io.Writer。因此只需要将lumberjack注册到zap里面就行

l := &lumberjack.Logger{
Filename:   "/var/log/myapp/foo.log",
MaxSize:    500// megabytes
MaxBackups: 3,
MaxAge:     28,   // days
Compress:   true// disabled by default
}
​
s1 := zapcore.AddSync(l)
s2 := zapcore.AddSync(os.Stderr)
​
s := zapcore.NewMultiWriteSyncer(s1, s2)
core := zapcore.NewCore(encoder, s, zapcore.InfoLevel)
logger := zap.New(core)