...

Source file src/github.com/gin-gonic/gin/logger.go

Documentation: github.com/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 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  }
   271  

View as plain text