gin-gonic / gin
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 100
	code := p.StatusCode
84

85 100
	switch {
86 100
	case code >= http.StatusOK && code < http.StatusMultipleChoices:
87 100
		return green
88 100
	case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
89 100
		return white
90 100
	case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
91 100
		return yellow
92 100
	default:
93 100
		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 100
	method := p.Method
100

101 100
	switch method {
102 100
	case http.MethodGet:
103 100
		return blue
104 100
	case http.MethodPost:
105 100
		return cyan
106 100
	case http.MethodPut:
107 100
		return yellow
108 100
	case http.MethodDelete:
109 100
		return red
110 100
	case http.MethodPatch:
111 100
		return green
112 100
	case http.MethodHead:
113 100
		return magenta
114 100
	case http.MethodOptions:
115 100
		return white
116 100
	default:
117 100
		return reset
118
	}
119
}
120

121
// ResetColor resets all escape attributes.
122
func (p *LogFormatterParams) ResetColor() string {
123 100
	return reset
124
}
125

126
// IsOutputColor indicates whether can colors be outputted to the log.
127
func (p *LogFormatterParams) IsOutputColor() bool {
128 100
	return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
129
}
130

131
// defaultLogFormatter is the default log format function Logger middleware uses.
132 100
var defaultLogFormatter = func(param LogFormatterParams) string {
133 100
	var statusColor, methodColor, resetColor string
134 100
	if param.IsOutputColor() {
135 100
		statusColor = param.StatusCodeColor()
136 100
		methodColor = param.MethodColor()
137 100
		resetColor = param.ResetColor()
138
	}
139

140 100
	if param.Latency > time.Minute {
141
		// Truncate in a golang < 1.8 safe way
142 100
		param.Latency = param.Latency - param.Latency%time.Second
143
	}
144 100
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
145 100
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
146 100
		statusColor, param.StatusCode, resetColor,
147 100
		param.Latency,
148 100
		param.ClientIP,
149 100
		methodColor, param.Method, resetColor,
150 100
		param.Path,
151 100
		param.ErrorMessage,
152 100
	)
153
}
154

155
// DisableConsoleColor disables color output in the console.
156
func DisableConsoleColor() {
157 100
	consoleColorMode = disableColor
158
}
159

160
// ForceConsoleColor force color output in the console.
161
func ForceConsoleColor() {
162 100
	consoleColorMode = forceColor
163
}
164

165
// ErrorLogger returns a handlerfunc for any error type.
166
func ErrorLogger() HandlerFunc {
167 100
	return ErrorLoggerT(ErrorTypeAny)
168
}
169

170
// ErrorLoggerT returns a handlerfunc for a given error type.
171
func ErrorLoggerT(typ ErrorType) HandlerFunc {
172 100
	return func(c *Context) {
173 100
		c.Next()
174 100
		errors := c.Errors.ByType(typ)
175 100
		if len(errors) > 0 {
176 100
			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 100
	return LoggerWithConfig(LoggerConfig{})
185
}
186

187
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
188
func LoggerWithFormatter(f LogFormatter) HandlerFunc {
189 100
	return LoggerWithConfig(LoggerConfig{
190 100
		Formatter: f,
191 100
	})
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 100
	return LoggerWithConfig(LoggerConfig{
198 100
		Output:    out,
199 100
		SkipPaths: notlogged,
200 100
	})
201
}
202

203
// LoggerWithConfig instance a Logger middleware with config.
204
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
205 100
	formatter := conf.Formatter
206 100
	if formatter == nil {
207 100
		formatter = defaultLogFormatter
208
	}
209

210 100
	out := conf.Output
211 100
	if out == nil {
212 100
		out = DefaultWriter
213
	}
214

215 100
	notlogged := conf.SkipPaths
216

217 100
	isTerm := true
218

219 100
	if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
220 100
		(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
221 100
		isTerm = false
222
	}
223

224 100
	var skip map[string]struct{}
225

226 100
	if length := len(notlogged); length > 0 {
227 100
		skip = make(map[string]struct{}, length)
228

229
		for _, path := range notlogged {
230 100
			skip[path] = struct{}{}
231
		}
232
	}
233

234 100
	return func(c *Context) {
235
		// Start timer
236 100
		start := time.Now()
237 100
		path := c.Request.URL.Path
238 100
		raw := c.Request.URL.RawQuery
239

240
		// Process request
241 100
		c.Next()
242

243
		// Log only when path is not being skipped
244 100
		if _, ok := skip[path]; !ok {
245 100
			param := LogFormatterParams{
246 100
				Request: c.Request,
247 100
				isTerm:  isTerm,
248 100
				Keys:    c.Keys,
249
			}
250

251
			// Stop timer
252 100
			param.TimeStamp = time.Now()
253 100
			param.Latency = param.TimeStamp.Sub(start)
254

255 100
			param.ClientIP = c.ClientIP()
256 100
			param.Method = c.Request.Method
257 100
			param.StatusCode = c.Writer.Status()
258 100
			param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
259

260 100
			param.BodySize = c.Writer.Size()
261

262 100
			if raw != "" {
263 100
				path = path + "?" + raw
264
			}
265

266 100
			param.Path = path
267

268 100
			fmt.Fprint(out, formatter(param))
269
		}
270
	}
271
}

Read our documentation on viewing source code .

Loading