Go 語言之在 gin 框架中使用 zap 日誌庫

来源:https://www.cnblogs.com/QiaoPengjun/archive/2023/06/17/17488013.html
-Advertisement-
Play Games

# Go 語言之在 gin 框架中使用 zap 日誌庫 ### gin 框架預設使用的是自帶的日誌 #### `gin.Default()`的源碼 Logger(), Recovery() ```go func Default() *Engine { debugPrintWARNINGDefault ...


Go 語言之在 gin 框架中使用 zap 日誌庫

gin 框架預設使用的是自帶的日誌

gin.Default()的源碼 Logger(), Recovery()

func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default, gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc {
	return LoggerWithConfig(LoggerConfig{})
}

// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc {
	return RecoveryWithWriter(DefaultErrorWriter)
}

// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
	if len(recovery) > 0 {
		return CustomRecoveryWithWriter(out, recovery[0])
	}
	return CustomRecoveryWithWriter(out, defaultHandleRecovery)
}

// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
	var logger *log.Logger
	if out != nil {
		logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
	}
	return func(c *Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					var se *os.SyscallError
					if errors.As(ne, &se) {
						seStr := strings.ToLower(se.Error())
						if strings.Contains(seStr, "broken pipe") ||
							strings.Contains(seStr, "connection reset by peer") {
							brokenPipe = true
						}
					}
				}
				if logger != nil {
					stack := stack(3)
					httpRequest, _ := httputil.DumpRequest(c.Request, false)
					headers := strings.Split(string(httpRequest), "\r\n")
					for idx, header := range headers {
						current := strings.Split(header, ":")
						if current[0] == "Authorization" {
							headers[idx] = current[0] + ": *"
						}
					}
					headersToStr := strings.Join(headers, "\r\n")
					if brokenPipe {
						logger.Printf("%s\n%s%s", err, headersToStr, reset)
					} else if IsDebugging() {
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
							timeFormat(time.Now()), headersToStr, err, stack, reset)
					} else {
						logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
							timeFormat(time.Now()), err, stack, reset)
					}
				}
				if brokenPipe {
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) //nolint: errcheck
					c.Abort()
				} else {
					handle(c, err)
				}
			}
		}()
		c.Next()
	}
}

自定義 Logger(), Recovery()

實操

package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"
)

// 定義一個全局 logger 實例
// Logger提供快速、分級、結構化的日誌記錄。所有方法對於併發使用都是安全的。
// Logger是為每一微秒和每一個分配都很重要的上下文設計的,
// 因此它的API有意傾向於性能和類型安全,而不是簡便性。
// 對於大多數應用程式,SugaredLogger在性能和人體工程學之間取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger將基本的Logger功能封裝在一個較慢但不那麼冗長的API中。任何Logger都可以通過其Sugar方法轉換為sugardlogger。
//與Logger不同,SugaredLogger並不堅持結構化日誌記錄。對於每個日誌級別,它公開了四個方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

//func main() {
//	// 初始化
//	InitLogger()
//	// Sync調用底層Core的Sync方法,刷新所有緩衝的日誌條目。應用程式在退出之前應該註意調用Sync。
//	// 在程式退出之前,把緩衝區里的日誌刷到磁碟上
//	defer logger.Sync()
//	simpleHttpGet("www.baidu.com")
//	simpleHttpGet("http://www.baidu.com")
//
//	for i := 0; i < 10000; i++ {
//		logger.Info("test lumberjack for log rotate....")
//	}
//}

func main() {
	InitLogger()
	//r := gin.Default()

	r := gin.New()
	r.Use(GinLogger(logger), GinRecovery(logger, true))
	r.GET("/hello", func(c *gin.Context) {
		c.String(http.StatusOK, "hello xiaoqiao!")
	})
	r.Run()
}

// GinLogger
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next() // 執行後續中間件

		// Since returns the time elapsed since t.
		// It is shorthand for time.Now().Sub(t).
		cost := time.Since(start)
		logger.Info(path,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("query", query),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost), // 運行時間
		)
	}
}

// GinRecovery
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore創建一個向WriteSyncer寫入日誌的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler決定在記錄消息時是否啟用給定的日誌級別。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.

	// AddCaller configures the Logger to annotate each message with the filename,
	// line number, and function name of zap's caller. See also WithCaller.
	logger = zap.New(core, zap.AddCaller())
	// Sugar封裝了Logger,以提供更符合人體工程學的API,但速度略慢。糖化一個Logger的成本非常低,
	// 因此一個應用程式同時使用Loggers和SugaredLoggers是合理的,在性能敏感代碼的邊界上在它們之間進行轉換。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder創建了一個快速、低分配的JSON編碼器。編碼器適當地轉義所有欄位鍵和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder創建一個編碼器,其輸出是為人類而不是機器設計的。
	// 它以純文本格式序列化核心日誌條目數據(消息、級別、時間戳等),並將結構化上下文保留為JSON。
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	return zapcore.NewConsoleEncoder(encoderConfig)
}

//func getLogWriter() zapcore.WriteSyncer {
//	// Create創建或截斷指定文件。如果文件已經存在,它將被截斷。如果該文件不存在,則以模式0666(在umask之前)創建。
//	// 如果成功,返回的File上的方法可以用於IO;關聯的文件描述符模式為O_RDWR。如果有一個錯誤,它的類型將是PathError。
//	//file, _ := os.Create("./test.log")
//	file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
//	if err != nil {
//		log.Fatalf("open log file failed with error: %v", err)
//	}
//	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
//	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
//	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
//	return zapcore.AddSync(file)
//}

func getLogWriter() zapcore.WriteSyncer {
	// Logger is an io.WriteCloser that writes to the specified filename.
	// 日誌記錄器在第一次寫入時打開或創建日誌文件。如果文件存在並且小於MaxSize兆位元組,則lumberjack將打開並追加該文件。
	// 如果該文件存在並且其大小為>= MaxSize兆位元組,
	// 則通過將當前時間放在文件擴展名(或者如果沒有擴展名則放在文件名的末尾)的名稱中的時間戳中來重命名該文件。
	// 然後使用原始文件名創建一個新的日誌文件。
	// 每當寫操作導致當前日誌文件超過MaxSize兆位元組時,將關閉當前文件,重新命名,並使用原始名稱創建新的日誌文件。
	// 因此,您給Logger的文件名始終是“當前”日誌文件。
	// 如果MaxBackups和MaxAge均為0,則不會刪除舊的日誌文件。
	lumberJackLogger := &lumberjack.Logger{
		// Filename是要寫入日誌的文件。備份日誌文件將保留在同一目錄下
		Filename: "./test.log",
		// MaxSize是日誌文件旋轉之前的最大大小(以兆位元組為單位)。預設為100兆位元組。
		MaxSize: 1, // M
		// MaxBackups是要保留的舊日誌文件的最大數量。預設是保留所有舊的日誌文件(儘管MaxAge仍然可能導致它們被刪除)。
		MaxBackups: 5, // 備份數量
		// MaxAge是根據文件名中編碼的時間戳保留舊日誌文件的最大天數。
		// 請註意,一天被定義為24小時,由於夏令時、閏秒等原因,可能與日曆日不完全對應。預設情況下,不根據時間刪除舊的日誌文件。
		MaxAge: 30, // 備份天數
		// Compress決定是否應該使用gzip壓縮旋轉的日誌文件。預設情況下不執行壓縮。
		Compress: false, // 是否壓縮
	}

	return zapcore.AddSync(lumberJackLogger)
}

func simpleHttpGet(url string) {
	// Get向指定的URL發出Get命令。如果響應是以下重定向代碼之一,則Get跟隨重定向,最多可重定向10個:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do來發出帶有自定義頭的請求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel記錄消息。該消息包括在日誌站點傳遞的任何欄位,以及日誌記錄器上積累的任何欄位。
		//logger.Error(

		// 錯誤使用fmt。以Sprint方式構造和記錄消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字元串用給定的鍵和值構造一個欄位。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel記錄消息。該消息包括在日誌站點傳遞的任何欄位,以及日誌記錄器上積累的任何欄位。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式構造和記錄消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

運行並訪問:http://localhost:8080/hello

Code/go/zap_demo via 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #基於回歸分析的波士頓房價分析 項目實現步驟: 1.項目結構 2.處理數據 3.處理繪圖 4.對數據進行分析 5.結果展示 一.項目結構 ![image](https://img2023.cnblogs.com/blog/3047082/202306/3047082-2023061722315431 ...
  • 好家伙,爬蟲來了 爬蟲,這玩意,不會怎麼辦, 誒,先抄一份作業回來 1.別人的爬蟲 Python爬蟲史上超詳細講解(零基礎入門,老年人都看的懂)_ChenBinBini的博客-CSDN博客 # -*- codeing = utf-8 -*- from bs4 import BeautifulSoup ...
  • ## 一、創建宿主機物理路徑 新建/mydata/mysql/data、log和conf三個文件夾 ```bash mkdir -p /mnt/mysql/log mkdir -p /mnt/mysql/data mkdir -p /mnt/mysql/config ``` 或者 ```bash m ...
  • 某日二師兄參加XXX科技公司的C++工程師開發崗位第17面: > 面試官:聊一聊指針? > > 二師兄:好的。 > > 面試官:你覺得指針本質上是什麼? > > 二師兄:這要從記憶體地址開始說起了。如果有一塊容量是1G的記憶體,假設它的地址是從`0x00000000` 到`0x3fffffff`,每一個 ...
  • # Java 註釋、絕對路徑、相對路徑、基本Dos命令 # 1. Java的三種註釋方式 ## 註釋能增加代碼的可讀性,習慣寫註釋能提升我們編寫代碼的能力 > ### 單行註釋:用//註釋一些代碼提示 > > ### 多行註釋:以/*為開頭 以 */為結束 > > ### 文檔註釋:/* > > # ...
  • # 1.Java 發展歷史 ### 由高斯林創建 1995年由甲骨文公司收購併發出第一版本,目前使用最多是Java8 及 Java11 原因是這兩個版本都是長期支持維護的,企業用的也比較多。 # 2.Java的一些特點 > ### 跨平臺性:主要是因為每個平臺都裝有 JVM > ### Java 是 ...
  • #### 1. 跳出/執行下一次迴圈。 ``` {標簽名}: for true { ... for true { ... break/continue {標簽名} //預設不加標簽,則跳出最近一層迴圈。加了標簽可以跳出標簽定義處所在迴圈 } } ``` #### 2. map的使用註意項。 因為ma ...
  • 最近在弄文件上傳、下載、線上預覽時經常需要設置請求標頭或者響應標頭的Content-Type 屬性。所以研究了一下spring支持哪些Content-Type,通過研究MediaTypeFactory.getMediaType的源碼,可以得知spring是將支持的Content-Type 維護在/o... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...