logger.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. package middleware
  2. import (
  3. "bytes"
  4. "io"
  5. "os"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/labstack/echo"
  11. "github.com/labstack/gommon/color"
  12. "github.com/valyala/fasttemplate"
  13. )
  14. type (
  15. // LoggerConfig defines the config for Logger middleware.
  16. LoggerConfig struct {
  17. // Skipper defines a function to skip middleware.
  18. Skipper Skipper
  19. // Tags to constructed the logger format.
  20. //
  21. // - time_unix
  22. // - time_unix_nano
  23. // - time_rfc3339
  24. // - time_rfc3339_nano
  25. // - time_custom
  26. // - id (Request ID)
  27. // - remote_ip
  28. // - uri
  29. // - host
  30. // - method
  31. // - path
  32. // - protocol
  33. // - referer
  34. // - user_agent
  35. // - status
  36. // - error
  37. // - latency (In nanoseconds)
  38. // - latency_human (Human readable)
  39. // - bytes_in (Bytes received)
  40. // - bytes_out (Bytes sent)
  41. // - header:<NAME>
  42. // - query:<NAME>
  43. // - form:<NAME>
  44. //
  45. // Example "${remote_ip} ${status}"
  46. //
  47. // Optional. Default value DefaultLoggerConfig.Format.
  48. Format string `yaml:"format"`
  49. // Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
  50. CustomTimeFormat string `yaml:"custom_time_format"`
  51. // Output is a writer where logs in JSON format are written.
  52. // Optional. Default value os.Stdout.
  53. Output io.Writer
  54. template *fasttemplate.Template
  55. colorer *color.Color
  56. pool *sync.Pool
  57. }
  58. )
  59. var (
  60. // DefaultLoggerConfig is the default Logger middleware config.
  61. DefaultLoggerConfig = LoggerConfig{
  62. Skipper: DefaultSkipper,
  63. Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}",` +
  64. `"method":"${method}","uri":"${uri}","status":${status},"error":"${error}","latency":${latency},` +
  65. `"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
  66. `"bytes_out":${bytes_out}}` + "\n",
  67. CustomTimeFormat: "2006-01-02 15:04:05.00000",
  68. Output: os.Stdout,
  69. colorer: color.New(),
  70. }
  71. )
  72. // Logger returns a middleware that logs HTTP requests.
  73. func Logger() echo.MiddlewareFunc {
  74. return LoggerWithConfig(DefaultLoggerConfig)
  75. }
  76. // LoggerWithConfig returns a Logger middleware with config.
  77. // See: `Logger()`.
  78. func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
  79. // Defaults
  80. if config.Skipper == nil {
  81. config.Skipper = DefaultLoggerConfig.Skipper
  82. }
  83. if config.Format == "" {
  84. config.Format = DefaultLoggerConfig.Format
  85. }
  86. if config.Output == nil {
  87. config.Output = DefaultLoggerConfig.Output
  88. }
  89. config.template = fasttemplate.New(config.Format, "${", "}")
  90. config.colorer = color.New()
  91. config.colorer.SetOutput(config.Output)
  92. config.pool = &sync.Pool{
  93. New: func() interface{} {
  94. return bytes.NewBuffer(make([]byte, 256))
  95. },
  96. }
  97. return func(next echo.HandlerFunc) echo.HandlerFunc {
  98. return func(c echo.Context) (err error) {
  99. if config.Skipper(c) {
  100. return next(c)
  101. }
  102. req := c.Request()
  103. res := c.Response()
  104. start := time.Now()
  105. if err = next(c); err != nil {
  106. c.Error(err)
  107. }
  108. stop := time.Now()
  109. buf := config.pool.Get().(*bytes.Buffer)
  110. buf.Reset()
  111. defer config.pool.Put(buf)
  112. if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
  113. switch tag {
  114. case "time_unix":
  115. return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
  116. case "time_unix_nano":
  117. return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
  118. case "time_rfc3339":
  119. return buf.WriteString(time.Now().Format(time.RFC3339))
  120. case "time_rfc3339_nano":
  121. return buf.WriteString(time.Now().Format(time.RFC3339Nano))
  122. case "time_custom":
  123. return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
  124. case "id":
  125. id := req.Header.Get(echo.HeaderXRequestID)
  126. if id == "" {
  127. id = res.Header().Get(echo.HeaderXRequestID)
  128. }
  129. return buf.WriteString(id)
  130. case "remote_ip":
  131. return buf.WriteString(c.RealIP())
  132. case "host":
  133. return buf.WriteString(req.Host)
  134. case "uri":
  135. return buf.WriteString(req.RequestURI)
  136. case "method":
  137. return buf.WriteString(req.Method)
  138. case "path":
  139. p := req.URL.Path
  140. if p == "" {
  141. p = "/"
  142. }
  143. return buf.WriteString(p)
  144. case "protocol":
  145. return buf.WriteString(req.Proto)
  146. case "referer":
  147. return buf.WriteString(req.Referer())
  148. case "user_agent":
  149. return buf.WriteString(req.UserAgent())
  150. case "status":
  151. n := res.Status
  152. s := config.colorer.Green(n)
  153. switch {
  154. case n >= 500:
  155. s = config.colorer.Red(n)
  156. case n >= 400:
  157. s = config.colorer.Yellow(n)
  158. case n >= 300:
  159. s = config.colorer.Cyan(n)
  160. }
  161. return buf.WriteString(s)
  162. case "error":
  163. if err != nil {
  164. return buf.WriteString(err.Error())
  165. }
  166. case "latency":
  167. l := stop.Sub(start)
  168. return buf.WriteString(strconv.FormatInt(int64(l), 10))
  169. case "latency_human":
  170. return buf.WriteString(stop.Sub(start).String())
  171. case "bytes_in":
  172. cl := req.Header.Get(echo.HeaderContentLength)
  173. if cl == "" {
  174. cl = "0"
  175. }
  176. return buf.WriteString(cl)
  177. case "bytes_out":
  178. return buf.WriteString(strconv.FormatInt(res.Size, 10))
  179. default:
  180. switch {
  181. case strings.HasPrefix(tag, "header:"):
  182. return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
  183. case strings.HasPrefix(tag, "query:"):
  184. return buf.Write([]byte(c.QueryParam(tag[6:])))
  185. case strings.HasPrefix(tag, "form:"):
  186. return buf.Write([]byte(c.FormValue(tag[5:])))
  187. case strings.HasPrefix(tag, "cookie:"):
  188. cookie, err := c.Cookie(tag[7:])
  189. if err == nil {
  190. return buf.Write([]byte(cookie.Value))
  191. }
  192. }
  193. }
  194. return 0, nil
  195. }); err != nil {
  196. return
  197. }
  198. _, err = config.Output.Write(buf.Bytes())
  199. return
  200. }
  201. }
  202. }