Go 紀錄檔記錄庫:uber-go 的紀錄檔操作庫 zap 使用
zap 是 uber 開源的一個高效能,結構化,分級記錄的紀錄檔記錄包。
go1.20.2
zap v1.24.0
高效能:zap 對紀錄檔輸出進行了多項優化以提高它的效能
紀錄檔分級:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等
紀錄檔記錄結構化:紀錄檔內容記錄是結構化的,比如 json 格式輸出
自定義格式:使用者可以自定義輸出的紀錄檔格式
自定義公共欄位:使用者可以自定義公共欄位,大家輸出的紀錄檔內容就共同擁有了這些欄位
偵錯:可以列印檔名、函數名、行號、紀錄檔時間等,便於偵錯程式
自定義呼叫棧級別:可以根據紀錄檔級別輸出它的呼叫棧資訊
Namespace:紀錄檔名稱空間。定義名稱空間後,所有紀錄檔內容就在這個名稱空間下。名稱空間相當於一個資料夾
支援 hook 操作
看github官網的對比圖,下面的對比圖來自:https://github.com/uber-go/zap#performance
Log a message and 10 fields:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 2900 ns/op | +0% | 5 allocs/op |
⚡ zap (sugared) | 3475 ns/op | +20% | 10 allocs/op |
zerolog | 10639 ns/op | +267% | 32 allocs/op |
go-kit | 14434 ns/op | +398% | 59 allocs/op |
logrus | 17104 ns/op | +490% | 81 allocs/op |
apex/log | 32424 ns/op | +1018% | 66 allocs/op |
log15 | 33579 ns/op | +1058% | 76 allocs/op |
Log a message with a logger that already has 10 fields of context:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 373 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 452 ns/op | +21% | 1 allocs/op |
zerolog | 288 ns/op | -23% | 0 allocs/op |
go-kit | 11785 ns/op | +3060% | 58 allocs/op |
logrus | 19629 ns/op | +5162% | 70 allocs/op |
log15 | 21866 ns/op | +5762% | 72 allocs/op |
apex/log | 30890 ns/op | +8182% | 55 allocs/op |
Log a static string, without any context or printf
-style templating:
Package | Time | Time % to zap | Objects Allocated |
---|---|---|---|
⚡ zap | 381 ns/op | +0% | 0 allocs/op |
⚡ zap (sugared) | 410 ns/op | +8% | 1 allocs/op |
zerolog | 369 ns/op | -3% | 0 allocs/op |
standard library | 385 ns/op | +1% | 2 allocs/op |
go-kit | 606 ns/op | +59% | 11 allocs/op |
logrus | 1730 ns/op | +354% | 25 allocs/op |
apex/log | 1998 ns/op | +424% | 7 allocs/op |
log15 | 4546 ns/op | +1093% | 22 allocs/op |
基於反射的序列化和字串格式化,它們都是 CPU 密集型計算且分配很多小的記憶體。具體到 Go 語言中,使用 encoding/json 和 fmt.Fprintf 格式化 interface{} 會使程式效能降低。
Zap 咋解決呢?Zap 使用一個無反射、零分配的 JOSN 編碼器,基礎 Logger 儘可能避免序列化開銷和記憶體分配開銷。在此基礎上,zap 還構建了更高階的 SuggaredLogger。
zap 安裝:
go get -u go.uber.org/zap
zap 提供了 2 種紀錄檔記錄器:SugaredLogger
和 Logger
。
在需要效能但不是很重要的情況下,使用 SugaredLogger 較合適。它比其它結構化紀錄檔包快 4-10 倍,包括 結構化紀錄檔和 printf 風格的 API。看下面使用 SugaredLogger 例子:
logger, _ := zap.NewProduction()
defer logger.Sync() // zap底層有緩衝。在任何情況下執行 defer logger.Sync() 是一個很好的習慣
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// 欄位是鬆散型別,不是強型別
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)
當效能和型別安全很重要時,請使用 Logger。它比 SugaredLogger 更快,分配的資源更少,但它只支援結構化紀錄檔和強型別欄位。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// 欄位是強型別,不是鬆散型別
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
zap 為我們提供了三種快速建立 logger 的方法: zap.NewProduction()
,zap.NewDevelopment()
,zap.NewExample()
。
見名思義,Example 一般用在測試程式碼中,Development 用在開發環境中,Production 用在生成環境中。這三種方法都預先設定好了設定資訊。
NewExample 構建一個 logger,專門為在 zap 的測試範例使用。它將 DebugLevel 及以上紀錄檔用 JSON 格式標準輸出,但它省略了時間戳和呼叫函數,以保持範例輸出的簡短和確定性。
為什麼說 zap.NewExample()
是 zap 為我們提供快速建立 logger 的方法呢?
因為在這個方法裡,zap 已經定義好了紀錄檔設定項部分預設值。來看它的程式碼:
// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
func NewExample(options ...Option) *Logger {
encoderCfg := zapcore.EncoderConfig{
MessageKey: "msg", // 紀錄檔內容key:val, 前面的key設為msg
LevelKey: "level", // 紀錄檔級別的key設為level
NameKey: "logger", // 紀錄檔名
EncodeLevel: zapcore.LowercaseLevelEncoder, //紀錄檔級別,預設小寫
EncodeTime: zapcore.ISO8601TimeEncoder, // 紀錄檔時間
EncodeDuration: zapcore.StringDurationEncoder,
}
core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel)
return New(core).WithOptions(options...)
}
使用例子:
package main
import (
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
logger.Debug("this is debug message")
logger.Info("this is info message")
logger.Info("this is info message with fileds",
zap.Int("age", 37),
zap.String("agender", "man"),
)
logger.Warn("this is warn message")
logger.Error("this is error 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":37,"agender":"man"}
{"level":"warn","msg":"this is warn message"}
{"level":"error","msg":"this is error message"}
NewDevelopment() 構建一個開發使用的 Logger,它以人性化的格式將 DebugLevel 及以上紀錄檔資訊輸出。它的底層使用
NewDevelopmentConfig().Build(...Option)
構建。它的紀錄檔格式各種設定在函數 NewDevelopmentEncoderConfig() 裡,想檢視詳情設定,請點進去檢視。
使用例子:
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewDevelopment()
defer logger.Sync()
logger.Info("failed to fetch url",
// 強型別欄位
zap.String("url", "http://example.com"),
zap.Int("attempt", 3),
zap.Duration("duration", time.Second),
)
logger.With(
// 強型別欄位
zap.String("url", "http://development.com"),
zap.Int("attempt", 4),
zap.Duration("duration", time.Second*5),
).Info("[With] failed to fetch url")
}
輸出:
2023-03-22T16:02:45.760+0800 INFO zapdemos/newdevelopment1.go:13 failed to fetch url {"url": "http://example.com", "attempt": 3, "duration": "1s"}
2023-03-22T16:02:45.786+0800 INFO zapdemos/newdevelopment1.go:25 [With] failed to fetch url {"url": "http://development.com", "attempt": 4, "duration": "5s"}
NewProduction() 構建了一個合理的 Prouction 紀錄檔記錄器,它將 info 及以上的紀錄檔內容以 JSON 格式記寫入標準錯誤裡。
它的底層使用 NewProductionConfig().Build(...Option)
構建。它的紀錄檔格式設定在函數 NewProductionEncoderConfig 裡。
使用例子:
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
url := "http://zap.uber.io"
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"time", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)
// 或更簡潔 Sugar() 使用
// sugar := zap.NewProduction().Sugar()
// defer sugar.Sync()
}
輸出:
{"level":"info","ts":1679472893.2944522,"caller":"zapdemos/newproduction1.go:16","msg":"failed to fetch URL","url":"http://zap.uber.io","attempt":3,"time":1}
{"level":"info","ts":1679472893.294975,"caller":"zapdemos/newproduction1.go:22","msg":"Failed to fetch URL: http://zap.uber.io"}
在這 3 個函數中,可以傳入一些設定項。為什麼能傳入設定項?我們來看看 NewExample() 函數定義:
func NewExample(options ...Option) *Logger
它的函數傳參有一個 ...Option
選項,是一個 interface 型別,它關聯的是 Logger struct。只要返回 Option 就可以傳進 NewExample() 裡。在 zap/options.go 檔案中可以看到很多返回 Option 的函數,也就是說這些函數都可以傳入 NewExample 函數裡。這裡用到了 Go 裡面的一個編碼技巧,函數選項模式。
zap.Fields() 新增欄位到 Logger 中:
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction(zap.Fields(
zap.String("log_name", "testlog"),
zap.String("log_author", "prometheus"),
))
defer logger.Sync()
logger.Info("test fields output")
logger.Warn("warn info")
}
輸出:
{"level":"info","ts":1679477929.842166,"caller":"zapdemos/fields.go:14","msg":"test fields output","log_name":"testlog","log_author":"prometheus"}
{"level":"warn","ts":1679477929.842166,"caller":"zapdemos/fields.go:16","msg":"warn info","log_name":"testlog","log_author":"prometheus"}
zap.Hook() 新增回撥函數:
Hook (勾點函數)回撥函數為使用者提供一種簡單方法,在每次紀錄檔內容記錄後執行這個回撥函數,執行使用者需要的操作。也就是說記錄完紀錄檔後你還想做其它事情就可以呼叫這個函數。
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("[zap.Hooks]test Hooks")
return nil
}))
defer logger.Sync()
logger.Info("test output")
logger.Warn("warn info")
}
輸出:
{"level":"info","msg":"test output"}
[zap.Hooks]test Hooks
{"level":"warn","msg":"warn info"}
[zap.Hooks]test Hooks
從上面例子中看出,zap 有 2 種格式化紀錄檔方式:logger 和 sugared logger。
- 它有很好的效能,比一般紀錄檔包快 4-10 倍。
- 支援結構化的紀錄檔。
- 支援 printf 風格的紀錄檔。
- 紀錄檔欄位不需要定義型別
- 它的效能比 sugared logger 還要快。
- 它只支援強型別的結構化紀錄檔。
它應用在對效能更加敏感紀錄檔記錄中,它的記憶體分配次數更少。
比如如果每一次記憶體分配都很重要的話可以使用這個。對型別安全有嚴格要求也可以使用這個。
logger 和 sugaredlogger 相互轉換:
// 建立 logger
logger := zap.NewExample()
defer logger.Sync()
// 轉換 SugaredLogger
sugar := logger.Sugar()
// 轉換 logger
plain := sugar.Desugar()
怎麼快速構建一個 logger 呢?有下面種幾種方法:
主要區別:
記錄紀錄檔資訊和結構不同。
Example 和 Production 是 json 格式輸出,Development 是普通一行格式輸出,如果後面帶有欄位輸出話用json格式。
相同點:
怎麼選擇:
快速構建 logger 紀錄檔記錄器最簡單的方法就是用 zap 預定義了設定的方法:NewExample(), NewProduction()
和NewDevelopment()
,這 3 個方法通過單個函數呼叫就可以構建一個紀錄檔計記錄器,也可以簡單設定。
但是有的專案需要更多的客製化,怎麼辦?zap 的 Config 結構和 zapcore 的 EncoderConfig 結構可以幫助你,讓你能夠進行自定義設定。
Config 設定項原始碼:
// zap v1.24.0
type Config struct {
// 動態改變紀錄檔級別,在執行時你可以安全改變紀錄檔級別
Level AtomicLevel `json:"level" yaml:"level"`
// 將紀錄檔記錄器設定為開發模式,在 WarnLevel 及以上級別紀錄檔會包含堆疊跟蹤資訊
Development bool `json:"development" yaml:"development"`
// 在紀錄檔中停止呼叫函數所在檔名、行數
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// 完全禁止自動堆疊跟蹤。預設情況下,在 development 中,warnlevel及以上紀錄檔級別會自動捕獲堆疊跟蹤資訊
// 在 production 中,ErrorLevel 及以上也會自動捕獲堆疊資訊
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// 設定取樣策略。沒有 SamplingConfing 將禁止取樣
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// 設定紀錄檔編碼。可以設定為 console 和 json。也可以通過 RegisterEncoder 設定第三方編碼格式
Encoding string `json:"encoding" yaml:"encoding"`
// 為encoder編碼器設定選項。詳細設定資訊在 zapcore.zapcore.EncoderConfig
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// 紀錄檔輸出地址可以是一個 URLs 地址或檔案路徑,可以設定多個
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// 錯誤紀錄檔輸出地址。預設輸出標準錯誤資訊
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// 可以新增自定義的欄位資訊到 root logger 中。也就是每條紀錄檔都會攜帶這些欄位資訊,公共欄位
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
EncoderConfig 結構原始碼,它裡面也有很多設定選項,具體請看 這裡:
// [email protected]
type EncoderConfig struct {
// 為log entry設定key。如果 key 為空,那麼在紀錄檔中的這部分資訊也會省略
MessageKey string `json:"messageKey" yaml:"messageKey"`//紀錄檔資訊的健名,預設為msg
LevelKey string `json:"levelKey" yaml:"levelKey"`//紀錄檔級別的健名,預設為level
TimeKey string `json:"timeKey" yaml:"timeKey"`//記錄紀錄檔時間的健名,預設為time
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// 紀錄檔編碼的一些設定項
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// 與其它編碼器不同, 這個編碼器可選
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// 設定 interface{} 型別編碼器。如果沒設定,將用 json.Encoder 進行編碼
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// 設定 console 中欄位分隔符。預設使用 tab
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Entry struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
}
package main
import (
"encoding/json"
"go.uber.org/zap"
)
// https://pkg.go.dev/go.uber.org/[email protected]#hdr-Configuring_Zap
func main() {
// 表示 zap.Config 的 json 原始編碼
// outputPath: 設定紀錄檔輸出路徑,紀錄檔內容輸出到標準輸出和檔案 logs.log
// errorOutputPaths:設定錯誤紀錄檔輸出路徑
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout", "./logs.log"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message-customer",
"levelKey": "level",
"levelEncoder": "lowercase"
}
}`)
// 把 json 格式資料解析到 zap.Config struct
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
// cfg.Build() 為設定物件建立一個 Logger
// zap.Must() 封裝了 Logger,Must()函數如果返回值不是 nil,就會報 panic。也就是檢查Build是否錯誤
logger := zap.Must(cfg.Build())
defer logger.Sync()
logger.Info("logger construction succeeded")
}
/*
Must() 函數
// var logger = zap.Must(zap.NewProduction())
func Must(logger *Logger, err error) *Logger {
if err != nil {
panic(err)
}
return logger
}
*/
consol 輸出如下:
{"level":"info","message-customer":"logger construction succeeded","foo":"bar"}
並且在程式目錄下生成了一個檔案 logs.log,裡面記錄的紀錄檔內容也是上面consol輸出內容。每執行一次就在紀錄檔檔案末尾append一次內容。
上面的設定只是基本的自定義設定,如果有一些複雜的需求,比如在多個檔案之間分割紀錄檔。
或者輸出到不是 file 的檔案中,比如輸出到 kafka 中,那麼就需要使用 zapcore 包。
在下面的例子中,我們將把紀錄檔輸出到 kafka 中,並且也輸出到 console 裡。並且我們對 kafka 不同主題進行編碼設定,對輸出到 console 編碼進行設定,也希望處理高優先順序的紀錄檔。
官方例子:
package main
import (
"io"
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// 首先,定義不同級別紀錄檔處理邏輯
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel
})
// 假設有2個kafka 的 topic,一個 debugging,一個 errors
// zapcore.AddSync 新增一個檔案控制程式碼。
topicDebugging := zapcore.AddSync(io.Discard)
topicErrors := zapcore.AddSync(io.Discard)
// 如果他們對並行使用不安全,我們可以用 zapcore.Lock 新增一個 mutex 互斥鎖。
consoleDebugging := zapcore.Lock(os.Stdout)
consoleErrors := zapcore.Lock(os.Stderr)
// 設定 kafka 和 console 輸出設定
kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// 把上面的設定加入到 zapcore.NewCore() 函數裡,然後再把他們加入到 zapcore.NewTee() 函數裡
core := zapcore.NewTee(
zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
)
// 最後呼叫 zap.New() 函數
logger := zap.New(core)
defer logger.Sync()
logger.Info("constructed a logger")
}
與上面例子2相似,但是比它簡單
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
writetofile()
}
func writetofile() {
// 設定一些設定引數
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder
fileEncoder := zapcore.NewJSONEncoder(config)
defaultLogLevel := zapcore.DebugLevel // 設定 loglevel
logFile, _ := os.OpenFile("./log-test-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666)
// or os.Create()
writer := zapcore.AddSync(logFile)
logger := zap.New(
zapcore.NewCore(fileEncoder, writer, defaultLogLevel),
zap.AddCaller(),
zap.AddStacktrace(zapcore.ErrorLevel),
)
defer logger.Sync()
url := "http://www.test.com"
logger.Info("write log to file",
zap.String("url", url),
zap.Int("attemp", 3),
)
}
這個與上面例子2相似
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
writeToFileWithLogLevel()
}
func writeToFileWithLogLevel() {
// 設定設定
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder
fileEncoder := zapcore.NewJSONEncoder(config)
logFile, _ := os.OpenFile("./log-debug-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //紀錄檔記錄debug資訊
errFile, _ := os.OpenFile("./log-err-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //紀錄檔記錄error資訊
teecore := zapcore.NewTee(
zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel),
zapcore.NewCore(fileEncoder, zapcore.AddSync(errFile), zap.ErrorLevel),
)
logger := zap.New(teecore, zap.AddCaller())
defer logger.Sync()
url := "http://www.diff-log-level.com"
logger.Info("write log to file",
zap.String("url", url),
zap.Int("time", 3),
)
logger.With(
zap.String("url", url),
zap.String("name", "jimmmyr"),
).Error("test error ")
}
主要是設定紀錄檔級別,和把 2 個設定的 NewCore 放入到方法 NewTee 中。
Hook (勾點函數)回撥函數為使用者提供一種簡單方法,在每次紀錄檔內容記錄後執行這個回撥函數,執行使用者需要的操作。也就是說記錄完紀錄檔後你還想做其它事情就可以呼叫這個函數。
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error {
fmt.Println("[zap.Hooks]test Hooks")
return nil
}))
defer logger.Sync()
logger.Info("test output")
logger.Warn("warn info")
}
建立一個名稱空間,後面的欄位都在這名稱空間中。Namespace 就像一個資料夾,後面檔案都放在這個資料夾裡。
package main
import (
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync()
logger.Info("some message",
zap.Namespace("shop"),
zap.String("name", "LiLei"),
zap.String("grade", "No2"),
)
logger.Error("some error message",
zap.Namespace("shop"),
zap.String("name", "LiLei"),
zap.String("grade", "No3"),
)
}
輸出:
{"level":"info","msg":"some message","shop":{"name":"LiLei","grade":"No2"}}
{"level":"error","msg":"some error message","shop":{"name":"LiLei","grade":"No3"}}
lumberjack 這個庫是按照紀錄檔大小切割紀錄檔檔案。
安裝 v2 版本:
go get -u github.com/natefinch/lumberjack@v2
Code:
log.SetOutput(&lumberjack.Logger{
Filename: "/var/log/myapp/foo.log", // 檔案位置
MaxSize: 500, // megabytes,M 為單位,達到這個設定數後就進行紀錄檔切割
MaxBackups: 3, // 保留舊檔案最大份數
MaxAge: 28, //days , 舊檔案最大儲存天數
Compress: true, // disabled by default,是否壓縮紀錄檔歸檔,預設不壓縮
})
參照它的檔案和結合上面自定義設定的例子,寫一個例子:
package main
import (
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
lumberjacklogger := &lumberjack.Logger{
Filename: "./log-rotate-test.json",
MaxSize: 1, // megabytes
MaxBackups: 3,
MaxAge: 28, //days
Compress: true, // disabled by default
}
defer lumberjacklogger.Close()
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder // 設定時間格式
fileEncoder := zapcore.NewJSONEncoder(config)
core := zapcore.NewCore(
fileEncoder, //編碼設定
zapcore.AddSync(lumberjacklogger), //輸出到檔案
zap.InfoLevel, //紀錄檔等級
)
logger := zap.New(core)
defer logger.Sync()
// 測試分割紀錄檔
for i := 0; i < 8000; i++ {
logger.With(
zap.String("url", fmt.Sprintf("www.test%d.com", i)),
zap.String("name", "jimmmyr"),
zap.Int("age", 23),
zap.String("agradege", "no111-000222"),
).Info("test info ")
}
}
zap 的使用,先建立 logger,再呼叫各個紀錄檔級別方法記錄紀錄檔資訊。比如 logger.Info()。
zap 提供了三種快速建立 logger 的方法: zap.Newproduction()
,zap.NewDevelopment()
,zap.NewExample()
。見名思義,Example 一般用在測試程式碼中,Development 用在開發環境中,Production 用在生成環境中。這三種方法都預先設定好了設定資訊。它們的紀錄檔資料型別輸出都是強型別。
當然,zap 也提供了給使用者自定義的方法 zap.New()
。比如使用者可以自定義一些設定資訊等。
在上面的例子中,幾乎都有 defer logger.Sync()
這段程式碼,為什麼?因為 zap 底層 API 允許緩衝紀錄檔以提高效能,在預設情況下,紀錄檔記錄器是沒有緩衝的。但是在程序退出之前呼叫 Sync()
方法是一個好習慣。
如果你在 zap 中使用了 sugaredlogger,把 zap 建立 logger 的三種方法用 logger.Sugar()
包裝下,那麼 zap 就支援 printf 風格的格式化輸出,也支援以 w 結尾的方法。如 Infow,Infof 等。這種就是通用型別紀錄檔輸出,不是強型別輸出,不需要強制指定輸出的資料型別。它們的效能區別,通用型別會比強型別下降 50% 左右。
比如 Infow 的輸出形式,Infow 不需要 zap.String 這種指定欄位的資料型別。如下程式碼:
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
強型別輸出,比如 Info 方法輸出欄位和值就需要指定資料型別:
logger.Info("failed to fetch url",
// 強型別欄位
zap.String("url", "http://example.com"),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
強型別輸出和通用型別輸出區別
通用型別輸出,經過 interface{} 轉換會有效能損失,標準庫的 fmt.Printf 為了通用性就用了 interface{} 這種」萬能型「的資料型別,另外它還使用了反射,效能進一步降低。
zap 強型別輸出,zap 為了提供紀錄檔輸出效能,zap 的強型別輸出沒有使用 interface{} 和反射。zap 預設輸出就是強型別。
上面介紹,zap 中 3 種建立 logger 方式(zap.Newproduction()
,zap.NewDevelopment()
,zap.NewExample()
)就是強型別紀錄檔欄位,當然,也可以轉化為通用型別,用 logger.Sugar()
方法建立 SugaredLogger。
zap.Namespace()
建立一個名稱空間,後面的欄位都在這名稱空間中。Namespace 就像一個資料夾,後面檔案都放在這個資料夾裡。
logger.Info("some message",
zap.Namespace("shop"),
zap.String("shopid", "s1234323"),
)
{"level":"info","msg":"some message","shop":{"shopid":"s1234323"}}