简介

​ Logger是Gin官方提供的一个开箱即用的中间件,用于格式化输出请求日志。

LoggerConfig对象

// LoggerConfig defines the config for Logger middleware.
type LoggerConfig struct {
   // Optional. Default value is gin.defaultLogFormatter
   Formatter LogFormatter

   // Output is a writer where logs are written.
   // Optional. Default value is gin.DefaultWriter.
   Output io.Writer

   // SkipPaths is a url path array which logs are not written.
   // Optional.
   SkipPaths []string
}

​ LoggerConfig是Logger中间件默认的配置结构体。

成员 作用
Formatter 将LogFormatterParams对象格式化成字符串输出
Output 输出句柄
SkipPaths 忽略日志的路径列表

LoggerWithConfig方法

​ 这个方法是Logger包内代码的主逻辑,下面主要通过注释来分析一下该方法:

// LoggerWithConfig instance a Logger middleware with config.
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
  // 先初始化一下formatter和output
   formatter := conf.Formatter
   if formatter == nil {
      formatter = defaultLogFormatter
   }

   out := conf.Output
   if out == nil {
      out = DefaultWriter
   }

   notlogged := conf.SkipPaths

   isTerm := true

   if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
      (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
      isTerm = false
   }
	// 构建忽略路径的Set,这里只var一下,应该是为了节省内存分配
   var skip map[string]struct{}

   if length := len(notlogged); length > 0 {
      skip = make(map[string]struct{}, length)

      for _, path := range notlogged {
         skip[path] = struct{}{}
      }
   }

   return func(c *Context) {
     // 记录请求的一些元信息,这里存储path的原因应该是为了防止context指针传递中被修改
      start := time.Now()
      path := c.Request.URL.Path
      raw := c.Request.URL.RawQuery

      // 这里将先执行后面注册的中间件,包括用户注册的逻辑
      c.Next()

      // 判断是否需要Log
      if _, ok := skip[path]; !ok {
         param := LogFormatterParams{
            Request: c.Request,
            isTerm:  isTerm,
            Keys:    c.Keys,
         }

         // Stop timer
         param.TimeStamp = time.Now()
         param.Latency = param.TimeStamp.Sub(start)

         param.ClientIP = c.ClientIP()
         param.Method = c.Request.Method
         param.StatusCode = c.Writer.Status()
         param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()

         param.BodySize = c.Writer.Size()

         if raw != "" {
            path = path + "?" + raw
         }

         param.Path = path
				// 输出到日志
         fmt.Fprint(out, formatter(param))
      }
   }
}

​ 可以看到整个Logger的核心在于c.Next()函数的调用,如果这边不先调用该函数,那么记录的时间只是该函数的处理时间,而不是整个请求的时间。

​ 因此,Logger中间件最好放在作为整个Handler处理链的 第一个中间件 注册。

默认实现的输出方法

// defaultLogFormatter is the default log format function Logger middleware uses.
var defaultLogFormatter = func(param LogFormatterParams) string {
   var statusColor, methodColor, resetColor string
   if param.IsOutputColor() {
      statusColor = param.StatusCodeColor()
      methodColor = param.MethodColor()
      resetColor = param.ResetColor()
   }
		// 如果延迟大于一分钟则截断掉秒级的尾巴
   if param.Latency > time.Minute {
      param.Latency = param.Latency.Truncate(time.Second)
   }
   return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
      param.TimeStamp.Format("2006/01/02 - 15:04:05"),
      statusColor, param.StatusCode, resetColor,
      param.Latency,
      param.ClientIP,
      methodColor, param.Method, resetColor,
      param.Path,
      param.ErrorMessage,
   )
}

​ 这边的实现就没什么可说的,主要是日志彩色输出和延迟的处理打印。


<
Previous Post
Gin开篇
>
Next Post
Gin的中间件探索之Recovery