golang 使用 Zap 日誌庫

  1. 簡要說明

zapuber 開源的 Go 高性能日誌庫,支持不同的日誌級別, 能夠打印基本信息等,但不支持日誌的分割,這裏我們可以使用 lumberjack 也是 zap 官方推薦用於日誌分割,結合這兩個庫我們就可以實現以下功能的日誌機制:

官網地址:https://github.com/uber-go/zap

  1. 下載安裝

使用下面命令安裝

go get github.com/uber-go/zap
  1. 配置 zap Logger

zap提供了兩種類型的日誌記錄器LoggerSugared Logger

區別是:

  1. 在每一微秒和每一次內存分配都很重要的上下文中,使用 Logger。它比 Sugared Logger 更快,內存分配次數也更少,但它只支持強類型的結構化日誌記錄。

  2. 在性能很好但不是很關鍵的上下文中,使用SugaredLogger。它比其他結構化日誌記錄包快 4-10 被,並且支持結構化和printf風格的日誌記錄。

3.1 Logger

3.1.1 NewExample

代碼示例:

package main

import (
  "go.uber.org/zap"
)

func main() {
  log := zap.NewExample()
  log.Debug("this is debug message")
  log.Info("this is info message")
  log.Info("this is info message with fileds",
    zap.Int("age", 24), zap.String("agender", "man"))
  log.Warn("this is warn message")
  log.Error("this is error message")
  log.Panic("this is panic message")

}

輸出結果:

{"level":"debug","msg":"this is debug message"}
{"level":"info","msg":"this is info message"}
{"level":"info","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","msg":"this is warn message"}
{"level":"error","msg":"this is error message"}
{"level":"panic","msg":"this is panic message"}
panic: this is panic message

3.1.2 NewDevelopment

代碼示例:

func main() {
  log, _ := zap.NewDevelopment()
  log.Debug("this is debug message")
  log.Info("this is info message")
  log.Info("this is info message with fileds",
    zap.Int("age", 24), zap.String("agender", "man"))
  log.Warn("this is warn message")
  log.Error("this is error message") 
  // log.DPanic("This is a DPANIC message")  
  // log.Panic("this is panic message")
  // log.Fatal("This is a FATAL message")

}

輸出結果:

2020-06-12T18:51:11.457+0800  DEBUG  task/main.go:9  this is debug message
2020-06-12T18:51:11.457+0800  INFO  task/main.go:10  this is info message
2020-06-12T18:51:11.457+0800  INFO  task/main.go:11  this is info message with fileds  {"age": 24, "agender": "man"}
2020-06-12T18:51:11.457+0800  WARN  task/main.go:13  this is warn message
main.main
  /home/wohu/GoCode/src/task/main.go:13
runtime.main
  /usr/local/go/src/runtime/proc.go:200
2020-06-12T18:51:11.457+0800  ERROR  task/main.go:14  this is error message
main.main
  /home/wohu/GoCode/src/task/main.go:14
runtime.main
  /usr/local/go/src/runtime/proc.go:200

3.1.3 NewProduction

代碼示例:

func main() {
  log, _ := zap.NewProduction()
  log.Debug("this is debug message")
  log.Info("this is info message")
  log.Info("this is info message with fileds",
    zap.Int("age", 24), zap.String("agender", "man"))
  log.Warn("this is warn message")
  log.Error("this is error message") 
  // log.DPanic("This is a DPANIC message")  
  // log.Panic("this is panic message")
  // log.Fatal("This is a FATAL message")
}

輸出結果:

{"level":"info","ts":1591959367.316352,"caller":"task/main.go:10","msg":"this is info message"}
{"level":"info","ts":1591959367.3163702,"caller":"task/main.go:11","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","ts":1591959367.3163917,"caller":"task/main.go:13","msg":"this is warn message"}
{"level":"error","ts":1591959367.3163974,"caller":"task/main.go:14","msg":"this is error message","stacktrace":"main.main\n\t/home/wohu/GoCode/src/task/main.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:200"}

3.1.4 對比總結

我們首先創建了一個Logger, 然後使用Info/ErrorLogger方法記錄消息

日誌記錄器方法的語法是這樣的:

func (log *Logger) MethodXXX(msg string,fields ...Field)

其中 MethodXXX 是一個可變參數函數,可以是 Info/Error/Debug/Panic 等。每個方法都接受一個消息字符串和任意數量的 zapcore.Field 長參數。

每個 zapcore.Field 其實就是一組鍵值對參數。

3.2 Sugared Logger

默認的zap記錄器需要結構化標籤,需要使用特定值類型的函數。

log.Info("this is info message with fields",
  zap.Int("age",24),zap.String("agender","man"))

雖然會先的很長,但是對性能要求較高的話,這是最快的選擇。也可以使用Sugared Logger, 它基於printf分割的反射類型檢測,提供更簡單的語法來添加混合類型的標籤。

我們使用 Sugared Logger 來實現相同的功能。

func main(){
    logger,_:=zap.NewDevelopment()
    slogger:=logger.Sugar()
    
      slogger.Debugf("debug message age is %d,agender is %s",19,"man")
      slogger.Info("Info() uses sprint")
      slogger.Infof("Infof() uses %s","sprintf")
        slogger.Infow("Infow() allows tags","name","Legolas","type",1)
}

輸出結果:

2020-06-12T19:23:54.184+0800  DEBUG  task/main.go:11  debug message age is 19, agender is man
2020-06-12T19:23:54.185+0800  INFO  task/main.go:12  Info() uses sprint
2020-06-12T19:23:54.185+0800  INFO  task/main.go:13  Infof() uses sprintf
2020-06-12T19:23:54.185+0800  INFO  task/main.go:14  Infow() allows tags  {"name": "Legolas", "type": 1}

如果需要,可以隨時使用記錄器上的. Desugar() 方法從 sugar logger 切換到標準記錄器。

log := slogger.Desugar()

log.Info("After Desugar; INFO message")
log.Warn("After Desugar; WARN message")
log.Error("After Desugar; ERROR message")
  1. 將日誌寫入文件

我們將使用zap.New()方法來手動傳遞所有配置,而不是使用像zap.NewProduction()這樣的預置方法來創建logger

func New(core zapcore.Core,options ...Option) *Logger

zapcore.Core 需要三個配置—Encoder,WriteSyncer,LogLevel。

zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
file,_:=os.Create("./test.log")
writeSyncer:=zapcore.AddSync(file)

代碼示例:

package main

import (
  "os"

  "go.uber.org/zap"
  "go.uber.org/zap/zapcore"
)

var sugarLogger *zap.SugaredLogger

func InitLogger() {

  encoder := getEncoder()
  writeSyncer := getLogWriter()
  core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

  // zap.AddCaller()  添加將調用函數信息記錄到日誌中的功能。
  logger := zap.New(core, zap.AddCaller())
  sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
  encoderConfig := zap.NewProductionEncoderConfig()
  encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改時間編碼器

  // 在日誌文件中使用大寫字母記錄日誌級別
  encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
  // NewConsoleEncoder 打印更符合人們觀察的方式
  return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter() zapcore.WriteSyncer {
  file, _ := os.Create("./test.log")
  return zapcore.AddSync(file)
}

func main() {
  InitLogger()
  sugarLogger.Info("this is info message")
  sugarLogger.Infof("this is %s, %d", "aaa", 1234)
  sugarLogger.Error("this is error message")
  sugarLogger.Info("this is info message")
}

輸出日誌文件:

2020-06-16T09:01:06.192+0800  INFO  task/main.go:40  this is info message
2020-06-16T09:01:06.192+0800  INFO  task/main.go:41  this is aaa, 1234
2020-06-16T09:01:06.192+0800  ERROR  task/main.go:42  this is error message
2020-06-16T09:01:06.192+0800  INFO  task/main.go:43  this is info message
  1. 使用 lumberjack 進行日誌切割歸檔

因爲zap本身不支持切割歸檔日誌文件,爲了添加日誌切割歸檔功能,我們將使用第三方庫lumberjack來實現

5.1 安裝 lumberjack

go get github.com/natefinch/lumberjack

5.2 將 lumberjack 加入 zap logger

要在 zap 中加入 lumberjack 支持,我們需要修改 WriteSyncer 代碼。我們將按照下面的代碼修改 getLogWriter() 函數:

func getLogWriter() zapcore.WriteSyncer{
  lumberJackLogger:=&lumberjack.Logger{
    Filename: "./test.log",
    MaxSize: 10,
    MaxBackups:5,
    MaxAge: 30,
    Compress: false, 
  }
  return zapcore.AddSync(lumberJackLogger)
}

Lumberjack Logger 採用以下屬性作爲輸入:

完整代碼:

package main

import (
  "github.com/natefinch/lumberjack"
  "go.uber.org/zap"
  "go.uber.org/zap/zapcore"
)

var sugarLogger *zap.SugaredLogger

func InitLogger() {

  encoder := getEncoder()
  writeSyncer := getLogWriter()
  core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

  // zap.AddCaller()  添加將調用函數信息記錄到日誌中的功能。
  logger := zap.New(core, zap.AddCaller())
  sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
  encoderConfig := zap.NewProductionEncoderConfig()
  encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改時間編碼器

  // 在日誌文件中使用大寫字母記錄日誌級別
  encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
  // NewConsoleEncoder 打印更符合人們觀察的方式
  return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter() zapcore.WriteSyncer {
  lumberJackLogger := &lumberjack.Logger{
    Filename:   "./test.log",
    MaxSize:    10,
    MaxBackups: 5,
    MaxAge:     30,
    Compress:   false,
  }
  return zapcore.AddSync(lumberJackLogger)
}

func main() {
  InitLogger()
  sugarLogger.Info("this is info message")
  sugarLogger.Infof("this is %s, %d", "aaa", 1234)
  sugarLogger.Error("this is error message")
  sugarLogger.Info("this is info message")
}
  1. Log 第三方庫 uber-zap 使用

package main
import (
    "time"
    "github.com/natefinch/lumberjack"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)
var logger *zap.Logger
// logpath 日誌文件路徑
// loglevel 日誌級別
func InitLogger(logpath string, loglevel string) {
    // 日誌分割
    hook := lumberjack.Logger{
        Filename:   logpath, // 日誌文件路徑,默認 os.TempDir()
        MaxSize:    10,      // 每個日誌文件保存10M,默認 100M
        MaxBackups: 30,      // 保留30個備份,默認不限
        MaxAge:     7,       // 保留7天,默認不限
        Compress:   true,    // 是否壓縮,默認不壓縮
    }
    write := zapcore.AddSync(&hook)
    // 設置日誌級別
    // debug 可以打印出 info debug warn
    // info  級別可以打印 warn info
    // warn  只能打印 warn
    // debug->info->warn->error
    var level zapcore.Level
    switch loglevel {
    case "debug":
        level = zap.DebugLevel
    case "info":
        level = zap.InfoLevel
    case "error":
        level = zap.ErrorLevel
    default:
        level = zap.InfoLevel
    }
    encoderConfig := zapcore.EncoderConfig{
        TimeKey:        "time",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "linenum",
        MessageKey:     "msg",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,  // 小寫編碼器
        EncodeTime:     zapcore.ISO8601TimeEncoder,     // ISO8601 UTC 時間格式
        EncodeDuration: zapcore.SecondsDurationEncoder, //
        EncodeCaller:   zapcore.FullCallerEncoder,      // 全路徑編碼器
        EncodeName:     zapcore.FullNameEncoder,
    }
    // 設置日誌級別
    atomicLevel := zap.NewAtomicLevel()
    atomicLevel.SetLevel(level)
    core := zapcore.NewCore(
        // zapcore.NewConsoleEncoder(encoderConfig),
        zapcore.NewJSONEncoder(encoderConfig),
        // zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(&write)), // 打印到控制檯和文件
        write,
        level,
    )
    // 開啓開發模式,堆棧跟蹤
    caller := zap.AddCaller()
    // 開啓文件及行號
    development := zap.Development()
    // 設置初始化字段,如:添加一個服務器名稱
    filed := zap.Fields(zap.String("serviceName", "serviceName"))
    // 構造日誌
    logger = zap.New(core, caller, development, filed)
    logger.Info("DefaultLogger init success")
}
func main() {
    // 歷史記錄日誌名字爲:all.log,服務重新啓動,日誌會追加,不會刪除
    InitLogger("./all.log", "debug")
    // 強結構形式
    logger.Info("test",
        zap.String("string", "string"),
        zap.Int("int", 3),
        zap.Duration("time", time.Second),
    )
    // 必須 key-value 結構形式 性能下降一點
    logger.Sugar().Infow("test-",
        "string", "string",
        "int", 1,
        "time", time.Second,
    )
}

從例子看出:

  1. 它同時提供了結構化日誌記錄和 printf 風格的日誌記錄

  2. 先初始化 lumberjack 後初始化 zap

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://zhuanlan.zhihu.com/p/371547318