Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Renovate | 6d5599e3f2 | |
Renovate | c04f470209 | |
Renovate | ec981ac676 | |
Renovate | 2dcbf79078 | |
Renovate | 2b863c0b43 | |
Renovate | c2faabe3fd | |
Renovate | 908422fb76 | |
Renovate | b446320fce | |
Dawid Wysokiński | 95cd0a13f3 | |
Dawid Wysokiński | bc41d0d7de | |
Dawid Wysokiński | 78893a70a2 | |
Renovate | 38e4720367 | |
renovate | 1a4780ee4d | |
renovate | ff3011b6eb | |
renovate | 3c7fb65673 | |
Dawid Wysokiński | 893b1cfd48 | |
Dawid Wysokiński | 0038d85083 |
36
.drone.yml
36
.drone.yml
|
@ -1,36 +0,0 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:1.20
|
||||
commands:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
branch:
|
||||
- master
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: check-go-mod
|
||||
|
||||
steps:
|
||||
- name: check go.mod
|
||||
image: golang:1.20
|
||||
commands:
|
||||
- go mod tidy
|
||||
- git diff --exit-code go.mod
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
branch:
|
||||
- master
|
|
@ -0,0 +1,124 @@
|
|||
run:
|
||||
tests: true
|
||||
timeout: 5m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- bidichk
|
||||
- exportloopref
|
||||
- depguard
|
||||
- errcheck
|
||||
- gocritic
|
||||
- gosec
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- lll
|
||||
- nestif
|
||||
- thelper
|
||||
- nonamedreturns
|
||||
- gocyclo
|
||||
- gomnd
|
||||
- tenv
|
||||
- testpackage
|
||||
- noctx
|
||||
- tparallel
|
||||
- usestdlibvars
|
||||
- unconvert
|
||||
- makezero
|
||||
- grouper
|
||||
- errname
|
||||
- exhaustive
|
||||
- tagliatelle
|
||||
- contextcheck
|
||||
- gocheckcompilerdirectives
|
||||
- errname
|
||||
- forcetypeassert
|
||||
- durationcheck
|
||||
- predeclared
|
||||
- promlinter
|
||||
- wastedassign
|
||||
|
||||
linters-settings:
|
||||
lll:
|
||||
line-length: 150
|
||||
gocyclo:
|
||||
min-complexity: 10
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- "$all"
|
||||
deny:
|
||||
- pkg: reflect
|
||||
desc: Please don't use reflect package
|
||||
- pkg: github.com/pkg/errors
|
||||
desc: Should be replaced by standard lib errors package
|
||||
govet:
|
||||
enable:
|
||||
- asmdecl
|
||||
- assign
|
||||
- atomic
|
||||
- atomicalign
|
||||
- bools
|
||||
- buildtag
|
||||
- cgocall
|
||||
- composites
|
||||
- copylocks
|
||||
- deepequalerrors
|
||||
- errorsas
|
||||
- findcall
|
||||
- framepointer
|
||||
- httpresponse
|
||||
- ifaceassert
|
||||
- loopclosure
|
||||
- lostcancel
|
||||
- nilfunc
|
||||
- nilness
|
||||
- printf
|
||||
- reflectvaluecompare
|
||||
- shadow
|
||||
- shift
|
||||
- sigchanyzer
|
||||
- sortslice
|
||||
- stdmethods
|
||||
- stringintconv
|
||||
- structtag
|
||||
- testinggoroutine
|
||||
- tests
|
||||
- unmarshal
|
||||
- unreachable
|
||||
- unsafeptr
|
||||
- unusedresult
|
||||
- unusedwrite
|
||||
gomnd:
|
||||
ignored-functions:
|
||||
- strconv.FormatInt
|
||||
- strconv.ParseInt
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- gocyclo
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
- linters:
|
||||
- lll
|
||||
source: "^// @Param"
|
|
@ -0,0 +1,31 @@
|
|||
when:
|
||||
- event: [pull_request]
|
||||
- event: push
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
variables:
|
||||
- &go_image 'golang:1.21'
|
||||
|
||||
steps:
|
||||
test:
|
||||
image: *go_image
|
||||
group: test
|
||||
pull: true
|
||||
commands:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
lint:
|
||||
image: golangci/golangci-lint:v1.58
|
||||
pull: true
|
||||
group: test
|
||||
commands:
|
||||
- golangci-lint run
|
||||
|
||||
check-go-mod:
|
||||
image: *go_image
|
||||
group: test
|
||||
pull: true
|
||||
commands:
|
||||
- go mod tidy
|
||||
- git diff --exit-code go.mod
|
|
@ -1,4 +1,4 @@
|
|||
# chizap [![Build Status](https://drone.dwysokinski.me/api/badges/Kichiyaki/chizap/status.svg?ref=refs/heads/master)](https://drone.dwysokinski.me/Kichiyaki/chizap)
|
||||
# chizap [![Build Status](https://woodpecker.dwysokinski.me/api/badges/4/status.svg)](https://woodpecker.dwysokinski.me/repos/4)
|
||||
|
||||
A request logging middleware for [go-chi](https://github.com/go-chi/chi) using [zap](https://github.com/uber-go/zap).
|
||||
|
||||
|
|
18
config.go
18
config.go
|
@ -1,6 +1,7 @@
|
|||
package chizap
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -12,13 +13,19 @@ type AdditionalFieldExtractor func(r *http.Request) []zap.Field
|
|||
|
||||
type config struct {
|
||||
filters []Filter
|
||||
ipFn func(r *http.Request) string
|
||||
additionalFieldExtractors []AdditionalFieldExtractor
|
||||
}
|
||||
|
||||
type Option func(*config)
|
||||
|
||||
func newConfig(opts ...Option) *config {
|
||||
cfg := &config{}
|
||||
cfg := &config{
|
||||
ipFn: func(r *http.Request) string {
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
return ip
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
|
@ -44,3 +51,12 @@ func WithAdditionalFieldExtractor(extractor AdditionalFieldExtractor) Option {
|
|||
c.additionalFieldExtractors = append(c.additionalFieldExtractors, extractor)
|
||||
}
|
||||
}
|
||||
|
||||
// WithIPFn takes a function that will be called on every
|
||||
// request and the returned ip will be added to the log entry.
|
||||
// http.Request RemoteAddr is logged by default.
|
||||
func WithIPFn(fn func(r *http.Request) string) Option {
|
||||
return func(c *config) {
|
||||
c.ipFn = fn
|
||||
}
|
||||
}
|
||||
|
|
9
go.mod
9
go.mod
|
@ -3,15 +3,14 @@ module gitea.dwysokinski.me/Kichiyaki/chizap
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/stretchr/testify v1.8.2
|
||||
go.uber.org/zap v1.24.0
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
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/pmezard/go-difflib v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
33
go.sum
33
go.sum
|
@ -1,32 +1,17 @@
|
|||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
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=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.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=
|
||||
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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
29
logger.go
29
logger.go
|
@ -1,13 +1,13 @@
|
|||
package chizap
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Logger returns a go-chi middleware that logs requests using go.uber.org/zap.
|
||||
|
@ -31,12 +31,11 @@ func Logger(logger *zap.Logger, opts ...Option) func(next http.Handler) http.Han
|
|||
|
||||
end := time.Now()
|
||||
statusCode := ww.Status()
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
|
||||
fields := []zap.Field{
|
||||
zap.Int("statusCode", statusCode),
|
||||
zap.Duration("duration", end.Sub(start)),
|
||||
zap.String("ip", ip),
|
||||
zap.String("ip", cfg.ipFn(r)),
|
||||
zap.String("method", r.Method),
|
||||
zap.String("query", query),
|
||||
zap.String("path", path),
|
||||
|
@ -47,17 +46,23 @@ func Logger(logger *zap.Logger, opts ...Option) func(next http.Handler) http.Han
|
|||
zap.String("routePattern", chi.RouteContext(r.Context()).RoutePattern()),
|
||||
}
|
||||
|
||||
for _, fn := range cfg.additionalFieldExtractors {
|
||||
fields = append(fields, fn(r)...)
|
||||
for _, f := range cfg.additionalFieldExtractors {
|
||||
fields = append(fields, f(r)...)
|
||||
}
|
||||
|
||||
if statusCode >= http.StatusInternalServerError {
|
||||
logger.Error(path, fields...)
|
||||
} else if statusCode >= http.StatusBadRequest {
|
||||
logger.Warn(path, fields...)
|
||||
} else {
|
||||
logger.Info(path, fields...)
|
||||
}
|
||||
logger.Log(statusCodeToLogLvl(statusCode), path, fields...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func statusCodeToLogLvl(statusCode int) zapcore.Level {
|
||||
if statusCode >= http.StatusInternalServerError {
|
||||
return zap.ErrorLevel
|
||||
}
|
||||
|
||||
if statusCode >= http.StatusBadRequest {
|
||||
return zap.WarnLevel
|
||||
}
|
||||
|
||||
return zap.InfoLevel
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ func TestLogger(t *testing.T) {
|
|||
name string
|
||||
req *http.Request
|
||||
excluded bool
|
||||
expectedIP string
|
||||
expectedLevel zapcore.Level
|
||||
expectedRoutePattern string
|
||||
expectedAdditionalFields []zap.Field
|
||||
|
@ -30,18 +31,21 @@ func TestLogger(t *testing.T) {
|
|||
{
|
||||
name: "/info?test=true",
|
||||
req: httptest.NewRequest(http.MethodGet, "/info?test=true", nil),
|
||||
expectedIP: "192.0.2.1",
|
||||
expectedLevel: zap.InfoLevel,
|
||||
expectedRoutePattern: "/info",
|
||||
},
|
||||
{
|
||||
name: "/warn?test=true",
|
||||
req: httptest.NewRequest(http.MethodGet, "/warn?test=true", nil),
|
||||
expectedIP: "192.0.2.1",
|
||||
expectedLevel: zap.WarnLevel,
|
||||
expectedRoutePattern: "/warn",
|
||||
},
|
||||
{
|
||||
name: "/error?test=true",
|
||||
req: httptest.NewRequest(http.MethodGet, "/error?test=true", nil),
|
||||
expectedIP: "192.0.2.1",
|
||||
expectedLevel: zap.ErrorLevel,
|
||||
expectedRoutePattern: "/error",
|
||||
},
|
||||
|
@ -53,12 +57,24 @@ func TestLogger(t *testing.T) {
|
|||
{
|
||||
name: "/delete/123",
|
||||
req: httptest.NewRequest(http.MethodDelete, "/delete/123", nil),
|
||||
expectedIP: "192.0.2.1",
|
||||
expectedLevel: zap.InfoLevel,
|
||||
expectedRoutePattern: "/delete/{id}",
|
||||
expectedAdditionalFields: []zap.Field{
|
||||
zap.String("id", "123"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "/x-forwarded-for",
|
||||
req: func() *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, "/x-forwarded-for", nil)
|
||||
req.Header.Set("X-Forwarded-For", "94.222.111.115")
|
||||
return req
|
||||
}(),
|
||||
expectedIP: "94.222.111.115",
|
||||
expectedLevel: zap.InfoLevel,
|
||||
expectedRoutePattern: "/x-forwarded-for",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -84,8 +100,7 @@ func TestLogger(t *testing.T) {
|
|||
assert.Equal(t, tt.req.URL.Path, entry.Message)
|
||||
assert.Equal(t, tt.expectedLevel, entry.Level)
|
||||
require.Len(t, entry.Context, 11+len(tt.expectedAdditionalFields))
|
||||
ip, _, _ := net.SplitHostPort(tt.req.RemoteAddr)
|
||||
assert.Contains(t, entry.Context, zap.String("ip", ip))
|
||||
assert.Contains(t, entry.Context, zap.String("ip", tt.expectedIP))
|
||||
assert.Contains(t, entry.Context, zap.Int("statusCode", rr.Code))
|
||||
assert.Contains(t, entry.Context, zap.String("method", tt.req.Method))
|
||||
assert.Contains(t, entry.Context, zap.String("path", tt.req.URL.Path))
|
||||
|
@ -128,6 +143,13 @@ func newRouter(logger *zap.Logger) *chi.Mux {
|
|||
zap.String("id", chi.URLParam(r, "id")),
|
||||
}
|
||||
}),
|
||||
chizap.WithIPFn(func(r *http.Request) string {
|
||||
if r.URL.Path != "/x-forwarded-for" {
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
return ip
|
||||
}
|
||||
return r.Header.Get("X-Forwarded-For")
|
||||
}),
|
||||
))
|
||||
r.Get("/info", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -141,6 +163,9 @@ func newRouter(logger *zap.Logger) *chi.Mux {
|
|||
r.Get("/excluded", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
r.Get("/x-forwarded-for", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
r.Delete("/delete/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue