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!