From 8d8755b7c53e682b990f76202147459ef7f93830 Mon Sep 17 00:00:00 2001 From: Roman Dobynda Date: Sun, 25 Feb 2024 11:35:16 +0300 Subject: [PATCH] Option using fields in snake_case. --- fiberzerolog/README.md | 1 + fiberzerolog/config.go | 82 ++++++++++++++++++++++++++---------- fiberzerolog/zerolog_test.go | 63 +++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/fiberzerolog/README.md b/fiberzerolog/README.md index 4524aeb1..a056c32f 100644 --- a/fiberzerolog/README.md +++ b/fiberzerolog/README.md @@ -39,6 +39,7 @@ fiberzerolog.New(config ...fiberzerolog.Config) fiber.Handler | GetLogger | `func(*fiber.Ctx) zerolog.Logger` | Get custom zerolog logger, if it's defined the returned logger will replace the `Logger` value. | `nil` | | Fields | `[]string` | Add fields what you want see. | `[]string{"latency", "status", "method", "url", "error"}` | | WrapHeaders | bool | Wrap headers to dictionary.
If false: `{"method":"POST", "header-key":"header value"}`
If true: `{"method":"POST", "reqHeaders": {"header-key":"header value"}}` | `false` | +| FieldsSnakeCase | bool | Use snake case for fields: FieldResBody, FieldQueryParams, FieldBytesReceived, FieldBytesSent, FieldRequestId, FieldReqHeaders, FieldResHeaders.
If false: `{"method":"POST", "resBody":"v", "queryParams":"v"}`
If true: `{"method":"POST", "res_body":"v", "query_params":"v"}` | `false` | | Messages | `[]string` | Custom response messages. | `[]string{"Server error", "Client error", "Success"}` | | Levels | `[]zerolog.Level` | Custom response levels. | `[]zerolog.Level{zerolog.ErrorLevel, zerolog.WarnLevel, zerolog.InfoLevel}` | | SkipURIs | `[]string` | Skip logging these URI. | `[]string{}` | diff --git a/fiberzerolog/config.go b/fiberzerolog/config.go index 55fd5d0c..42253671 100644 --- a/fiberzerolog/config.go +++ b/fiberzerolog/config.go @@ -32,6 +32,14 @@ const ( FieldError = "error" FieldReqHeaders = "reqHeaders" FieldResHeaders = "resHeaders" + + fieldResBody_ = "res_body" + fieldQueryParams_ = "query_params" + fieldBytesReceived_ = "bytes_received" + fieldBytesSent_ = "bytes_sent" + fieldRequestID_ = "request_id" + fieldReqHeaders_ = "req_headers" + fieldResHeaders_ = "res_headers" ) // Config defines the config for middleware. @@ -87,6 +95,13 @@ type Config struct { // Optional. Default: false WrapHeaders bool + // Use snake case for fields: FieldResBody, FieldQueryParams, FieldBytesReceived, FieldBytesSent, FieldRequestId, FieldReqHeaders, FieldResHeaders. + // If false: {"method":"POST", "resBody":"v", "queryParams":"v"} + // If true: {"method":"POST", "res_body":"v", "query_params":"v"} + // + // Optional. Default: false + FieldsSnakeCase bool + // Custom response messages. // Response codes >= 500 will be logged with Messages[0]. // Response codes >= 400 will be logged with Messages[1]. @@ -122,76 +137,97 @@ func (c *Config) logger(fc *fiber.Ctx, latency time.Duration, err error) zerolog for _, field := range c.Fields { switch field { case FieldReferer: - zc = zc.Str(FieldReferer, fc.Get(fiber.HeaderReferer)) + zc = zc.Str(field, fc.Get(fiber.HeaderReferer)) case FieldProtocol: - zc = zc.Str(FieldProtocol, fc.Protocol()) + zc = zc.Str(field, fc.Protocol()) case FieldPID: - zc = zc.Int(FieldPID, os.Getpid()) + zc = zc.Int(field, os.Getpid()) case FieldPort: - zc = zc.Str(FieldPort, fc.Port()) + zc = zc.Str(field, fc.Port()) case FieldIP: - zc = zc.Str(FieldIP, fc.IP()) + zc = zc.Str(field, fc.IP()) case FieldIPs: - zc = zc.Str(FieldIPs, fc.Get(fiber.HeaderXForwardedFor)) + zc = zc.Str(field, fc.Get(fiber.HeaderXForwardedFor)) case FieldHost: - zc = zc.Str(FieldHost, fc.Hostname()) + zc = zc.Str(field, fc.Hostname()) case FieldPath: - zc = zc.Str(FieldPath, fc.Path()) + zc = zc.Str(field, fc.Path()) case FieldURL: - zc = zc.Str(FieldURL, fc.OriginalURL()) + zc = zc.Str(field, fc.OriginalURL()) case FieldUserAgent: - zc = zc.Str(FieldUserAgent, fc.Get(fiber.HeaderUserAgent)) + zc = zc.Str(field, fc.Get(fiber.HeaderUserAgent)) case FieldLatency: - zc = zc.Str(FieldLatency, latency.String()) + zc = zc.Str(field, latency.String()) case FieldStatus: - zc = zc.Int(FieldStatus, fc.Response().StatusCode()) + zc = zc.Int(field, fc.Response().StatusCode()) case FieldResBody: + if c.FieldsSnakeCase { + field = fieldResBody_ + } if c.SkipResBody == nil || !c.SkipResBody(fc) { if c.GetResBody == nil { - zc = zc.Bytes(FieldResBody, fc.Response().Body()) + zc = zc.Bytes(field, fc.Response().Body()) } else { - zc = zc.Bytes(FieldResBody, c.GetResBody(fc)) + zc = zc.Bytes(field, c.GetResBody(fc)) } } case FieldQueryParams: - zc = zc.Stringer(FieldQueryParams, fc.Request().URI().QueryArgs()) + if c.FieldsSnakeCase { + field = fieldQueryParams_ + } + zc = zc.Stringer(field, fc.Request().URI().QueryArgs()) case FieldBody: if c.SkipBody == nil || !c.SkipBody(fc) { - zc = zc.Bytes(FieldBody, fc.Body()) + zc = zc.Bytes(field, fc.Body()) } case FieldBytesReceived: - zc = zc.Int(FieldBytesReceived, len(fc.Request().Body())) + if c.FieldsSnakeCase { + field = fieldBytesReceived_ + } + zc = zc.Int(field, len(fc.Request().Body())) case FieldBytesSent: - zc = zc.Int(FieldBytesSent, len(fc.Response().Body())) + if c.FieldsSnakeCase { + field = fieldBytesSent_ + } + zc = zc.Int(field, len(fc.Response().Body())) case FieldRoute: - zc = zc.Str(FieldRoute, fc.Route().Path) + zc = zc.Str(field, fc.Route().Path) case FieldMethod: - zc = zc.Str(FieldMethod, fc.Method()) + zc = zc.Str(field, fc.Method()) case FieldRequestID: - zc = zc.Str(FieldRequestID, fc.GetRespHeader(fiber.HeaderXRequestID)) + if c.FieldsSnakeCase { + field = fieldRequestID_ + } + zc = zc.Str(field, fc.GetRespHeader(fiber.HeaderXRequestID)) case FieldError: if err != nil { zc = zc.Err(err) } case FieldReqHeaders: + if c.FieldsSnakeCase { + field = fieldReqHeaders_ + } if c.WrapHeaders { dict := zerolog.Dict() fc.Request().Header.VisitAll(func(k, v []byte) { dict.Bytes(string(k), v) }) - zc = zc.Dict(FieldReqHeaders, dict) + zc = zc.Dict(field, dict) } else { fc.Request().Header.VisitAll(func(k, v []byte) { zc = zc.Bytes(string(k), v) }) } case FieldResHeaders: + if c.FieldsSnakeCase { + field = fieldResHeaders_ + } if c.WrapHeaders { dict := zerolog.Dict() fc.Response().Header.VisitAll(func(k, v []byte) { dict.Bytes(string(k), v) }) - zc = zc.Dict(FieldResHeaders, dict) + zc = zc.Dict(field, dict) } else { fc.Response().Header.VisitAll(func(k, v []byte) { zc = zc.Bytes(string(k), v) diff --git a/fiberzerolog/zerolog_test.go b/fiberzerolog/zerolog_test.go index bb761c48..35d2f90e 100644 --- a/fiberzerolog/zerolog_test.go +++ b/fiberzerolog/zerolog_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/requestid" "github.com/gofiber/fiber/v2/utils" "github.com/rs/zerolog" ) @@ -471,6 +472,68 @@ func Test_Res_Headers_WrapHeaders(t *testing.T) { utils.AssertEqual(t, expected, logs) } +func Test_FieldsSnakeCase(t *testing.T) { + t.Parallel() + + var buf bytes.Buffer + logger := zerolog.New(&buf) + + app := fiber.New() + app.Use(requestid.New()) + app.Use(New(Config{ + Logger: &logger, + Fields: []string{ + FieldResBody, + FieldQueryParams, + FieldBytesReceived, + FieldBytesSent, + FieldRequestID, + FieldResHeaders, + FieldReqHeaders, + }, + FieldsSnakeCase: true, + WrapHeaders: true, + })) + + app.Get("/", func(c *fiber.Ctx) error { + c.Set("Foo", "bar") + return c.SendString("hello") + }) + + req := httptest.NewRequest("GET", "/?param=value", nil) + req.Header.Add("X-Request-ID", "uuid") + req.Header.Add("Baz", "foo") + + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) + + expected := map[string]interface{}{ + "bytes_received": float64(0), + "bytes_sent": float64(5), + "query_params": "param=value", + "req_headers": map[string]interface{}{ + "Host": "example.com", + "Baz": "foo", + "X-Request-Id": "uuid", + }, + "res_headers": map[string]interface{}{ + "Content-Type": "text/plain; charset=utf-8", + "Foo": "bar", + "X-Request-Id": "uuid", + }, + "request_id": "uuid", + "res_body": "hello", + "level": "info", + "message": "Success", + } + + var logs map[string]any + _ = json.Unmarshal(buf.Bytes(), &logs) + + utils.AssertEqual(t, expected, logs) +} + func Test_LoggerLevelsAndMessages(t *testing.T) { t.Parallel()