日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Golang高性能日志库zap + lumberjack 日志切割组件详解

發布時間:2023/12/8 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Golang高性能日志库zap + lumberjack 日志切割组件详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章篇幅較長,可以先收藏防止迷路~

目錄

  • zap日志庫
    • 1. why zap?
    • 2. 簡單使用
    • 3. 自定義logger例子
    • 4. Gin項目使用zap
    • 6. lumberjack 日志切割組件

zap日志庫

在許多Go語言項目中,我們需要一個好的日志記錄器能夠提供下面這些功能:

  • 能夠將事件記錄到文件中,而不是應用程序控制臺;
  • 日志切割-能夠根據文件大小、時間或間隔等來切割日志文件;
  • 支持不同的日志級別。例如INFO,DEBUG,ERROR等;
  • 能夠打印基本信息,如調用文件/函數名和行號,日志時間等;
  • 1. why zap?

  • 比較全的日志級別

  • 支持結構化日志

  • 性能

  • 2. 簡單使用

    go get -u go.uber.org/zap

    Zap提供了兩種類型的日志記錄器 — Sugared Logger 和 Logger

    • Sugared Logger 并重性能與易用性,支持結構化和 printf 風格的日志記錄。

    • Logger 非常強調性能,不提供 printf 風格的 api (減少了 interface{} 與 反射的性能損耗),如下例子:

      func main() {// sugaredsugar := zap.NewExample().Sugar()sugar.Infof("hello! name:%s,age:%d", "xiaomin", 20) // printf 風格,易用性// loggerlogger := zap.NewExample()logger.Info("hello!", zap.String("name", "xiaomin"), zap.Int("age", 20)) // 強調性能 }

      輸出結果:

      // output {"level":"info","msg":"hello! name:xiaomin,age:20"} {"level":"info","msg":"hello!","name":"xiaomin","age":20}

    zap 有三種默認配置創建出一個 logger,分別為 example,development,production,示例:

    func main() {// examplelogger := zap.NewExample()logger.Info("example")// Developmentlogger,_ = zap.NewDevelopment()logger.Info("Development")// Productionlogger,_ = zap.NewProduction()logger.Info("Production") }

    可以看出:日志等級,日志輸出格式,默認字段都有所差異。

    也可以自定義 logger,如下:

    func main() {encoder := getEncoder()sync := getWriteSync()core := zapcore.NewCore(encoder, sync, zapcore.InfoLevel)logger := zap.New(core)logger.Info("info 日志",zap.Int("line", 1))logger.Error("info 日志", zap.Int("line", 2)) }// 負責設置 encoding 的日志格式 func getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) }// 負責日志寫入的位置 func getWriteSync() zapcore.WriteSyncer {file, _ := os.OpenFile("./log.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)syncFile := zapcore.AddSync(file)syncConsole := zapcore.AddSync(os.Stderr)return zapcore.NewMultiWriteSyncer(syncConsole, syncFile) }

    運行結果:

    // output // 創建 log.txt,追加日志 // console 打印日志 //{"level":"info","ts":1636471657.16419,"msg":"info 日志","line":1} //{"level":"error","ts":1636471657.1643898,"msg":"info 日志","line":2}

    從 New(core zapcore.Core, options ...Option) *Logger 出發,需要構造 zapcore.Core

  • 通過 NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core 方法,又需要傳入三個參數
    • Encoder : 負責設置 encoding 的日志格式, 可以設置 json 或者 text結構,也可以自定義json中 key 值,時間格式…
    • ws WriteSyncer: 負責日志寫入的位置,上述例子往 file 與 console 同時寫入,這里也可以寫入網絡。
    • LevelEnabler: 設置日志記錄級別
  • 3. 自定義logger例子

    ./util/zap.go

  • 定義結構體:

    package utilimport ("net""net/http""net/http/httputil""os""runtime/debug""strings""time""github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore" )type LogConfig struct {Level string `json:"level"` // Level 最低日志等級,DEBUG<INFO<WARN<ERROR<FATAL 例如:info-->收集info等級以上的日志FileName string `json:"file_name"` // FileName 日志文件位置MaxSize int `json:"max_size"` // MaxSize 進行切割之前,日志文件的最大大小(MB為單位),默認為100MBMaxAge int `json:"max_age"` // MaxAge 是根據文件名中編碼的時間戳保留舊日志文件的最大天數。MaxBackups int `json:"max_backups"` // MaxBackups 是要保留的舊日志文件的最大數量。默認是保留所有舊的日志文件(盡管 MaxAge 可能仍會導致它們被刪除。) }
  • 日志配置:

    var logger *zap.Logger// 負責設置 encoding 的日志格式 func getEncoder() zapcore.Encoder {// 獲取一個指定的的EncoderConfig,進行自定義encodeConfig := zap.NewProductionEncoderConfig()// 設置每個日志條目使用的鍵。如果有任何鍵為空,則省略該條目的部分。// 序列化時間。eg: 2022-09-01T19:11:35.921+0800encodeConfig.EncodeTime = zapcore.ISO8601TimeEncoder// "time":"2022-09-01T19:11:35.921+0800"encodeConfig.TimeKey = "time"// 將Level序列化為全大寫字符串。例如,將info level序列化為INFO。encodeConfig.EncodeLevel = zapcore.CapitalLevelEncoder// 以 package/file:行 的格式 序列化調用程序,從完整路徑中刪除除最后一個目錄外的所有目錄。encodeConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encodeConfig) }// 負責日志寫入的位置 func getLogWriter(filename string, maxsize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename: filename, // 文件位置MaxSize: maxsize, // 進行切割之前,日志文件的最大大小(MB為單位)MaxAge: maxAge, // 保留舊文件的最大天數MaxBackups: maxBackup, // 保留舊文件的最大個數Compress: false, // 是否壓縮/歸檔舊文件}// AddSync 將 io.Writer 轉換為 WriteSyncer。// 它試圖變得智能:如果 io.Writer 的具體類型實現了 WriteSyncer,我們將使用現有的 Sync 方法。// 如果沒有,我們將添加一個無操作同步。return zapcore.AddSync(lumberJackLogger) }// InitLogger 初始化Logger func InitLogger(lCfg LogConfig) (err error) {// 獲取日志寫入位置writeSyncer := getLogWriter(lCfg.FileName, lCfg.MaxSize, lCfg.MaxBackups, lCfg.MaxAge)// 獲取日志編碼格式encoder := getEncoder()// 獲取日志最低等級,即>=該等級,才會被寫入。var l = new(zapcore.Level)err = l.UnmarshalText([]byte(lCfg.Level))if err != nil {return}// 創建一個將日志寫入 WriteSyncer 的核心。core := zapcore.NewCore(encoder, writeSyncer, l)logger = zap.New(core, zap.AddCaller())// 替換zap包中全局的logger實例,后續在其他包中只需使用zap.L()調用即可zap.ReplaceGlobals(logger) return }

    函數解釋:

    • getEncoder():負責設置 encoding 的日志格式,如果看不懂上面代碼里的注釋,可以結合這里的例子理解每一步的作用:

      encodeConfig := zap.NewProductionEncoderConfig() // 打印格式: {"level":"info","ts":1662032576.6267354,"msg":"test","line":1}encodeConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 打印格式:{"level":"info","ts":"2022-09-01T19:43:07.178+0800","msg":"test","line":1}encodeConfig.TimeKey = "time" // 打印格式:{"level":"info","time":"2022-09-01T19:43:20.558+0800","msg":"test","line":1}encodeConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 打印格式:{"level":"INFO","time":"2022-09-01T19:43:41.192+0800","msg":"test","line":1}encodeConfig.EncodeCaller = zapcore.ShortCallerEncoder // 打印格式:{"level":"INFO","time":"2022-09-01T19:41:39.819+0800","caller":"test/test.go:20","msg":"test","line":1} // 這個需要注意,是要結合 logger := zap.New(core, zap.AddCaller()),一起使用的
    • getLogWriter(filename string, maxsize, maxBackup, maxAge int) :負責日志寫入的位置

      關于lumberjack.Logger,下面會單獨講述,這里只需要知道這個函數的作用是設置日志寫入的位置即可。

      如果同時想要打印到文件和控制臺可以這樣:

      syncFile := zapcore.AddSync(lumberJackLogger) // 打印到文件 syncConsole := zapcore.AddSync(os.Stderr) // 打印到控制臺 return zapcore.NewMultiWriteSyncer(syncFile, syncConsole)
    • InitLogger(lCfg LogConfig):初始化Logger

      • getLogWriter和getEncoder剛才已經講過了,這里不再贅述;

      • UnmarshalText(),我們lCfg.Level是string類型,而這個方法就是可以通過string解碼出對應的zapcore.Level類型,我們查看源碼可以看到,這個類型其實是int8類型的別名:

        type Level int8 const (DebugLevel Level = iota - 1InfoLevelWarnLevel ... )

        例如,我們的lCfg.Level="debug",l.UnmarshalText([]byte(lCfg.Level))解析后,此時l的日志等級就是DebugLevel

      • AddCaller():將 Logger 配置為 使用 zap 的 調用者 的 文件名、行號和函數名注釋每條消息。

        {"level":"INFO","time":"2022-09-01T19:41:39.819+0800","caller":"test/test.go:20","msg":"test","line":1}
    • ReplaceGlobals():替換zap包中全局的logger實例,后續在其他包中只需使用zap.L()調用即可;

  • 測試:

    main.go

    package mainimport ("fmt""ginstudy02/util""net/http""go.uber.org/zap" )func main() {lc := util.LogConfig{Level: "debug",FileName: fmt.Sprintf("./log/%v.log", time.Now().Unix()),MaxSize: 1,MaxBackups: 5,MaxAge: 30,}err := util.InitLogger(lc)if err != nil {fmt.Println(err)}// L():獲取全局loggerlogger := zap.L()// 調用內核的Sync方法,刷新所有緩沖的日志條目。// 應用程序應該注意在退出之前調用Sync。defer logger.Sync()simpleHttpGet(logger, "www.sogo.com")simpleHttpGet(logger, "http://www.sogo.com") }func simpleHttpGet(logger *zap.Logger, url string) {sugarLogger := logger.Sugar()sugarLogger.Debugf("Trying to hit GET request for %s", url)resp, err := http.Get(url)if err != nil {sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)} else {sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)resp.Body.Close()} }

    執行結果:

    ./log/1662027710.log

    {"level":"DEBUG","time":"2022-09-01T17:46:58.378+0800","caller":"ginstudy02/main.go:53","msg":"Trying to hit GET request for www.sogo.com"} {"level":"ERROR","time":"2022-09-01T17:46:58.393+0800","caller":"ginstudy02/main.go:56","msg":"Error fetching URL www.sogo.com : Error = Get \"www.sogo.com\": unsupported protocol scheme \"\""} {"level":"DEBUG","time":"2022-09-01T17:46:58.393+0800","caller":"ginstudy02/main.go:53","msg":"Trying to hit GET request for http://www.sogo.com"} {"level":"INFO","time":"2022-09-01T17:46:58.681+0800","caller":"ginstudy02/main.go:58","msg":"Success! statusCode = 200 OK for URL http://www.sogo.com"}

    可以看到這里是json格式的,是因為我們在getEncoder()中返回的是一個JSON Encoder的編碼器,現在,我們希望將編碼器從JSON Encoder更改為普通Encoder。為此,我們需要將NewJSONEncoder()更改為NewConsoleEncoder();

    //return zapcore.NewJSONEncoder(encodeConfig) return zapcore.NewConsoleEncoder(encodeConfig)

    再次運行:

    2022-09-01T17:53:09.870+0800 DEBUG ginstudy02/main.go:53 Trying to hit GET request for www.sogo.com 2022-09-01T17:53:09.887+0800 ERROR ginstudy02/main.go:56 Error fetching URL www.sogo.com : Error = Get "www.sogo.com": unsupported protocol scheme "" 2022-09-01T17:53:09.887+0800 DEBUG ginstudy02/main.go:53 Trying to hit GET request for http://www.sogo.com 2022-09-01T17:53:10.145+0800 INFO ginstudy02/main.go:58 Success! statusCode = 200 OK for URL http://www.sogo.com

    4. Gin項目使用zap

    util/zap.go

    在上面的代碼的基礎上,添加下面兩個中間件:

    // GinLogger 接收gin框架默認的日志 func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Path // 請求路徑 eg: /testquery := c.Request.URL.RawQuery //query類型的請求參數:?name=1&password=2// 掛起當前中間件,執行下一個中間件c.Next()cost := time.Since(start)// Field 是 Field 的別名。給這個類型起別名極大地提高了這個包的 API 文檔的可導航性。// type Field struct {// Key string// Type FieldType // 類型,數字對應具體類型,eg: 15--->string// Integer int64// String string// Interface interface{}//}logger.Info(path,zap.Int("status", c.Writer.Status()), // 狀態碼 eg: 200zap.String("method", c.Request.Method), // 請求方法類型 eg: GETzap.String("path", path), // 請求路徑 eg: /testzap.String("query", query), // 請求參數 eg: name=1&password=2zap.String("ip", c.ClientIP()), // 返回真實的客戶端IP eg: ::1(這個就是本機IP,ipv6地址)zap.String("user-agent", c.Request.UserAgent()), // 返回客戶端的用戶代理。 eg: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), // 返回Errors 切片中ErrorTypePrivate類型的錯誤zap.Duration("cost", cost), // 返回花費時間)} }// GinRecovery recover掉項目可能出現的panic,并使用zap記錄相關日志 func GinRecovery(stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); interface{}(err) != nil {// 檢查斷開的連接,因為它不是保證緊急堆棧跟蹤的真正條件。var brokenPipe bool// OpError 是 net 包中的函數通常返回的錯誤類型。它描述了錯誤的操作、網絡類型和地址。if ne, ok := interface{}(err).(*net.OpError); ok {// SyscallError 記錄來自特定系統調用的錯誤。if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") {brokenPipe = true}}}// DumpRequest 以 HTTP/1.x 連線形式返回給定的請求httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {logger.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// 如果連接死了,我們就不能給它寫狀態c.Error(interface{}(err).(error))c.Abort() // 終止該中間件return}if stack {logger.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),zap.String("starck", string(debug.Stack())), // 返回調用它的goroutine的格式化堆棧跟蹤。)} else {logger.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),)}// 調用 `Abort()` 并使用指定的狀態代碼寫入標頭。// StatusInternalServerError:500c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()} }

    測試:

    ./main.go

    func main() { r := gin.New()r.Use(util.GinLogger(), util.GinRecovery(false))r.GET("./test", func(c *gin.Context) {lc := util.LogConfig{Level: "debug",FileName: fmt.Sprintf("./log/%v.log", time.Now().Unix()),MaxSize: 1,MaxBackups: 5,MaxAge: 30,}err := util.InitLogger(lc)if err != nil {fmt.Println(err)}logger := zap.L().Sugar()// 調用內核的Sync方法,刷新所有緩沖的日志條目。應用程序應該注意在退出之前調用Sync。defer logger.Sync()})r.Run() }

    請求地址:http://localhost:8080/test

    測試結果:

    ./log/test.log

    {"level":"INFO","time":"2022-09-01T18:11:47.600+0800","caller":"util/zap.go:105","msg":"/test","status":200,"method":"GET","path":"/test","query":"","ip":"::1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36","errors":"","cost":0.0001746}

    6. lumberjack 日志切割組件

    ? Golang 語言標準庫的 log 包和 zap 日志庫 不支持日志切割,然而如果我們業務每天產生海量日志,日志文件就會越來越大,甚至會觸發磁盤空間不足的報警,此時如果我們移動或者刪除日志文件,需要先將業務停止寫日志,很不方便。

    ? 而且大日志文件也不方便查詢,多少有點失去日志的意義。所以實際業務開發中,我們通常會按照日志文件大小或者日期進行日志切割。

    Golang 語言第三方庫 lumberjack 的作用就是進行日志切割;

    lumberjack 提供了一個滾動記錄器 logger,它是一個控制寫入日志的文件的日志組件,目前最新版本是 v2.0,需要使用 gopkg.in 導入。

  • 安裝:

    go get -u github.com/natefinch/lumberjack
  • 導入方式:

    import "gopkg.in/natefinch/lumberjack.v2"
  • 使用:

    • 與標準庫的 log 包一起使用,只需在應用程序啟動時將它傳遞到 SetOutput 函數即可:

      log.SetOutput(&lumberjack.Logger{Filename: "./log/test.log",MaxSize: 1, // 單位: MBMaxBackups: 3,MaxAge: 28, //單位: 天Compress: true, // 默認情況下禁用 })
    • 與Go第三方庫zap 一起使用:

      func getLogWriter(filename string, maxsize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename: filename, // 文件位置MaxSize: maxsize, // 進行切割之前,日志文件的最大大小(MB為單位)MaxAge: maxAge, // 保留舊文件的最大天數MaxBackups: maxBackup, // 保留舊文件的最大個數Compress: false, // 是否壓縮/歸檔舊文件}// AddSync 將 io.Writer 轉換為 WriteSyncer。// 它試圖變得智能:如果 io.Writer 的具體類型實現了 WriteSyncer,我們將使用現有的 Sync 方法。// 如果沒有,我們將添加一個無操作同步。return zapcore.AddSync(lumberJackLogger) }

    可以看出,重點在lumberjack.Logger上,查看源碼我們可以知道:

  • Logger 是一個寫入指定文件名的 io.WriteCloser。

  • Logger 在第一次寫入時打開或創建日志文件。如果文件存在并且小于 MaxSize 兆字節,lumberjack 將打開并附加到該文件。如果文件存在并且其大小 >= MaxSize 兆字節,則通過將當前時間放在文件擴展名之前的名稱中的時間戳中來重命名文件(如果沒有擴展名,則放在文件名的末尾)。然后使用原始文件名創建一個新的日志文件。

    每當寫入會導致當前日志文件超過 MaxSize 兆字節時,當前文件將被關閉、重命名,并使用原始名稱創建新的日志文件。因此,你給 Logger 的文件名始終是“當前”日志文件。

    可以看到,原文件寫到MaxSize大小之后,會被重命名,格式為:原文件名+當前時間(時間格式為time.Time 格式),而創建一個新的文件,命名為原文件名。

  • 備份

    備份使用提供給 Logger 的日志文件名,格式為 name-timestamp.ext 其中 name 是不帶擴展名的文件名,timestamp 是使用 time.Time 格式格式化的日志輪換時間2006-01-02T15-04-05.000,擴展名ext是原始擴展名。

    例如,如果您的 Logger.Filename 是/var/log/foo/server.log,則在 2016 年 11 月 11 日下午 6:30 創建的備份將使用文件名 /var/log/foo/server-2016- 11-04T18-30-00.000.log

  • 清理舊的日志文件
    每當創建新的日志文件時,可能會刪除舊的日志文件。根據編碼時間戳的最新文件將被保留,最多等于 MaxBackups 的數量(如果 MaxBackups 為 0,則保留所有文件)。無論 MaxBackups 是什么,任何編碼時間戳早于 MaxAge 天的文件都會被刪除。請注意,時間戳中編碼的時間是輪換時間,可能與上次寫入該文件的時間不同。

  • 如果 MaxBackups 和 MaxAge 都為 0,則不會刪除舊的日志文件。

  • type Logger struct {// Filename 寫入日志的文件。備份的日志文件將保留在同一目錄下。// 如果為空,則在os.TempDir()中使用-lumberjack.log。Filename string `json:"filename" yaml:"filename"`// MaxSize 是日志文件在輪換之前的最大大小(以 MB 為單位)。默認為 100 兆字節。MaxSize int `json:"maxsize" yaml:"maxsize"`// MaxAge 是根據文件名中編碼的時間戳保留舊日志文件的最大天數。// 請注意,一天被定義為 24 小時,由于夏令時、閏秒等原因,可能與日歷日不完全對應。// 默認情況下不會根據年齡刪除舊日志文件。MaxAge int `json:"maxage" yaml:"maxage"`// MaxBackups 是要保留的舊日志文件的最大數量。// 默認是保留所有舊的日志文件(盡管 MaxAge 可能仍會導致它們被刪除。)MaxBackups int `json:"maxbackups" yaml:"maxbackups"`// LocalTime 確定用于格式化備份文件中時間戳的時間是否是計算機的本地時間。默認是使用 UTC 時間。LocalTime bool `json:"localtime" yaml:"localtime"`// Compress 確定是否應使用 gzip 壓縮旋轉的日志文件。默認是不執行壓縮。Compress bool `json:"compress" yaml:"compress"`... }

    總結

    以上是生活随笔為你收集整理的Golang高性能日志库zap + lumberjack 日志切割组件详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。