Gin源码分析 – 中间件 – Logger
1 介绍
本文将对Gin的内置中间件Logger进行分析,Gin在创建默认的Enginer时将添加到全局中间件中,代码如下。
// Default returns an Engine instance // with the Logger and Recovery middleware already attached. func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine }
2 Logger
有四种创建Logger中间件的形式,分别是:
(1)func Logger() HandlerFunc,这个是最基本的形式,代码如下。在内部使用LoggerWithConfig使用默认的配置创建中间件,通过注释可以看出,通过该方法创建的中间件将日志写到gin.DefaultWriter,默认情况下是os.Stdout。
// Logger instances a Logger middleware // that will write the logs to gin.DefaultWriter. // By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { return LoggerWithConfig(LoggerConfig{}) }
(2)func LoggerWithFormatter(f LogFormatter) HandlerFunc,代码如下,同样使用LoggerWithConfig完成中间件的创建,使用用户提供的格式记录日志。
// LoggerWithFormatter instance a Logger middleware // with the specified log format function. func LoggerWithFormatter(f LogFormatter) HandlerFunc { return LoggerWithConfig(LoggerConfig{ Formatter: f, }) }
(3)func LoggerWithWriter(out io.Writer, notlogged …string) HandlerFunc,代码如下,将日志就到特定的Writer中,同时忽略相应的路径。
// LoggerWithWriter instance a Logger middleware // with the specified writer buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { return LoggerWithConfig(LoggerConfig{ Output: out, SkipPaths: notlogged, }) }
(4)func LoggerWithConfig(conf LoggerConfig) HandlerFunc,这个函数可以使用更加详细的配置用于创建中间件,是本中间件中最复杂的一个函数,放到最后在进行分析。
3 基础数据结构
3.1 LoggerConfig
// LoggerConfig defines the config for Logger middleware. type LoggerConfig struct { // Optional. Default value is gin.defaultLogFormatter Formatter LogFormatter // Output is a writer where logs are written. // Optional. Default value is gin.DefaultWriter. Output io.Writer // SkipPaths is a url path array which logs are not written. // Optional. SkipPaths []string }
(1)Formatter,日志输出格式,默认值是gin.defaultLogFormatter;
(2)Output,日志输出器,默认是gin.DefaultWriter,在文件mode.go中,代码如下:
// DefaultWriter is the default io.Writer used by Gin for debug output // and middleware output like Logger() or Recovery(). // Note that both Logger and Recovery provides // custom ways to configure their // output io.Writer. // To support coloring in Windows use: // import "github.com/mattn/go-colorable" // gin.DefaultWriter = colorable.NewColorableStdout() var DefaultWriter io.Writer = os.Stdout
(3)SkipPaths,是一个忽略就的路径数组,默认是空。
LoggerConfig的创建在函数LoggerWithConfig的开始部分,参加如下代码:
// LoggerWithConfig instance a Logger middleware with config. func LoggerWithConfig(conf LoggerConfig) HandlerFunc { // 如果为空,则formatter设置为defaultLogFormatter formatter := conf.Formatter if formatter == nil { formatter = defaultLogFormatter } // 如果为空, 则out设置为DefaultWriter out := conf.Output if out == nil { out = DefaultWriter } notlogged := conf.SkipPaths
3.2 LogFormatter
日式格式化器是一个方法,方法的定义如下:
// LogFormatter gives the signature of the formatter function // passed to LoggerWithFormatter type LogFormatter func(params LogFormatterParams) string
将各种参数进行格式化后形成最终的日志字符串,格式化参数是一个相对复杂的结构体,其中保存了如下记录的各种参数,定义如下。
// LogFormatterParams is the structure any formatter // will be handed when time to log comes type LogFormatterParams struct { Request *http.Request // TimeStamp shows the time after the server returns a response. TimeStamp time.Time // StatusCode is HTTP response code. StatusCode int // Latency is how much time the server cost to process a certain request. Latency time.Duration // ClientIP equals Context's ClientIP method. ClientIP string // Method is the HTTP method given to the request. Method string // Path is a path the client requests. Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string // isTerm shows whether does gin's output descriptor // refers to a terminal. isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. Keys map[string]interface{} }
- Request,HTTP的请求体;
- TimeStame,返回响应体Response的时间;
- StatusCode,返回的HTTP状态码;
- Latency,处理HTTP请求的用时;
- ClientIP,客户端IP;
- Method,HTTP请求方法;
- Path,请求路径;
- ErrorMessage,错误信息;
- isTerm,是否是将日志输出到终端;
- BodySize,响应体的大小;
- 请求上下文中的元数据。
下面介绍一些进行格式化日志的辅助函数。
(1)StatusCodeColor,根据HTTP响应码设置相应的显示颜色。
// StatusCodeColor is the ANSI color for appropriately // logging http status code to a terminal. func (p *LogFormatterParams) StatusCodeColor() string { code := p.StatusCode switch { case code >= http.StatusOK && code < http.StatusMultipleChoices: return green case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: return white case code >= http.StatusBadRequest && code < http.StatusInternalServerError: return yellow default: return red } }
该中间件中定义了一组常用的颜色,代码如下:
const ( green = "\033[97;42m" white = "\033[90;47m" yellow = "\033[90;43m" red = "\033[97;41m" blue = "\033[97;44m" magenta = "\033[97;45m" cyan = "\033[97;46m" reset = "\033[0m" )
(2)MethodColor,根据HTTP请求类型设置响应的显示颜色。
// MethodColor is the ANSI color for appropriately // logging http method to a terminal. func (p *LogFormatterParams) MethodColor() string { method := p.Method switch method { case http.MethodGet: return blue case http.MethodPost: return cyan case http.MethodPut: return yellow case http.MethodDelete: return red case http.MethodPatch: return green case http.MethodHead: return magenta case http.MethodOptions: return white default: return reset } }
(3)颜色控制方法
- RestColor,重新颜色;
- IsOutputColor,判断是否能够已有色模式进行输出,有自动颜色输出模式(autoColor)和强制颜色输出模式(autoColor),在自动模式下如果输出终端是控制台则进行有颜色输出,否则无颜色。
- DisableConsoleColor,禁止控制台输出颜色
- ForceConsoleColor,控制台输出颜色
type consoleColorModeValue int const ( autoColor consoleColorModeValue = iota disableColor forceColor ) var consoleColorMode = autoColor // ResetColor resets all escape attributes. func (p *LogFormatterParams) ResetColor() string { return reset } // IsOutputColor indicates whether can colors be outputted to the log. func (p *LogFormatterParams) IsOutputColor() bool { return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) } // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { consoleColorMode = forceColor }
3.3 defaultLogFormatter
下面开始分析defaultLogFormatter,该方法提供了默认的日志格式化操作,具体代码如下:
// defaultLogFormatter is the default log format function // Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { // 设置各种颜色控制 var statusColor, methodColor, resetColor string if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() } // 如何大于1分钟,则将时间换算为整秒 if param.Latency > time.Minute { // Truncate in a golang < 1.8 safe way param.Latency = param.Latency - param.Latency%time.Second } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, param.Latency, param.ClientIP, methodColor, param.Method, resetColor, param.Path, param.ErrorMessage, ) }
4 LoggerWithConfig
完成上述的各种铺垫后让我们开始分析LoggerWithConfig方法。
(1)这部分已经分析过了,主要是获取Formatter,Output和SkipPaths。
// LoggerWithConfig instance a Logger middleware with config. func LoggerWithConfig(conf LoggerConfig) HandlerFunc { formatter := conf.Formatter if formatter == nil { formatter = defaultLogFormatter } out := conf.Output if out == nil { out = DefaultWriter } notlogged := conf.SkipPaths
(2)这部分主要是判读当前的输出是否是终端。
isTerm := true if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false }
(3)将SkipPaths保存到map中,方便后续的过滤,把过滤的Path作为map的key进行存储。
var skip map[string]struct{} if length := len(notlogged); length > 0 { skip = make(map[string]struct{}, length) for _, path := range notlogged { skip[path] = struct{}{} } }
(4)后面的具体的中间件处理函数,首先是获取开始时间,请求路径和请求参数
return func(c *Context) { // Start timer start := time.Now() path := c.Request.URL.Path raw := c.Request.URL.RawQuery // Process request c.Next()
(5)计算处理时间,获取请求的路径,然后生成日志字符串,并输出到out中。
// Log only when path is not being skipped if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, isTerm: isTerm, Keys: c.Keys, } // Stop timer param.TimeStamp = time.Now() param.Latency = param.TimeStamp.Sub(start) param.ClientIP = c.ClientIP() param.Method = c.Request.Method param.StatusCode = c.Writer.Status() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() param.BodySize = c.Writer.Size() if raw != "" { path = path + "?" + raw } param.Path = path fmt.Fprint(out, formatter(param)) }
5 总结
本文将逐行的对该中间件的实现进行分析,Logger中间件在logger.go文件中实现,一共272行非常适合新手学习。