天天看點

Golang 日志架構 Zap 入坑指南簡介Cases

文章目錄

  • 簡介
  • Cases
    • case 1: Hello World
    • case 2: SugaredLogger
    • case 3: 定制化 SugaredLogger
      • 使用 Console 格式
      • 修改日期顯示格式
      • 增加 caller 資訊
      • 修改日志級别
      • 添加自定義字段

簡介

衆所周知,Zap 是個很 nb 的日志架構,作為入門篇,本文主要用幾個例子來直覺地感受下 Zap 寫出來的日志長什麼樣,符不符合我們的需求(主要是審美需求),性能什麼的我們攢錢不 care。

以下栗子由淺入深,循序漸進,大部分栗子可以直接 copy 運作。

Cases

case 1: Hello World

Zap 的 Hello World 代碼大概長下面這樣:

package main

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

func main() {
	var logger *zap.Logger
	logger, _ = zap.NewProduction()
	logger.Debug("i am debug")   // 這行不會列印,因為預設日志級别是 INFO
	logger.Info("i am info")     // INFO  級别日志,這個會正常列印
	logger.Warn("i am warn")     // WARN  級别日志,這個會正常列印
	logger.Error("i am error")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	logger.Fatal("i am fatal")   // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出大概如下:

{"level":"info","ts":1608362501.672074,"caller":"zap2/main.go:11","msg":"i am info"}
{"level":"warn","ts":1608362501.672139,"caller":"zap2/main.go:12","msg":"i am warn"}
{"level":"error","ts":1608362501.672147,"caller":"zap2/main.go:13","msg":"i am error","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap2/main.go:13\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
{"level":"fatal","ts":1608362501.6721768,"caller":"zap2/main.go:14","msg":"i am fatal","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap2/main.go:14\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
           

以上代碼應該是相當簡潔易懂了,在這裡需要再唠嗑一下的是關于輸出的日志的幾個字段的說明:

  • level: 顧名思義,就是日志的級别了,預設的日志級别是 INFO,是以我們的第一行日志沒有被列印出來
  • ts: timestamp,時間戳的意思
  • caller: 調用者,就是列印日志的那行代碼的位置
  • stacktrace: 調用堆棧,及列印日志的那行代碼所在的堆棧資訊,預設 ERROR 級别及以上的日志會附帶堆棧資訊,可以修改

case 2: SugaredLogger

Sugar 是糖的意思,顧名思義,

zap.SugaredLogger

就是對

zap.Logger

進行了封裝,提供了一些進階的文法糖特性,如支援使用類似

fmt.Printf

的形式進行格式化輸出,使用

zap.SugaredLogger

改造一下上面的例子:

package main

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

func main() {
	var sugaredLogger *zap.SugaredLogger
	logger, _ := zap.NewProduction()
	sugaredLogger = logger.Sugar()
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行不會列印,因為預設日志級别是 INFO
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}

           

編譯運作該程式,輸出類似如下:

{"level":"info","ts":1608365481.615607,"caller":"zap3/main.go:13","msg":"i am info, using sugar"}
{"level":"warn","ts":1608365481.615681,"caller":"zap3/main.go:14","msg":"i am warn, using sugar"}
{"level":"error","ts":1608365481.6156878,"caller":"zap3/main.go:15","msg":"i am error, using sugar","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap3/main.go:15\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
{"level":"fatal","ts":1608365481.6157131,"caller":"zap3/main.go:16","msg":"i am fatal, using sugar","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap3/main.go:16\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
           

case 3: 定制化 SugaredLogger

使用

zap.NewProduction()

實際上是建立了一個帶了預置配置的

zap.Logger

,我們可以使用

zap.New(core zapcore.Core, options ...Option)

來手動傳遞所有配置項,

zapcore.Core

需要 3 個配置:

  • Encoder: 編碼器,用于告訴

    zap

    以什麼樣的形式寫入日志。

    zap

    提供了

    jsonEncoder

    consoleEncoder

    兩種内置的編碼器
  • WriterSyncer: 告訴

    zap

    将日志寫到哪裡,比如檔案(

    os.File

    ) 或标準輸出(

    os.stdout

    )
  • LevelEnabler: 用于設定

    zap.Logger

    的日志級别

我們用定制化的方式來改寫上述栗子:

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {

	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)  // 設定日志輸出的裝置,這裡還是使用标準輸出,也可以傳一個 File 類型讓它寫入到檔案
	encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())  // 設定編碼器,即日志輸出的格式,預設提供了 json 和 console 兩種編碼器,這裡我們還是使用 json 的編碼器
	core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel) // 設定日志的預設級别
	logger := zap.New(core)
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行不會列印,因為日志級别是 INFO
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

{"level":"info","ts":1608367820.9526541,"msg":"i am info, using sugar"}
{"level":"warn","ts":1608367820.952697,"msg":"i am warn, using sugar"}
{"level":"error","ts":1608367820.9527,"msg":"i am error, using sugar"}
{"level":"fatal","ts":1608367820.952702,"msg":"i am fatal, using sugar"}
           

和上面的相比,這裡不會列印

caller

stackstace

,因為我們沒有配置,需要我們顯式去配置它們。

使用 Console 格式

預設的

json

格式非常方面用于專業的第三日志分析工具(如 elk)處理,但可讀性就不是非常好,如果日志是用來給人看的話,設定成

console

格式的可讀性會好一點,特别是存在換行的時候(

json

格式的每條日志隻會顯示一行),将上述栗子改成

console

格式:

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {

	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)
	encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())  // 設定 console 編碼器
	core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)
	logger := zap.New(core)
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行不會列印,因為日志級别是 INFO
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

1.60837067575392e+09    info    i am info, using sugar
1.6083706757539682e+09  warn    i am warn, using sugar
1.608370675753972e+09   error   i am error, using sugar
1.6083706757539752e+09  fatal   i am fatal, using sugar
           

修改日期顯示格式

上面栗子的時間戳看起來不是人能看得懂的對不對?所有我們要修改成人能看懂的(

human readable

),繼續修改上述栗子:

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {
	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)

	// 格式相關的配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder  // 修改時間戳的格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志級别使用大寫顯示

	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)
	logger := zap.New(core)
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行不會列印,因為日志級别是 INFO
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

2020-12-19T17:44:19.031+0800    INFO    i am info, using sugar
2020-12-19T17:44:19.031+0800    WARN    i am warn, using sugar
2020-12-19T17:44:19.031+0800    ERROR   i am error, using sugar
2020-12-19T17:44:19.031+0800    FATAL   i am fatal, using sugar
           

呃…看起來正常點了,上述栗子順便把日志級别顯示改成大寫了,看起來更習慣一點~

增加 caller 資訊

在最前面的栗子中我們可以看到,使用預設的配置列印出來的日志是帶有

caller

(即列印日志的代碼所在的位置資訊)的,如果需要的話我們可以把它加上去:

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {

	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)

	// 格式相關的配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder  // 修改時間戳的格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志級别使用大寫顯示
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)
	logger := zap.New(core, zap.AddCaller())  // 增加 caller 資訊
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行不會列印,因為日志級别是 INFO
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

2020-12-19T17:50:24.257+0800    INFO    zap3/main.go:29 i am info, using sugar
2020-12-19T17:50:24.257+0800    WARN    zap3/main.go:30 i am warn, using sugar
2020-12-19T17:50:24.257+0800    ERROR   zap3/main.go:31 i am error, using sugar
2020-12-19T17:50:24.257+0800    FATAL   zap3/main.go:32 i am fatal, using sugar
           

修改日志級别

預設的級别的

INFO

,我們把它改成

DEBUG

, 這個也簡單:

package main

import (
	"fmt"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {

	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)

	// 格式相關的配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder  // 修改時間戳的格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志級别使用大寫顯示
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	core := zapcore.NewCore(encoder, writer, zapcore.DebugLevel)  // 将日志級别設定為 DEBUG
	logger := zap.New(core, zap.AddCaller())  // 增加 caller 資訊
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行現在可以列印出來了!
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

2020-12-19T17:52:29.438+0800    DEBUG   zap3/main.go:28 i am debug, using sugar
2020-12-19T17:52:29.439+0800    INFO    zap3/main.go:29 i am info, using sugar
2020-12-19T17:52:29.439+0800    WARN    zap3/main.go:30 i am warn, using sugar
2020-12-19T17:52:29.439+0800    ERROR   zap3/main.go:31 i am error, using sugar
2020-12-19T17:52:29.439+0800    FATAL   zap3/main.go:32 i am fatal, using sugar
           

可以看到

DEBUG

級别的日志也列印出來了。

添加自定義字段

有時候我們需要在每行日志中都加入一些字段,用來辨別這些日志,可以在調用

zap.New()

建立

logger

的時候把這些字段設定上去,如下:

package main

import (
	"fmt"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"os"
)

func main() {

	// 配置 sugaredLogger
	var sugaredLogger *zap.SugaredLogger
	writer := zapcore.AddSync(os.Stdout)

	// 格式相關的配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder  // 修改時間戳的格式
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志級别使用大寫顯示
	encoder := zapcore.NewConsoleEncoder(encoderConfig)

	core := zapcore.NewCore(encoder, writer, zapcore.DebugLevel)  // 将日志級别設定為 DEBUG
	logger := zap.New(core, zap.AddCaller(), zap.Fields(zapcore.Field{  // 添加 uuid 字段
		Key:       "uuid",
		Type:      zapcore.StringType,
		String:    uuid.New().String(),
	}))
	sugaredLogger = logger.Sugar()

	// 列印日志
	sugaredLogger.Debugf("i am debug, using %s", "sugar")   // 這行現在可以列印出來了!
	sugaredLogger.Infof("i am info, using %s", "sugar")      // INFO  級别日志,這個會正常列印
	sugaredLogger.Warnf("i am warn, using %s", "sugar")    // WARN  級别日志,這個會正常列印
	sugaredLogger.Errorf("i am error, using %s", "sugar")   // ERROR 級别日志,這個會列印,并附帶堆棧資訊
	sugaredLogger.Fatalf("i am fatal, using %s", "sugar")    // FATAL 級别日志,這個會列印,附帶堆棧資訊,并調用 os.Exit 退出
	fmt.Println("can i be printed?")  // 這行不會列印,呃...上面已經退出了
}
           

編譯運作該程式,輸出類似如下:

2020-12-19T18:02:15.093+0800    DEBUG   zap3/main.go:32 i am debug, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800    INFO    zap3/main.go:33 i am info, using sugar  {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800    WARN    zap3/main.go:34 i am warn, using sugar  {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800    ERROR   zap3/main.go:35 i am error, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800    FATAL   zap3/main.go:36 i am fatal, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
           

可以看到每行日志都帶有

uuid

資訊了。