欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang日志操作庫(kù)zap的使用詳解

 更新時(shí)間:2024年03月08日 10:45:33   作者:九卷  
zap?是?uber?開源的一個(gè)高性能,結(jié)構(gòu)化,分級(jí)記錄的日志記錄包,本文主要為大家詳細(xì)介紹了zap的具體使用,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

一、簡(jiǎn)介

zap 是 uber 開源的一個(gè)高性能,結(jié)構(gòu)化,分級(jí)記錄的日志記錄包。

go1.20.2

zap v1.24.0

zap的特性

  • 高性能:zap 對(duì)日志輸出進(jìn)行了多項(xiàng)優(yōu)化以提高它的性能
  • 日志分級(jí):有 Debug,Info,Warn,Error,DPanic,Panic,F(xiàn)atal 等
  • 日志記錄結(jié)構(gòu)化:日志內(nèi)容記錄是結(jié)構(gòu)化的,比如 json 格式輸出
  • 自定義格式:用戶可以自定義輸出的日志格式
  • 自定義公共字段:用戶可以自定義公共字段,大家輸出的日志內(nèi)容就共同擁有了這些字段
  • 調(diào)試:可以打印文件名、函數(shù)名、行號(hào)、日志時(shí)間等,便于調(diào)試程序
  • 自定義調(diào)用棧級(jí)別:可以根據(jù)日志級(jí)別輸出它的調(diào)用棧信息
  • Namespace:日志命名空間。定義命名空間后,所有日志內(nèi)容就在這個(gè)命名空間下。命名空間相當(dāng)于一個(gè)文件夾
  • 支持 hook 操作

高性能介紹

與其它日志庫(kù)對(duì)比

github官網(wǎng)的對(duì)比圖,下面的對(duì)比圖來(lái)自:https://github.com/uber-go/zap#performance

Log a message and 10 fields:

PackageTimeTime % to zapObjects Allocated
? zap2900 ns/op+0%5 allocs/op
? zap (sugared)3475 ns/op+20%10 allocs/op
zerolog10639 ns/op+267%32 allocs/op
go-kit14434 ns/op+398%59 allocs/op
logrus17104 ns/op+490%81 allocs/op
apex/log32424 ns/op+1018%66 allocs/op
log1533579 ns/op+1058%76 allocs/op

Log a message with a logger that already has 10 fields of context:

PackageTimeTime % to zapObjects Allocated
? zap373 ns/op+0%0 allocs/op
? zap (sugared)452 ns/op+21%1 allocs/op
zerolog288 ns/op-23%0 allocs/op
go-kit11785 ns/op+3060%58 allocs/op
logrus19629 ns/op+5162%70 allocs/op
log1521866 ns/op+5762%72 allocs/op
apex/log30890 ns/op+8182%55 allocs/op

Log a static string, without any context or printf-style templating:

PackageTimeTime % to zapObjects Allocated
? zap381 ns/op+0%0 allocs/op
? zap (sugared)410 ns/op+8%1 allocs/op
zerolog369 ns/op-3%0 allocs/op
standard library385 ns/op+1%2 allocs/op
go-kit606 ns/op+59%11 allocs/op
logrus1730 ns/op+354%25 allocs/op
apex/log1998 ns/op+424%7 allocs/op
log154546 ns/op+1093%22 allocs/op

做了哪些優(yōu)化

基于反射的序列化和字符串格式化,它們都是 CPU 密集型計(jì)算且分配很多小的內(nèi)存。具體到 Go 語(yǔ)言中,使用 encoding/json 和 fmt.Fprintf 格式化 interface{} 會(huì)使程序性能降低。

Zap 咋解決呢?Zap 使用一個(gè)無(wú)反射、零分配的 JOSN 編碼器,基礎(chǔ) Logger 盡可能避免序列化開銷和內(nèi)存分配開銷。在此基礎(chǔ)上,zap 還構(gòu)建了更高級(jí)的 SuggaredLogger。

二、quickstart快速開始

zap 安裝:

go get -u go.uber.org/zap

zap 提供了 2 種日志記錄器:SugaredLogger 和 Logger。

在需要性能但不是很重要的情況下,使用 SugaredLogger 較合適。它比其它結(jié)構(gòu)化日志包快 4-10 倍,包括 結(jié)構(gòu)化日志和 printf 風(fēng)格的 API。看下面使用 SugaredLogger 例子:

logger, _ := zap.NewProduction()
defer logger.Sync() // zap底層有緩沖。在任何情況下執(zhí)行 defer logger.Sync() 是一個(gè)很好的習(xí)慣
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
 // 字段是松散類型,不是強(qiáng)類型
  "url", url,
  "attempt", 3,
  "backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)

當(dāng)性能和類型安全很重要時(shí),請(qǐng)使用 Logger。它比 SugaredLogger 更快,分配的資源更少,但它只支持結(jié)構(gòu)化日志和強(qiáng)類型字段。

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
  // 字段是強(qiáng)類型,不是松散類型
  zap.String("url", url),
  zap.Int("attempt", 3),
  zap.Duration("backoff", time.Second),
)

三、NewExample/NewDevelopment/NewProduction使用

zap 為我們提供了三種快速創(chuàng)建 logger 的方法: zap.NewProduction()zap.NewDevelopment(),zap.NewExample()。

見(jiàn)名思義,Example 一般用在測(cè)試代碼中,Development 用在開發(fā)環(huán)境中,Production 用在生成環(huán)境中。這三種方法都預(yù)先設(shè)置好了配置信息。

NewExample()使用

NewExample 構(gòu)建一個(gè) logger,專門為在 zap 的測(cè)試示例使用。它將 DebugLevel 及以上日志用 JSON 格式標(biāo)準(zhǔn)輸出,但它省略了時(shí)間戳和調(diào)用函數(shù),以保持示例輸出的簡(jiǎn)短和確定性。

為什么說(shuō) zap.NewExample() 是 zap 為我們提供快速創(chuàng)建 logger 的方法呢?

因?yàn)樵谶@個(gè)方法里,zap 已經(jīng)定義好了日志配置項(xiàng)部分默認(rèn)值。來(lái)看它的代碼:

// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127
func NewExample(options ...Option) *Logger {
	encoderCfg := zapcore.EncoderConfig{
        MessageKey:     "msg",  // 日志內(nèi)容key:val, 前面的key設(shè)為msg
		LevelKey:       "level", // 日志級(jí)別的key設(shè)為level
		NameKey:        "logger", // 日志名
		EncodeLevel:    zapcore.LowercaseLevelEncoder, //日志級(jí)別,默認(rèn)小寫
		EncodeTime:     zapcore.ISO8601TimeEncoder, // 日志時(shí)間
		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()使用

NewDevelopment() 構(gòu)建一個(gè)開發(fā)使用的 Logger,它以人性化的格式將 DebugLevel 及以上日志信息輸出。它的底層使用

NewDevelopmentConfig().Build(...Option) 構(gòu)建。它的日志格式各種設(shè)置在函數(shù) NewDevelopmentEncoderConfig() 里,想查看詳情設(shè)置,請(qǐng)點(diǎn)進(jìn)去查看。

使用例子:

package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	logger, _ := zap.NewDevelopment()
	defer logger.Sync()

	logger.Info("failed to fetch url",
		// 強(qiáng)類型字段
		zap.String("url", "http://example.com"),
		zap.Int("attempt", 3),
		zap.Duration("duration", time.Second),
	)

	logger.With(
		// 強(qiáng)類型字段
		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"}

上面日志輸出了文件名和行號(hào),NewExample() 沒(méi)有

NewProduction()使用

NewProduction() 構(gòu)建了一個(gè)合理的 Prouction 日志記錄器,它將 info 及以上的日志內(nèi)容以 JSON 格式記寫入標(biāo)準(zhǔn)錯(cuò)誤里。

它的底層使用 NewProductionConfig().Build(...Option) 構(gòu)建。它的日志格式設(shè)置在函數(shù) 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)

	// 或更簡(jiǎn)潔 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"}

上面日志輸出了文件名和行號(hào),NewExample() 沒(méi)有

使用配置

在這 3 個(gè)函數(shù)中,可以傳入一些配置項(xiàng)。為什么能傳入配置項(xiàng)?我們來(lái)看看 NewExample() 函數(shù)定義:

func NewExample(options ...Option) *Logger

它的函數(shù)傳參有一個(gè) ...Option 選項(xiàng),是一個(gè) interface 類型,它關(guān)聯(lián)的是 Logger struct。只要返回 Option 就可以傳進(jìn) NewExample() 里。在 zap/options.go 文件中可以看到很多返回 Option 的函數(shù),也就是說(shuō)這些函數(shù)都可以傳入 NewExample 函數(shù)里。這里用到了 Go 里面的一個(gè)編碼技巧,函數(shù)選項(xiàng)模式。

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() 添加回調(diào)函數(shù):

Hook (鉤子函數(shù))回調(diào)函數(shù)為用戶提供一種簡(jiǎn)單方法,在每次日志內(nèi)容記錄后運(yùn)行這個(gè)回調(diào)函數(shù),執(zhí)行用戶需要的操作。也就是說(shuō)記錄完日志后你還想做其它事情就可以調(diào)用這個(gè)函數(shù)。

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

四、logger和sugaredlogger區(qū)別

從上面例子中看出,zap 有 2 種格式化日志方式:logger 和 sugared logger。

sugared logger:

  • 它有很好的性能,比一般日志包快 4-10 倍。
  • 支持結(jié)構(gòu)化的日志。
  • 支持 printf 風(fēng)格的日志。
  • 日志字段不需要定義類型

logger(沒(méi)有sugar)

  • 它的性能比 sugared logger 還要快。
  • 它只支持強(qiáng)類型的結(jié)構(gòu)化日志。
  • 它應(yīng)用在對(duì)性能更加敏感日志記錄中,它的內(nèi)存分配次數(shù)更少。
  • 比如如果每一次內(nèi)存分配都很重要的話可以使用這個(gè)。對(duì)類型安全有嚴(yán)格要求也可以使用這個(gè)。

logger 和 sugaredlogger 相互轉(zhuǎn)換:

// 創(chuàng)建 logger
logger := zap.NewExample()
defer logger.Sync()

// 轉(zhuǎn)換 SugaredLogger
sugar := logger.Sugar()

// 轉(zhuǎn)換 logger
plain := sugar.Desugar()

怎么快速構(gòu)建一個(gè) logger 呢?有下面種幾種方法:

  • zap.NewProduction()
  • zap.NewDevelopment()
  • zap.Example()

主要區(qū)別:

  • 記錄日志信息和結(jié)構(gòu)不同。
  • Example 和 Production 是 json 格式輸出,Development 是普通一行格式輸出,如果后面帶有字段輸出話用json格式。

相同點(diǎn):

  • 默認(rèn)情況下都會(huì)打印日志信息到 console 界面
  • 都是通過(guò) logger 調(diào)用 Info、Error 等方法

怎么選擇:

  • 需要不錯(cuò)的性能但不是很重要的情況下,可以選擇 sugaredlogger。它支持結(jié)構(gòu)化日志和 printf 風(fēng)格的日志記錄。sugaredlogger 的日志記錄是松散類型的,不是強(qiáng)類型,能接受可變數(shù)量的鍵值對(duì)。如果你要用強(qiáng)類型字段記錄,可以使用 SugaredLogger.With 方法。
  • 如果是每次或每微秒記錄日志都很重要情況下,可以使用 logger,它比 sugaredlogger 每次分配內(nèi)存更少,性能更高。但它僅支持強(qiáng)類型的結(jié)構(gòu)化日志記錄。

五、自定義配置

快速構(gòu)建 logger 日志記錄器最簡(jiǎn)單的方法就是用 zap 預(yù)定義了配置的方法:NewExample(), NewProduction() 和NewDevelopment(),這 3 個(gè)方法通過(guò)單個(gè)函數(shù)調(diào)用就可以構(gòu)建一個(gè)日志計(jì)記錄器,也可以簡(jiǎn)單配置。

但是有的項(xiàng)目需要更多的定制,怎么辦?zap 的 Config 結(jié)構(gòu)和 zapcore 的 EncoderConfig 結(jié)構(gòu)可以幫助你,讓你能夠進(jìn)行自定義配置。

配置結(jié)構(gòu)說(shuō)明

Config 配置項(xiàng)源碼:

// zap v1.24.0
type Config struct {
    // 動(dòng)態(tài)改變?nèi)罩炯?jí)別,在運(yùn)行時(shí)你可以安全改變?nèi)罩炯?jí)別
	Level AtomicLevel `json:"level" yaml:"level"`
    // 將日志記錄器設(shè)置為開發(fā)模式,在 WarnLevel 及以上級(jí)別日志會(huì)包含堆棧跟蹤信息
	Development bool `json:"development" yaml:"development"`
    // 在日志中停止調(diào)用函數(shù)所在文件名、行數(shù)
	DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
    // 完全禁止自動(dòng)堆棧跟蹤。默認(rèn)情況下,在 development 中,warnlevel及以上日志級(jí)別會(huì)自動(dòng)捕獲堆棧跟蹤信息
    // 在 production 中,ErrorLevel 及以上也會(huì)自動(dòng)捕獲堆棧信息
	DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
    // 設(shè)置采樣策略。沒(méi)有 SamplingConfing 將禁止采樣
	Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
    // 設(shè)置日志編碼。可以設(shè)置為 console 和 json。也可以通過(guò) RegisterEncoder 設(shè)置第三方編碼格式
	Encoding string `json:"encoding" yaml:"encoding"`
    // 為encoder編碼器設(shè)置選項(xiàng)。詳細(xì)設(shè)置信息在 zapcore.zapcore.EncoderConfig
	EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
    // 日志輸出地址可以是一個(gè) URLs 地址或文件路徑,可以設(shè)置多個(gè)
	OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
    // 錯(cuò)誤日志輸出地址。默認(rèn)輸出標(biāo)準(zhǔn)錯(cuò)誤信息
	ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
    // 可以添加自定義的字段信息到 root logger 中。也就是每條日志都會(huì)攜帶這些字段信息,公共字段
	InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}

EncoderConfig 結(jié)構(gòu)源碼,它里面也有很多配置選項(xiàng),具體請(qǐng)看 這里:

// zapcore@v1.24.0
type EncoderConfig struct {
    // 為log entry設(shè)置key。如果 key 為空,那么在日志中的這部分信息也會(huì)省略
	MessageKey     string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默認(rèn)為msg
	LevelKey       string `json:"levelKey" yaml:"levelKey"`//日志級(jí)別的健名,默認(rèn)為level
	TimeKey        string `json:"timeKey" yaml:"timeKey"`//記錄日志時(shí)間的健名,默認(rèn)為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"`
    // 日志編碼的一些設(shè)置項(xiàng)
	EncodeLevel    LevelEncoder    `json:"levelEncoder" yaml:"levelEncoder"`
	EncodeTime     TimeEncoder     `json:"timeEncoder" yaml:"timeEncoder"`
	EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
	EncodeCaller   CallerEncoder   `json:"callerEncoder" yaml:"callerEncoder"`
    // 與其它編碼器不同, 這個(gè)編碼器可選
	EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
    // 配置 interface{} 類型編碼器。如果沒(méi)設(shè)置,將用 json.Encoder 進(jìn)行編碼
	NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
    // 配置 console 中字段分隔符。默認(rèn)使用 tab 
	ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}
type Entry struct {
	Level      Level
	Time       time.Time
	LoggerName string
	Message    string
	Caller     EntryCaller
	Stack      string
}

例子1:基本配置

zap.Config 自定義配置,看官方的一個(gè)基本例子:

package main

import (
	"encoding/json"

	"go.uber.org/zap"
)

// https://pkg.go.dev/go.uber.org/zap@v1.24.0#hdr-Configuring_Zap
func main() {
	// 表示 zap.Config 的 json 原始編碼
	// outputPath: 設(shè)置日志輸出路徑,日志內(nèi)容輸出到標(biāo)準(zhǔn)輸出和文件 logs.log
	// errorOutputPaths:設(shè)置錯(cuò)誤日志輸出路徑
	rawJSON := []byte(`{
      "level": "debug",
      "encoding": "json",
      "outputPaths": ["stdout", "./logs.log"],
      "errorOutputPaths": ["stderr"],
      "initialFields": {"foo": "bar"},
      "encoderConfig": {
        "messageKey": "message-customer",
        "levelKey": "level",
        "levelEncoder": "lowercase"
      }
    }`)

	// 把 json 格式數(shù)據(jù)解析到 zap.Config struct
	var cfg zap.Config
	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
		panic(err)
	}
	// cfg.Build() 為配置對(duì)象創(chuàng)建一個(gè) Logger
	// zap.Must() 封裝了 Logger,Must()函數(shù)如果返回值不是 nil,就會(huì)報(bào) panic。也就是檢查Build是否錯(cuò)誤
	logger := zap.Must(cfg.Build())
	defer logger.Sync()

	logger.Info("logger construction succeeded")
}

/*
Must() 函數(shù)
//  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"}

并且在程序目錄下生成了一個(gè)文件 logs.log,里面記錄的日志內(nèi)容也是上面consol輸出內(nèi)容。每運(yùn)行一次就在日志文件末尾append一次內(nèi)容。

例子2:高級(jí)配置

上面的配置只是基本的自定義配置,如果有一些復(fù)雜的需求,比如在多個(gè)文件之間分割日志。

或者輸出到不是 file 的文件中,比如輸出到 kafka 中,那么就需要使用 zapcore 包。

在下面的例子中,我們將把日志輸出到 kafka 中,并且也輸出到 console 里。并且我們對(duì) kafka 不同主題進(jìn)行編碼設(shè)置,對(duì)輸出到 console 編碼進(jìn)行設(shè)置,也希望處理高優(yōu)先級(jí)的日志。

官方例子:

package main

import (
	"io"
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	// 首先,定義不同級(jí)別日志處理邏輯
	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl < zapcore.ErrorLevel
	})

	// 假設(shè)有2個(gè)kafka 的 topic,一個(gè) debugging,一個(gè) errors

	// zapcore.AddSync 添加一個(gè)文件句柄。
	topicDebugging := zapcore.AddSync(io.Discard)
	topicErrors := zapcore.AddSync(io.Discard)

	// 如果他們對(duì)并發(fā)使用不安全,我們可以用 zapcore.Lock 添加一個(gè) mutex 互斥鎖。
	consoleDebugging := zapcore.Lock(os.Stdout)
	consoleErrors := zapcore.Lock(os.Stderr)

	// 設(shè)置 kafka 和 console 輸出配置
	kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
	consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())

	// 把上面的設(shè)置加入到 zapcore.NewCore() 函數(shù)里,然后再把他們加入到 zapcore.NewTee() 函數(shù)里
	core := zapcore.NewTee(
		zapcore.NewCore(kafkaEncoder, topicErrors, highPriority),
		zapcore.NewCore(consoleEncoder, consoleErrors, highPriority),
		zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority),
		zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
	)

	// 最后調(diào)用 zap.New() 函數(shù)
	logger := zap.New(core)
	defer logger.Sync()
	logger.Info("constructed a logger")
}

例子3:日志寫入文件

與上面例子2相似,但是比它簡(jiǎn)單

package main

import (
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	writetofile()
}

func writetofile() {
	// 設(shè)置一些配置參數(shù)
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.ISO8601TimeEncoder
	fileEncoder := zapcore.NewJSONEncoder(config)
	defaultLogLevel := zapcore.DebugLevel // 設(shè)置 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),
	)
}

例子4:根據(jù)日志級(jí)別寫入不同文件

這個(gè)與上面例子2相似

package main

import (
	"os"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	writeToFileWithLogLevel()
}

func writeToFileWithLogLevel() {
	// 設(shè)置配置
	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 ")
}

主要是設(shè)置日志級(jí)別,和把 2 個(gè)設(shè)置的 NewCore 放入到方法 NewTee 中。

六、Hook和Namespace

zap.Hook() :

Hook (鉤子函數(shù))回調(diào)函數(shù)為用戶提供一種簡(jiǎn)單方法,在每次日志內(nèi)容記錄后運(yùn)行這個(gè)回調(diào)函數(shù),執(zhí)行用戶需要的操作。也就是說(shuō)記錄完日志后你還想做其它事情就可以調(diào)用這個(gè)函數(shù)。

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

zap.Namespace():

創(chuàng)建一個(gè)命名空間,后面的字段都在這名字空間中。Namespace 就像一個(gè)文件夾,后面文件都放在這個(gè)文件夾里。

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 這個(gè)庫(kù)是按照日志大小切割日志文件。

安裝 v2 版本:

go get -u github.com/natefinch/lumberjack@v2

Code:

log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log", // 文件位置
    MaxSize:    500,  // megabytes,M 為單位,達(dá)到這個(gè)設(shè)置數(shù)后就進(jìn)行日志切割
    MaxBackups: 3,    // 保留舊文件最大份數(shù)
    MaxAge:     28,   //days , 舊文件最大保存天數(shù)
    Compress:   true, // disabled by default,是否壓縮日志歸檔,默認(rèn)不壓縮
})

參照它的文檔和結(jié)合上面自定義配置的例子,寫一個(gè)例子:

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 // 設(shè)置時(shí)間格式
	fileEncoder := zapcore.NewJSONEncoder(config)

	core := zapcore.NewCore(
		fileEncoder,                       //編碼設(shè)置
		zapcore.AddSync(lumberjacklogger), //輸出到文件
		zap.InfoLevel,                     //日志等級(jí)
	)

	logger := zap.New(core)
	defer logger.Sync()

    // 測(cè)試分割日志
	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 ")
	}

}

八、其它方法使用

全局 Logger

zap提供了 2 種全局 Logger,一個(gè)是 zap.Logger,調(diào)用 zap.L() 獲?。?/p>

另外一個(gè)是 zap.SugaredLogger ,調(diào)用 zap.S() 獲取。

注意:直接調(diào)用 zap.L() 或 zap.S() 記錄日志的話,它是不會(huì)記錄任何日志信息。需要調(diào)用 ReplaceGlobals() 函數(shù)將它設(shè)置為全局 Logger。

ReplaceGlobals 替換全局 Logger 和 SugaredLogger,并返回一個(gè)函數(shù)來(lái)恢復(fù)原始值。

并發(fā)使用它是安全的。

看看 zap/global.go 中的源碼:

// https://github.com/uber-go/zap/blob/v1.24.0/global.go

var (
	_globalMu sync.RWMutex
	_globalL  = NewNop()
	_globalS  = _globalL.Sugar()
)

func L() *Logger {
	_globalMu.RLock() // 加了讀鎖,所以并發(fā)使用是安全的
	l := _globalL
	_globalMu.RUnlock()
	return l
}

func S() *SugaredLogger {
	_globalMu.RLock() // 加了讀鎖,所以并發(fā)使用是安全的
	s := _globalS
	_globalMu.RUnlock()
	return s
}

func ReplaceGlobals(logger *Logger) func() {
	_globalMu.Lock()
	prev := _globalL
	_globalL = logger
	_globalS = logger.Sugar()
	_globalMu.Unlock()
	return func() { ReplaceGlobals(prev) } // 返回一個(gè)函數(shù)類型
}

上面源碼中的關(guān)鍵是 _globalL = NewNop() , NewNop 函數(shù)源碼在 zap/logger.go 中,這個(gè)函數(shù)返回初始化了的一個(gè) *Logger:

// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L85

func NewNop() *Logger {
	return &Logger{
		core:        zapcore.NewNopCore(),
		errorOutput: zapcore.AddSync(io.Discard),
		addStack:    zapcore.FatalLevel + 1,
		clock:       zapcore.DefaultClock,
	}
}

上面是源碼簡(jiǎn)析,下面給出一個(gè)簡(jiǎn)單使用的例子。

簡(jiǎn)單使用例子:

package main

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

func main() {
	// 直接調(diào)用是不會(huì)記錄日志信息的,所以下面日志信息不會(huì)輸出
	zap.L().Info("no log info")
	zap.S().Info("no log info [sugared]")

	logger := zap.NewExample()
	defer logger.Sync()

	zap.ReplaceGlobals(logger) // 全局logger,zap.L() 和 zap.S() 需要調(diào)用 ReplaceGlobals 函數(shù)才會(huì)記錄日志信息
	zap.L().Info("log info")
	zap.S().Info("log info [sugared]")
}

運(yùn)行輸出:

{"level":"info","msg":"log info"} {"level":"info","msg":"log info [sugared]"}

與標(biāo)準(zhǔn)日志庫(kù)搭配

zap 提供了一個(gè)函數(shù) NewStdLog,可以把標(biāo)準(zhǔn)日志庫(kù) log 轉(zhuǎn)換為 zap 的日志,這為我們從標(biāo)準(zhǔn)日志庫(kù)轉(zhuǎn)換到 zap 日志庫(kù)的使用提供了簡(jiǎn)潔的轉(zhuǎn)換操作。

例子:

package main

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

func main() {
	logger := zap.NewExample()
	defer logger.Sync()

	std := zap.NewStdLog(logger)
	std.Print("standard logger wrapper")
}

運(yùn)行輸出:

{"level":"info","msg":"standard logger wrapper"}

如果你還想設(shè)置日志級(jí)別,可以使用另外一個(gè)函數(shù) NewStdLogAt,它的第二個(gè)參數(shù)就是日志級(jí)別:

NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error)

一段代碼中使用log另外的使用zap

zap 還提供了另外一個(gè)函數(shù) RedirectStdLog,它可以幫助我們?cè)谝欢未a中使用標(biāo)準(zhǔn)日志庫(kù) log,其它地方還是使用 zap.Logger。如下例子:

package main

import (
	"log"

	"go.uber.org/zap"
)

func main() {
	logger := zap.NewExample()
	defer logger.Sync()

	undo := zap.RedirectStdLog(logger)
	log.Print("redirected standard library")
	undo()

	log.Print("this zap logger")
}

輸出:

{"level":"info","msg":"redirected standard library"} 2023/05/06 00:47:11 this zap logger

同樣如果想增加日志級(jí)別,可以使用函數(shù) RedirectStdLogAt:

func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error)

輸出調(diào)用堆棧

主要是調(diào)用函數(shù) zap.AddStacktrace(),見(jiàn)下面例子:

package main

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

func Hello() {
	Warn("hello", zap.String("h", "world"), zap.Int("c", 1))
}

func Warn(msg string, fields ...zap.Field) {
	zap.L().Warn(msg, fields...)
}

func main() {
	logger, _ := zap.NewProduction(zap.AddStacktrace(zapcore.WarnLevel))
	defer logger.Sync()

	zap.ReplaceGlobals(logger)

	Hello()
}

運(yùn)行輸出:

{"level":"warn","ts":1683306442.3578277,"caller":"zapdemos/addstacktrace.go:13","msg":"hello","h":"world","c":1, "stacktrace": "main.Warn\n\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:13\n main.Hello\n\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:9\n main.main\n\tD:/work/mygo/go-exercises/zapdemos/addstacktrace.go:22\n runtime.main\n\tE:/programfile/go/src/runtime/proc.go:250"}

輸出文件名和行號(hào)

AddCaller 將 Logger 配置為使用 zap 調(diào)用者的文件名、行號(hào)和函數(shù)名稱,把這些信息添加到日志記錄中。它底層調(diào)用的是 WithCaller。

addcaller.go:

package main

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

func main() {
	logger, _ := zap.NewProduction(zap.AddCaller())
	defer logger.Sync()

	logger.Info("AddCaller:line No and filename")
}

輸出:

{"level":"info","ts":1683307204.6184027,"caller":"zapdemos/addcaller.go:11","msg":"AddCaller:line No and filename"}

logger.Info() 方法在第11行被調(diào)用。

zap 還提供了另外一個(gè)函數(shù) zap.AddCallerSkip(skip int) Option,可以設(shè)置向上跳幾層,然后記錄文件名和行號(hào)。向上跳幾層就是跳過(guò)調(diào)用者的數(shù)量。有時(shí)函數(shù)調(diào)用可能有嵌套,用這個(gè)函數(shù)可以定位到里面的函數(shù)。

addcallerskip.go

package main

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

func main() {
	logger, _ := zap.NewProduction(zap.AddCaller(), zap.AddCallerSkip(1))
	defer logger.Sync()

	zap.ReplaceGlobals(logger)

	Hello()
}

func Hello() {
	Warn("hello", zap.String("h", "world"), zap.Int("c", 1))
}

func Warn(msg string, fields ...zap.Field) {
	zap.L().Warn(msg, fields...)
}

輸出:

{"level":"warn","ts":1683308118.1684704,"caller":"zapdemos/addcallerskip.go:17","msg":"hello","h":"world","c":1}

日志中的 17 表示 Hello() 函數(shù)里的 Warn() 的行號(hào)。

如果 zap.AddCallerSkip(2) ,日志中顯示行號(hào)為 13,表示 Hello() 的行號(hào)。

九、zap使用總結(jié)

  • zap 的使用,先創(chuàng)建 logger,再調(diào)用各個(gè)日志級(jí)別方法記錄日志信息。比如 logger.Info()。
  • zap 提供了三種快速創(chuàng)建 logger 的方法: zap.Newproduction(),zap.NewDevelopment(),zap.NewExample()。見(jiàn)名思義,Example 一般用在測(cè)試代碼中,Development 用在開發(fā)環(huán)境中,Production 用在生成環(huán)境中。這三種方法都預(yù)先設(shè)置好了配置信息。它們的日志數(shù)據(jù)類型輸出都是強(qiáng)類型。
  • 當(dāng)然,zap 也提供了給用戶自定義的方法 zap.New()。比如用戶可以自定義一些配置信息等。
  • 在上面的例子中,幾乎都有 defer logger.Sync() 這段代碼,為什么?因?yàn)?zap 底層 API 允許緩沖日志以提高性能,在默認(rèn)情況下,日志記錄器是沒(méi)有緩沖的。但是在進(jìn)程退出之前調(diào)用 Sync() 方法是一個(gè)好習(xí)慣。
  • 如果你在 zap 中使用了 sugaredlogger,把 zap 創(chuàng)建 logger 的三種方法用 logger.Sugar() 包裝下,那么 zap 就支持 printf 風(fēng)格的格式化輸出,也支持以 w 結(jié)尾的方法。如 Infow,Infof 等。這種就是通用類型日志輸出,不是強(qiáng)類型輸出,不需要強(qiáng)制指定輸出的數(shù)據(jù)類型。它們的性能區(qū)別,通用類型會(huì)比強(qiáng)類型下降 50% 左右。

比如 Infow 的輸出形式,Infow 不需要 zap.String 這種指定字段的數(shù)據(jù)類型。如下代碼:

sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
            "url", url,
            "attempt", 3,
            "backoff", time.Second,
)

強(qiáng)類型輸出,比如 Info 方法輸出字段和值就需要指定數(shù)據(jù)類型:

logger.Info("failed to fetch url",
		// 強(qiáng)類型字段
		zap.String("url", "http://example.com"),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second),
)

強(qiáng)類型輸出和通用類型輸出區(qū)別

通用類型輸出,經(jīng)過(guò) interface{} 轉(zhuǎn)換會(huì)有性能損失,標(biāo)準(zhǔn)庫(kù)的 fmt.Printf 為了通用性就用了 interface{} 這種”萬(wàn)能型“的數(shù)據(jù)類型,另外它還使用了反射,性能進(jìn)一步降低。

zap 強(qiáng)類型輸出,zap 為了提供日志輸出性能,zap 的強(qiáng)類型輸出沒(méi)有使用 interface{} 和反射。zap 默認(rèn)輸出就是強(qiáng)類型。

上面介紹,zap 中 3 種創(chuàng)建 logger 方式(zap.Newproduction(),zap.NewDevelopment(),zap.NewExample())就是強(qiáng)類型日志字段,當(dāng)然,也可以轉(zhuǎn)化為通用類型,用 logger.Sugar() 方法創(chuàng)建 SugaredLogger。

zap.Namespace() 創(chuàng)建一個(gè)命名空間,后面的字段都在這名字空間中。Namespace 就像一個(gè)文件夾,后面文件都放在這個(gè)文件夾里。

logger.Info("some message",
    zap.Namespace("shop"),
    zap.String("shopid", "s1234323"),
  )

{"level":"info","msg":"some message","shop":{"shopid":"s1234323"}} 

十、Demo源碼地址

zap demos

https://github.com/jiujuan/go-exercises/tree/main/zapdemos

以上就是Golang日志操作庫(kù)zap的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Go日志操作庫(kù)zap的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go的切片擴(kuò)容機(jī)制詳解

    go的切片擴(kuò)容機(jī)制詳解

    本文主要介紹了go的切片擴(kuò)容機(jī)制詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • golang中對(duì)

    golang中對(duì)"引用傳遞"的誤解

    這篇文章主要介紹了golang中對(duì)“引用傳遞”的誤解,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-06-06
  • golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式

    golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式

    這篇文章主要介紹了golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • 簡(jiǎn)單講解Go程序中使用MySQL的方法

    簡(jiǎn)單講解Go程序中使用MySQL的方法

    這篇文章主要介紹了Go程序中使用MySQL的方法,需要使用第三方包來(lái)進(jìn)行連接,需要的朋友可以參考下
    2015-10-10
  • 深入了解Go語(yǔ)言中的作用域和變量重聲明

    深入了解Go語(yǔ)言中的作用域和變量重聲明

    在?Go?語(yǔ)言中,代碼塊的嵌套和作用域是程序設(shè)計(jì)的關(guān)鍵概念之一,本文將探討如何在?Go?語(yǔ)言中利用代碼塊的嵌套和作用域來(lái)組織代碼,并介紹變量重聲明的規(guī)則,感興趣的可以了解下
    2023-11-11
  • Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解

    Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解

    你是否遇到過(guò)在無(wú)法準(zhǔn)確確定json層級(jí)關(guān)系的情況下對(duì)json進(jìn)行解析的需求呢?本文就來(lái)和大家介紹一次解析不確定的json對(duì)象的經(jīng)歷,以及遇到的問(wèn)題和解決方法
    2023-02-02
  • 詳解Golang中的各種時(shí)間操作

    詳解Golang中的各種時(shí)間操作

    這篇文章主要介紹了詳解Golang中的各種時(shí)間操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Golang 斷言與閉包使用解析

    Golang 斷言與閉包使用解析

    這篇文章主要介紹了Golang 斷言與閉包使用解析,Go中的斷言用于判斷變量的類型,更多相關(guān)內(nèi)容需要的朋友可以參考一下
    2022-07-07
  • Golang中的panic之避免和處理程序中的異常情況

    Golang中的panic之避免和處理程序中的異常情況

    Golang中的panic是一種異常處理機(jī)制,可以在程序出現(xiàn)異常情況時(shí)終止程序并打印錯(cuò)誤信息。為了避免panic對(duì)程序的影響,開發(fā)者可以采用一系列技巧,如defer+recover、編寫可重入的代碼、使用錯(cuò)誤返回值等。這些技巧可以幫助開發(fā)者優(yōu)雅地處理程序中的異常情況
    2023-04-04
  • go?mod文件內(nèi)容版本號(hào)簡(jiǎn)單用法詳解

    go?mod文件內(nèi)容版本號(hào)簡(jiǎn)單用法詳解

    這篇文章主要為大家介紹了go?mod文件內(nèi)容版本號(hào)簡(jiǎn)單用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10

最新評(píng)論