Golang一日一庫之 紀錄檔庫 zap

2023-04-13 06:00:56

簡介

在開發過程中 會使用到紀錄檔庫去記錄錯誤的紀錄檔,尤其是golang中 有無窮無盡的error 如果不記錄,當你的程式碼出錯,就無從排錯了。
zap 是開源的 Go 高效能紀錄檔庫 主要有以下特點:

  1. 支援不同的紀錄檔級別
  2. 能夠列印基本資訊等但不支援紀錄檔的分割 但是可以使用 lumberjack 也是 zap 官方推薦用於紀錄檔分割

官網:https://github.com/uber-go/zap
https://pkg.go.dev/go.uber.org/zap

安裝

go get -u go.uber.org/zap

zap只支援Go的兩個最新小版本。

紀錄檔記錄器 logger和 sugared logger

zap庫的使用與其他的紀錄檔庫非常相似。先建立一個logger,然後呼叫各個級別的方法記錄紀錄檔
而 zap庫給我們提供兩種模式的紀錄檔記錄

  1. Logger
  2. Sugared Logger
    至於你想問他們之間有什麼區別,很簡單,我們先來看程式碼
    這裡我就直接用官網的例用程式碼了

Logger

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
	// Structured context as strongly typed Field values.
	zap.String("url", "https://www.baidu.com"),
	zap.Int("attempt", 3),
	zap.Duration("backoff", time.Second),
)


說實話我是很不喜歡logger模式的紀錄檔的
呼叫起來是真的麻煩 還要指定 int型別 string型別 這個型別那個型別
但優點也很明顯那就是 而且記憶體分配少效能至上

Sugared Logger

logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
	// Structured context as loosely typed key-value pairs.
	"url", "https://www.baidu.com",
	"attempt", 3,
	"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", "https://www.baidu.com")

這種就是printf風格的呼叫起來方便即開即用

Example 和 Production 以及development

Example

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")

結果

Production

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.Panic("this is panic message")

結果

NewDevelopment

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.Panic("this is panic message")

結果

三者對比

由上文可見
Example和Production使用的是json格式
而development使用行的形式
除此之外
Example和Production 所輸出的多少也不一樣。

具體如下:

Development

  • 從警告級別向上列印到堆疊中來跟蹤
  • 始終列印包/檔案/行(方法)
  • 在行尾新增任何額外欄位作為json字串
  • 以大寫形式列印級別名稱
  • 以毫秒為單位列印ISO8601格式的時間戳

Production

  • 偵錯級別訊息不記錄
  • Error,Dpanic級別的記錄,會在堆疊中跟蹤檔案,warn不會
  • 始終將呼叫者新增到檔案中
  • 以時間戳格式列印日期
  • 以小寫形式列印級別名稱

調整紀錄檔輸出的 格式

如下文程式碼所示

func getEncoder() zapcore.Encoder {
    encoderConfig := zap.NewDevelopmentEncoderConfig()
    {
        // LevelKey值變為 level
        encoderConfig.LevelKey = "level"
        // MessageKey值變為 msg
        encoderConfig.MessageKey = "msg"
        // TimeKey值 變成time
        encoderConfig.TimeKey = "time"
        // 把輸出的info 變成INFO 只需要丟物件 不許執行
        encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
        // 對時間進行格式化處理
        encoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
            encoder.AppendString(t.Local().Format("2006-01-02 15:04:05"))
        }
    }

    return zapcore.NewJSONEncoder(encoderConfig)
}

如上程式碼所示,可以調節任意位置,我註釋也標的很清楚

使用lumberjack進行配合

官網: https://pkg.go.dev/gopkg.in/natefinch/lumberjack.v2
zap沒有切割紀錄檔的功能,所以我們必須藉助第三方庫來實現

使用

要將 lumberjack 與標準庫的紀錄檔包一起使用,只需在應用程式啟動時將其傳遞到 SetOutput 函數中即可。

log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log",
    MaxSize:    500, // megabytes
    MaxBackups: 3,
    MaxAge:     28, //days
    Compress:   true, // disabled by default
})

如果要和Zap所結合的話 需要放入到zapcore.AddSync中

zapcore.AddSync(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log",
    MaxSize:    500, // megabytes
    MaxBackups: 3,
    MaxAge:     28, //days
    Compress:   true, // disabled by default
}) 

宣告紀錄檔並且初始化使用

我們上文以及設定好了紀錄檔的格式以及規定了紀錄檔輸出的位置 也做好了 lumberjack紀錄檔的切割
那我們該如何初始化呢
只需要宣告core 然後把這三個丟進去即可
如下程式碼所示

core := zapcore.NewCore(getEncoder(), zapcore.NewMultiWriteSyncer(zapcore.AddSync(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log",
    MaxSize:    500, // megabytes
    MaxBackups: 3,
    MaxAge:     28, //days
    Compress:   true, // disabled by default
}) , zapcore.AddSync(os.Stdout)), zapcore.DebugLevel)
zap.New(core).Sugar()

當然 會發現 我還加了一個值 zapcore.AddSync(os.Stdout))
這句程式碼是代表除了輸出到檔案中還會輸出到終端中,完成多個終端的輸出

完整程式碼 紀錄檔庫初始化元件

package conf

import (
    "fmt"
    "github.com/natefinch/lumberjack"
    "github.com/spf13/viper"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "os"
    "path/filepath"
    "time"
)

func InitLogger() *zap.SugaredLogger {
    logMode := zapcore.InfoLevel

    if viper.GetBool("model.development") {
        logMode = zapcore.DebugLevel
    }
    // 第一個引數是輸出的格式 第二個引數 輸出的位置

    //zapcore.NewMultiWriteSyncer 輸出到多個終端 比如 檔案 console中
    core := zapcore.NewCore(getEncoder(), zapcore.NewMultiWriteSyncer(getWriterSyncer(), zapcore.AddSync(os.Stdout)), logMode)
    return zap.New(core).Sugar()
}

// def 輸出紀錄檔的格式
func getEncoder() zapcore.Encoder {
    encoderConfig := zap.NewDevelopmentEncoderConfig()
    {
        // LevelKey值變為 level
        encoderConfig.LevelKey = "level"
        // MessageKey值變為 msg
        encoderConfig.MessageKey = "msg"
        // TimeKey值 變成time
        encoderConfig.TimeKey = "time"
        // 把輸出的info 變成INFO 只需要丟物件 不許執行
        encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
        // 對時間進行格式化處理
        encoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
            encoder.AppendString(t.Local().Format("2006-01-02 15:04:05"))
        }
    }

    return zapcore.NewJSONEncoder(encoderConfig)
}

// def 紀錄檔要輸出到什麼地方
func getWriterSyncer() zapcore.WriteSyncer {
    stSeparator := string(filepath.Separator)
    stRootDir, _ := os.Getwd()
    stLogFilePath := stRootDir + stSeparator + "log" + stSeparator + time.Now().Format("2006-01-02") + ".log"
    fmt.Println(stLogFilePath)

    // 紀錄檔分割
    hook := lumberjack.Logger{
        Filename:   stLogFilePath,                  // 紀錄檔檔案路徑,預設 os.TempDir()
        MaxSize:    viper.GetInt("log.MaxSize"),    // 每個紀錄檔檔案儲存500M,預設 100M
        MaxBackups: viper.GetInt("log.MaxBackups"), // 保留3個備份,預設不限
        MaxAge:     viper.GetInt("log.MaxAge"),     // 保留28天,預設不限
        Compress:   viper.GetBool("log.Compress"),  // 是否壓縮,預設不壓縮
    }

    return zapcore.AddSync(&hook)
}