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