a58a2f9bf3
Add a function `ForceConsoleColor`, like `DisableConsoleColor` but to force coloring the output. It usefull when some IDE's integrated console (like IntelliJ or Goland) are not detected as TTY, but can display colors. Also helps if one want to output color in log file (#1590) and as a workaround for #1547.
238 lines
5.9 KiB
Go
238 lines
5.9 KiB
Go
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
// Use of this source code is governed by a MIT style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gin
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/mattn/go-isatty"
|
|
)
|
|
|
|
var (
|
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
|
yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109})
|
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
|
reset = string([]byte{27, 91, 48, 109})
|
|
disableColor = false
|
|
forceColor = false
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// defaultLogFormatter is the default log format function Logger middleware uses.
|
|
var defaultLogFormatter = func(param LogFormatterParams) string {
|
|
var statusColor, methodColor, resetColor string
|
|
if param.IsTerm {
|
|
statusColor = colorForStatus(param.StatusCode)
|
|
methodColor = colorForMethod(param.Method)
|
|
resetColor = reset
|
|
}
|
|
|
|
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\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,
|
|
)
|
|
}
|
|
|
|
// DisableConsoleColor disables color output in the console.
|
|
func DisableConsoleColor() {
|
|
disableColor = true
|
|
}
|
|
|
|
// ForceConsoleColor force color output in the console.
|
|
func ForceConsoleColor() {
|
|
forceColor = true
|
|
}
|
|
|
|
// ErrorLogger returns a handlerfunc for any error type.
|
|
func ErrorLogger() HandlerFunc {
|
|
return ErrorLoggerT(ErrorTypeAny)
|
|
}
|
|
|
|
// ErrorLoggerT returns a handlerfunc for a given error type.
|
|
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
|
return func(c *Context) {
|
|
c.Next()
|
|
errors := c.Errors.ByType(typ)
|
|
if len(errors) > 0 {
|
|
c.JSON(-1, errors)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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{})
|
|
}
|
|
|
|
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
|
|
func LoggerWithFormatter(f LogFormatter) HandlerFunc {
|
|
return LoggerWithConfig(LoggerConfig{
|
|
Formatter: f,
|
|
})
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// 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
|
|
|
|
isTerm := true
|
|
|
|
if w, ok := out.(*os.File); (!ok ||
|
|
(os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) ||
|
|
disableColor) && !forceColor {
|
|
isTerm = false
|
|
}
|
|
|
|
var skip map[string]struct{}
|
|
|
|
if length := len(notlogged); length > 0 {
|
|
skip = make(map[string]struct{}, length)
|
|
|
|
for _, path := range notlogged {
|
|
skip[path] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return func(c *Context) {
|
|
// Start timer
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
raw := c.Request.URL.RawQuery
|
|
|
|
// Process request
|
|
c.Next()
|
|
|
|
// Log only when path is not being skipped
|
|
if _, ok := skip[path]; !ok {
|
|
param := LogFormatterParams{
|
|
Request: c.Request,
|
|
IsTerm: isTerm,
|
|
}
|
|
|
|
// 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()
|
|
|
|
if raw != "" {
|
|
path = path + "?" + raw
|
|
}
|
|
|
|
param.Path = path
|
|
|
|
fmt.Fprint(out, formatter(param))
|
|
}
|
|
}
|
|
}
|
|
|
|
func colorForStatus(code int) string {
|
|
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
|
|
}
|
|
}
|
|
|
|
func colorForMethod(method string) string {
|
|
switch method {
|
|
case "GET":
|
|
return blue
|
|
case "POST":
|
|
return cyan
|
|
case "PUT":
|
|
return yellow
|
|
case "DELETE":
|
|
return red
|
|
case "PATCH":
|
|
return green
|
|
case "HEAD":
|
|
return magenta
|
|
case "OPTIONS":
|
|
return white
|
|
default:
|
|
return reset
|
|
}
|
|
}
|