Go專案實戰之日誌必備篇[開源十年專案第11次更新]

語言: CN / TW / HK

一個深漂近10年的程式設計師立志用未來10年時間去維護一個專案,這個專案的功能未定,用到什麼就做什麼,遇到什麼就寫什麼。其主要目的有二,一為加深自己的技術深度,二為其他學習者提供參考。

感興趣的可以跟著我一起做這個專案,不收費、不套路、人間自有真情在。

備註:前面專案中用到的程式碼已經分享到GitHub中去了,並且以後所有專案中會出現的程式碼都會提交上去,歡迎查閱。

地址:http://github.com/kaiyuan10nian/kaiyuan10nian 感興趣的可以點個star哦~開源十年專案的更新首發於公眾號:計算機自學平臺,有興趣的小夥伴可以持續關注,並歡迎各位加我的微信(kaiyuan10nian)跟我一起完成並推動專案的發展。

哎呀呀,這次更新間隔拉得有點久,是在是對不住各位支援我的小夥伴,最近公司剛啟動了一個新專案,時間實在是緊張,希望各位多多見諒。下面開始今天的分享正文。

Go專案實戰之日誌必備篇

你在某公司技術部經常聽到的一句話就是:“稍等,讓我查下 日誌 再回復你。”

日誌---其實就是專案在執行期間留下的痕跡。

就好比你冬天去打兔子,第一件事就是找雪地上兔子留下的腳印,然後順著腳印去找到兔子的老窩,最後滿載而歸。

專案開發也是一樣的道理,你要在專案開發期間想盡辦法去讓業務邏輯在執行期間能留下更多的關鍵資訊,這樣在專案正常執行後就會留下它執行的痕跡,你就可以通過這個痕跡快速尋找到問題的根源,從而一網打盡。

而在Go語言中只提供了一個標準庫log.

不需要安裝即可使用,它是一個非常小巧的日誌庫,大家有空可以去看看。

log只提供了三個簡單的介面,對於某些大型專案來說有點太雞肋,所以本篇我要介紹的不是log而是它的哥哥logrus。

logrus是一個完全相容log的標準庫,目前在GitHub上已經超20K stars了。

它支援兩種日誌輸出格式:文字 And JSON

這對於想通過日誌去做更多資料分析的專案來說簡直是太爽了。

本篇分兩部分:

  • 1、logrus的基本用法介紹

  • 2、封裝logrus用於 開源十年 專案中

​ 2.1)關於使用lumberjack對logrus生成的日誌進行分包

1、logrus的基本用法介紹

1)安裝

go get -u github.com/sirupsen/logrus

2)設定日誌輸出等級

​ 老程式設計師都知道,一個專案完整的開發週期分好幾個階段,所以在不同的階段我們需要的日誌資訊也是不一樣的。所以在使用logrus的時候我們要去設定其日誌輸出的等級,從而篩選出我們需要的資訊內容。

​ 那麼在設定日誌輸出等級之前,我們需要了解logrus共區分了幾個等級?

| 級別 | 等級 | 解釋 | | ---- | ----------------- | ---------------------------------------------------- | | 1 | logrus.TraceLevel | 非常小粒度的資訊 | | 2 | logrus.DebusLevel | 一般程式中輸出的除錯資訊 | | 3 | logrus.InfoLevel | 關鍵操作(核心流程日誌) | | 4 | logrus.WarnLevel | 警告資訊 | | 5 | logrus.ErrorLevel | 錯誤資訊 | | 6 | logrus.FatalLevel | 致命錯誤,出現後程序無法執行,輸出日之後程式停止執行 | | 7 | logrus.PanicLevel | 記錄日誌,然後panic |

左邊有個 級別 大家一定要記清楚順序,因為在logrus中,高於設定級別的日誌是不會輸出的,預設設定級別是InfoLevel

示例:

logrus.SetLevel(logrus.TraceLevel)
logrus.Trace("1---trace---msg")
logrus.Debug("2---debug---msg")
logrus.Info("3---info---msg")
logrus.Warn("4---warn---msg")
logrus.Error("5---error---msg")
logrus.Fatal("6---fatal---msg")
logrus.Panic("7---panic---msg")

執行之後我們看下日誌輸出情況:

TRAC[0000] 1---trace---msg                              
DEBU[0000] 2---debug---msg                              
INFO[0000] 3---info---msg                               
WARN[0000] 4---warn---msg                               
ERRO[0000] 5---error---msg                              
FATA[0000] 6---fatal---msg

如果上面程式碼中我們把:

logrus.SetLevel(logrus.TraceLevel)

修改為:

logrus.SetLevel(logrus.InfoLevel)

然後,再執行看下輸出情況:

INFO[0000] 3---info---msg                               
WARN[0000] 4---warn---msg                               
ERRO[0000] 5---error---msg                              
FATA[0000] 6---fatal---msg

可以看到,比info級別低的就不再輸出了。

3)在日誌中輸出具體檔案和函式位置

為了快速定位問題根源,很多時候我們會在除錯階段直接把檔案路徑和函式直接輸出出來,這樣我們就不需要再去定位尋找了,解決問題的效率將會得到大大的提升。

logrus提供了專門的配置,只需要在初始化logrus的時候呼叫SetReportCaller()函式並設定為true即可。

示例:

logrus.SetReportCaller(true)
	logrus.Info("3---info---msg")

直接執行看效果:

INFO[0000]/Users/fu/GolandProjects/logrusDemo/main.go:29 main.main() 3---info---msg

4)新增附屬資訊

我們做為後端開發人員,時刻把併發的問題放在心頭是本能。所以在記錄日誌時,你可能也會思考怎麼去區分日誌。

比如:

哪些日誌是張三留下的?哪些日誌是李四留下的?為什麼同樣的邏輯流程張三和李四輸出的結果不一樣呢?

這個時候你或許在想,如果我給這些日誌打上“張三”“李四”的備註是不是就好找多了?

logrus提供瞭解決方案,就是WithField和WithFields ,允許在輸出中新增一些欄位,比如:

logrus.WithFields(logrus.Fields{
		"UUID": "12345678",
	}).Info("info msg")

日誌輸出:

INFO[0000] 3---info---msg                                UUID=12345678

這是針對單個的使用方式,如果想批量使用更好辦:

requestLogger := logrus.WithFields(logrus.Fields{
		"UUID": "12345678",
	})
requestLogger.Info("3---info---msg")
requestLogger.Error("5---error---msg")

日誌輸出:

INFO[0000] 3---info---msg                                UUID=12345678
ERRO[0000] 5---error---msg                               UUID=12345678

5)JSON格式輸出日誌

上面我們輸出日誌的時候用的是預設的輸出格式,也就是文字格式。但是在很多業務中我們做資料統計或者資料分析的時候依靠的源資料都是日誌,如果是文字格式那麼用起來就不是那麼的順手,換成json格式的話會不會帶來很大的方便呢?

logrus不同於log的最大之處就是提供了json格式的輸出,只需要在初始化的時候設定SetFormatter即可。

logrus.SetLevel(logrus.TraceLevel)
	logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.Trace("1---trace---msg")
	logrus.Debug("2---debug---msg")
	logrus.Info("3---info---msg")
	logrus.Warn("4---warn---msg")
	logrus.Error("5---error---msg")
	logrus.Fatal("6---fatal---msg")
	logrus.Panic("7---panic---msg")

跟1)中的例項一樣,只是添加了logrus.SetFormatter(&logrus.JSONFormatter{}),下面我們看下輸出的格式:

{"level":"trace","msg":"1---trace---msg","time":"2022-05-14T11:37:56+08:00"}
{"level":"debug","msg":"2---debug---msg","time":"2022-05-14T11:37:56+08:00"}
{"level":"info","msg":"3---info---msg","time":"2022-05-14T11:37:56+08:00"}
{"level":"warning","msg":"4---warn---msg","time":"2022-05-14T11:37:56+08:00"}
{"level":"error","msg":"5---error---msg","time":"2022-05-14T11:37:56+08:00"}
{"level":"fatal","msg":"6---fatal---msg","time":"2022-05-14T11:37:56+08:00"}

ok,到這裡logrus的基本操作我們就明白了,下面針對在開源十年專案中我們怎麼去系統的運用它。

2、封裝logrus用於 開源十年 專案中

我封裝了一個logger.go的檔案,放在了config目錄下面,下面我把程式碼完整放上來,然後在備註中去一一解釋一下。

package config

import (
	"github.com/gin-gonic/gin"
	"github.com/natefinch/lumberjack"
	"github.com/sirupsen/logrus"
	"path"
	"time"
)

var logger *logrus.Logger
//日誌名稱
const (
	//日誌檔名
	LOG_NAME = "kaiyuanshinian"
	//日誌檔案字尾
	LOG_SUFFIX = ".log"
	//單個日誌檔案大小,單位MB
	LOG_SIZE = 50
	//日誌檔案個數
	LOG_BACKUP = 10
	//日誌檔案最大天數
	LOG_DATE = 7
)

//設定日誌輸出到檔案
func setOutPut(log *logrus.Logger, log_file_path string) {
	logconf := &lumberjack.Logger{
		Filename:   log_file_path,
		MaxSize:    LOG_SIZE,   // 日誌檔案大小,單位是 MB
		MaxBackups: LOG_BACKUP, // 最大過期日誌保留個數
		MaxAge:     LOG_DATE,   // 保留過期檔案最大時間,單位 天
		Compress:   true,       // 是否壓縮日誌,預設是不壓縮。這裡設定為true,壓縮日誌
	}
	log.SetOutput(logconf)
}

//初始化日誌模組
func InitLogger() {
	log_file_path := path.Join("./", LOG_NAME+LOG_SUFFIX)
	logger = logrus.New()
	setOutPut(logger, log_file_path)
	logger.SetLevel(logrus.DebugLevel)
	logger.SetFormatter(&logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})
}
//獲取logrus操作物件
func GetLogger() *logrus.Logger {
	return logger
}

//gin請求訊息也寫入日誌
func LoggerToFile() gin.HandlerFunc {
	return func(c *gin.Context) {
		startTime := time.Now()               // 開始時間
		c.Next()                              // 處理請求
		endTime := time.Now()                 // 結束時間
		latencyTime := endTime.Sub(startTime) // 執行時間
		reqMethod := c.Request.Method         // 請求方式
		reqUri := c.Request.RequestURI        // 請求路由
		statusCode := c.Writer.Status()       // 狀態碼
		clientIP := c.ClientIP()              // 請求IP
		logger.Infof("| %3d | %13v | %15s | %s | %s", statusCode, latencyTime, clientIP, reqMethod, reqUri ) // 日誌格式
	}
}

上面是對logrus的封裝,大家應該都看的明白的我就不一一囉嗦了,那麼怎麼使用呢?(上面程式碼中只有lumberjack是我們之前沒有提及過得,下面解釋)

1)初始化

直接在專案啟動的時候把logrus的初始化加進去即可

func InitConfig() {
	config.InitLogger()//初始化logrus
	viper.SetConfigName("application")
	viper.SetConfigType("yml")
	viper.AddConfigPath("./config/")
	err := viper.ReadInConfig()
	if err != nil {
		panic(""+err.Error())
	}
}

2)使用

使用就更簡單了,直接在專案需要的位置進行呼叫即可。

2.1)route中的使用

func CollectRoute(r *gin.Engine) *gin.Engine  {
	r.Use(config.LoggerToFile())//新增日誌記錄
	r.POST("/v1/account/register", controller.Register)
	
	...//省略了一些程式碼,都是以前寫的,專案中有

	return r
}

2.2)邏輯中的使用

config.GetLogger().Debugf("aaaaa")

3)lumberjack

上面封裝的程式碼中大家發現多了個新東西lumberjack,他是幹啥用的呢?

對,切分日誌檔案的。

有的時候我們的日誌需要大量的去記載,如果都記錄在一個檔案中,萬一發生點什麼不可描述的事情導致檔案丟失了那我們就只有哭的份了。

所以一是為了安全,二是為了方便,我們要針對日誌檔案進行分割儲存。

logconf := &lumberjack.Logger{
		Filename:   log_file_path,
		MaxSize:    LOG_SIZE,   // 日誌檔案大小,單位是 MB
		MaxBackups: LOG_BACKUP, // 最大過期日誌保留個數
		MaxAge:     LOG_DATE,   // 保留過期檔案最大時間,單位 天
		Compress:   true,       // 是否壓縮日誌,預設是不壓縮。這裡設定為true,壓縮日誌
	}
	log.SetOutput(logconf)

它的使用非常簡單,設定好引數,在SetoutPut中傳入進去就可以了,當檔案大於我們設定的MaxSize時,會自動進行分割儲存。

ok ,just this...

今天就先寫這麼多吧。謝謝大家的支援哦~