Golang一日一庫之 日誌庫 zap

始識發表於2023-04-12

簡介

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

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

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

安裝

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

相關文章