21

I need to log the response body in a middleware of gin, but I don't find how to get the response body. Can anyone help?

I am using a middleware like this:

func Logger() gin.HandlerFunc {

    return func(c *gin.Context) {

        c.Next()

        statusCode := c.Writer.Status()
        if statusCode >= 400 {
            //ok this is an request with error, let's make a record for it
            //log body here
        }
    }
}

My question is, how to get response body from Context in middleware?

John Zeng
  • 1,174
  • 2
  • 9
  • 22
  • if not already, please check the error handling mentioned here https://github.com/gin-gonic/gin/issues/274 – Aravind Jul 21 '16 at 10:31
  • Hmm, I think I get your point, I should append an error to the context and handle it in the middleware, right? But I am still curiosity about how to get the response body in middleware, what if I need to do something on the body before I send it back to client? Or any strange requirement makes me have to get the response body? – John Zeng Jul 22 '16 at 01:00

2 Answers2

47

You need to intercept writing of response and store it somewhere first. Then you can log it. And to do that you need to implement your own Writer intercepting Write() calls.

For example, as follows:

type bodyLogWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
    w.body.Write(b)
    return w.ResponseWriter.Write(b)
}

func ginBodyLogMiddleware(c *gin.Context) {
    blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
    c.Writer = blw
    c.Next()
    statusCode := c.Writer.Status()
    if statusCode >= 400 {
        //ok this is an request with error, let's make a record for it
        // now print body (or log in your preferred way)
        fmt.Println("Response body: " + blw.body.String())
    }
}

Then use this middleware like this:

router.Use(ginBodyLogMiddleware)

Note that this sill won't work for static files as gin does not seem to use c.Writer for them. But in most cases, that's what you want anyway.

If you want to intercept all files, you need to use a slightly more complicated approach. Instead of Middleware, you'll need to implement a wrapper http.Handler that will wrap gin.Engine and will use same approach as shown above to intercept and log whatever is written to http.ResponseWriter. Then run gin server like this:

ginRouter := gin.New()
// configure your middleware and routes as required

// Run http server as follows, where bodyLogHandler is your wrapper handler
http.ListenAndServe(bindAddress, &bodyLogHandler{wrappedHandler: ginRouter}
udondan
  • 57,263
  • 20
  • 190
  • 175
Seva
  • 2,388
  • 10
  • 9
  • Thanks, I get that, the writer can only write, so I can't read it directly. Thnaks. – John Zeng Jul 25 '16 at 02:44
  • Thanks, you know how i get the post parameters too? – André Betiolo Sep 26 '16 at 14:10
  • If you're using middleware approach, you get them inside ginBodyLogMiddleware from gin.Context same way as you get them when processing the request in an actual handler. You obviously can't get them from the response writer inside bodyLogWriter' Write function, but you can always add gin.Context to the bodyLogWriter struct. It's instantiated for each request anyway. – Seva Sep 27 '16 at 23:39
  • If you're using bodyLogHandler approach then http.Request will be supplied to your ServeHTTP function. – Seva Sep 27 '16 at 23:40
  • how I known when the body all written out? – Jiang YD Jan 26 '21 at 07:55
  • Once c.Next() call returns, you can be sure that any wrapped handlers have finished their writing. – Seva Feb 03 '21 at 23:10
2

FYI

Note: implement WriteString() if using c.String() for writing response body

type bodyLogWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
    w.body.Write(b)
    return w.ResponseWriter.Write(b)
}

func (w bodyLogWriter) WriteString(s string) (int, error) {
    w.body.WriteString(s)
    return w.ResponseWriter.WriteString(s)
}

func ginBodyLogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
        c.Writer = blw
        c.Next()

        fmt.Println("Response body: " + blw.body.String())
    }
}

...

// register
router := r.Group("/", ginBodyLogMiddleware())
Till
  • 1,097
  • 13
  • 13