使用Go Gin框架记录JSON日志

10
我正在使用 Go Gin 搭建一个小型 API,但我无法让记录器输出 JSON 格式。默认情况下,它是键/值对字符串格式,但我需要它为 JSON 格式。
我该如何实现?感觉应该很容易支持,但我一直在尝试使用自定义格式化函数来解决不同参数的问题。
另外,我也使用 uber/zap 记录器进行手动日志记录,但我还没有找到一种方法来用我的记录器替换 gin 的记录器。
如果有任何指针或建议,将不胜感激,因为 gin 在 github 上的文档并不太有帮助。
编辑:为了澄清,添加一个中间件可以帮助记录请求,但我正在寻找设置 Gin 的 JSON 记录的单个点(例如:包括与请求无关的日志记录,如框架内部的调试/信息日志)。
3个回答

14

访问日志记录器

https://github.com/sbecker/gin-api-demo/blob/master/middleware/json_logger.go


// JSONLogMiddleware logs a gin HTTP request in JSON format, with some additional custom key/values
func JSONLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Start timer
        start := time.Now()

        // Process Request
        c.Next()

        // Stop timer
        duration := util.GetDurationInMillseconds(start)

        entry := log.WithFields(log.Fields{
            "client_ip":  util.GetClientIP(c),
            "duration":   duration,
            "method":     c.Request.Method,
            "path":       c.Request.RequestURI,
            "status":     c.Writer.Status(),
            "user_id":    util.GetUserID(c),
            "referrer":   c.Request.Referer(),
            "request_id": c.Writer.Header().Get("Request-Id"),
            // "api_version": util.ApiVersion,
        })

        if c.Writer.Status() >= 500 {
            entry.Error(c.Errors.String())
        } else {
            entry.Info("")
        }
    }
}

调试日志记录器

查看gin源代码,发现调试日志输出到io.Writer。重写该对象将输出重定向为JSON格式,类似于处理http.Server.Errorlog的输出方法。

func debugPrint(format string, values ...interface{}) {
    if IsDebugging() {
        if !strings.HasSuffix(format, "\n") {
            format += "\n"
        }
        fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
    }
}

设置调试输出,此代码未经过测试。

// WriteFunc convert func to io.Writer.
type WriteFunc func([]byte) (int, error)
func (fn WriteFunc) Write(data []byte) (int, error) {
    return fn(data)
}

func NewLogrusWrite() io.Writer {
    return WriteFunc(func(data []byte) (int, error) {
        logrus.Debugf("%s", data)
        return 0, nil
    })
}
// set gin write to logrus debug.
gin.DefaultWriter = NewLogrusWrite()

获取所有http.Server的错误日志。

htt.Server

http.Server将日志输出到log.Logger,并创建一个由指定io.Writer接收来自http.Server的错误日志的log.Logger输出。如果需要使用自定义Server的代码,请查阅gin文档以获取详细信息。

srv := &http.Server{
    // log level is bebug, please create a error level io.Writer
    ErrorLog: log.New(NewLogrusWrite(), "", 0),
} 


谢谢回复,但这不是我需要的。我尝试了这个中间件,它可以用于记录请求,但我希望所有日志都是json格式的(例如包括框架的调试日志、其他根据级别记录的内部日志等)。 - Andrei Dascalu
立即更新答案。 - eudore
为什么gin默认情况下没有支持这个功能呢,@eudore? - uberrebu
@uberrebu 可能只设计了路由功能,没有日志设计。 - eudore

6

除了@audore的答案之外; 他的方法将更改默认输出,即默认情况下为stdout。我们仍然希望使用stdout,但如果我们想要更改整个输出,我们应该执行以下步骤。

1- 将gin初始化从

r := gin.Default()

to

r := gin.New()
r.Use(gin.Recovery()) // to recover gin automatically
r.Use(jsonLoggerMiddleware()) // we'll define it later

因为gin.Default()使用了默认的Logger()中间件。如果我们继续使用它,gin会输出两次。

2- 将中间件添加到您的应用程序中。

func jsonLoggerMiddleware() gin.HandlerFunc {
    return gin.LoggerWithFormatter(
        func(params gin.LogFormatterParams) string {
            log := make(map[string]interface{})

            log["status_code"] = params.StatusCode
            log["path"] = params.Path
            log["method"] = params.Method
            log["start_time"] = params.TimeStamp.Format("2006/01/02 - 15:04:05")
            log["remote_addr"] = params.ClientIP
            log["response_time"] = params.Latency.String()

            s, _ := json.Marshal(log)
            return string(s) + "\n"
        },
    )
}

你的输出将是:

{"method":"GET","path":"/v1/somepath","remote_addr":"::1","response_time":"5.719ms","start_time":"2022/10/03 - 17:26:11","status_code":200}

从像Loki+Grafana这样的日志侦听器中解析将变得非常容易。


0
对于 go > 1.21,我们有 log/slog 包,可以帮助进行结构化日志记录。
因此,目前 gin 的最佳 slog 选项是:samber/slog-gin 包。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接