Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Configurable IncluHeader configuration item #5

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
The MIT License (MIT)

Copyright (c) Bruno Paz [email protected] (https://github.com/brpaz)
Copyright (c) Bruno Paz [email protected] (https://github.com/brpaz), BerryPay (https://www.berrypay.com)


Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export GO111MODULE=on
dev-deps: ## Install Development dependencies
npm i -g conventional-changelog-cli commitizen

dev: ## Setup the Development envrionment
dev: ## Setup the Development environment
pre-commit install

fmt: ## Formats the go code using gofmt
Expand Down
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

# echozap
# echozap - BerryPay Public Fork

> Middleware for Golang [Echo](https://echo.labstack.com/) framework that provides integration with Uber´s [Zap](https://github.com/uber-go/zap) logging library for logging HTTP requests.

Expand All @@ -25,7 +25,7 @@ package main
import (
"net/http"

"github.com/brpaz/echozap"
"github.com/berrypay/echozap"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -55,6 +55,45 @@ The following information is logged:
* Hostname
* Remote IP Address

## Configuration

Customization can be made on 2 configurable items:

1. Skipper: skip logging based on the given condition
2. IncludeHeader: add custom log field based on the provided list of header keys

Usage:

```go
package main

import (
"net/http"

"github.com/berrypay/echozap"
"github.com/labstack/echo/v4"
"go.uber.org/zap"
)

func main() {
e := echo.New()

zapLogger, _ := zap.NewProduction()

e.Use(echozap.ZapLoggerWithConfig(zapLogger, echozap.ZapLoggerConfig{
Skipper: nil,
IncludeHeader: []string{
echo.HeaderXRequestID,
},
}))

e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
```

## Todo

* Add more customization options.
Expand All @@ -78,6 +117,10 @@ Give a ⭐️ to the project, or just:
* Website: [https://github.com/brpaz](https://github.com/brpaz)
* Github: [@brpaz](https://github.com/brpaz)

👤 **Sallehuddin Abdul Latif**
* Website: [https://www.berrypay.com](https://www.berrypay.com)
* Github: [@salleh](https://github.com/salleh) [@BerryPay](https://github.com/berrypay)

## 📝 License

Copyright © 2019 [Bruno Paz](https://github.com/brpaz).
Expand Down
29 changes: 21 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
module github.com/brpaz/echozap
module github.com/berrypay/echozap

go 1.12
go 1.22

require (
github.com/labstack/echo/v4 v4.1.10
github.com/pkg/errors v0.8.1 // indirect
github.com/stretchr/testify v1.4.0
go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.2.0 // indirect
go.uber.org/zap v1.10.0
github.com/labstack/echo/v4 v4.12.0
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
73 changes: 33 additions & 40 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,46 +1,39 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/labstack/echo/v4 v4.1.10 h1:/yhIpO50CBInUbE/nHJtGIyhBv0dJe2cDAYxc3V3uMo=
github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4=
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
75 changes: 59 additions & 16 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,57 @@ package echozap

import (
"fmt"
"strings"
"time"

"github.com/labstack/echo/v4"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

type (
Skipper func(c echo.Context) bool

// ZapLoggerConfig defines the config for ZapLogger middleware
ZapLoggerConfig struct {
// Skipper defines a function to skip middleware
Skipper Skipper
// IncludeRequestId
IncludeHeader []string
}
)

var (
// DefaultZapLoggerConfig is the default ZapLogger middleware config.
DefaultZapLoggerConfig = ZapLoggerConfig{
Skipper: DefaultSkipper,
IncludeHeader: nil,
}
)

// DefaultSkipper returns false which processes the middleware
func DefaultSkipper(echo.Context) bool {
return false
}

// ZapLogger is a middleware and zap to provide an "access log" like logging for each request.
func ZapLogger(log *zap.Logger) echo.MiddlewareFunc {
return ZapLoggerWithConfig(log, DefaultZapLoggerConfig)
}

// ZapLoggerWithConfig is a middleware (with configuration) and zap to provide an "access log" like logging for each request.
func ZapLoggerWithConfig(log *zap.Logger, config ZapLoggerConfig) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
// Defaults
if config.Skipper == nil {
config.Skipper = DefaultZapLoggerConfig.Skipper
}

return func(c echo.Context) error {
if config.Skipper(c) {
return next(c)
}

start := time.Now()

err := next(c)
Expand All @@ -33,25 +73,28 @@ func ZapLogger(log *zap.Logger) echo.MiddlewareFunc {
zap.String("user_agent", req.UserAgent()),
}

id := req.Header.Get(echo.HeaderXRequestID)
if id == "" {
id = res.Header().Get(echo.HeaderXRequestID)
}
fields = append(fields, zap.String("request_id", id))

n := res.Status
switch {
case n >= 500:
log.With(zap.Error(err)).Error("Server error", fields...)
case n >= 400:
log.With(zap.Error(err)).Warn("Client error", fields...)
case n >= 300:
log.Info("Redirection", fields...)
default:
log.Info("Success", fields...)
if config.IncludeHeader != nil {
for _, header := range config.IncludeHeader {
fields = append(fields, zap.String(strings.ToLower(header), req.Header.Get(header)))
}
}

writeLog(log, res.Status, err, fields)

return nil
}
}
}

func writeLog(log *zap.Logger, status int, err error, fields []zapcore.Field) {
switch {
case status >= 500:
log.With(zap.Error(err)).Error("Server error", fields...)
case status >= 400:
log.With(zap.Error(err)).Warn("Client error", fields...)
case status >= 300:
log.Info("Redirection", fields...)
default:
log.Info("Success", fields...)
}
}
67 changes: 65 additions & 2 deletions logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package echozap
import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/labstack/echo/v4"
Expand All @@ -12,8 +13,10 @@ import (
)

func TestZapLogger(t *testing.T) {
testPathA := "/path-A"

e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/something", nil)
req := httptest.NewRequest(http.MethodGet, testPathA, nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

Expand All @@ -34,7 +37,67 @@ func TestZapLogger(t *testing.T) {
assert.Equal(t, 1, logs.Len())
assert.Equal(t, int64(200), logFields["status"])
assert.NotNil(t, logFields["latency"])
assert.Equal(t, "GET /something", logFields["request"])
assert.Equal(t, "GET "+testPathA, logFields["request"])
assert.NotNil(t, logFields["host"])
assert.NotNil(t, logFields["size"])
}

func TestZapLoggerWithConfig(t *testing.T) {
testPathB := "/path-B"

e := echo.New()
req := httptest.NewRequest(http.MethodGet, testPathB, nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

h := func(c echo.Context) error {
return c.String(http.StatusOK, "")
}

obs, logs := observer.New(zap.DebugLevel)

logger := zap.New(obs)

err := ZapLoggerWithConfig(logger, ZapLoggerConfig{
Skipper: func(ctx echo.Context) bool {
return strings.Contains(ctx.Request().URL.Path, testPathB)
},
})(h)(c)

assert.Nil(t, err)

assert.Equal(t, 0, logs.Len())
}

func TestZapLoggerWithConfigIncludeHeader(t *testing.T) {
testPathC := "/path-C"
customHeaderKey := "My-Custom-Header"

e := echo.New()
req := httptest.NewRequest(http.MethodGet, testPathC, nil)
req.Header.Set(echo.HeaderXRequestID, "test-request-id")
req.Header.Set(customHeaderKey, "my-custom-header-value")
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)

h := func(c echo.Context) error {
return c.String(http.StatusOK, "")
}

obs, logs := observer.New(zap.DebugLevel)

logger := zap.New(obs)

err := ZapLoggerWithConfig(logger, ZapLoggerConfig{
IncludeHeader: []string{echo.HeaderXRequestID, customHeaderKey},
})(h)(c)

assert.Nil(t, err)

logFields := logs.AllUntimed()[0].ContextMap()
assert.Equal(t, 1, logs.Len())
assert.NotNil(t, logFields[strings.ToLower(echo.HeaderXRequestID)])
assert.Equal(t, "test-request-id", logFields[strings.ToLower(echo.HeaderXRequestID)])
assert.NotNil(t, logFields[strings.ToLower(customHeaderKey)])
assert.Equal(t, "my-custom-header-value", logFields[strings.ToLower(customHeaderKey)])
}