1

How to customise the logger format of Gin api app ?

I tried to do it with the logrus package, but my problem is that : when I intentionally made 404 or 400 error, there's no error message printing in the console.

Also I want the logger to display response body in the logger.

func requestLoggingMiddleware(logger *logrus.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        // Read the request body
        bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
        c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
        bodyString := string(bodyBytes)

        // Process request
        c.Next()

        // Log request details
        // Include Request error message
        logger.WithFields(logrus.Fields{
            "status":       c.Writer.Status(),
            "method":       c.Request.Method,
            "path":         c.Request.URL.Path,
            "query_params": c.Request.URL.Query(),
            "req_body":         bodyString,
            // "res_error_1":        c.Errors.ByType(gin.ErrorTypePrivate).String(),
            "res_error_2": c.Errors.String(),
        }).Info("Request details")
    }
}

func main() {
    logrus.Info("Starting the application...")

    // 1. Create a new instance of the application.
    app := gin.New()

    // OPTION 1
    logger := logrus.New()
    logger.SetLevel(logrus.InfoLevel)
    app.Use(gin.LoggerWithWriter(logger.Writer()))
    app.Use(requestLoggingMiddleware(logger))
    ...
}

This is what displayed on the console :

INFO[0015] Request details                               body="{\"d\":\"D\"}" error= method=POST path=/ping query_params="map[]" status=404
Jackk-Doe
  • 109
  • 7
  • What you mean by `there's no error message printing in the console`? As based on the code, `requestLoggingMiddleware` will print the log and it will be INFO, because you added as `logger.WithFields(...).Info("Request details")` – PRATHEESH PC Jun 19 '23 at 15:55
  • See this to log the response body in gin - https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin – PRATHEESH PC Jun 19 '23 at 16:06

1 Answers1

0

as suggested by @PRATHEESH PC, I put together a small example based on your needs. The code is divided up into three parts: the middleware, the HTTP handler, and the main package. Let's start with the HTTP handler.

handlers/handlers.go file

package handlers

import (
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

type message struct {
    Name string `json:"name" binding:"required"`
}

func Ping(c *gin.Context) {
    var message message
    if err := c.ShouldBindBodyWith(&message, binding.JSON); err != nil {
        c.JSON(http.StatusBadRequest, err.Error())
        return
    }
    c.JSON(http.StatusOK, message)
}

The only thing to mention here is the usage of the ShouldBindBodyWith method that gives you the possibility to read the request payload multiple times. In fact, the first time it has been called (within the middleware), it copies the request body into the context. The subsequent calls will read the body from there (e.g. the call in the HTTP handler).

middlewares/middlewares.go file

package middlewares

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/sirupsen/logrus"
    log "github.com/sirupsen/logrus"
)

type ginBodyLogger struct {
    // get all the methods implementation from the original one
    // override only the Write() method
    gin.ResponseWriter
    body bytes.Buffer
}

func (g *ginBodyLogger) Write(b []byte) (int, error) {
    g.body.Write(b)
    return g.ResponseWriter.Write(b)
}

func RequestLoggingMiddleware(logger *logrus.Logger) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ginBodyLogger := &ginBodyLogger{
            body:           bytes.Buffer{},
            ResponseWriter: ctx.Writer,
        }
        ctx.Writer = ginBodyLogger
        var req interface{}
        if err := ctx.ShouldBindBodyWith(&req, binding.JSON); err != nil {
            ctx.JSON(http.StatusBadRequest, err.Error())
            return
        }
        data, err := json.Marshal(req)
        if err != nil {
            panic(fmt.Errorf("err while marshaling req msg: %v", err))
        }
        ctx.Next()
        logger.WithFields(log.Fields{
            "status":       ctx.Writer.Status(),
            "method":       ctx.Request.Method,
            "path":         ctx.Request.URL.Path,
            "query_params": ctx.Request.URL.Query(),
            "req_body":     string(data),
            "res_body":     ginBodyLogger.body.String(),
        }).Info("request details")
    }
}

Here, we did three main things.
First, we defined the ginBodyLogger struct that embeds the gin.ResponseWriter struct and adds a bytes.Buffer to record the response payload we care about.
Then, we provided a custom implementation for the method Write that will be called when writing to the response stream. Before writing to it, we'll save the information in the buffer that belongs to the ginBodyLogger struct.
Finally, we trace this information through the logger we provided to the middleware function.

main.go file

package main

import (
    "ginlogger/handlers"
    "ginlogger/middlewares"

    "github.com/gin-gonic/gin"
    "github.com/sirupsen/logrus"
    log "github.com/sirupsen/logrus"
)

var logger *log.Logger

func init() {
    logger = logrus.New()
    logger.SetLevel(log.InfoLevel)
}

func main() {
    gin.SetMode(gin.DebugMode)
    r := gin.Default()

    r.Use(middlewares.RequestLoggingMiddleware(logger))
    r.Use(gin.LoggerWithWriter(logger.Writer()))
    r.POST("/ping", handlers.Ping)

    r.Run(":8000")
}

The main package is in charge of initializing everything we need in our program. It involves two functions: init and main.
Within the init function, we initialize the logger.
Within the main function, we initialize the gin.Engine instance, instrument it, and run.

If you run the code, you should have the logs as desired. If that's not the case, just let me know and I'll come back to you, thanks!

ossan
  • 1,665
  • 4
  • 10