logger.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // Copyright 2014 Manu Martinez-Almeida. All rights reserved.
  2. // Use of this source code is governed by a MIT style
  3. // license that can be found in the LICENSE file.
  4. package gin
  5. import (
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "time"
  11. "github.com/mattn/go-isatty"
  12. )
  13. type consoleColorModeValue int
  14. const (
  15. autoColor consoleColorModeValue = iota
  16. disableColor
  17. forceColor
  18. )
  19. const (
  20. green = "\033[97;42m"
  21. white = "\033[90;47m"
  22. yellow = "\033[90;43m"
  23. red = "\033[97;41m"
  24. blue = "\033[97;44m"
  25. magenta = "\033[97;45m"
  26. cyan = "\033[97;46m"
  27. reset = "\033[0m"
  28. )
  29. var consoleColorMode = autoColor
  30. // LoggerConfig defines the config for Logger middleware.
  31. type LoggerConfig struct {
  32. // Optional. Default value is gin.defaultLogFormatter
  33. Formatter LogFormatter
  34. // Output is a writer where logs are written.
  35. // Optional. Default value is gin.DefaultWriter.
  36. Output io.Writer
  37. // SkipPaths is an url path array which logs are not written.
  38. // Optional.
  39. SkipPaths []string
  40. // Skip is a Skipper that indicates which logs should not be written.
  41. // Optional.
  42. Skip Skipper
  43. }
  44. // Skipper is a function to skip logs based on provided Context
  45. type Skipper func(c *Context) bool
  46. // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
  47. type LogFormatter func(params LogFormatterParams) string
  48. // LogFormatterParams is the structure any formatter will be handed when time to log comes
  49. type LogFormatterParams struct {
  50. Request *http.Request
  51. // TimeStamp shows the time after the server returns a response.
  52. TimeStamp time.Time
  53. // StatusCode is HTTP response code.
  54. StatusCode int
  55. // Latency is how much time the server cost to process a certain request.
  56. Latency time.Duration
  57. // ClientIP equals Context's ClientIP method.
  58. ClientIP string
  59. // Method is the HTTP method given to the request.
  60. Method string
  61. // Path is a path the client requests.
  62. Path string
  63. // ErrorMessage is set if error has occurred in processing the request.
  64. ErrorMessage string
  65. // isTerm shows whether gin's output descriptor refers to a terminal.
  66. isTerm bool
  67. // BodySize is the size of the Response Body
  68. BodySize int
  69. // Keys are the keys set on the request's context.
  70. Keys map[string]any
  71. }
  72. // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
  73. func (p *LogFormatterParams) StatusCodeColor() string {
  74. code := p.StatusCode
  75. switch {
  76. case code >= http.StatusContinue && code < http.StatusOK:
  77. return white
  78. case code >= http.StatusOK && code < http.StatusMultipleChoices:
  79. return green
  80. case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
  81. return white
  82. case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
  83. return yellow
  84. default:
  85. return red
  86. }
  87. }
  88. // MethodColor is the ANSI color for appropriately logging http method to a terminal.
  89. func (p *LogFormatterParams) MethodColor() string {
  90. method := p.Method
  91. switch method {
  92. case http.MethodGet:
  93. return blue
  94. case http.MethodPost:
  95. return cyan
  96. case http.MethodPut:
  97. return yellow
  98. case http.MethodDelete:
  99. return red
  100. case http.MethodPatch:
  101. return green
  102. case http.MethodHead:
  103. return magenta
  104. case http.MethodOptions:
  105. return white
  106. default:
  107. return reset
  108. }
  109. }
  110. // ResetColor resets all escape attributes.
  111. func (p *LogFormatterParams) ResetColor() string {
  112. return reset
  113. }
  114. // IsOutputColor indicates whether can colors be outputted to the log.
  115. func (p *LogFormatterParams) IsOutputColor() bool {
  116. return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
  117. }
  118. // defaultLogFormatter is the default log format function Logger middleware uses.
  119. var defaultLogFormatter = func(param LogFormatterParams) string {
  120. var statusColor, methodColor, resetColor string
  121. if param.IsOutputColor() {
  122. statusColor = param.StatusCodeColor()
  123. methodColor = param.MethodColor()
  124. resetColor = param.ResetColor()
  125. }
  126. if param.Latency > time.Minute {
  127. param.Latency = param.Latency.Truncate(time.Second)
  128. }
  129. return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
  130. param.TimeStamp.Format("2006/01/02 - 15:04:05"),
  131. statusColor, param.StatusCode, resetColor,
  132. param.Latency,
  133. param.ClientIP,
  134. methodColor, param.Method, resetColor,
  135. param.Path,
  136. param.ErrorMessage,
  137. )
  138. }
  139. // DisableConsoleColor disables color output in the console.
  140. func DisableConsoleColor() {
  141. consoleColorMode = disableColor
  142. }
  143. // ForceConsoleColor force color output in the console.
  144. func ForceConsoleColor() {
  145. consoleColorMode = forceColor
  146. }
  147. // ErrorLogger returns a HandlerFunc for any error type.
  148. func ErrorLogger() HandlerFunc {
  149. return ErrorLoggerT(ErrorTypeAny)
  150. }
  151. // ErrorLoggerT returns a HandlerFunc for a given error type.
  152. func ErrorLoggerT(typ ErrorType) HandlerFunc {
  153. return func(c *Context) {
  154. c.Next()
  155. errors := c.Errors.ByType(typ)
  156. if len(errors) > 0 {
  157. c.JSON(-1, errors)
  158. }
  159. }
  160. }
  161. // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
  162. // By default, gin.DefaultWriter = os.Stdout.
  163. func Logger() HandlerFunc {
  164. return LoggerWithConfig(LoggerConfig{})
  165. }
  166. // LoggerWithFormatter instance a Logger middleware with the specified log format function.
  167. func LoggerWithFormatter(f LogFormatter) HandlerFunc {
  168. return LoggerWithConfig(LoggerConfig{
  169. Formatter: f,
  170. })
  171. }
  172. // LoggerWithWriter instance a Logger middleware with the specified writer buffer.
  173. // Example: os.Stdout, a file opened in write mode, a socket...
  174. func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
  175. return LoggerWithConfig(LoggerConfig{
  176. Output: out,
  177. SkipPaths: notlogged,
  178. })
  179. }
  180. // LoggerWithConfig instance a Logger middleware with config.
  181. func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
  182. formatter := conf.Formatter
  183. if formatter == nil {
  184. formatter = defaultLogFormatter
  185. }
  186. out := conf.Output
  187. if out == nil {
  188. out = DefaultWriter
  189. }
  190. notlogged := conf.SkipPaths
  191. isTerm := true
  192. if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
  193. (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
  194. isTerm = false
  195. }
  196. var skip map[string]struct{}
  197. if length := len(notlogged); length > 0 {
  198. skip = make(map[string]struct{}, length)
  199. for _, path := range notlogged {
  200. skip[path] = struct{}{}
  201. }
  202. }
  203. return func(c *Context) {
  204. // Start timer
  205. start := time.Now()
  206. path := c.Request.URL.Path
  207. raw := c.Request.URL.RawQuery
  208. // Process request
  209. c.Next()
  210. // Log only when it is not being skipped
  211. if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {
  212. return
  213. }
  214. param := LogFormatterParams{
  215. Request: c.Request,
  216. isTerm: isTerm,
  217. Keys: c.Keys,
  218. }
  219. // Stop timer
  220. param.TimeStamp = time.Now()
  221. param.Latency = param.TimeStamp.Sub(start)
  222. param.ClientIP = c.ClientIP()
  223. param.Method = c.Request.Method
  224. param.StatusCode = c.Writer.Status()
  225. param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
  226. param.BodySize = c.Writer.Size()
  227. if raw != "" {
  228. path = path + "?" + raw
  229. }
  230. param.Path = path
  231. fmt.Fprint(out, formatter(param))
  232. }
  233. }