文章目錄
- 簡介
- 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
資訊了。