我們知道,使用者向伺服器發起的每一次Web請求,都會通過HTTP協議頭部或Body攜帶許多的請求元資訊給伺服器,如請求的URL地址,請求方法,請求頭部和請求IP地址等等諸多原始資訊,而在Gin框架中,我們可以使用日誌的方式記錄和輸出這些資訊,記錄使用者的每一次請求行為。
下面是一條Gin框架在控制檯中輸出的日誌:
[GIN] 2019/05/04 - 22:08:56 | 200 | 5.9997ms | ::1 | GET /test
複製程式碼
好了,下面看看要如何輸出上面的日誌吧!
日誌中件間
在Gin框架中,要輸出使用者的http請求日誌,最直接簡單的方式就是藉助日誌中介軟體,下面Gin框架的中介軟體定義:
func Logger() HandlerFunc
複製程式碼
所以,當我們使用下面的程式碼建立一個gin.Engine
時,會在控制檯中使用者的請求日誌:
router := gin.Default()
複製程式碼
而使用下面的程式碼建立gin.Engine
時,則不會在控制檯輸出使用者的請求日誌:
router := gin.New()
複製程式碼
這是為什麼呢?這是由於使用Default()
函式建立的gin.Engine例項預設使用了日誌中件間gin.Logger()
,所以,當我們使用第二種方式建立gin.Engine時,可以呼叫gin.Engine中的Use()方法呼叫gin.Logger()
,如下:
router := gin.New()
router.Use(gin.Logger())
複製程式碼
在控制檯輸出日誌
Gin框架請求日誌預設是在我們執行程式的控制檯中輸出,而且輸出的日誌中有些字型有標顏色,如下圖所示:
當然,我們可以使用DisableConsoleColor()
函式禁用控制檯日誌的顏色輸出,程式碼如下所示
gin.DisableConsoleColor()//禁用請求日誌控制檯字型顏色
router := gin.Default()
router.GET("test",func(c *gin.Context){
c.JSON(200,"test")
})
複製程式碼
執行後發出Web請求,在控制檯輸出日誌字型則沒有顏色:
雖然Gin框架預設是開始日誌字型顏色的,但可以使用DisableConsoleColor()
函式來禁用,但當被禁用後,在程式中執行需要重新開啟控制檯日誌的字型顏色輸出時,可以使用ForceConsoleColor()
函式重新開啟,使用如下:
gin.ForceConsoleColor()
複製程式碼
在檔案輸出日誌
Gin框架的請求日誌預設在控制檯輸出,但更多的時候,尤其上線執行時,我們希望將使用者的請求日誌儲存到日誌檔案中,以便更好的分析與備份。
1. DefaultWriter
在Gin框架中,通過gin.DefaultWriter
變數可能控制日誌的儲存方式,gin.DefaultWriter
在Gin框架中的定義如下:
var DefaultWriter io.Writer = os.Stdout
複製程式碼
從上面的定義我們可以看出,gin.DefaultWriter
的型別為io.Writer
,預設值為os.Stdout
,即控制檯輸出,因此我們可以通過修改gin.DefaultWriter
值來將請求日誌儲存到日誌檔案或其他地方(比如資料庫)。
package main
import (
"github.com/gin-gonic/gin"
"io"
"os"
)
func main() {
gin.DisableConsoleColor()//儲存到檔案不需要顏色
file, _ := os.Create("access.log")
gin.DefaultWriter = file
//gin.DefaultWriter = io.MultiWriter(file) 效果是一樣的
router := gin.Default()
router.GET("/test", func(c *gin.Context) {
c.String(200, "test")
})
_ = router.Run(":8080")
}
複製程式碼
執行後上面的程式,會在程式所在目錄建立access.log
檔案,當我們發起Web請求後,請求的日誌會儲存到access.log
檔案,而不會在控制檯輸出。
通過下面的程式碼,也可能讓請求日誌同行儲存到檔案和在控制檯輸出:
file, _ := os.Create("access.log")
gin.DefaultWriter = io.MultiWriter(file,os.Stdout) //同時儲存到檔案和在控制檯中輸出
複製程式碼
2. LoggerWithWriter
另外,我們可以使用gin.LoggerWithWriter
中介軟體,其定義如下:
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
複製程式碼
示例程式碼:
package main
import (
"github.com/gin-gonic/gin"
"os"
)
func main() {
gin.DisableConsoleColor()
router := gin.New()
file, _ := os.Create("access.log")
router.Use(gin.LoggerWithWriter(file,""))
router.GET("test", func(c *gin.Context) {
c.JSON(200,"test")
})
_ = router.Run()
}
複製程式碼
gin.LoggerWithWriter
中介軟體的第二個引數,可以指定哪個請求路徑不輸出請求日誌,例如下面程式碼,/test
請求不會輸出請求日誌,而/ping
請求日誌則會輸出請求日誌。
router.Use(gin.LoggerWithWriter(file,"/test"))//指定/test請求不輸出日誌
router.GET("test", func(c *gin.Context) {
c.JSON(200,"test")
})
router.GET("ping", func(c *gin.Context) {
c.JSON(200,"pong")
})
複製程式碼
定製日誌格式
1. LogFormatterParams
上面的例子,我們都是採用Gin框架預設的日誌格式,但預設格式可能並不能滿足我們的需求,所以,我們可以使用Gin框架提供的gin.LoggterWithFormatter()
中介軟體,定製日誌格式,gin.LoggterWithFormatter()
中介軟體的定義如下:
func LoggerWithFormatter(f LogFormatter) HandlerFunc
複製程式碼
從gin.LoggterWithFormatter()
中介軟體的定義可以看到該中介軟體的接受一個資料型別為LogFormatter
的引數,LogFormatter
定義如下:
type LogFormatter func(params LogFormatterParams) string
複製程式碼
從LogFormatter
的定義看到該型別為func(params LogFormatterParams) string
的函式,其引數是為LogFormatterParams
,其定義如下:
type LogFormatterParams struct {
Request *http.Request
TimeStamp time.Time
StatusCode int
Latency time.Duration
ClientIP string
Method string
Path string
ErrorMessage string
BodySize int
Keys map[string]interface{}
}
複製程式碼
定製日誌格式示例程式碼:
func main() {
router := gin.New()
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
//定製日誌格式
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
_ = router.Run(":8080")
}
複製程式碼
執行上面的程式後,發起Web請求,控制檯會輸出以下格式的請求日誌:
::1 - [Wed, 08 May 2019 21:53:17 CST] "GET /ping HTTP/1.1 200 1.0169ms "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36" "
複製程式碼
2. LoggerWithConfig
在前面的例子中,我們使用gin.Logger()
開啟請求日誌、使用gin.LoggerWithWriter
將日誌寫到檔案中,使用gin.LoggerWithFormatter
定製日誌格式,而實際上,這三個中介軟體,其底層都是呼叫gin.LoggerWithConfig
中介軟體,也就說,我們使用gin.LoggerWithConfig
中介軟體,便可以完成上述中介軟體所有的功能,gin.LoggerWithConfig
的定義如下:
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
複製程式碼
gin.LoggerWithConfig
中介軟體的引數為LoggerConfig
結構,該結構體定義如下:
type LoggerConfig struct {
// 設定日誌格式
// 可選 預設值為:gin.defaultLogFormatter
Formatter LogFormatter
// Output用於設定日誌將寫到哪裡去
// 可選. 預設值為:gin.DefaultWriter.
Output io.Writer
// 可選,SkipPaths切片用於定製哪些請求url不在請求日誌中輸出.
SkipPaths []string
}
複製程式碼
以下例子演示如何使用gin.LoggerConfig
達到日誌格式、輸出日誌檔案以及忽略某些路徑的用法:
func main() {
router := gin.New()
file, _ := os.Create("access.log")
c := gin.LoggerConfig{
Output:file,
SkipPaths:[]string{"/test"},
Formatter: func(params gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
params.ClientIP,
params.TimeStamp.Format(time.RFC1123),
params.Method,
params.Path,
params.Request.Proto,
params.StatusCode,
params.Latency,
params.Request.UserAgent(),
params.ErrorMessage,
)
},
}
router.Use(gin.LoggerWithConfig(c))
router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.GET("/test", func(c *gin.Context) {
c.String(200, "test")
})
_ = router.Run(":8080")
}
複製程式碼
執行上面的程式後,發起Web請求,控制檯會輸出以下格式的請求日誌:
::1 - [Wed, 08 May 2019 22:39:43 CST] "GET /ping HTTP/1.1 200 0s "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36" "
::1 - [Wed, 08 May 2019 22:39:46 CST] "GET /ping HTTP/1.1 200 0s "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36" "
複製程式碼
小結
每條HTTP請求日誌,都對應一次使用者的請求行為,記錄每一條使用者請求日誌,對於我們追蹤使用者行為,過濾使用者非法請求,排查程式執行產生的各種問題至關重要,因此,開發Web應用時一定要記錄使用者請求行為,並且定時分析過濾。