...
1
2
3
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
39 type LoggerConfig struct {
40
41 Formatter LogFormatter
42
43
44
45 Output io.Writer
46
47
48
49 SkipPaths []string
50 }
51
52
53 type LogFormatter func(params LogFormatterParams) string
54
55
56 type LogFormatterParams struct {
57 Request *http.Request
58
59
60 TimeStamp time.Time
61
62 StatusCode int
63
64 Latency time.Duration
65
66 ClientIP string
67
68 Method string
69
70 Path string
71
72 ErrorMessage string
73
74 isTerm bool
75
76 BodySize int
77
78 Keys map[string]any
79 }
80
81
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
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
122 func (p *LogFormatterParams) ResetColor() string {
123 return reset
124 }
125
126
127 func (p *LogFormatterParams) IsOutputColor() bool {
128 return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
129 }
130
131
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
155 func DisableConsoleColor() {
156 consoleColorMode = disableColor
157 }
158
159
160 func ForceConsoleColor() {
161 consoleColorMode = forceColor
162 }
163
164
165 func ErrorLogger() HandlerFunc {
166 return ErrorLoggerT(ErrorTypeAny)
167 }
168
169
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
181
182 func Logger() HandlerFunc {
183 return LoggerWithConfig(LoggerConfig{})
184 }
185
186
187 func LoggerWithFormatter(f LogFormatter) HandlerFunc {
188 return LoggerWithConfig(LoggerConfig{
189 Formatter: f,
190 })
191 }
192
193
194
195 func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
196 return LoggerWithConfig(LoggerConfig{
197 Output: out,
198 SkipPaths: notlogged,
199 })
200 }
201
202
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
235 start := time.Now()
236 path := c.Request.URL.Path
237 raw := c.Request.URL.RawQuery
238
239
240 c.Next()
241
242
243 if _, ok := skip[path]; !ok {
244 param := LogFormatterParams{
245 Request: c.Request,
246 isTerm: isTerm,
247 Keys: c.Keys,
248 }
249
250
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 }
271
View as plain text