diff --git a/logger.go b/logger.go index 1e6cf77a..846bd240 100644 --- a/logger.go +++ b/logger.go @@ -5,6 +5,7 @@ package gin import ( + "bytes" "fmt" "io" "net/http" @@ -47,6 +48,10 @@ type LoggerConfig struct { // SkipPaths is an url path array which logs are not written. // Optional. SkipPaths []string + + // RequestBody is a bool to enable request body logging + // Optional. Default value is false + RequestBody bool } // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter @@ -238,11 +243,20 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { path := c.Request.URL.Path raw := c.Request.URL.RawQuery + var body []byte + if conf.RequestBody { + body, _ = io.ReadAll(c.Request.Body) + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + // Process request c.Next() // Log only when path is not being skipped if _, ok := skip[path]; !ok { + if conf.RequestBody { + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } param := LogFormatterParams{ Request: c.Request, isTerm: isTerm, diff --git a/logger_test.go b/logger_test.go index b93e1e04..6199d299 100644 --- a/logger_test.go +++ b/logger_test.go @@ -5,8 +5,10 @@ package gin import ( + "bytes" "errors" "fmt" + "io" "net/http" "strings" "testing" @@ -182,18 +184,21 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams var gotKeys map[string]any + var gotBody []byte buffer := new(strings.Builder) router := New() router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs() router.Use(LoggerWithConfig(LoggerConfig{ - Output: buffer, + Output: buffer, + RequestBody: true, Formatter: func(param LogFormatterParams) string { // for assert test gotParam = param + gotBody, _ = io.ReadAll(param.Request.Body) - return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.StatusCode, param.Latency, @@ -201,6 +206,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { param.Method, param.Path, param.ErrorMessage, + string(gotBody), ) }, })) @@ -229,6 +235,16 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) + + router.POST("/example", func(c *Context) { + // set dummy ClientIP + c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") + time.Sleep(time.Millisecond) + }) + PerformBodyRequest(router, "POST", "/example", []header{}, bytes.NewBufferString(`{"name":"test"}`)) + + // LogFormatterParams post body test + assert.Equal(t, string(gotBody), `{"name":"test"}`) } func TestDefaultLogFormatter(t *testing.T) { diff --git a/routes_test.go b/routes_test.go index 7a51f817..47cabdd7 100644 --- a/routes_test.go +++ b/routes_test.go @@ -6,6 +6,7 @@ package gin import ( "fmt" + "io" "net/http" "net/http/httptest" "os" @@ -30,7 +31,15 @@ func PerformRequest(r http.Handler, method, path string, headers ...header) *htt r.ServeHTTP(w, req) return w } - +func PerformBodyRequest(r http.Handler, method, path string, headers []header, body io.Reader) *httptest.ResponseRecorder { + req := httptest.NewRequest(method, path, body) + for _, h := range headers { + req.Header.Add(h.Key, h.Value) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + return w +} func testRouteOK(method string, t *testing.T) { passed := false passedAny := false