Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com> Co-authored-by: thinkerou <thinkerou@gmail.com>
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 a 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 does 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]interface{} |
|
79 |
}
|
|
80 |
|
|
81 |
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
|
82 |
func (p *LogFormatterParams) StatusCodeColor() string { |
|
83 | 18 |
code := p.StatusCode |
84 |
|
|
85 | 18 |
switch { |
86 | 18 |
case code >= http.StatusOK && code < http.StatusMultipleChoices: |
87 | 18 |
return green |
88 | 18 |
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: |
89 | 18 |
return white |
90 | 18 |
case code >= http.StatusBadRequest && code < http.StatusInternalServerError: |
91 | 18 |
return yellow |
92 | 18 |
default: |
93 | 18 |
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 | 18 |
method := p.Method |
100 |
|
|
101 | 18 |
switch method { |
102 | 18 |
case http.MethodGet: |
103 | 18 |
return blue |
104 | 18 |
case http.MethodPost: |
105 | 18 |
return cyan |
106 | 18 |
case http.MethodPut: |
107 | 18 |
return yellow |
108 | 18 |
case http.MethodDelete: |
109 | 18 |
return red |
110 | 18 |
case http.MethodPatch: |
111 | 18 |
return green |
112 | 18 |
case http.MethodHead: |
113 | 18 |
return magenta |
114 | 18 |
case http.MethodOptions: |
115 | 18 |
return white |
116 | 18 |
default: |
117 | 18 |
return reset |
118 |
}
|
|
119 |
}
|
|
120 |
|
|
121 |
// ResetColor resets all escape attributes.
|
|
122 |
func (p *LogFormatterParams) ResetColor() string { |
|
123 | 18 |
return reset |
124 |
}
|
|
125 |
|
|
126 |
// IsOutputColor indicates whether can colors be outputted to the log.
|
|
127 |
func (p *LogFormatterParams) IsOutputColor() bool { |
|
128 | 18 |
return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) |
129 |
}
|
|
130 |
|
|
131 |
// defaultLogFormatter is the default log format function Logger middleware uses.
|
|
132 | 18 |
var defaultLogFormatter = func(param LogFormatterParams) string { |
133 | 18 |
var statusColor, methodColor, resetColor string |
134 |
if param.IsOutputColor() { |
|
135 | 18 |
statusColor = param.StatusCodeColor() |
136 | 18 |
methodColor = param.MethodColor() |
137 | 18 |
resetColor = param.ResetColor() |
138 |
}
|
|
139 |
|
|
140 |
if param.Latency > time.Minute { |
|
141 |
// Truncate in a golang < 1.8 safe way
|
|
142 | 18 |
param.Latency = param.Latency - param.Latency%time.Second |
143 |
}
|
|
144 | 18 |
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", |
145 | 18 |
param.TimeStamp.Format("2006/01/02 - 15:04:05"), |
146 | 18 |
statusColor, param.StatusCode, resetColor, |
147 | 18 |
param.Latency, |
148 | 18 |
param.ClientIP, |
149 | 18 |
methodColor, param.Method, resetColor, |
150 | 18 |
param.Path, |
151 | 18 |
param.ErrorMessage, |
152 | 18 |
)
|
153 |
}
|
|
154 |
|
|
155 |
// DisableConsoleColor disables color output in the console.
|
|
156 |
func DisableConsoleColor() { |
|
157 | 18 |
consoleColorMode = disableColor |
158 |
}
|
|
159 |
|
|
160 |
// ForceConsoleColor force color output in the console.
|
|
161 |
func ForceConsoleColor() { |
|
162 | 18 |
consoleColorMode = forceColor |
163 |
}
|
|
164 |
|
|
165 |
// ErrorLogger returns a handlerfunc for any error type.
|
|
166 |
func ErrorLogger() HandlerFunc { |
|
167 | 18 |
return ErrorLoggerT(ErrorTypeAny) |
168 |
}
|
|
169 |
|
|
170 |
// ErrorLoggerT returns a handlerfunc for a given error type.
|
|
171 |
func ErrorLoggerT(typ ErrorType) HandlerFunc { |
|
172 |
return func(c *Context) { |
|
173 | 18 |
c.Next() |
174 | 18 |
errors := c.Errors.ByType(typ) |
175 | 18 |
if len(errors) > 0 { |
176 | 18 |
c.JSON(-1, errors) |
177 |
}
|
|
178 |
}
|
|
179 |
}
|
|
180 |
|
|
181 |
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
|
|
182 |
// By default gin.DefaultWriter = os.Stdout.
|
|
183 |
func Logger() HandlerFunc { |
|
184 | 18 |
return LoggerWithConfig(LoggerConfig{}) |
185 |
}
|
|
186 |
|
|
187 |
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
|
|
188 |
func LoggerWithFormatter(f LogFormatter) HandlerFunc { |
|
189 | 18 |
return LoggerWithConfig(LoggerConfig{ |
190 | 18 |
Formatter: f, |
191 | 18 |
})
|
192 |
}
|
|
193 |
|
|
194 |
// LoggerWithWriter instance a Logger middleware with the specified writer buffer.
|
|
195 |
// Example: os.Stdout, a file opened in write mode, a socket...
|
|
196 |
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { |
|
197 | 18 |
return LoggerWithConfig(LoggerConfig{ |
198 | 18 |
Output: out, |
199 | 18 |
SkipPaths: notlogged, |
200 | 18 |
})
|
201 |
}
|
|
202 |
|
|
203 |
// LoggerWithConfig instance a Logger middleware with config.
|
|
204 |
func LoggerWithConfig(conf LoggerConfig) HandlerFunc { |
|
205 | 18 |
formatter := conf.Formatter |
206 |
if formatter == nil { |
|
207 | 18 |
formatter = defaultLogFormatter |
208 |
}
|
|
209 |
|
|
210 | 18 |
out := conf.Output |
211 |
if out == nil { |
|
212 | 18 |
out = DefaultWriter |
213 |
}
|
|
214 |
|
|
215 | 18 |
notlogged := conf.SkipPaths |
216 |
|
|
217 | 18 |
isTerm := true |
218 |
|
|
219 | 18 |
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || |
220 | 18 |
(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { |
221 | 18 |
isTerm = false |
222 |
}
|
|
223 |
|
|
224 | 18 |
var skip map[string]struct{} |
225 |
|
|
226 |
if length := len(notlogged); length > 0 { |
|
227 | 18 |
skip = make(map[string]struct{}, length) |
228 |
|
|
229 |
for _, path := range notlogged { |
|
230 | 18 |
skip[path] = struct{}{} |
231 |
}
|
|
232 |
}
|
|
233 |
|
|
234 |
return func(c *Context) { |
|
235 |
// Start timer
|
|
236 | 18 |
start := time.Now() |
237 | 18 |
path := c.Request.URL.Path |
238 | 18 |
raw := c.Request.URL.RawQuery |
239 |
|
|
240 |
// Process request
|
|
241 | 18 |
c.Next() |
242 |
|
|
243 |
// Log only when path is not being skipped
|
|
244 |
if _, ok := skip[path]; !ok { |
|
245 | 18 |
param := LogFormatterParams{ |
246 | 18 |
Request: c.Request, |
247 | 18 |
isTerm: isTerm, |
248 | 18 |
Keys: c.Keys, |
249 |
}
|
|
250 |
|
|
251 |
// Stop timer
|
|
252 | 18 |
param.TimeStamp = time.Now() |
253 | 18 |
param.Latency = param.TimeStamp.Sub(start) |
254 |
|
|
255 | 18 |
param.ClientIP = c.ClientIP() |
256 | 18 |
param.Method = c.Request.Method |
257 | 18 |
param.StatusCode = c.Writer.Status() |
258 | 18 |
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() |
259 |
|
|
260 | 18 |
param.BodySize = c.Writer.Size() |
261 |
|
|
262 |
if raw != "" { |
|
263 | 18 |
path = path + "?" + raw |
264 |
}
|
|
265 |
|
|
266 | 18 |
param.Path = path |
267 |
|
|
268 | 18 |
fmt.Fprint(out, formatter(param)) |
269 |
}
|
|
270 |
}
|
|
271 |
}
|
Read our documentation on viewing source code .