亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go每日一庫(kù)之zap日志庫(kù)的安裝使用指南

 更新時(shí)間:2023年05月17日 08:38:56   作者:darjun  
這篇文章主要為大家介紹了Go每日一庫(kù)之zap安裝使用示例學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡(jiǎn)介

在很早之前的文章中,我們介紹過(guò) Go 標(biāo)準(zhǔn)日志庫(kù)log和結(jié)構(gòu)化的日志庫(kù)logrus。在熱點(diǎn)函數(shù)中記錄日志對(duì)日志庫(kù)的執(zhí)行性能有較高的要求,不能影響正常邏輯的執(zhí)行時(shí)間。uber開源的日志庫(kù)zap,對(duì)性能和內(nèi)存分配做了極致的優(yōu)化。

快速使用

先安裝:

$ go get go.uber.org/zap

后使用:

package main
import (
  "time"
  "go.uber.org/zap"
)
func main() {
  logger := zap.NewExample()
  defer logger.Sync()
  url := "http://example.org/api"
  logger.Info("failed to fetch URL",
    zap.String("url", url),
    zap.Int("attempt", 3),
    zap.Duration("backoff", time.Second),
  )
  sugar := logger.Sugar()
  sugar.Infow("failed to fetch URL",
    "url", url,
    "attempt", 3,
    "backoff", time.Second,
  )
  sugar.Infof("Failed to fetch URL: %s", url)
}

zap庫(kù)的使用與其他的日志庫(kù)非常相似。先創(chuàng)建一個(gè)logger,然后調(diào)用各個(gè)級(jí)別的方法記錄日志(Debug/Info/Error/Warn)。zap提供了幾個(gè)快速創(chuàng)建logger的方法,zap.NewExample()、zap.NewDevelopment()、zap.NewProduction(),還有高度定制化的創(chuàng)建方法zap.New()。創(chuàng)建前 3 個(gè)logger時(shí),zap會(huì)使用一些預(yù)定義的設(shè)置,它們的使用場(chǎng)景也有所不同。Example適合用在測(cè)試代碼中,Development在開發(fā)環(huán)境中使用,Production用在生成環(huán)境。

zap底層 API 可以設(shè)置緩存,所以一般使用defer logger.Sync()將緩存同步到文件中。

由于fmt.Printf之類的方法大量使用interface{}和反射,會(huì)有不少性能損失,并且增加了內(nèi)存分配的頻次。zap為了提高性能、減少內(nèi)存分配次數(shù),沒(méi)有使用反射,而且默認(rèn)的Logger只支持強(qiáng)類型的、結(jié)構(gòu)化的日志。必須使用zap提供的方法記錄字段。zap為 Go 語(yǔ)言中所有的基本類型和其他常見(jiàn)類型都提供了方法。這些方法的名稱也比較好記憶,zap.TypeTypebool/int/uint/float64/complex64/time.Time/time.Duration/error等)就表示該類型的字段,zap.Typepp結(jié)尾表示該類型指針的字段,zap.Typess結(jié)尾表示該類型切片的字段。如:

  • zap.Bool(key string, val bool) Fieldbool字段
  • zap.Boolp(key string, val *bool) Fieldbool指針字段;
  • zap.Bools(key string, val []bool) Fieldbool切片字段。

當(dāng)然也有一些特殊類型的字段:

  • zap.Any(key string, value interface{}) Field:任意類型的字段;
  • zap.Binary(key string, val []byte) Field:二進(jìn)制串的字段。

當(dāng)然,每個(gè)字段都用方法包一層用起來(lái)比較繁瑣。zap也提供了便捷的方法SugarLogger,可以使用printf格式符的方式。調(diào)用logger.Sugar()即可創(chuàng)建SugaredLogger。SugaredLogger的使用比Logger簡(jiǎn)單,只是性能比Logger低 50% 左右,可以用在非熱點(diǎn)函數(shù)中。調(diào)用SugarLoggerf結(jié)尾的方法與fmt.Printf沒(méi)什么區(qū)別,如例子中的Infof。同時(shí)SugarLogger還支持以w結(jié)尾的方法,這種方式不需要先創(chuàng)建字段對(duì)象,直接將字段名和值依次放在參數(shù)中即可,如例子中的Infow。

默認(rèn)情況下,Example輸出的日志為 JSON 格式:

{"level":"info","msg":"failed to fetch URL","url":"http://example.org/api","attempt":3,"backoff":"1s"}
{"level":"info","msg":"failed to fetch URL","url":"http://example.org/api","attempt":3,"backoff":"1s"}
{"level":"info","msg":"Failed to fetch URL: http://example.org/api"}

記錄層級(jí)關(guān)系

前面我們記錄的日志都是一層結(jié)構(gòu),沒(méi)有嵌套的層級(jí)。我們可以使用zap.Namespace(key string) Field構(gòu)建一個(gè)命名空間,后續(xù)的Field都記錄在此命名空間中:

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

  logger.Info("tracked some metrics",
    zap.Namespace("metrics"),
    zap.Int("counter", 1),
  )

  logger2 := logger.With(
    zap.Namespace("metrics"),
    zap.Int("counter", 1),
  )
  logger2.Info("tracked some metrics")
}

輸出:

{"level":"info","msg":"tracked some metrics","metrics":{"counter":1}}
{"level":"info","msg":"tracked some metrices","metrics":{"counter":1}}

上面我們演示了兩種Namespace的用法,一種是直接作為字段傳入Debug/Info等方法,一種是調(diào)用With()創(chuàng)建一個(gè)新的Logger,新的Logger記錄日志時(shí)總是帶上預(yù)設(shè)的字段。With()方法實(shí)際上是創(chuàng)建了一個(gè)新的Logger

// src/go.uber.org/zap/logger.go
func (log *Logger) With(fields ...Field) *Logger {
  if len(fields) == 0 {
    return log
  }
  l := log.clone()
  l.core = l.core.With(fields)
  return l
}

定制Logger

調(diào)用NexExample()/NewDevelopment()/NewProduction()這 3 個(gè)方法,zap使用默認(rèn)的配置。我們也可以手動(dòng)調(diào)整,配置結(jié)構(gòu)如下:

// src/go.uber.org/zap/config.go
type Config struct {
  Level AtomicLevel `json:"level" yaml:"level"`
  Encoding string `json:"encoding" yaml:"encoding"`
  EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
  OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
  ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
  InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
  • Level:日志級(jí)別;
  • Encoding:輸出的日志格式,默認(rèn)為 JSON;
  • OutputPaths:可以配置多個(gè)輸出路徑,路徑可以是文件路徑和stdout(標(biāo)準(zhǔn)輸出);
  • ErrorOutputPaths:錯(cuò)誤輸出路徑,也可以是多個(gè);
  • InitialFields:每條日志中都會(huì)輸出這些值。

其中EncoderConfig為編碼配置:

// src/go.uber.org/zap/zapcore/encoder.go
type EncoderConfig struct {
  MessageKey    string `json:"messageKey" yaml:"messageKey"`
  LevelKey      string `json:"levelKey" yaml:"levelKey"`
  TimeKey       string `json:"timeKey" yaml:"timeKey"`
  NameKey       string `json:"nameKey" yaml:"nameKey"`
  CallerKey     string `json:"callerKey" yaml:"callerKey"`
  StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
  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"`
}
  • MessageKey:日志中信息的鍵名,默認(rèn)為msg;
  • LevelKey:日志中級(jí)別的鍵名,默認(rèn)為level;
  • EncodeLevel:日志中級(jí)別的格式,默認(rèn)為小寫,如debug/info。

調(diào)用zap.ConfigBuild()方法即可使用該配置對(duì)象創(chuàng)建一個(gè)Logger

func main() {
  rawJSON := []byte(`{
    "level":"debug",
    "encoding":"json",
    "outputPaths": ["stdout", "server.log"],
    "errorOutputPaths": ["stderr"],
    "initialFields":{"name":"dj"},
    "encoderConfig": {
      "messageKey": "message",
      "levelKey": "level",
      "levelEncoder": "lowercase"
    }
  }`)
  var cfg zap.Config
  if err := json.Unmarshal(rawJSON, &cfg); err != nil {
    panic(err)
  }
  logger, err := cfg.Build()
  if err != nil {
    panic(err)
  }
  defer logger.Sync()
  logger.Info("server start work successfully!")
}

上面創(chuàng)建一個(gè)輸出到標(biāo)準(zhǔn)輸出stdout和文件server.logLogger。觀察輸出:

{"level":"info","message":"server start work successfully!","name":"dj"}

使用NewDevelopment()創(chuàng)建的Logger使用的是如下的配置:

// src/go.uber.org/zap/config.go
func NewDevelopmentConfig() Config {
  return Config{
    Level:            NewAtomicLevelAt(DebugLevel),
    Development:      true,
    Encoding:         "console",
    EncoderConfig:    NewDevelopmentEncoderConfig(),
    OutputPaths:      []string{"stderr"},
    ErrorOutputPaths: []string{"stderr"},
  }
}

func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
  return zapcore.EncoderConfig{
    // Keys can be anything except the empty string.
    TimeKey:        "T",
    LevelKey:       "L",
    NameKey:        "N",
    CallerKey:      "C",
    MessageKey:     "M",
    StacktraceKey:  "S",
    LineEnding:     zapcore.DefaultLineEnding,
    EncodeLevel:    zapcore.CapitalLevelEncoder,
    EncodeTime:     zapcore.ISO8601TimeEncoder,
    EncodeDuration: zapcore.StringDurationEncoder,
    EncodeCaller:   zapcore.ShortCallerEncoder,
  }
}

NewProduction()的配置可自行查看。

選項(xiàng)

NewExample()/NewDevelopment()/NewProduction()這 3 個(gè)函數(shù)可以傳入若干類型為zap.Option的選項(xiàng),從而定制Logger的行為。又一次見(jiàn)到了選項(xiàng)模式??!

zap提供了豐富的選項(xiàng)供我們選擇。

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

調(diào)用zap.AddCaller()返回的選項(xiàng)設(shè)置輸出文件名和行號(hào)。但是有一個(gè)前提,必須設(shè)置配置對(duì)象Config中的CallerKey字段。也因此NewExample()不能輸出這個(gè)信息(它的Config沒(méi)有設(shè)置CallerKey)。

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

  logger.Info("hello world")
}

輸出:

{"level":"info","ts":1587740198.9508286,"caller":"caller/main.go:9","msg":"hello world"}

Info()方法在main.go的第 9 行被調(diào)用。AddCaller()zap.WithCaller(true)等價(jià)。

有時(shí)我們稍微封裝了一下記錄日志的方法,但是我們希望輸出的文件名和行號(hào)是調(diào)用封裝函數(shù)的位置。這時(shí)可以使用zap.AddCallerSkip(skip int)向上跳 1 層:

func Output(msg string, fields ...zap.Field) {
  zap.L().Info(msg, fields...)
}
func main() {
  logger, _ := zap.NewProduction(zap.AddCaller(), zap.AddCallerSkip(1))
  defer logger.Sync()
  zap.ReplaceGlobals(logger)
  Output("hello world")
}

輸出:

{"level":"info","ts":1587740501.5592482,"caller":"skip/main.go:15","msg":"hello world"}

輸出在main函數(shù)中調(diào)用Output()的位置。如果不指定zap.AddCallerSkip(1),將輸出"caller":"skip/main.go:6",這是在Output()函數(shù)中調(diào)用zap.Info()的位置。因?yàn)檫@個(gè)Output()函數(shù)可能在很多地方被調(diào)用,所以這個(gè)位置參考意義并不大。試試看!

輸出調(diào)用堆棧

有時(shí)候在某個(gè)函數(shù)處理中遇到了異常情況,因?yàn)檫@個(gè)函數(shù)可能在很多地方被調(diào)用。如果我們能輸出此次調(diào)用的堆棧,那么分析起來(lái)就會(huì)很方便。我們可以使用zap.AddStackTrace(lvl zapcore.LevelEnabler)達(dá)成這個(gè)目的。該函數(shù)指定lvl和之上的級(jí)別都需要輸出調(diào)用堆棧:

func f1() {
  f2("hello world")
}
func f2(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)
  f1()
}

zapcore.WarnLevel傳入AddStacktrace(),之后Warn()/Error()等級(jí)別的日志會(huì)輸出堆棧,Debug()/Info()這些級(jí)別不會(huì)。運(yùn)行結(jié)果:

{"level":"warn","ts":1587740883.4965692,"caller":"stacktrace/main.go:13","msg":"hello world","stacktrace":"main.f2\n\td:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:13\nmain.f1\n\td:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:9\nmain.main\n\td:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:22\nruntime.main\n\tC:/Go/src/runtime/proc.go:203"}

stacktrace單獨(dú)拉出來(lái):

main.f2
d:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:13
  main.f1
  d:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:9
    main.main
    d:/code/golang/src/github.com/darjun/go-daily-lib/zap/option/stacktrace/main.go:22
      runtime.main
      C:/Go/src/runtime/proc.go:203

很清楚地看到調(diào)用路徑。

全局Logger

為了方便使用,zap提供了兩個(gè)全局的Logger,一個(gè)是*zap.Logger,可調(diào)用zap.L()獲得;另一個(gè)是*zap.SugaredLogger,可調(diào)用zap.S()獲得。需要注意的是,全局的Logger默認(rèn)并不會(huì)記錄日志!它是一個(gè)無(wú)實(shí)際效果的Logger??丛创a:

// go.uber.org/zap/global.go
var (
  _globalMu sync.RWMutex
  _globalL  = NewNop()
  _globalS  = _globalL.Sugar()
)

我們可以使用ReplaceGlobals(logger *Logger) func()logger設(shè)置為全局的Logger,該函數(shù)返回一個(gè)無(wú)參函數(shù),用于恢復(fù)全局Logger設(shè)置:

func main() {
  zap.L().Info("global Logger before")
  zap.S().Info("global SugaredLogger before")
  logger := zap.NewExample()
  defer logger.Sync()
  zap.ReplaceGlobals(logger)
  zap.L().Info("global Logger after")
  zap.S().Info("global SugaredLogger after")
}

輸出:

{"level":"info","msg":"global Logger after"}
{"level":"info","msg":"global SugaredLogger after"}

可以看到在調(diào)用ReplaceGlobals之前記錄的日志并沒(méi)有輸出。

預(yù)設(shè)日志字段

如果每條日志都要記錄一些共用的字段,那么使用zap.Fields(fs ...Field)創(chuàng)建的選項(xiàng)。例如在服務(wù)器日志中記錄可能都需要記錄serverIdserverName

func main() {
  logger := zap.NewExample(zap.Fields(
    zap.Int("serverId", 90),
    zap.String("serverName", "awesome web"),
  ))
  logger.Info("hello world")
}

輸出:

{"level":"info","msg":"hello world","serverId":90,"serverName":"awesome web"}

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

如果項(xiàng)目一開始使用的是標(biāo)準(zhǔn)日志庫(kù)log,后面想轉(zhuǎn)為zap。這時(shí)不必修改每一個(gè)文件。我們可以調(diào)用zap.NewStdLog(l *Logger) *log.Logger返回一個(gè)標(biāo)準(zhǔn)的log.Logger,內(nèi)部實(shí)際上寫入的還是我們之前創(chuàng)建的zap.Logger

func main() {
  logger := zap.NewExample()
  defer logger.Sync()
  std := zap.NewStdLog(logger)
  std.Print("standard logger wrapper")
}

輸出:

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

很方便不是嗎?我們還可以使用NewStdLogAt(l *logger, level zapcore.Level) (*log.Logger, error)讓標(biāo)準(zhǔn)接口以level級(jí)別寫入內(nèi)部的*zap.Logger。

如果我們只是想在一段代碼內(nèi)使用標(biāo)準(zhǔn)日志庫(kù)log,其它地方還是使用zap.Logger??梢哉{(diào)用RedirectStdLog(l *Logger) func()。它會(huì)返回一個(gè)無(wú)參函數(shù)恢復(fù)設(shè)置:

func main() {
  logger := zap.NewExample()
  defer logger.Sync()
  undo := zap.RedirectStdLog(logger)
  log.Print("redirected standard library")
  undo()
  log.Print("restored standard library")
}

看前后輸出變化:

{"level":"info","msg":"redirected standard library"}
2020/04/24 22:13:58 restored standard library

當(dāng)然RedirectStdLog也有一個(gè)對(duì)應(yīng)的RedirectStdLogAt以特定的級(jí)別調(diào)用內(nèi)部的*zap.Logger方法。

總結(jié)

zap用在日志性能和內(nèi)存分配比較關(guān)鍵的地方。本文僅介紹了zap庫(kù)的基本使用,子包zapcore中有更底層的接口,可以定制豐富多樣的Logger

大家如果發(fā)現(xiàn)好玩、好用的 Go 語(yǔ)言庫(kù),歡迎到 Go 每日一庫(kù) GitHub 上提交 issue??

參考

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

相關(guān)文章

  • golang實(shí)現(xiàn)PHP數(shù)組特性的方法

    golang實(shí)現(xiàn)PHP數(shù)組特性的方法

    我們做業(yè)務(wù)過(guò)程中,對(duì)應(yīng)強(qiáng)類型語(yǔ)言使用有個(gè)痛點(diǎn),就是使用變量之前一定要定義變量類型,那么本文就來(lái)介紹一下golang實(shí)現(xiàn)PHP數(shù)組特性的方法
    2021-12-12
  • Go語(yǔ)言使用字符串的幾個(gè)技巧分享

    Go語(yǔ)言使用字符串的幾個(gè)技巧分享

    這篇文章中小編將給出一些Go語(yǔ)言在處理字符串方面的技巧,對(duì)大家學(xué)習(xí)Go語(yǔ)言具有一定的參考借鑒價(jià)值,下面一起看看吧。
    2016-09-09
  • go如何使用gin結(jié)合jwt做登錄功能簡(jiǎn)單示例

    go如何使用gin結(jié)合jwt做登錄功能簡(jiǎn)單示例

    jwt全稱Json web token,是一種認(rèn)證和信息交流的工具,這篇文章主要給大家介紹了關(guān)于go如何使用gin結(jié)合jwt做登錄功能的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Golang中使用Date進(jìn)行日期格式化(沿用Java風(fēng)格)

    Golang中使用Date進(jìn)行日期格式化(沿用Java風(fēng)格)

    這篇文章主要介紹了Golang中使用Date進(jìn)行日期格式化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 淺析go中的map數(shù)據(jù)結(jié)構(gòu)字典

    淺析go中的map數(shù)據(jù)結(jié)構(gòu)字典

    golang中的map是一種數(shù)據(jù)類型,將鍵與值綁定到一起,底層是用哈希表實(shí)現(xiàn)的,可以快速的通過(guò)鍵找到對(duì)應(yīng)的值。這篇文章主要介紹了go中的數(shù)據(jù)結(jié)構(gòu)字典-map,需要的朋友可以參考下
    2019-11-11
  • go語(yǔ)言中os包的用法實(shí)戰(zhàn)大全

    go語(yǔ)言中os包的用法實(shí)戰(zhàn)大全

    Go在os中提供了文件的基本操作,包括通常意義的打開、創(chuàng)建、讀寫等操作,除此以外為了追求便捷以及性能上,Go還在io/ioutil以及bufio提供一些其他函數(shù)供開發(fā)者使用,這篇文章主要給大家介紹了關(guān)于go語(yǔ)言中os包用法的相關(guān)資料,需要的朋友可以參考下
    2024-02-02
  • Go?實(shí)現(xiàn)?WebSockets之創(chuàng)建?WebSockets

    Go?實(shí)現(xiàn)?WebSockets之創(chuàng)建?WebSockets

    這篇文章主要介紹了Go?實(shí)現(xiàn)?WebSockets之創(chuàng)建?WebSockets,文章主要探索?WebSockets,并簡(jiǎn)要介紹了它們的工作原理,并仔細(xì)研究了全雙工通信,想了解更多相關(guān)內(nèi)容的小伙伴可以參考一下
    2022-04-04
  • MacOS下本地golang環(huán)境搭建詳細(xì)教程

    MacOS下本地golang環(huán)境搭建詳細(xì)教程

    這篇文章主要介紹了MacOS下本地golang環(huán)境搭建詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • go實(shí)現(xiàn)一個(gè)分布式限流器的方法步驟

    go實(shí)現(xiàn)一個(gè)分布式限流器的方法步驟

    項(xiàng)目中需要對(duì)api的接口進(jìn)行限流,本文主要介紹了go實(shí)現(xiàn)一個(gè)分布式限流器的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • go循環(huán)依賴的最佳解決方案

    go循環(huán)依賴的最佳解決方案

    ? import cycle not allowed(循環(huán)依賴不被允許)相信作為每一個(gè)golang語(yǔ)言使用研發(fā),都遇到過(guò)這個(gè)令人頭痛的報(bào)錯(cuò),循環(huán)依賴是指兩個(gè)或多個(gè)模塊之間互相依賴,形成了一個(gè)閉環(huán)的情況,本文會(huì)結(jié)合部分案例對(duì)解決方案進(jìn)行講解,需要的朋友可以參考下
    2023-10-10

最新評(píng)論