diff --git a/README.md b/README.md index 98dbf9ff..ae974267 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,23 @@ func main() { }) } + // Example of WithPathNoQuery usage + nq := r.Group("/no-query", logger.SetLogger( + logger.WithPathNoQuery(true), + logger.WithPathLevel(map[string]zerolog.Level{ + // normally this wouldn't match /no-query/boring?foo, but WithPathNoQuery(true) makes it match + "/no-query/boring": zerolog.DebugLevel, + }), + )) + { + nq.GET("/boring", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + nq.GET("/interesting", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + } + // Listen and Server in 0.0.0.0:8080 if err := r.Run(":8080"); err != nil { log.Fatal().Msg("can' start server with 8080 port") diff --git a/_example/main.go b/_example/main.go index c78530ac..9cb6b061 100644 --- a/_example/main.go +++ b/_example/main.go @@ -173,6 +173,23 @@ func main() { }) } + // Example of WithPathNoQuery usage + nq := r.Group("/no-query", logger.SetLogger( + logger.WithPathNoQuery(true), + logger.WithPathLevel(map[string]zerolog.Level{ + // normally this wouldn't match /no-query/boring?foo, but WithPathNoQuery(true) makes it match + "/no-query/boring": zerolog.DebugLevel, + }), + )) + { + nq.GET("/boring", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + nq.GET("/interesting", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + } + // Listen and Server in 0.0.0.0:8080 if err := r.Run(":8080"); err != nil { log.Fatal().Msg("can' start server with 8080 port") diff --git a/logger.go b/logger.go index 3badd3ed..94dc1ac5 100644 --- a/logger.go +++ b/logger.go @@ -80,6 +80,12 @@ type config struct { pathLevels is a map of specific paths to log levels for requests with status code < 400. */ pathLevels map[string]zerolog.Level + /* + pathNoQuery specifies if the path should be handled without the query + string for both logging and matching against pathLevels. If enabled, the + query string will be logged in its own field. + */ + pathNoQuery bool /* message is a custom string that sets a log-message when http-request has finished */ @@ -157,17 +163,22 @@ func SetLogger(opts ...Option) gin.HandlerFunc { start := time.Now() path := c.Request.URL.Path - if raw := c.Request.URL.RawQuery; raw != "" { - path += "?" + raw + rawQuery := c.Request.URL.RawQuery + if rawQuery != "" && !cfg.pathNoQuery { + path += "?" + rawQuery } track := !shouldSkipLogging(path, skip, cfg, c) contextLogger := rl if track { - contextLogger = rl.With(). + rlc := rl.With(). Str("method", c.Request.Method). - Str("path", path). + Str("path", path) + if cfg.pathNoQuery && rawQuery != "" { + rlc = rlc.Str("query", rawQuery) + } + contextLogger = rlc. Str("ip", c.ClientIP()). Str("user_agent", c.Request.UserAgent()). Logger() @@ -194,10 +205,14 @@ func SetLogger(opts ...Option) gin.HandlerFunc { evt = cfg.context(c, evt) } - evt. + evt = evt. Int("status", c.Writer.Status()). Str("method", c.Request.Method). - Str("path", path). + Str("path", path) + if cfg.pathNoQuery && rawQuery != "" { + evt = evt.Str("query", rawQuery) + } + evt. Str("ip", c.ClientIP()). Dur("latency", latency). Str("user_agent", c.Request.UserAgent()). diff --git a/logger_test.go b/logger_test.go index 8aa5ca46..57bd2cf6 100644 --- a/logger_test.go +++ b/logger_test.go @@ -348,6 +348,43 @@ func TestLoggerCustomMessageWithErrors(t *testing.T) { assert.Equal(t, strings.Count(buffer.String(), " with errors: "), 1) } +func TestLoggerPathNoQuery(t *testing.T) { + buffer := new(bytes.Buffer) + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(SetLogger( + WithWriter(buffer), + WithPathNoQuery(true), + WithPathLevel(map[string]zerolog.Level{ + "/example2": zerolog.WarnLevel, + }), + )) + r.GET("/example", func(c *gin.Context) { + l := Get(c) + l.Debug().Msg("contextlogger") + }) + r.GET("/example2", func(c *gin.Context) {}) + + performRequest(r, "GET", "/example?foo=bar") + lines := strings.Split(strings.TrimSpace(buffer.String()), "\n") + assert.Len(t, lines, 2) + for i, line := range lines { + assert.Contains(t, line, " query=foo=bar ", "line %d", i) + } + + // with no query string, the field should be omitted + buffer.Reset() + performRequest(r, "GET", "/example") + assert.NotContains(t, buffer.String(), "query=") + + buffer.Reset() + performRequest(r, "GET", "/example2?foo=bar") + // this one should be logged at warn level because the query-free path matches + // the level map entry + assert.Contains(t, buffer.String(), "WRN") + assert.Contains(t, buffer.String(), " query=foo=bar ") +} + func BenchmarkLogger(b *testing.B) { gin.SetMode(gin.ReleaseMode) r := gin.New() diff --git a/options.go b/options.go index 4186a2f3..0338bb71 100644 --- a/options.go +++ b/options.go @@ -269,3 +269,9 @@ func WithSpecificLogLevelByStatusCode(statusCodes map[int]zerolog.Level) Option c.specificLevelByStatusCode = statusCodes }) } + +func WithPathNoQuery(b bool) Option { + return optionFunc(func(c *config) { + c.pathNoQuery = b + }) +}