Go Web轻量级框架Gin学习系列:HTTP请求日志

https://juejin.im/post/5cc84cfa51882562457fe311

我们知道,用户向服务器发起的每一次Web请求,都会通过HTTP协议头部或Body携带许多的请求元信息给服务器,如请求的URL地址,请求方法,请求头部和请求IP地址等等诸多原始信息,而在Gin框架中,我们可以使用日志的方式记录和输出这些信息,记录用户的每一次请求行为。

下面是一条Gin框架在控制台中输出的日志:

  1. [GIN] 2019/05/04 - 22:08:56 | 200 | 5.9997ms | ::1 | GET /test

好了,下面看看要如何输出上面的日志吧!

日志中件间

在Gin框架中,要输出用户的http请求日志,最直接简单的方式就是借助日志中间件,下面Gin框架的中间件定义:

  1. func Logger() HandlerFunc

所以,当我们使用下面的代码创建一个gin.Engine时,会在控制台中用户的请求日志:

  1. router := gin.Default()

而使用下面的代码创建gin.Engine时,则不会在控制台输出用户的请求日志:

  1. router := gin.New()

这是为什么呢?这是由于使用Default()函数创建的gin.Engine实例默认使用了日志中件间gin.Logger(),所以,当我们使用第二种方式创建gin.Engine时,可以调用gin.Engine中的Use()方法调用gin.Logger(),如下:

  1. router := gin.New()
  2. router.Use(gin.Logger())

在控制台输出日志

Gin框架请求日志默认是在我们运行程序的控制台中输出,而且输出的日志中有些字体有标颜色,如下图所示:






当然,我们可以使用DisableConsoleColor()函数禁用控制台日志的颜色输出,代码如下所示

  1. gin.DisableConsoleColor()//禁用请求日志控制台字体颜色
  2. router := gin.Default()
  3. router.GET("test",func(c *gin.Context){
  4. c.JSON(200,"test")
  5. })

运行后发出Web请求,在控制台输出日志字体则没有颜色:






虽然Gin框架默认是开始日志字体颜色的,但可以使用DisableConsoleColor()函数来禁用,但当被禁用后,在程序中运行需要重新打开控制台日志的字体颜色输出时,可以使用ForceConsoleColor()函数重新开启,使用如下:

  1. gin.ForceConsoleColor()

在文件输出日志

Gin框架的请求日志默认在控制台输出,但更多的时候,尤其上线运行时,我们希望将用户的请求日志保存到日志文件中,以便更好的分析与备份。

1. DefaultWriter

在Gin框架中,通过gin.DefaultWriter变量可能控制日志的保存方式,gin.DefaultWriter在Gin框架中的定义如下:

  1. var DefaultWriter io.Writer = os.Stdout

从上面的定义我们可以看出,gin.DefaultWriter的类型为io.Writer,默认值为os.Stdout,即控制台输出,因此我们可以通过修改gin.DefaultWriter值来将请求日志保存到日志文件或其他地方(比如数据库)。

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "io"
  5. "os"
  6. )
  7. func main() {
  8. gin.DisableConsoleColor()//保存到文件不需要颜色
  9. file, _ := os.Create("access.log")
  10. gin.DefaultWriter = file
  11. //gin.DefaultWriter = io.MultiWriter(file) 效果是一样的
  12. router := gin.Default()
  13. router.GET("/test", func(c *gin.Context) {
  14. c.String(200, "test")
  15. })
  16. _ = router.Run(":8080")
  17. }

运行后上面的程序,会在程序所在目录创建access.log文件,当我们发起Web请求后,请求的日志会保存到access.log文件,而不会在控制台输出。

通过下面的代码,也可能让请求日志同行保存到文件和在控制台输出:

  1. file, _ := os.Create("access.log")
  2. gin.DefaultWriter = io.MultiWriter(file,os.Stdout) //同时保存到文件和在控制台中输出

2. LoggerWithWriter

另外,我们可以使用gin.LoggerWithWriter中间件,其定义如下:

  1. func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc

示例代码:

  1. package main
  2. import (
  3. "github.com/gin-gonic/gin"
  4. "os"
  5. )
  6. func main() {
  7. gin.DisableConsoleColor()
  8. router := gin.New()
  9. file, _ := os.Create("access.log")
  10. router.Use(gin.LoggerWithWriter(file,""))
  11. router.GET("test", func(c *gin.Context) {
  12. c.JSON(200,"test")
  13. })
  14. _ = router.Run()
  15. }

gin.LoggerWithWriter中间件的第二个参数,可以指定哪个请求路径不输出请求日志,例如下面代码,/test请求不会输出请求日志,而/ping请求日志则会输出请求日志。

  1. router.Use(gin.LoggerWithWriter(file,"/test"))//指定/test请求不输出日志
  2. router.GET("test", func(c *gin.Context) {
  3. c.JSON(200,"test")
  4. })
  5. router.GET("ping", func(c *gin.Context) {
  6. c.JSON(200,"pong")
  7. })

定制日志格式

1. LogFormatterParams

上面的例子,我们都是采用Gin框架默认的日志格式,但默认格式可能并不能满足我们的需求,所以,我们可以使用Gin框架提供的gin.LoggterWithFormatter()中间件,定制日志格式,gin.LoggterWithFormatter()中间件的定义如下:

  1. func LoggerWithFormatter(f LogFormatter) HandlerFunc

gin.LoggterWithFormatter()中间件的定义可以看到该中间件的接受一个数据类型为LogFormatter的参数,LogFormatter定义如下:

  1. type LogFormatter func(params LogFormatterParams) string

LogFormatter的定义看到该类型为func(params LogFormatterParams) string的函数,其参数是为LogFormatterParams,其定义如下:

  1. type LogFormatterParams struct {
  2. Request *http.Request
  3. TimeStamp time.Time
  4. StatusCode int
  5. Latency time.Duration
  6. ClientIP string
  7. Method string
  8. Path string
  9. ErrorMessage string
  10. BodySize int
  11. Keys map[string]interface{}
  12. }

定制日志格式示例代码:

  1. func main() {
  2. router := gin.New()
  3. router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
  4. //定制日志格式
  5. return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
  6. param.ClientIP,
  7. param.TimeStamp.Format(time.RFC1123),
  8. param.Method,
  9. param.Path,
  10. param.Request.Proto,
  11. param.StatusCode,
  12. param.Latency,
  13. param.Request.UserAgent(),
  14. param.ErrorMessage,
  15. )
  16. }))
  17. router.Use(gin.Recovery())
  18. router.GET("/ping", func(c *gin.Context) {
  19. c.String(200, "pong")
  20. })
  21. _ = router.Run(":8080")
  22. }

运行上面的程序后,发起Web请求,控制台会输出以下格式的请求日志:

  1. ::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的定义如下:

  1. func LoggerWithConfig(conf LoggerConfig) HandlerFunc

gin.LoggerWithConfig中间件的参数为LoggerConfig结构,该结构体定义如下:

  1. type LoggerConfig struct {
  2. // 设置日志格式
  3. // 可选 默认值为:gin.defaultLogFormatter
  4. Formatter LogFormatter
  5. // Output用于设置日志将写到哪里去
  6. // 可选. 默认值为:gin.DefaultWriter.
  7. Output io.Writer
  8. // 可选,SkipPaths切片用于定制哪些请求url不在请求日志中输出.
  9. SkipPaths []string
  10. }

以下例子演示如何使用gin.LoggerConfig达到日志格式、输出日志文件以及忽略某些路径的用法:

  1. func main() {
  2. router := gin.New()
  3. file, _ := os.Create("access.log")
  4. c := gin.LoggerConfig{
  5. Output:file,
  6. SkipPaths:[]string{"/test"},
  7. Formatter: func(params gin.LogFormatterParams) string {
  8. return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
  9. params.ClientIP,
  10. params.TimeStamp.Format(time.RFC1123),
  11. params.Method,
  12. params.Path,
  13. params.Request.Proto,
  14. params.StatusCode,
  15. params.Latency,
  16. params.Request.UserAgent(),
  17. params.ErrorMessage,
  18. )
  19. },
  20. }
  21. router.Use(gin.LoggerWithConfig(c))
  22. router.Use(gin.Recovery())
  23. router.GET("/ping", func(c *gin.Context) {
  24. c.String(200, "pong")
  25. })
  26. router.GET("/test", func(c *gin.Context) {
  27. c.String(200, "test")
  28. })
  29. _ = router.Run(":8080")
  30. }

运行上面的程序后,发起Web请求,控制台会输出以下格式的请求日志:

  1. ::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" "
  2. ::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应用时一定要记录用户请求行为,并且定时分析过滤。

ft_authoradmin  ft_create_time2019-08-03 17:34
 ft_update_time2019-08-03 17:34