Compare commits

...

11 Commits

Author SHA1 Message Date
27ce2d134b style: happy linting
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-02-24 09:22:51 +00:00
1d7e0dc701 style: happy linting
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-02-24 09:21:52 +00:00
d5b5c131e7 feat(requestid): the handler is request id aware
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
if the request has an header `X-Request-ID`, the middleware will extract
  it and use it as uuid on the logs.
2023-02-24 09:07:06 +00:00
623bdbcded ci: uses golang 1.18 for build process
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-23 13:58:48 +01:00
43246d257e refactor!: sets an default context logger and returns now pointers of zerolog.Logger
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2022-03-23 13:54:44 +01:00
59613e6475 build: uses gotestsum for testing 2022-03-23 13:44:46 +01:00
a9c5c1f2cc chore: zerolog version bump 2022-03-23 13:43:18 +01:00
0aada80108 feat!: FromRequest and FromCtx will return the global logger if the one
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
extracted is disabled
2022-02-18 15:41:57 +01:00
0e1d0c345a chore: upgrade golangci-lint 2022-02-18 15:40:28 +01:00
cbeba830d4 chore(golangci-lint): upgrade
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-01-28 13:00:25 +01:00
c77f9dc518 ci: uses woodpecker instead of drone
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-01-28 12:47:08 +01:00
40 changed files with 807 additions and 294 deletions

View File

@ -1,83 +0,0 @@
kind: pipeline
name: default
type: docker
golang-image: &golang-image golang:1.17-alpine
gobin-volume: &gobin-volume
name: gobin
path: /go/bin
usr-volume: &usr-volume
name: usr
path: /usr
deps-command: &deps-command apk add build-base git
steps:
- name: tags
image: alpine/git
commands:
- git fetch --tags
- name: install-deps
image: *golang-image
volumes:
- *gobin-volume
- *usr-volume
commands:
- *deps-command
- name: lint
image: *golang-image
volumes:
- *gobin-volume
- *usr-volume
commands:
- make lint
depends_on:
- tags
- install-deps
- name: test
image: *golang-image
volumes:
- *gobin-volume
- *usr-volume
commands:
- make test
depends_on:
- install-deps
- tags
- lint
- name: build
image: *golang-image
volumes:
- *gobin-volume
- *usr-volume
commands:
- make build
depends_on:
- tags
- lint
- test
- install-deps
when:
event:
exclude:
- tag
- name: release
image: *golang-image
volumes:
- *gobin-volume
- *usr-volume
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- make release
depends_on:
- test
- lint
- tags
- install-deps
when:
event:
- tag
volumes:
- name: gobin
temp: {}
- name: usr
temp: {}

4
.gitignore vendored
View File

@ -1,2 +1,6 @@
dist/* dist/*
.envrc .envrc
coverage.out
report.xml
.gobin/*
.cache/*

26
.woodpecker.yml Normal file
View File

@ -0,0 +1,26 @@
pipeline:
tags:
image: alpine/git
commands:
- git fetch --tags
lint:
image: golang:1.18
commands:
- make lint
test:
image: golang:1.18
commands:
- make test
build:
image: golang:1.18
commands:
- make build
release:
image: golang:1.18
commands:
- make release
secrets:
- gitea_token
when:
event:
- tag

View File

@ -1,10 +1,28 @@
GO := go GO := go
GORELEASER := $(GO) run github.com/goreleaser/goreleaser@v1.3.1
GOLANGCI_LINT := $(GO) run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0 ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
CACHE_DIR := $(ROOT_DIR)/.cache
export GOBIN := $(ROOT_DIR)/.gobin
export PATH := $(GOBIN):$(PATH)
GORELEASER := $(GO) run github.com/goreleaser/goreleaser@v1.7.0
GOLANGCI_LINT := $(GO) run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.0
GOTESTSUM_URL := gotest.tools/gotestsum@v1.7.0
GOTESTSUM := $(GO) run $(GOTESTSUM_URL)
.PHONY: test .PHONY: test
test: test:
$(GO) test -count=1 -v ./... $(GOTESTSUM) \
--junitfile report.xml \
-- \
-race \
-cover \
-coverprofile=coverage.out \
-tags=integration \
./... \
-timeout=120m
.PHONY: lint .PHONY: lint
lint: lint:

3
go.mod
View File

@ -6,7 +6,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/justinas/alice v1.2.0 github.com/justinas/alice v1.2.0
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/rs/zerolog v1.20.0 github.com/rs/xid v1.3.0
github.com/rs/zerolog v1.26.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

34
go.sum
View File

@ -1,8 +1,9 @@
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
@ -11,23 +12,42 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -8,11 +8,21 @@ import (
"time" "time"
"github.com/justinas/alice" "github.com/justinas/alice"
"github.com/rs/xid"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/hlog" "github.com/rs/zerolog/hlog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const (
UUIDKey = "uuid"
UUIDHeader = "X-Request-ID"
)
func init() { //nolint:gochecknoinits
zerolog.DefaultContextLogger = &log.Logger
}
// GetUUID gets the requests UUID from a request. // GetUUID gets the requests UUID from a request.
func GetUUID(r *http.Request) (string, bool) { func GetUUID(r *http.Request) (string, bool) {
uuid, ok := hlog.IDFromRequest(r) uuid, ok := hlog.IDFromRequest(r)
@ -23,14 +33,53 @@ func GetUUID(r *http.Request) (string, bool) {
return uuid.String(), true return uuid.String(), true
} }
// FromRequest returns a logger with the UUID set from request. // FromRequest returns a logger set from request.
func FromRequest(r *http.Request) zerolog.Logger { // If no one could be found, it will return the global one.
return *hlog.FromRequest(r) func FromRequest(r *http.Request) *zerolog.Logger {
return hlog.FromRequest(r)
} }
// FromCtx returns a logger with the UUID set from ctx. // FromCtx returns a logger set from ctx.
func FromCtx(ctx context.Context) zerolog.Logger { // If no one could be found, it will return the global one.
return *log.Ctx(ctx) func FromCtx(ctx context.Context) *zerolog.Logger {
return zerolog.Ctx(ctx)
}
// RequestIDHandler looks in the header for an existing request id. Else it will create one.
func RequestIDHandler() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get(UUIDHeader)
if id != "" {
ctx := r.Context()
log := zerolog.Ctx(ctx)
uuid, err := xid.FromString(id)
if err != nil {
log.Error().Err(err).Msg("couldnt parse uuid")
hlog.RequestIDHandler(UUIDKey, UUIDHeader)(next).ServeHTTP(w, r)
return
}
ctx = hlog.CtxWithID(ctx, uuid)
r = r.WithContext(ctx)
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str(UUIDKey, uuid.String())
})
w.Header().Set(UUIDHeader, uuid.String())
next.ServeHTTP(w, r)
} else {
hlog.RequestIDHandler(UUIDKey, UUIDHeader)(next).ServeHTTP(w, r)
}
})
}
} }
func Handler(log zerolog.Logger) func(http.Handler) http.Handler { func Handler(log zerolog.Logger) func(http.Handler) http.Handler {
@ -50,7 +99,7 @@ func Handler(log zerolog.Logger) func(http.Handler) http.Handler {
hlog.RemoteAddrHandler("remote"), hlog.RemoteAddrHandler("remote"),
hlog.UserAgentHandler("user-agent"), hlog.UserAgentHandler("user-agent"),
hlog.RefererHandler("referer"), hlog.RefererHandler("referer"),
hlog.RequestIDHandler("uuid", "X-Request-ID"), RequestIDHandler(),
).Then(next) ).Then(next)
return chain return chain

View File

@ -1,15 +1,18 @@
//nolint:funlen
package logginghandler_test package logginghandler_test
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.xsfx.dev/logginghandler" "go.xsfx.dev/logginghandler"
@ -18,13 +21,17 @@ import (
func Example() { func Example() {
logger := log.With().Logger() logger := log.With().Logger()
handler := logginghandler.Handler(logger)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := logginghandler.Handler(
logger := logginghandler.FromRequest(r) logger,
)(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger := logginghandler.FromRequest(r)
logger.Info().Msg("this is a request") logger.Info().Msg("this is a request")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
})) }),
)
http.Handle("/", handler) http.Handle("/", handler)
log.Fatal().Msg(http.ListenAndServe(":5000", nil).Error()) log.Fatal().Msg(http.ListenAndServe(":5000", nil).Error())
@ -45,7 +52,7 @@ func TestUUID(t *testing.T) {
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.NotEmpty(rr.Header().Get("X-Request-ID")) assert.NotEmpty(rr.Header().Get(logginghandler.UUIDHeader))
} }
func TestFromCtx(t *testing.T) { func TestFromCtx(t *testing.T) {
@ -58,10 +65,14 @@ func TestFromCtx(t *testing.T) {
var output bytes.Buffer var output bytes.Buffer
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
handler := logginghandler.Handler(zerolog.New(&output))(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler := logginghandler.Handler(
log := logginghandler.FromCtx(r.Context()) zerolog.New(&output),
log.Info().Msg("hello world") )(
})) http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := logginghandler.FromCtx(r.Context())
log.Info().Msg("hello world")
}),
)
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
@ -75,3 +86,69 @@ func TestFromCtx(t *testing.T) {
assert.NotEmpty(jOut) assert.NotEmpty(jOut)
} }
func TestRequestIDHandler(t *testing.T) {
t.Parallel()
assert := require.New(t)
handler := logginghandler.RequestIDHandler()(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
log.Info().Msg("hello from TestRequestID")
}),
)
id := "cfrj1ro330reqgvfpgu0"
// Create buffer to store output.
var output bytes.Buffer
req, err := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
assert.NoError(err)
rr := httptest.NewRecorder()
h := hlog.NewHandler(zerolog.New(&output))(handler)
h.ServeHTTP(rr, req)
assert.NotEmpty(rr.Header().Get(logginghandler.UUIDHeader))
assert.NotEqual(rr.Header().Get(logginghandler.UUIDHeader), id)
// Now test with request id in header.
nr := httptest.NewRecorder()
nReq, err := http.NewRequestWithContext(context.Background(), "GET", "/", nil)
assert.NoError(err)
nReq.Header.Add(logginghandler.UUIDHeader, id)
h.ServeHTTP(nr, nReq)
assert.NotEmpty(nr.Header().Get(logginghandler.UUIDHeader))
assert.Equal(nr.Header().Get(logginghandler.UUIDHeader), id)
logs := strings.Split(output.String(), "\n")
assert.Len(logs, 3)
getUUID := func(l string) (string, error) {
var out struct{ UUID string }
err := json.Unmarshal([]byte(l), &out)
if err != nil {
return "", fmt.Errorf("failed to unmarshal log: %w", err)
}
return out.UUID, nil
}
uuid1, err := getUUID(logs[0])
assert.NoError(err)
assert.NotEqual(id, uuid1)
uuid2, err := getUUID(logs[1])
assert.NoError(err)
assert.Equal(id, uuid2)
}

13
vendor/github.com/rs/xid/README.md generated vendored
View File

@ -2,9 +2,9 @@
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xid) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xid/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xid.svg?branch=master)](https://travis-ci.org/rs/xid) [![Coverage](http://gocover.io/_badge/github.com/rs/xid)](http://gocover.io/github.com/rs/xid) [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/xid) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/xid/master/LICENSE) [![Build Status](https://travis-ci.org/rs/xid.svg?branch=master)](https://travis-ci.org/rs/xid) [![Coverage](http://gocover.io/_badge/github.com/rs/xid)](http://gocover.io/github.com/rs/xid)
Package xid is a globally unique id generator library, ready to be used safely directly in your server code. Package xid is a globally unique id generator library, ready to safely be used directly in your server code.
Xid is using Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string: Xid uses the Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string:
https://docs.mongodb.org/manual/reference/object-id/ https://docs.mongodb.org/manual/reference/object-id/
- 4-byte value representing the seconds since the Unix epoch, - 4-byte value representing the seconds since the Unix epoch,
@ -33,7 +33,7 @@ is required so it can be used directly in server's code.
|-------------|-------------|----------------|---------------- |-------------|-------------|----------------|----------------
| [UUID] | 16 bytes | 36 chars | configuration free, not sortable | [UUID] | 16 bytes | 36 chars | configuration free, not sortable
| [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable | [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable
| [Snowflake] | 8 bytes | up to 20 chars | needs machin/DC configuration, needs central server, sortable | [Snowflake] | 8 bytes | up to 20 chars | needs machine/DC configuration, needs central server, sortable
| [MongoID] | 12 bytes | 24 chars | configuration free, sortable | [MongoID] | 12 bytes | 24 chars | configuration free, sortable
| xid | 12 bytes | 20 chars | configuration free, sortable | xid | 12 bytes | 20 chars | configuration free, sortable
@ -57,7 +57,7 @@ Best used with [zerolog](https://github.com/rs/zerolog)'s
Notes: Notes:
- Xid is dependent on the system time, a monotonic counter and so is not cryptographically secure. If unpredictability of IDs is important, you should not use Xids. It is worth noting that most of the other UUID like implementations are also not cryptographically secure. You shoud use libraries that rely on cryptographically secure sources (like /dev/urandom on unix, crypto/rand in golang), if you want a truly random ID generator. - Xid is dependent on the system time, a monotonic counter and so is not cryptographically secure. If unpredictability of IDs is important, you should not use Xids. It is worth noting that most other UUID-like implementations are also not cryptographically secure. You should use libraries that rely on cryptographically secure sources (like /dev/urandom on unix, crypto/rand in golang), if you want a truly random ID generator.
References: References:
@ -66,6 +66,9 @@ References:
- https://blog.twitter.com/2010/announcing-snowflake - https://blog.twitter.com/2010/announcing-snowflake
- Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid - Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid
- Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride - Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride
- Rust port by [Jérôme Renard](https://github.com/jeromer/): https://github.com/jeromer/libxid
- Ruby port by [Valar](https://github.com/valarpirai/): https://github.com/valarpirai/ruby_xid
- Java port by [0xShamil](https://github.com/0xShamil/): https://github.com/0xShamil/java-xid
## Install ## Install
@ -105,7 +108,7 @@ BenchmarkUUIDv4-2 1000000 1427 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op
``` ```
Note: UUIDv1 requires a global lock, hence the performence degrading as we add more CPUs. Note: UUIDv1 requires a global lock, hence the performance degradation as we add more CPUs.
## Licenses ## Licenses

2
vendor/github.com/rs/xid/go.mod generated vendored
View File

@ -1 +1,3 @@
module github.com/rs/xid module github.com/rs/xid
go 1.12

View File

@ -5,6 +5,9 @@ package xid
import "io/ioutil" import "io/ioutil"
func readPlatformMachineID() (string, error) { func readPlatformMachineID() (string, error) {
b, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid") b, err := ioutil.ReadFile("/etc/machine-id")
if err != nil || len(b) == 0 {
b, err = ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
}
return string(b), err return string(b), err
} }

81
vendor/github.com/rs/xid/id.go generated vendored
View File

@ -55,6 +55,7 @@ import (
"sort" "sort"
"sync/atomic" "sync/atomic"
"time" "time"
"unsafe"
) )
// Code inspired from mgo/bson ObjectId // Code inspired from mgo/bson ObjectId
@ -177,7 +178,13 @@ func FromString(id string) (ID, error) {
func (id ID) String() string { func (id ID) String() string {
text := make([]byte, encodedLen) text := make([]byte, encodedLen)
encode(text, id[:]) encode(text, id[:])
return string(text) return *(*string)(unsafe.Pointer(&text))
}
// Encode encodes the id using base32 encoding, writing 20 bytes to dst and return it.
func (id ID) Encode(dst []byte) []byte {
encode(dst, id[:])
return dst
} }
// MarshalText implements encoding/text TextMarshaler interface // MarshalText implements encoding/text TextMarshaler interface
@ -192,32 +199,37 @@ func (id ID) MarshalJSON() ([]byte, error) {
if id.IsNil() { if id.IsNil() {
return []byte("null"), nil return []byte("null"), nil
} }
text, err := id.MarshalText() text := make([]byte, encodedLen+2)
return []byte(`"` + string(text) + `"`), err encode(text[1:encodedLen+1], id[:])
text[0], text[encodedLen+1] = '"', '"'
return text, nil
} }
// encode by unrolling the stdlib base32 algorithm + removing all safe checks // encode by unrolling the stdlib base32 algorithm + removing all safe checks
func encode(dst, id []byte) { func encode(dst, id []byte) {
dst[0] = encoding[id[0]>>3] _ = dst[19]
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F] _ = id[11]
dst[2] = encoding[(id[1]>>1)&0x1F]
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
dst[5] = encoding[(id[3]>>2)&0x1F]
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
dst[7] = encoding[id[4]&0x1F]
dst[8] = encoding[id[5]>>3]
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
dst[10] = encoding[(id[6]>>1)&0x1F]
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
dst[13] = encoding[(id[8]>>2)&0x1F]
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
dst[15] = encoding[id[9]&0x1F]
dst[16] = encoding[id[10]>>3]
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
dst[18] = encoding[(id[11]>>1)&0x1F]
dst[19] = encoding[(id[11]<<4)&0x1F] dst[19] = encoding[(id[11]<<4)&0x1F]
dst[18] = encoding[(id[11]>>1)&0x1F]
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
dst[16] = encoding[id[10]>>3]
dst[15] = encoding[id[9]&0x1F]
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
dst[13] = encoding[(id[8]>>2)&0x1F]
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
dst[10] = encoding[(id[6]>>1)&0x1F]
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
dst[8] = encoding[id[5]>>3]
dst[7] = encoding[id[4]&0x1F]
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
dst[5] = encoding[(id[3]>>2)&0x1F]
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
dst[2] = encoding[(id[1]>>1)&0x1F]
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F]
dst[0] = encoding[id[0]>>3]
} }
// UnmarshalText implements encoding/text TextUnmarshaler interface // UnmarshalText implements encoding/text TextUnmarshaler interface
@ -246,18 +258,21 @@ func (id *ID) UnmarshalJSON(b []byte) error {
// decode by unrolling the stdlib base32 algorithm + removing all safe checks // decode by unrolling the stdlib base32 algorithm + removing all safe checks
func decode(id *ID, src []byte) { func decode(id *ID, src []byte) {
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2 _ = src[19]
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4 _ = id[11]
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
id[4] = dec[src[6]]<<5 | dec[src[7]]
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
id[9] = dec[src[14]]<<5 | dec[src[15]]
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4 id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
id[9] = dec[src[14]]<<5 | dec[src[15]]
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
id[4] = dec[src[6]]<<5 | dec[src[7]]
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2
} }
// Time returns the timestamp part of the id. // Time returns the timestamp part of the id.

View File

@ -1,15 +0,0 @@
language: go
go:
- "1.7"
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.12"
- "master"
matrix:
allow_failures:
- go: "master"
script:
- go test -v -race -cpu=1,2,4 -bench . -benchmem ./...
- go test -v -tags binary_log -race -cpu=1,2,4 -bench . -benchmem ./...

View File

@ -18,16 +18,17 @@ Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog)
## Features ## Features
* Blazing fast * [Blazing fast](#benchmarks)
* Low to zero allocation * [Low to zero allocation](#benchmarks)
* Level logging * [Leveled logging](#leveled-logging)
* Sampling * [Sampling](#log-sampling)
* Hooks * [Hooks](#hooks)
* Contextual fields * [Contextual fields](#contextual-logging)
* `context.Context` integration * `context.Context` integration
* `net/http` helpers * [Integration with `net/http`](#integration-with-nethttp)
* JSON and CBOR encoding formats * [JSON and CBOR encoding formats](#binary-encoding)
* Pretty logging for development * [Pretty logging for development](#pretty-logging)
* [Error Logging (with optional Stacktrace)](#error-logging)
## Installation ## Installation
@ -51,8 +52,6 @@ import (
func main() { func main() {
// UNIX Time is faster and smaller than most timestamps // UNIX Time is faster and smaller than most timestamps
// If you set zerolog.TimeFieldFormat to an empty string,
// logs will write with UNIX time
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Print("hello world") log.Print("hello world")
@ -205,6 +204,80 @@ func main() {
// Output: {"time":1494567715,"foo":"bar"} // Output: {"time":1494567715,"foo":"bar"}
``` ```
### Error Logging
You can log errors using the `Err` method
```go
package main
import (
"errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
err := errors.New("seems we have an error here")
log.Error().Err(err).Msg("")
}
// Output: {"level":"error","error":"seems we have an error here","time":1609085256}
```
> The default field name for errors is `error`, you can change this by setting `zerolog.ErrorFieldName` to meet your needs.
#### Error Logging with Stacktrace
Using `github.com/pkg/errors`, you can add a formatted stacktrace to your errors.
```go
package main
import (
"github.com/pkg/errors"
"github.com/rs/zerolog/pkgerrors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
err := outer()
log.Error().Stack().Err(err).Msg("")
}
func inner() error {
return errors.New("seems we have an error here")
}
func middle() error {
err := inner()
if err != nil {
return err
}
return nil
}
func outer() error {
err := middle()
if err != nil {
return err
}
return nil
}
// Output: {"level":"error","stack":[{"func":"inner","line":"20","source":"errors.go"},{"func":"middle","line":"24","source":"errors.go"},{"func":"outer","line":"32","source":"errors.go"},{"func":"main","line":"15","source":"errors.go"},{"func":"main","line":"204","source":"proc.go"},{"func":"goexit","line":"1374","source":"asm_amd64.s"}],"error":"seems we have an error here","time":1609086683}
```
> zerolog.ErrorStackMarshaler must be set in order for the stack to output anything.
#### Logging Fatal Messages #### Logging Fatal Messages
```go ```go
@ -235,6 +308,7 @@ func main() {
> NOTE: Using `Msgf` generates one allocation even when the logger is disabled. > NOTE: Using `Msgf` generates one allocation even when the logger is disabled.
### Create logger instance to manage different outputs ### Create logger instance to manage different outputs
```go ```go
@ -517,6 +591,7 @@ Some settings can be changed and will by applied to all loggers:
### Advanced Fields ### Advanced Fields
* `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name. * `Err`: Takes an `error` and renders it as a string using the `zerolog.ErrorFieldName` field name.
* `Func`: Run a `func` only if the level is enabled.
* `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`. * `Timestamp`: Inserts a timestamp field with `zerolog.TimestampFieldName` field name, formatted using `zerolog.TimeFieldFormat`.
* `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`. * `Time`: Adds a field with time formatted with `zerolog.TimeFieldFormat`.
* `Dur`: Adds a field with `time.Duration`. * `Dur`: Adds a field with `time.Duration`.
@ -541,6 +616,8 @@ with zerolog library is [CSD](https://github.com/toravir/csd/).
## Related Projects ## Related Projects
* [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog` * [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog`
* [overlog](https://github.com/Trendyol/overlog): Implementation of `Mapped Diagnostic Context` interface using `zerolog`
* [zerologr](https://github.com/go-logr/zerologr): Implementation of `logr.LogSink` interface using `zerolog`
## Benchmarks ## Benchmarks

View File

@ -231,3 +231,10 @@ func (a *Array) MACAddr(ha net.HardwareAddr) *Array {
a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha) a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha)
return a return a
} }
// Dict adds the dict Event to the array
func (a *Array) Dict(dict *Event) *Array {
dict.buf = enc.AppendEndMarker(dict.buf)
a.buf = append(enc.AppendArrayDelim(a.buf), dict.buf...)
return a
}

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -57,6 +58,9 @@ type ConsoleWriter struct {
// PartsOrder defines the order of parts in output. // PartsOrder defines the order of parts in output.
PartsOrder []string PartsOrder []string
// PartsExclude defines parts to not display in output.
PartsExclude []string
FormatTimestamp Formatter FormatTimestamp Formatter
FormatLevel Formatter FormatLevel Formatter
FormatCaller Formatter FormatCaller Formatter
@ -208,6 +212,14 @@ func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer
func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) { func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) {
var f Formatter var f Formatter
if w.PartsExclude != nil && len(w.PartsExclude) > 0 {
for _, exclude := range w.PartsExclude {
if exclude == p {
return
}
}
}
switch p { switch p {
case LevelFieldName: case LevelFieldName:
if w.FormatLevel == nil { if w.FormatLevel == nil {
@ -321,19 +333,19 @@ func consoleDefaultFormatLevel(noColor bool) Formatter {
var l string var l string
if ll, ok := i.(string); ok { if ll, ok := i.(string); ok {
switch ll { switch ll {
case "trace": case LevelTraceValue:
l = colorize("TRC", colorMagenta, noColor) l = colorize("TRC", colorMagenta, noColor)
case "debug": case LevelDebugValue:
l = colorize("DBG", colorYellow, noColor) l = colorize("DBG", colorYellow, noColor)
case "info": case LevelInfoValue:
l = colorize("INF", colorGreen, noColor) l = colorize("INF", colorGreen, noColor)
case "warn": case LevelWarnValue:
l = colorize("WRN", colorRed, noColor) l = colorize("WRN", colorRed, noColor)
case "error": case LevelErrorValue:
l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor)
case "fatal": case LevelFatalValue:
l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor)
case "panic": case LevelPanicValue:
l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor) l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor)
default: default:
l = colorize("???", colorBold, noColor) l = colorize("???", colorBold, noColor)
@ -356,10 +368,10 @@ func consoleDefaultFormatCaller(noColor bool) Formatter {
c = cc c = cc
} }
if len(c) > 0 { if len(c) > 0 {
cwd, err := os.Getwd() if cwd, err := os.Getwd(); err == nil {
if err == nil { if rel, err := filepath.Rel(cwd, c); err == nil {
c = strings.TrimPrefix(c, cwd) c = rel
c = strings.TrimPrefix(c, "/") }
} }
c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor) c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor)
} }
@ -386,7 +398,7 @@ func consoleDefaultFormatFieldValue(i interface{}) string {
func consoleDefaultFormatErrFieldName(noColor bool) Formatter { func consoleDefaultFormatErrFieldName(noColor bool) Formatter {
return func(i interface{}) string { return func(i interface{}) string {
return colorize(fmt.Sprintf("%s=", i), colorRed, noColor) return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor)
} }
} }

View File

@ -18,8 +18,10 @@ func (c Context) Logger() Logger {
return c.l return c.l
} }
// Fields is a helper function to use a map to set fields using type assertion. // Fields is a helper function to use a map or slice to set fields using type assertion.
func (c Context) Fields(fields map[string]interface{}) Context { // Only map[string]interface{} and []interface{} are accepted. []interface{} must
// alternate string keys and arbitrary values, and extraneous ones are ignored.
func (c Context) Fields(fields interface{}) Context {
c.l.context = appendFields(c.l.context, fields) c.l.context = appendFields(c.l.context, fields)
return c return c
} }
@ -406,17 +408,9 @@ func (c Context) CallerWithSkipFrameCount(skipFrameCount int) Context {
return c return c
} }
type stackTraceHook struct{}
func (sh stackTraceHook) Run(e *Event, level Level, msg string) {
e.Stack()
}
var sh = stackTraceHook{}
// Stack enables stack trace printing for the error passed to Err(). // Stack enables stack trace printing for the error passed to Err().
func (c Context) Stack() Context { func (c Context) Stack() Context {
c.l = c.l.Hook(sh) c.l.stack = true
return c return c
} }

View File

@ -39,10 +39,13 @@ func (l *Logger) WithContext(ctx context.Context) context.Context {
} }
// Ctx returns the Logger associated with the ctx. If no logger // Ctx returns the Logger associated with the ctx. If no logger
// is associated, a disabled logger is returned. // is associated, DefaultContextLogger is returned, unless DefaultContextLogger
// is nil, in which case a disabled logger is returned.
func Ctx(ctx context.Context) *Logger { func Ctx(ctx context.Context) *Logger {
if l, ok := ctx.Value(ctxKey{}).(*Logger); ok { if l, ok := ctx.Value(ctxKey{}).(*Logger); ok {
return l return l
} else if l = DefaultContextLogger; l != nil {
return l
} }
return disabledLogger return disabledLogger
} }

View File

@ -14,6 +14,13 @@ var (
enc = cbor.Encoder{} enc = cbor.Encoder{}
) )
func init() {
// using closure to reflect the changes at runtime.
cbor.JSONMarshalFunc = func(v interface{}) ([]byte, error) {
return InterfaceMarshalFunc(v)
}
}
func appendJSON(dst []byte, j []byte) []byte { func appendJSON(dst []byte, j []byte) []byte {
return cbor.AppendEmbeddedJSON(dst, j) return cbor.AppendEmbeddedJSON(dst, j)
} }

View File

@ -15,6 +15,13 @@ var (
enc = json.Encoder{} enc = json.Encoder{}
) )
func init() {
// using closure to reflect the changes at runtime.
json.JSONMarshalFunc = func(v interface{}) ([]byte, error) {
return InterfaceMarshalFunc(v)
}
}
func appendJSON(dst []byte, j []byte) []byte { func appendJSON(dst []byte, j []byte) []byte {
return append(dst, j...) return append(dst, j...)
} }

View File

@ -20,12 +20,13 @@ var eventPool = &sync.Pool{
// Event represents a log event. It is instanced by one of the level method of // Event represents a log event. It is instanced by one of the level method of
// Logger and finalized by the Msg or Msgf method. // Logger and finalized by the Msg or Msgf method.
type Event struct { type Event struct {
buf []byte buf []byte
w LevelWriter w LevelWriter
level Level level Level
done func(msg string) done func(msg string)
stack bool // enable error stack trace stack bool // enable error stack trace
ch []Hook // hooks from context ch []Hook // hooks from context
skipFrame int // The number of additional frames to skip when printing the caller.
} }
func putEvent(e *Event) { func putEvent(e *Event) {
@ -62,6 +63,7 @@ func newEvent(w LevelWriter, level Level) *Event {
e.w = w e.w = w
e.level = level e.level = level
e.stack = false e.stack = false
e.skipFrame = 0
return e return e
} }
@ -146,8 +148,10 @@ func (e *Event) msg(msg string) {
} }
} }
// Fields is a helper function to use a map to set fields using type assertion. // Fields is a helper function to use a map or slice to set fields using type assertion.
func (e *Event) Fields(fields map[string]interface{}) *Event { // Only map[string]interface{} and []interface{} are accepted. []interface{} must
// alternate string keys and arbitrary values, and extraneous ones are ignored.
func (e *Event) Fields(fields interface{}) *Event {
if e == nil { if e == nil {
return e return e
} }
@ -205,15 +209,32 @@ func (e *Event) Object(key string, obj LogObjectMarshaler) *Event {
return e return e
} }
e.buf = enc.AppendKey(e.buf, key) e.buf = enc.AppendKey(e.buf, key)
if obj == nil {
e.buf = enc.AppendNil(e.buf)
return e
}
e.appendObject(obj) e.appendObject(obj)
return e return e
} }
// Func allows an anonymous func to run only if the event is enabled.
func (e *Event) Func(f func(e *Event)) *Event {
if e != nil && e.Enabled() {
f(e)
}
return e
}
// EmbedObject marshals an object that implement the LogObjectMarshaler interface. // EmbedObject marshals an object that implement the LogObjectMarshaler interface.
func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event {
if e == nil { if e == nil {
return e return e
} }
if obj == nil {
return e
}
obj.MarshalZerologObject(e) obj.MarshalZerologObject(e)
return e return e
} }
@ -236,18 +257,24 @@ func (e *Event) Strs(key string, vals []string) *Event {
return e return e
} }
// Stringer adds the field key with val.String() (or null if val is nil) to the *Event context. // Stringer adds the field key with val.String() (or null if val is nil)
// to the *Event context.
func (e *Event) Stringer(key string, val fmt.Stringer) *Event { func (e *Event) Stringer(key string, val fmt.Stringer) *Event {
if e == nil { if e == nil {
return e return e
} }
e.buf = enc.AppendStringer(enc.AppendKey(e.buf, key), val)
return e
}
if val != nil { // Stringers adds the field key with vals where each individual val
e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val.String()) // is used as val.String() (or null if val is empty) to the *Event
// context.
func (e *Event) Stringers(key string, vals []fmt.Stringer) *Event {
if e == nil {
return e return e
} }
e.buf = enc.AppendStringers(enc.AppendKey(e.buf, key), vals)
e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), nil)
return e return e
} }
@ -685,6 +712,16 @@ func (e *Event) Interface(key string, i interface{}) *Event {
return e return e
} }
// CallerSkipFrame instructs any future Caller calls to skip the specified number of frames.
// This includes those added via hooks from the context.
func (e *Event) CallerSkipFrame(skip int) *Event {
if e == nil {
return e
}
e.skipFrame += skip
return e
}
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key. // Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
// The argument skip is the number of stack frames to ascend // The argument skip is the number of stack frames to ascend
// Skip If not passed, use the global variable CallerSkipFrameCount // Skip If not passed, use the global variable CallerSkipFrameCount
@ -700,7 +737,7 @@ func (e *Event) caller(skip int) *Event {
if e == nil { if e == nil {
return e return e
} }
_, file, line, ok := runtime.Caller(skip) _, file, line, ok := runtime.Caller(skip + e.skipFrame)
if !ok { if !ok {
return e return e
} }

View File

@ -1,6 +1,7 @@
package zerolog package zerolog
import ( import (
"encoding/json"
"net" "net"
"sort" "sort"
"time" "time"
@ -11,15 +12,36 @@ func isNilValue(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
} }
func appendFields(dst []byte, fields map[string]interface{}) []byte { func appendFields(dst []byte, fields interface{}) []byte {
keys := make([]string, 0, len(fields)) switch fields := fields.(type) {
for key := range fields { case []interface{}:
keys = append(keys, key) if n := len(fields); n&0x1 == 1 { // odd number
fields = fields[:n-1]
}
dst = appendFieldList(dst, fields)
case map[string]interface{}:
keys := make([]string, 0, len(fields))
for key := range fields {
keys = append(keys, key)
}
sort.Strings(keys)
kv := make([]interface{}, 2)
for _, key := range keys {
kv[0], kv[1] = key, fields[key]
dst = appendFieldList(dst, kv)
}
} }
sort.Strings(keys) return dst
for _, key := range keys { }
dst = enc.AppendKey(dst, key)
val := fields[key] func appendFieldList(dst []byte, kvList []interface{}) []byte {
for i, n := 0, len(kvList); i < n; i += 2 {
key, val := kvList[i], kvList[i+1]
if key, ok := key.(string); ok {
dst = enc.AppendKey(dst, key)
} else {
continue
}
if val, ok := val.(LogObjectMarshaler); ok { if val, ok := val.(LogObjectMarshaler); ok {
e := newEvent(nil, 0) e := newEvent(nil, 0)
e.buf = e.buf[:0] e.buf = e.buf[:0]
@ -245,6 +267,8 @@ func appendFields(dst []byte, fields map[string]interface{}) []byte {
dst = enc.AppendIPPrefix(dst, val) dst = enc.AppendIPPrefix(dst, val)
case net.HardwareAddr: case net.HardwareAddr:
dst = enc.AppendMACAddr(dst, val) dst = enc.AppendMACAddr(dst, val)
case json.RawMessage:
dst = appendJSON(dst, val)
default: default:
dst = enc.AppendInterface(dst, val) dst = enc.AppendInterface(dst, val)
} }

View File

@ -1,6 +1,7 @@
package zerolog package zerolog
import ( import (
"encoding/json"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
"time" "time"
@ -27,7 +28,22 @@ var (
// LevelFieldName is the field name used for the level field. // LevelFieldName is the field name used for the level field.
LevelFieldName = "level" LevelFieldName = "level"
// LevelFieldMarshalFunc allows customization of global level field marshaling // LevelTraceValue is the value used for the trace level field.
LevelTraceValue = "trace"
// LevelDebugValue is the value used for the debug level field.
LevelDebugValue = "debug"
// LevelInfoValue is the value used for the info level field.
LevelInfoValue = "info"
// LevelWarnValue is the value used for the warn level field.
LevelWarnValue = "warn"
// LevelErrorValue is the value used for the error level field.
LevelErrorValue = "error"
// LevelFatalValue is the value used for the fatal level field.
LevelFatalValue = "fatal"
// LevelPanicValue is the value used for the panic level field.
LevelPanicValue = "panic"
// LevelFieldMarshalFunc allows customization of global level field marshaling.
LevelFieldMarshalFunc = func(l Level) string { LevelFieldMarshalFunc = func(l Level) string {
return l.String() return l.String()
} }
@ -60,6 +76,10 @@ var (
return err return err
} }
// InterfaceMarshalFunc allows customization of interface marshaling.
// Default: "encoding/json.Marshal"
InterfaceMarshalFunc = json.Marshal
// TimeFieldFormat defines the time format of the Time field type. If set to // TimeFieldFormat defines the time format of the Time field type. If set to
// TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as an UNIX // TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as an UNIX
// timestamp as integer. // timestamp as integer.
@ -80,6 +100,10 @@ var (
// output. If not set, an error is printed on the stderr. This handler must // output. If not set, an error is printed on the stderr. This handler must
// be thread safe and non-blocking. // be thread safe and non-blocking.
ErrorHandler func(err error) ErrorHandler func(err error)
// DefaultContextLogger is returned from Ctx() if there is no logger associated
// with the context.
DefaultContextLogger *Logger
) )
var ( var (

11
vendor/github.com/rs/zerolog/go.mod generated vendored
View File

@ -1,8 +1,11 @@
module github.com/rs/zerolog module github.com/rs/zerolog
go 1.15
require ( require (
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e github.com/coreos/go-systemd/v22 v22.3.2
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1 github.com/rs/xid v1.3.0
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect
golang.org/x/tools v0.1.7
) )

39
vendor/github.com/rs/zerolog/go.sum generated vendored
View File

@ -1,14 +1,37 @@
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -3,7 +3,6 @@ package hlog
import ( import (
"context" "context"
"net"
"net/http" "net/http"
"time" "time"
@ -79,10 +78,10 @@ func RequestHandler(fieldKey string) func(next http.Handler) http.Handler {
func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.Handler { func RemoteAddrHandler(fieldKey string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { if r.RemoteAddr != "" {
log := zerolog.Ctx(r.Context()) log := zerolog.Ctx(r.Context())
log.UpdateContext(func(c zerolog.Context) zerolog.Context { log.UpdateContext(func(c zerolog.Context) zerolog.Context {
return c.Str(fieldKey, host) return c.Str(fieldKey, r.RemoteAddr)
}) })
} }
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
@ -138,6 +137,11 @@ func IDFromCtx(ctx context.Context) (id xid.ID, ok bool) {
return return
} }
// CtxWithID adds the given xid.ID to the context
func CtxWithID(ctx context.Context, id xid.ID) context.Context {
return context.WithValue(ctx, idKey{}, id)
}
// RequestIDHandler returns a handler setting a unique id to the request which can // RequestIDHandler returns a handler setting a unique id to the request which can
// be gathered using IDFromRequest(req). This generated id is added as a field to the // be gathered using IDFromRequest(req). This generated id is added as a field to the
// logger using the passed fieldKey as field name. The id is also added as a response // logger using the passed fieldKey as field name. The id is also added as a response
@ -154,7 +158,7 @@ func RequestIDHandler(fieldKey, headerName string) func(next http.Handler) http.
id, ok := IDFromRequest(r) id, ok := IDFromRequest(r)
if !ok { if !ok {
id = xid.New() id = xid.New()
ctx = context.WithValue(ctx, idKey{}, id) ctx = CtxWithID(ctx, id)
r = r.WithContext(ctx) r = r.WithContext(ctx)
} }
if fieldKey != "" { if fieldKey != "" {

View File

@ -124,11 +124,16 @@ func (f *fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) {
if f.basicWriter.tee != nil { if f.basicWriter.tee != nil {
return io.Copy(&f.basicWriter, r) n, err := io.Copy(&f.basicWriter, r)
f.bytes += int(n)
return n, err
} }
rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
f.basicWriter.maybeWriteHeader() f.basicWriter.maybeWriteHeader()
return rf.ReadFrom(r)
n, err := rf.ReadFrom(r)
f.bytes += int(n)
return n, err
} }
type flushWriter struct { type flushWriter struct {

View File

@ -1,5 +1,13 @@
package cbor package cbor
// JSONMarshalFunc is used to marshal interface to JSON encoded byte slice.
// Making it package level instead of embedded in Encoder brings
// some extra efforts at importing, but avoids value copy when the functions
// of Encoder being invoked.
// DO REMEMBER to set this variable at importing, or
// you might get a nil pointer dereference panic at runtime.
var JSONMarshalFunc func(v interface{}) ([]byte, error)
type Encoder struct{} type Encoder struct{}
// AppendKey adds a key (string) to the binary encoded log message // AppendKey adds a key (string) to the binary encoded log message

View File

@ -1,5 +1,7 @@
package cbor package cbor
import "fmt"
// AppendStrings encodes and adds an array of strings to the dst byte array. // AppendStrings encodes and adds an array of strings to the dst byte array.
func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { func (e Encoder) AppendStrings(dst []byte, vals []string) []byte {
major := majorTypeArray major := majorTypeArray
@ -30,6 +32,31 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
return append(dst, s...) return append(dst, s...)
} }
// AppendStringers encodes and adds an array of Stringer values
// to the dst byte array.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
return e.AppendArrayEnd(e.AppendArrayStart(dst))
}
dst = e.AppendArrayStart(dst)
dst = e.AppendStringer(dst, vals[0])
if len(vals) > 1 {
for _, val := range vals[1:] {
dst = e.AppendStringer(dst, val)
}
}
return e.AppendArrayEnd(dst)
}
// AppendStringer encodes and adds the Stringer value to the dst
// byte array.
func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte {
if val == nil {
return e.AppendNil(dst)
}
return e.AppendString(dst, val.String())
}
// AppendBytes encodes and adds an array of bytes to the dst byte array. // AppendBytes encodes and adds an array of bytes to the dst byte array.
func (Encoder) AppendBytes(dst, s []byte) []byte { func (Encoder) AppendBytes(dst, s []byte) []byte {
major := majorTypeByteString major := majorTypeByteString

View File

@ -1,7 +1,6 @@
package cbor package cbor
import ( import (
"encoding/json"
"fmt" "fmt"
"math" "math"
"net" "net"
@ -432,7 +431,7 @@ func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
// AppendInterface takes an arbitrary object and converts it to JSON and embeds it dst. // AppendInterface takes an arbitrary object and converts it to JSON and embeds it dst.
func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
marshaled, err := json.Marshal(i) marshaled, err := JSONMarshalFunc(i)
if err != nil { if err != nil {
return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err))
} }

View File

@ -1,5 +1,13 @@
package json package json
// JSONMarshalFunc is used to marshal interface to JSON encoded byte slice.
// Making it package level instead of embedded in Encoder brings
// some extra efforts at importing, but avoids value copy when the functions
// of Encoder being invoked.
// DO REMEMBER to set this variable at importing, or
// you might get a nil pointer dereference panic at runtime.
var JSONMarshalFunc func(v interface{}) ([]byte, error)
type Encoder struct{} type Encoder struct{}
// AppendKey appends a new key to the output JSON. // AppendKey appends a new key to the output JSON.

View File

@ -1,6 +1,9 @@
package json package json
import "unicode/utf8" import (
"fmt"
"unicode/utf8"
)
const hex = "0123456789abcdef" const hex = "0123456789abcdef"
@ -60,7 +63,32 @@ func (Encoder) AppendString(dst []byte, s string) []byte {
return append(dst, '"') return append(dst, '"')
} }
// appendStringComplex is used by appendString to take over an in // AppendStringers encodes the provided Stringer list to json and
// appends the encoded Stringer list to the input byte slice.
func (e Encoder) AppendStringers(dst []byte, vals []fmt.Stringer) []byte {
if len(vals) == 0 {
return append(dst, '[', ']')
}
dst = append(dst, '[')
dst = e.AppendStringer(dst, vals[0])
if len(vals) > 1 {
for _, val := range vals[1:] {
dst = e.AppendStringer(append(dst, ','), val)
}
}
return append(dst, ']')
}
// AppendStringer encodes the input Stringer to json and appends the
// encoded Stringer value to the input byte slice.
func (e Encoder) AppendStringer(dst []byte, val fmt.Stringer) []byte {
if val == nil {
return e.AppendInterface(dst, nil)
}
return e.AppendString(dst, val.String())
}
//// appendStringComplex is used by appendString to take over an in
// progress JSON string encoding that encountered a character that needs // progress JSON string encoding that encountered a character that needs
// to be encoded. // to be encoded.
func appendStringComplex(dst []byte, s string, i int) []byte { func appendStringComplex(dst []byte, s string, i int) []byte {

View File

@ -1,7 +1,6 @@
package json package json
import ( import (
"encoding/json"
"fmt" "fmt"
"math" "math"
"net" "net"
@ -350,7 +349,7 @@ func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
return append(dst, '[', ']') return append(dst, '[', ']')
} }
dst = append(dst, '[') dst = append(dst, '[')
dst = appendFloat(dst, vals[0], 32) dst = appendFloat(dst, vals[0], 64)
if len(vals) > 1 { if len(vals) > 1 {
for _, val := range vals[1:] { for _, val := range vals[1:] {
dst = appendFloat(append(dst, ','), val, 64) dst = appendFloat(append(dst, ','), val, 64)
@ -363,7 +362,7 @@ func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte {
// AppendInterface marshals the input interface to a string and // AppendInterface marshals the input interface to a string and
// appends the encoded string to the input byte slice. // appends the encoded string to the input byte slice.
func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte {
marshaled, err := json.Marshal(i) marshaled, err := JSONMarshalFunc(i)
if err != nil { if err != nil {
return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err))
} }

45
vendor/github.com/rs/zerolog/log.go generated vendored
View File

@ -129,28 +129,31 @@ const (
// TraceLevel defines trace log level. // TraceLevel defines trace log level.
TraceLevel Level = -1 TraceLevel Level = -1
// Values less than TraceLevel are handled as numbers.
) )
func (l Level) String() string { func (l Level) String() string {
switch l { switch l {
case TraceLevel: case TraceLevel:
return "trace" return LevelTraceValue
case DebugLevel: case DebugLevel:
return "debug" return LevelDebugValue
case InfoLevel: case InfoLevel:
return "info" return LevelInfoValue
case WarnLevel: case WarnLevel:
return "warn" return LevelWarnValue
case ErrorLevel: case ErrorLevel:
return "error" return LevelErrorValue
case FatalLevel: case FatalLevel:
return "fatal" return LevelFatalValue
case PanicLevel: case PanicLevel:
return "panic" return LevelPanicValue
case Disabled:
return "disabled"
case NoLevel: case NoLevel:
return "" return ""
} }
return "" return strconv.Itoa(int(l))
} }
// ParseLevel converts a level string into a zerolog Level value. // ParseLevel converts a level string into a zerolog Level value.
@ -171,10 +174,19 @@ func ParseLevel(levelStr string) (Level, error) {
return FatalLevel, nil return FatalLevel, nil
case LevelFieldMarshalFunc(PanicLevel): case LevelFieldMarshalFunc(PanicLevel):
return PanicLevel, nil return PanicLevel, nil
case LevelFieldMarshalFunc(Disabled):
return Disabled, nil
case LevelFieldMarshalFunc(NoLevel): case LevelFieldMarshalFunc(NoLevel):
return NoLevel, nil return NoLevel, nil
} }
return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) i, err := strconv.Atoi(levelStr)
if err != nil {
return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr)
}
if i > 127 || i < -128 {
return NoLevel, fmt.Errorf("Out-Of-Bounds Level: '%d', defaulting to NoLevel", i)
}
return Level(i), nil
} }
// A Logger represents an active logging object that generates lines // A Logger represents an active logging object that generates lines
@ -188,6 +200,7 @@ type Logger struct {
sampler Sampler sampler Sampler
context []byte context []byte
hooks []Hook hooks []Hook
stack bool
} }
// New creates a root logger with given output writer. If the output writer implements // New creates a root logger with given output writer. If the output writer implements
@ -218,6 +231,7 @@ func (l Logger) Output(w io.Writer) Logger {
l2 := New(w) l2 := New(w)
l2.level = l.level l2.level = l.level
l2.sampler = l.sampler l2.sampler = l.sampler
l2.stack = l.stack
if len(l.hooks) > 0 { if len(l.hooks) > 0 {
l2.hooks = append(l2.hooks, l.hooks...) l2.hooks = append(l2.hooks, l.hooks...)
} }
@ -371,7 +385,7 @@ func (l *Logger) WithLevel(level Level) *Event {
case Disabled: case Disabled:
return nil return nil
default: default:
panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) return l.newEvent(level, nil)
} }
} }
@ -387,7 +401,7 @@ func (l *Logger) Log() *Event {
// Arguments are handled in the manner of fmt.Print. // Arguments are handled in the manner of fmt.Print.
func (l *Logger) Print(v ...interface{}) { func (l *Logger) Print(v ...interface{}) {
if e := l.Debug(); e.Enabled() { if e := l.Debug(); e.Enabled() {
e.Msg(fmt.Sprint(v...)) e.CallerSkipFrame(1).Msg(fmt.Sprint(v...))
} }
} }
@ -395,7 +409,7 @@ func (l *Logger) Print(v ...interface{}) {
// Arguments are handled in the manner of fmt.Printf. // Arguments are handled in the manner of fmt.Printf.
func (l *Logger) Printf(format string, v ...interface{}) { func (l *Logger) Printf(format string, v ...interface{}) {
if e := l.Debug(); e.Enabled() { if e := l.Debug(); e.Enabled() {
e.Msg(fmt.Sprintf(format, v...)) e.CallerSkipFrame(1).Msg(fmt.Sprintf(format, v...))
} }
} }
@ -407,7 +421,7 @@ func (l Logger) Write(p []byte) (n int, err error) {
// Trim CR added by stdlog. // Trim CR added by stdlog.
p = p[0 : n-1] p = p[0 : n-1]
} }
l.Log().Msg(string(p)) l.Log().CallerSkipFrame(1).Msg(string(p))
return return
} }
@ -419,12 +433,15 @@ func (l *Logger) newEvent(level Level, done func(string)) *Event {
e := newEvent(l.w, level) e := newEvent(l.w, level)
e.done = done e.done = done
e.ch = l.hooks e.ch = l.hooks
if level != NoLevel { if level != NoLevel && LevelFieldName != "" {
e.Str(LevelFieldName, LevelFieldMarshalFunc(level)) e.Str(LevelFieldName, LevelFieldMarshalFunc(level))
} }
if l.context != nil && len(l.context) > 1 { if l.context != nil && len(l.context) > 1 {
e.buf = enc.AppendObjectData(e.buf, l.context) e.buf = enc.AppendObjectData(e.buf, l.context)
} }
if l.stack {
e.Stack()
}
return e return e
} }

View File

@ -3,6 +3,7 @@ package log
import ( import (
"context" "context"
"fmt"
"io" "io"
"os" "os"
@ -114,13 +115,13 @@ func Log() *zerolog.Event {
// Print sends a log event using debug level and no extra field. // Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print. // Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) { func Print(v ...interface{}) {
Logger.Print(v...) Logger.Debug().CallerSkipFrame(1).Msg(fmt.Sprint(v...))
} }
// Printf sends a log event using debug level and no extra field. // Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf. // Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) { func Printf(format string, v ...interface{}) {
Logger.Printf(format, v...) Logger.Debug().CallerSkipFrame(1).Msgf(format, v...)
} }
// Ctx returns the Logger associated with the ctx. If no logger // Ctx returns the Logger associated with the ctx. If no logger

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -38,7 +38,7 @@ func (s RandomSampler) Sample(lvl Level) bool {
} }
// BasicSampler is a sampler that will send every Nth events, regardless of // BasicSampler is a sampler that will send every Nth events, regardless of
// there level. // their level.
type BasicSampler struct { type BasicSampler struct {
N uint32 N uint32
counter uint32 counter uint32

View File

@ -7,6 +7,10 @@ import (
"io" "io"
) )
// See http://cee.mitre.org/language/1.0-beta1/clt.html#syslog
// or https://www.rsyslog.com/json-elasticsearch/
const ceePrefix = "@cee:"
// SyslogWriter is an interface matching a syslog.Writer struct. // SyslogWriter is an interface matching a syslog.Writer struct.
type SyslogWriter interface { type SyslogWriter interface {
io.Writer io.Writer
@ -19,17 +23,34 @@ type SyslogWriter interface {
} }
type syslogWriter struct { type syslogWriter struct {
w SyslogWriter w SyslogWriter
prefix string
} }
// SyslogLevelWriter wraps a SyslogWriter and call the right syslog level // SyslogLevelWriter wraps a SyslogWriter and call the right syslog level
// method matching the zerolog level. // method matching the zerolog level.
func SyslogLevelWriter(w SyslogWriter) LevelWriter { func SyslogLevelWriter(w SyslogWriter) LevelWriter {
return syslogWriter{w} return syslogWriter{w, ""}
}
// SyslogCEEWriter wraps a SyslogWriter with a SyslogLevelWriter that adds a
// MITRE CEE prefix for JSON syslog entries, compatible with rsyslog
// and syslog-ng JSON logging support.
// See https://www.rsyslog.com/json-elasticsearch/
func SyslogCEEWriter(w SyslogWriter) LevelWriter {
return syslogWriter{w, ceePrefix}
} }
func (sw syslogWriter) Write(p []byte) (n int, err error) { func (sw syslogWriter) Write(p []byte) (n int, err error) {
return sw.w.Write(p) var pn int
if sw.prefix != "" {
pn, err = sw.w.Write([]byte(sw.prefix))
if err != nil {
return pn, err
}
}
n, err = sw.w.Write(p)
return pn + n, err
} }
// WriteLevel implements LevelWriter interface. // WriteLevel implements LevelWriter interface.
@ -37,22 +58,23 @@ func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) {
switch level { switch level {
case TraceLevel: case TraceLevel:
case DebugLevel: case DebugLevel:
err = sw.w.Debug(string(p)) err = sw.w.Debug(sw.prefix + string(p))
case InfoLevel: case InfoLevel:
err = sw.w.Info(string(p)) err = sw.w.Info(sw.prefix + string(p))
case WarnLevel: case WarnLevel:
err = sw.w.Warning(string(p)) err = sw.w.Warning(sw.prefix + string(p))
case ErrorLevel: case ErrorLevel:
err = sw.w.Err(string(p)) err = sw.w.Err(sw.prefix + string(p))
case FatalLevel: case FatalLevel:
err = sw.w.Emerg(string(p)) err = sw.w.Emerg(sw.prefix + string(p))
case PanicLevel: case PanicLevel:
err = sw.w.Crit(string(p)) err = sw.w.Crit(sw.prefix + string(p))
case NoLevel: case NoLevel:
err = sw.w.Info(string(p)) err = sw.w.Info(sw.prefix + string(p))
default: default:
panic("invalid level") panic("invalid level")
} }
// Any CEE prefix is not part of the message, so we don't include its length
n = len(p) n = len(p)
return return
} }

View File

@ -1,7 +1,12 @@
package zerolog package zerolog
import ( import (
"bytes"
"io" "io"
"path"
"runtime"
"strconv"
"strings"
"sync" "sync"
) )
@ -56,30 +61,30 @@ type multiLevelWriter struct {
func (t multiLevelWriter) Write(p []byte) (n int, err error) { func (t multiLevelWriter) Write(p []byte) (n int, err error) {
for _, w := range t.writers { for _, w := range t.writers {
n, err = w.Write(p) if _n, _err := w.Write(p); err == nil {
if err != nil { n = _n
return if _err != nil {
} err = _err
if n != len(p) { } else if _n != len(p) {
err = io.ErrShortWrite err = io.ErrShortWrite
return }
} }
} }
return len(p), nil return n, err
} }
func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) {
for _, w := range t.writers { for _, w := range t.writers {
n, err = w.WriteLevel(l, p) if _n, _err := w.WriteLevel(l, p); err == nil {
if err != nil { n = _n
return if _err != nil {
} err = _err
if n != len(p) { } else if _n != len(p) {
err = io.ErrShortWrite err = io.ErrShortWrite
return }
} }
} }
return len(p), nil return n, err
} }
// MultiLevelWriter creates a writer that duplicates its writes to all the // MultiLevelWriter creates a writer that duplicates its writes to all the
@ -96,3 +101,54 @@ func MultiLevelWriter(writers ...io.Writer) LevelWriter {
} }
return multiLevelWriter{lwriters} return multiLevelWriter{lwriters}
} }
// TestingLog is the logging interface of testing.TB.
type TestingLog interface {
Log(args ...interface{})
Logf(format string, args ...interface{})
Helper()
}
// TestWriter is a writer that writes to testing.TB.
type TestWriter struct {
T TestingLog
// Frame skips caller frames to capture the original file and line numbers.
Frame int
}
// NewTestWriter creates a writer that logs to the testing.TB.
func NewTestWriter(t TestingLog) TestWriter {
return TestWriter{T: t}
}
// Write to testing.TB.
func (t TestWriter) Write(p []byte) (n int, err error) {
t.T.Helper()
n = len(p)
// Strip trailing newline because t.Log always adds one.
p = bytes.TrimRight(p, "\n")
// Try to correct the log file and line number to the caller.
if t.Frame > 0 {
_, origFile, origLine, _ := runtime.Caller(1)
_, frameFile, frameLine, ok := runtime.Caller(1 + t.Frame)
if ok {
erase := strings.Repeat("\b", len(path.Base(origFile))+len(strconv.Itoa(origLine))+3)
t.T.Logf("%s%s:%d: %s", erase, path.Base(frameFile), frameLine, p)
return n, err
}
}
t.T.Log(string(p))
return n, err
}
// ConsoleTestWriter creates an option that correctly sets the file frame depth for testing.TB log.
func ConsoleTestWriter(t TestingLog) func(w *ConsoleWriter) {
return func(w *ConsoleWriter) {
w.Out = TestWriter{T: t, Frame: 6}
}
}

5
vendor/modules.txt vendored
View File

@ -8,9 +8,10 @@ github.com/justinas/alice
## explicit ## explicit
# github.com/pmezard/go-difflib v1.0.0 # github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib github.com/pmezard/go-difflib/difflib
# github.com/rs/xid v1.2.1 # github.com/rs/xid v1.3.0
## explicit
github.com/rs/xid github.com/rs/xid
# github.com/rs/zerolog v1.20.0 # github.com/rs/zerolog v1.26.1
## explicit ## explicit
github.com/rs/zerolog github.com/rs/zerolog
github.com/rs/zerolog/hlog github.com/rs/zerolog/hlog