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

Go語言中日志統(tǒng)一處理詳解

 更新時間:2024年01月15日 10:32:01   作者:吳佳浩  
在現(xiàn)代軟件開發(fā)中,日志記錄是一項至關(guān)重要的任務(wù),它不僅幫助開發(fā)人員診斷問題,還有助于監(jiān)控和維護應(yīng)用程序,本文主要來和大家聊聊日志的統(tǒng)一處理,感興趣的小伙伴可以了解下

簡介

在現(xiàn)代軟件開發(fā)中,日志記錄是一項至關(guān)重要的任務(wù),它不僅幫助開發(fā)人員診斷問題,還有助于監(jiān)控和維護應(yīng)用程序。在Go語言中,性能和內(nèi)存分配是至關(guān)重要的考慮因素,因此選擇一個高性能的日志庫非常重要。本文將介紹Uber開源的zap日志庫,它在性能和內(nèi)存分配方面進行了極致的優(yōu)化,成為Go語言中的一種理想選擇。

快速使用

先安裝:

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

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

由于fmt.Printf之類的方法大量使用interface{}和反射,會有不少性能損失,并且增加了內(nèi)存分配的頻次。zap為了提高性能、減少內(nèi)存分配次數(shù),沒有使用反射,而且默認的Logger只支持強類型的、結(jié)構(gòu)化的日志。必須使用zap提供的方法記錄字段。zap為 Go 語言中所有的基本類型和其他常見類型都提供了方法。這些方法的名稱也比較好記憶,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切片字段。

當然也有一些特殊類型的字段:

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

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

默認情況下,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"}

記錄層級關(guān)系

前面我們記錄的日志都是一層結(jié)構(gòu),沒有嵌套的層級。我們可以使用zap.Namespace(key string) Field構(gòu)建一個命名空間,后續(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)建一個新的Logger,新的Logger記錄日志時總是帶上預(yù)設(shè)的字段。With()方法實際上是創(chuàng)建了一個新的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 個方法,zap使用默認的配置。我們也可以手動調(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:日志級別;
  • Encoding:輸出的日志格式,默認為 JSON;
  • OutputPaths:可以配置多個輸出路徑,路徑可以是文件路徑和stdout(標準輸出);
  • ErrorOutputPaths:錯誤輸出路徑,也可以是多個;
  • InitialFields:每條日志中都會輸出這些值。

其中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:日志中信息的鍵名,默認為msg;
  • LevelKey:日志中級別的鍵名,默認為level;
  • EncodeLevel:日志中級別的格式,默認為小寫,如debug/info。

調(diào)用zap.ConfigBuild()方法即可使用該配置對象創(chuàng)建一個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)建一個輸出到標準輸出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()的配置可自行查看。

選項

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

zap提供了豐富的選項供我們選擇。

輸出文件名和行號

調(diào)用zap.AddCaller()返回的選項設(shè)置輸出文件名和行號。但是有一個前提,必須設(shè)置配置對象Config中的CallerKey字段。也因此NewExample()不能輸出這個信息(它的Config沒有設(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)等價。

有時我們稍微封裝了一下記錄日志的方法,但是我們希望輸出的文件名和行號是調(diào)用封裝函數(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()的位置。因為這個Output()函數(shù)可能在很多地方被調(diào)用,所以這個位置參考意義并不大。試試看!

輸出調(diào)用堆棧

有時候在某個函數(shù)處理中遇到了異常情況,因為這個函數(shù)可能在很多地方被調(diào)用。如果我們能輸出此次調(diào)用的堆棧,那么分析起來就會很方便。我們可以使用zap.AddStackTrace(lvl zapcore.LevelEnabler)達成這個目的。該函數(shù)指定lvl和之上的級別都需要輸出調(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()等級別的日志會輸出堆棧,Debug()/Info()這些級別不會。運行結(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單獨拉出來:

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

全局Logger

為了方便使用,zap提供了兩個全局的Logger,一個是*zap.Logger,可調(diào)用zap.L()獲得;另一個是*zap.SugaredLogger,可調(diào)用zap.S()獲得。需要注意的是,全局的Logger默認并不會記錄日志!它是一個無實際效果的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ù)返回一個無參函數(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之前記錄的日志并沒有輸出。

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

如果每條日志都要記錄一些共用的字段,那么使用zap.Fields(fs ...Field)創(chuà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"}

與標準日志庫搭配使用

如果項目一開始使用的是標準日志庫log,后面想轉(zhuǎn)為zap。這時不必修改每一個文件。我們可以調(diào)用zap.NewStdLog(l *Logger) *log.Logger返回一個標準的log.Logger,內(nèi)部實際上寫入的還是我們之前創(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)讓標準接口以level級別寫入內(nèi)部的*zap.Logger。

如果我們只是想在一段代碼內(nèi)使用標準日志庫log,其它地方還是使用zap.Logger。可以調(diào)用RedirectStdLog(l *Logger) func()。它會返回一個無參函數(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

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

驚喜來啦 通用zap封裝拿走直接用

package common
/*
Package common provides a logging utility that utilizes the Zap logger library
for structured and performant logging. It includes functions for logging at
different log levels and is configured to write log entries to a file using
the Lumberjack log rotation mechanism.

Author: wujiahao

Initial Description:
This package sets up a structured logging system using Uber's Zap logger and
Lumberjack for log rotation. It allows you to log messages at different
severity levels, such as Debug, Info, Warn, Error, DPanic, and Fatal, and
supports both plain and formatted log messages. The log output is directed to
a file with rotation based on size, and each log entry includes a timestamp
in ISO8601 format. Additionally, caller information can be included in log
entries for debugging purposes.

Usage:
To use this logging utility, simply import the package and make calls to the
logging functions as needed. The logger is initialized with default
configuration, but you can customize it by modifying the init() function in
this package.
*/
import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)




var (
	logger *zap.SugaredLogger
)

func init()  {
	//log file name
	fileName :="micro.log"
	writeSyncer := zapcore.AddSync(&lumberjack.Logger{
		Filename: fileName, //file name
		MaxSize:  521,      //the file max size *MB
		//MaxAge:     0,		//the destroy time
		MaxBackups: 0,    //the max back up
		LocalTime:  true, //start local time
		Compress:   true, //is zip
	})
	//encode
	encoder := zap.NewProductionEncoderConfig()
	//time format
	encoder.EncodeTime = zapcore.ISO8601TimeEncoder
	core := zapcore.NewCore(
		//encoder
		zapcore.NewJSONEncoder(encoder),
		writeSyncer,
		zap.NewAtomicLevelAt(zap.DebugLevel))
	log := zap.New(
		core,
		zap.AddCaller(),
		zap.AddCallerSkip(1))//有時我們稍微封裝了一下記錄日志的方法,但是我們希望輸出的文件名和行號是調(diào)用封裝函數(shù)的位置。這時可以使用zap.AddCallerSkip(skip int)向上跳 1 層:
	logger= log.Sugar()
}
func Debug(args ...interface{})  {
	logger.Debug(args)
}
func Debugf(template string,args ...interface{}) {
	logger.Debugf(template, args)
}

func Info(args ...interface{})  {
     logger.Info(args...)
}
func Infof(template string,arg ...interface{})  {
	logger.Infof(template,arg...)
}
func Warn(args ...interface{})  {
	logger.Warn(args...)
}
func Warnf(template string,args ...interface{})  {
	logger.Warnf(template,args...)
}
func Error(args ...interface{})  {
	logger.Error(args...)
}
func Errorf(template string,args ...interface{})  {
	logger.Errorf(template,args)
}
func DPanic(args ...interface{})  {
	logger.DPanic(args...)
}
func DPanicf(template string,args ...interface{})  {
	logger.DPanicf(template,args...)
}
func Fatal(args ...interface{})  {
	logger.Fatal(args...)
}
func FatalF(tempalte string,args ...interface{})  {
	logger.Fatalf(tempalte,args...)
}

總結(jié)

使用zap庫,我們可以輕松地實現(xiàn)高性能、結(jié)構(gòu)化的日志記錄,同時減少內(nèi)存分配和性能損失。

本文從安裝、快速使用、配置、全局Logger等方面介紹了zap的基本用法。

它的性能和優(yōu)勢使得它成為處理熱點函數(shù)中日志記錄的首選工具。

通過掌握zap庫,開發(fā)人員可以更好地管理和分析應(yīng)用程序的日志,提高開發(fā)和維護效率,確保應(yīng)用程序的穩(wěn)定性和可維護性。

因此,我們鼓勵開發(fā)人員深入學(xué)習(xí)和應(yīng)用zap庫,以提高Go語言應(yīng)用程序的日志記錄質(zhì)量和性能。

以上就是Go語言中日志統(tǒng)一處理詳解的詳細內(nèi)容,更多關(guān)于Go日志處理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言通過TCP協(xié)議實現(xiàn)聊天室功能

    Go語言通過TCP協(xié)議實現(xiàn)聊天室功能

    這篇文章主要為大家詳細介紹了Go語言中如何通過TCP協(xié)議實現(xiàn)聊天室功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • Golang斷言判斷值類型的實現(xiàn)方法

    Golang斷言判斷值類型的實現(xiàn)方法

    這篇文章主要介紹了Golang斷言判斷值類型的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Go語法糖之‘...’ 的使用實例詳解

    Go語法糖之‘...’ 的使用實例詳解

    語法糖(Syntactic sugar),也譯為糖衣語法,指計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。這篇文章主要給大家介紹Go語法糖之‘...’ 的使用,感興趣的朋友一起看看吧
    2018-10-10
  • 一文帶你了解Golang中的并發(fā)性

    一文帶你了解Golang中的并發(fā)性

    并發(fā)是一個很酷的話題,一旦你掌握了它,就會成為一筆巨大的財富。所以本文就來和大家一起來聊聊Golang中的并發(fā)性,感興趣的可以了解一下
    2023-03-03
  • 詳解Go語言如何進行Http調(diào)用

    詳解Go語言如何進行Http調(diào)用

    無論是微服務(wù)還是單體架構(gòu)等,服務(wù)間都有相互通信的時候,而最直接的通信方法就是 HTTP 調(diào)用,本文將會介紹在 Go 語言里,如何進行 HTTP 調(diào)用,需要的可以參考一下
    2022-12-12
  • golang雙鏈表的實現(xiàn)代碼示例

    golang雙鏈表的實現(xiàn)代碼示例

    這篇文章主要介紹了golang雙鏈表的實現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Golang中優(yōu)秀的消息隊列NSQ基礎(chǔ)安裝及使用詳解

    Golang中優(yōu)秀的消息隊列NSQ基礎(chǔ)安裝及使用詳解

    這篇文章主要介紹了Golang中優(yōu)秀的消息隊列NSQ基礎(chǔ)安裝及使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 詳解如何用Golang處理每分鐘100萬個請求

    詳解如何用Golang處理每分鐘100萬個請求

    在項目開發(fā)中,我們常常會遇到處理來自數(shù)百萬個端點的大量POST請求,本文主要介紹了Golang實現(xiàn)處理每分鐘100萬個請求的方法,希望對大家有所幫助
    2023-04-04
  • Go語言resty http包調(diào)用jenkins api實例

    Go語言resty http包調(diào)用jenkins api實例

    這篇文章主要為大家介紹了Go語言resty http包調(diào)用jenkins api實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Golang中for循環(huán)的用法示例詳解

    Golang中for循環(huán)的用法示例詳解

    for循環(huán)就是讓一段代碼循環(huán)的執(zhí)行,接下來通過本文給大家講解Golang中for循環(huán)的用法,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12

最新評論