├── .github ├── dependabot.yml └── workflows │ ├── bearer.yml │ ├── codeql.yml │ ├── go.yml │ └── goreleaser.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── _example ├── go.mod ├── go.sum └── main.go ├── go.mod ├── go.sum ├── images └── screenshot.png ├── logger.go ├── logger_test.go └── options.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/bearer.yml: -------------------------------------------------------------------------------- 1 | name: Bearer PR Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | 11 | jobs: 12 | rule_check: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - uses: reviewdog/action-setup@v1 19 | with: 20 | reviewdog_version: latest 21 | 22 | - name: Run Report 23 | id: report 24 | uses: bearer/bearer-action@v2 25 | with: 26 | format: rdjson 27 | output: rd.json 28 | diff: true 29 | 30 | - name: Run reviewdog 31 | if: always() 32 | env: 33 | REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | run: | 35 | cat rd.json | reviewdog -f=rdjson -reporter=github-pr-review 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "41 23 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["go"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v3 55 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version-file: "go.mod" 20 | check-latest: true 21 | - name: Setup golangci-lint 22 | uses: golangci/golangci-lint-action@v8 23 | with: 24 | version: v2.1 25 | args: --verbose 26 | 27 | - name: Bearer 28 | uses: bearer/bearer-action@v2 29 | with: 30 | diff: true 31 | 32 | test: 33 | strategy: 34 | matrix: 35 | os: [ubuntu-latest] 36 | go: [1.23, 1.24] 37 | include: 38 | - os: ubuntu-latest 39 | go-build: ~/.cache/go-build 40 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 41 | runs-on: ${{ matrix.os }} 42 | env: 43 | GO111MODULE: on 44 | GOPROXY: https://proxy.golang.org 45 | steps: 46 | - name: Set up Go ${{ matrix.go }} 47 | uses: actions/setup-go@v5 48 | with: 49 | go-version: ${{ matrix.go }} 50 | 51 | - name: Checkout Code 52 | uses: actions/checkout@v4 53 | with: 54 | ref: ${{ github.ref }} 55 | 56 | - uses: actions/cache@v4 57 | with: 58 | path: | 59 | ${{ matrix.go-build }} 60 | ~/go/pkg/mod 61 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 62 | restore-keys: | 63 | ${{ runner.os }}-go- 64 | - name: Run Tests 65 | run: | 66 | go test -v -covermode=atomic -coverprofile=coverage.out 67 | 68 | - name: Upload coverage to Codecov 69 | uses: codecov/codecov-action@v5 70 | with: 71 | flags: ${{ matrix.os }},go-${{ matrix.go }} 72 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | - name: Run GoReleaser 26 | uses: goreleaser/goreleaser-action@v6 27 | with: 28 | # either 'goreleaser' (default) or 'goreleaser-pro' 29 | distribution: goreleaser 30 | version: latest 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | /.idea -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - bodyclose 6 | - dogsled 7 | - dupl 8 | - errcheck 9 | - exhaustive 10 | - gochecknoinits 11 | - goconst 12 | - gocritic 13 | - gocyclo 14 | - goprintffuncname 15 | - gosec 16 | - govet 17 | - ineffassign 18 | - lll 19 | - misspell 20 | - nakedret 21 | - noctx 22 | - nolintlint 23 | - rowserrcheck 24 | - staticcheck 25 | - unconvert 26 | - unparam 27 | - unused 28 | - whitespace 29 | exclusions: 30 | generated: lax 31 | presets: 32 | - comments 33 | - common-false-positives 34 | - legacy 35 | - std-error-handling 36 | paths: 37 | - third_party$ 38 | - builtin$ 39 | - examples$ 40 | formatters: 41 | enable: 42 | - gofmt 43 | - gofumpt 44 | - goimports 45 | exclusions: 46 | generated: lax 47 | paths: 48 | - third_party$ 49 | - builtin$ 50 | - examples$ 51 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - # If true, skip the build. 3 | # Useful for library projects. 4 | # Default is false 5 | skip: true 6 | 7 | changelog: 8 | use: github 9 | groups: 10 | - title: Features 11 | regexp: "^.*feat[(\\w)]*:+.*$" 12 | order: 0 13 | - title: "Bug fixes" 14 | regexp: "^.*fix[(\\w)]*:+.*$" 15 | order: 1 16 | - title: "Enhancements" 17 | regexp: "^.*chore[(\\w)]*:+.*$" 18 | order: 2 19 | - title: "Refactor" 20 | regexp: "^.*refactor[(\\w)]*:+.*$" 21 | order: 3 22 | - title: "Build process updates" 23 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 24 | order: 4 25 | - title: "Documentation updates" 26 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 27 | order: 4 28 | - title: Others 29 | order: 999 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 gin-contrib 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # logger 2 | 3 | [![Run Tests](https://github.com/gin-contrib/logger/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/gin-contrib/logger/actions/workflows/go.yml) 4 | [![codecov](https://codecov.io/gh/gin-contrib/logger/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/logger) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/logger)](https://goreportcard.com/report/github.com/gin-contrib/logger) 6 | [![GoDoc](https://godoc.org/github.com/gin-contrib/logger?status.svg)](https://godoc.org/github.com/gin-contrib/logger) 7 | [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin) 8 | 9 | Gin middleware/handler to log URL paths using [rs/zerolog](https://github.com/rs/zerolog). 10 | 11 | ## Example 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "net/http" 19 | "regexp" 20 | "time" 21 | 22 | "github.com/gin-contrib/logger" 23 | "github.com/gin-contrib/requestid" 24 | "github.com/gin-gonic/gin" 25 | "github.com/rs/zerolog" 26 | "github.com/rs/zerolog/log" 27 | ) 28 | 29 | var rxURL = regexp.MustCompile(`^/regexp\d*`) 30 | 31 | func main() { 32 | r := gin.New() 33 | 34 | // Add a logger middleware, which: 35 | // - Logs all requests, like a combined access and error log. 36 | // - Logs to stdout. 37 | // r.Use(logger.SetLogger()) 38 | 39 | // Example pong request. 40 | r.GET("/pong", logger.SetLogger(), func(c *gin.Context) { 41 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 42 | }) 43 | 44 | // Example ping request. 45 | r.GET("/ping", logger.SetLogger( 46 | logger.WithSkipPath([]string{"/skip"}), 47 | logger.WithUTC(true), 48 | logger.WithSkipPathRegexps(rxURL), 49 | ), func(c *gin.Context) { 50 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 51 | }) 52 | 53 | // Example skip path request. 54 | r.GET("/skip", logger.SetLogger( 55 | logger.WithSkipPath([]string{"/skip"}), 56 | ), func(c *gin.Context) { 57 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 58 | }) 59 | 60 | // Example skip path request. 61 | r.GET("/regexp1", logger.SetLogger( 62 | logger.WithSkipPathRegexps(rxURL), 63 | ), func(c *gin.Context) { 64 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 65 | }) 66 | 67 | // Example skip path request. 68 | r.GET("/regexp2", logger.SetLogger( 69 | logger.WithSkipPathRegexps(rxURL), 70 | ), func(c *gin.Context) { 71 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 72 | }) 73 | 74 | // add custom fields. 75 | r.GET("/id", requestid.New(requestid.WithGenerator(func() string { 76 | return "foobar" 77 | })), logger.SetLogger( 78 | logger.WithLogger(func(c *gin.Context, l zerolog.Logger) zerolog.Logger { 79 | if trace.SpanFromContext(c.Request.Context()).SpanContext().IsValid() { 80 | l = l.With(). 81 | Str("trace_id", trace.SpanFromContext(c.Request.Context()).SpanContext().TraceID().String()). 82 | Str("span_id", trace.SpanFromContext(c.Request.Context()).SpanContext().SpanID().String()). 83 | Logger() 84 | } 85 | 86 | return l.With(). 87 | Str("id", requestid.Get(c)). 88 | Str("foo", "bar"). 89 | Str("path", c.Request.URL.Path). 90 | Logger() 91 | }), 92 | ), func(c *gin.Context) { 93 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 94 | }) 95 | 96 | // Example of JSON format log 97 | r.GET("/json", logger.SetLogger( 98 | logger.WithLogger(func(_ *gin.Context, l zerolog.Logger) zerolog.Logger { 99 | return l.Output(gin.DefaultWriter).With().Logger() 100 | }), 101 | ), func(c *gin.Context) { 102 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 103 | }) 104 | 105 | // Example of logging data on gin.Context 106 | r.GET("/context", logger.SetLogger( 107 | logger.WithContext(func(c *gin.Context, e *zerolog.Event) *zerolog.Event { 108 | return e.Any("data1", c.MustGet("data1")).Any("data2", c.MustGet("data2")) 109 | }), 110 | ), func(c *gin.Context) { 111 | c.Set("data1", rand.Intn(100)) 112 | c.Set("data2", rand.Intn(100)) 113 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 114 | }) 115 | 116 | // Example of skipper usage 117 | r.GET("/health", logger.SetLogger( 118 | logger.WithSkipper(func(c *gin.Context) bool { 119 | return c.Request.URL.Path == "/health" 120 | }), 121 | ), func(c *gin.Context) { 122 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 123 | }) 124 | 125 | // Example of skipper usage 126 | v1 := r.Group("/v1", logger.SetLogger( 127 | logger.WithSkipper(func(c *gin.Context) bool { 128 | return c.Request.Method == "GET" 129 | }))) 130 | { 131 | v1.GET("/ping", func(c *gin.Context) { 132 | c.String(http.StatusOK, "pong01 "+fmt.Sprint(time.Now().Unix())) 133 | }) 134 | v1.POST("/ping", func(c *gin.Context) { 135 | c.String(http.StatusOK, "pong02 "+fmt.Sprint(time.Now().Unix())) 136 | }) 137 | } 138 | 139 | // Listen and Server in 0.0.0.0:8080 140 | if err := r.Run(":8080"); err != nil { 141 | log.Fatal().Msg("can' start server with 8080 port") 142 | } 143 | } 144 | ``` 145 | 146 | ## Screenshot 147 | 148 | Run app server: 149 | 150 | ```sh 151 | go run _example/main.go 152 | ``` 153 | 154 | Test request: 155 | 156 | ```sh 157 | curl http://localhost:8080/ping 158 | curl http://localhost:8080/pong 159 | curl http://localhost:8080/json 160 | ``` 161 | 162 | ![screenshot](./images/screenshot.png) 163 | -------------------------------------------------------------------------------- /_example/go.mod: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-contrib/logger v0.2.6 7 | github.com/gin-contrib/requestid v1.0.5 8 | github.com/gin-gonic/gin v1.10.0 9 | github.com/rs/zerolog v1.34.0 10 | go.opentelemetry.io/otel/trace v1.35.0 11 | ) 12 | 13 | require ( 14 | github.com/bytedance/sonic v1.13.2 // indirect 15 | github.com/bytedance/sonic/loader v0.2.4 // indirect 16 | github.com/cloudwego/base64x v0.1.5 // indirect 17 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 18 | github.com/gin-contrib/sse v1.0.0 // indirect 19 | github.com/go-playground/locales v0.14.1 // indirect 20 | github.com/go-playground/universal-translator v0.18.1 // indirect 21 | github.com/go-playground/validator/v10 v10.26.0 // indirect 22 | github.com/goccy/go-json v0.10.5 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/json-iterator/go v1.1.12 // indirect 25 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 26 | github.com/leodido/go-urn v1.4.0 // indirect 27 | github.com/mattn/go-colorable v0.1.14 // indirect 28 | github.com/mattn/go-isatty v0.0.20 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 32 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 33 | github.com/ugorji/go/codec v1.2.12 // indirect 34 | go.opentelemetry.io/otel v1.35.0 // indirect 35 | golang.org/x/arch v0.15.0 // indirect 36 | golang.org/x/crypto v0.36.0 // indirect 37 | golang.org/x/net v0.38.0 // indirect 38 | golang.org/x/sys v0.31.0 // indirect 39 | golang.org/x/text v0.23.0 // indirect 40 | google.golang.org/protobuf v1.36.6 // indirect 41 | gopkg.in/yaml.v3 v3.0.1 // indirect 42 | ) 43 | 44 | replace github.com/gin-contrib/logger => ../ 45 | -------------------------------------------------------------------------------- /_example/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 14 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 15 | github.com/gin-contrib/requestid v1.0.5 h1:oye4jWPpTmJHLepQWzb36lFZkKzl+gf8R0K/ButxJUY= 16 | github.com/gin-contrib/requestid v1.0.5/go.mod h1:vkfMTJPx8IBXnavnuQSM9j5isaQfNja1f1hTB516ilU= 17 | github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= 18 | github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= 19 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 20 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 21 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 22 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 23 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 24 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 25 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 26 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 27 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 28 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 29 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 30 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 31 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 32 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 33 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 34 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 35 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 36 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 37 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 38 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 39 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 40 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 41 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 42 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 43 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 44 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 45 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 46 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 47 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 48 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 49 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 50 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 51 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 52 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 53 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 54 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 55 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 56 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 60 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 61 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 62 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 63 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 64 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 65 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 66 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 67 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 68 | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 69 | github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= 70 | github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 71 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 72 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 73 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 74 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 75 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 76 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 77 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 78 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 79 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 80 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 81 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 82 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 83 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 84 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 85 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 86 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 87 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 88 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 89 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 90 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 91 | golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= 92 | golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= 93 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= 94 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 95 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 96 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 97 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 101 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 102 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 103 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 104 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 105 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 106 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 108 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 109 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 110 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 111 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 112 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 113 | -------------------------------------------------------------------------------- /_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "net/http" 8 | "regexp" 9 | "time" 10 | 11 | "github.com/gin-contrib/logger" 12 | "github.com/gin-contrib/requestid" 13 | "github.com/gin-gonic/gin" 14 | "github.com/rs/zerolog" 15 | "github.com/rs/zerolog/log" 16 | "go.opentelemetry.io/otel/trace" 17 | ) 18 | 19 | var rxURL = regexp.MustCompile(`^/regexp\d*`) 20 | 21 | // main initializes the Gin router, sets up various routes with different logging 22 | // configurations, and starts the server on port 8080. The routes demonstrate 23 | // different ways to use the logger middleware, including logging all requests, 24 | // skipping certain paths, adding custom fields, and using JSON format logs. 25 | // 26 | // Routes: 27 | // - GET /pong: Logs request with default logger settings. 28 | // - GET /ping: Logs request with custom settings, including skipping paths and using UTC time. 29 | // - GET /skip: Logs request but skips logging for the /skip path. 30 | // - GET /regexp1: Logs request but skips logging for paths matching the provided regex. 31 | // - GET /regexp2: Logs request but skips logging for paths matching the provided regex. 32 | // - GET /id: Logs request with custom fields including trace ID, span ID, and a custom request ID. 33 | // - GET /json: Logs request in JSON format. 34 | // - GET /health: Skips logging for the /health path. 35 | // - GET /v1/ping: Skips logging for GET requests in the /v1 group. 36 | // - POST /v1/ping: Logs request for POST requests in the /v1 group. 37 | // 38 | // The server listens on 0.0.0.0:8080. 39 | func main() { 40 | r := gin.New() 41 | 42 | // Add a logger middleware, which: 43 | // - Logs all requests, like a combined access and error log. 44 | // - Logs to stdout. 45 | // r.Use(logger.SetLogger()) 46 | 47 | // Example pong request. 48 | r.GET("/pong", logger.SetLogger(), func(c *gin.Context) { 49 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 50 | }) 51 | 52 | // Example ping request. 53 | r.GET("/ping", logger.SetLogger( 54 | logger.WithSkipPath([]string{"/skip"}), 55 | logger.WithUTC(true), 56 | logger.WithSkipPathRegexps(rxURL), 57 | ), func(c *gin.Context) { 58 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 59 | }) 60 | 61 | // Example skip path request. 62 | r.GET("/skip", logger.SetLogger( 63 | logger.WithSkipPath([]string{"/skip"}), 64 | ), func(c *gin.Context) { 65 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 66 | }) 67 | 68 | // Example skip path request. 69 | r.GET("/regexp1", logger.SetLogger( 70 | logger.WithSkipPathRegexps(rxURL), 71 | ), func(c *gin.Context) { 72 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 73 | }) 74 | 75 | // Example skip path request. 76 | r.GET("/regexp2", logger.SetLogger( 77 | logger.WithSkipPathRegexps(rxURL), 78 | ), func(c *gin.Context) { 79 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 80 | }) 81 | 82 | // add custom fields. 83 | r.GET("/id", requestid.New(requestid.WithGenerator(func() string { 84 | return "foobar" 85 | })), logger.SetLogger( 86 | logger.WithLogger(func(c *gin.Context, l zerolog.Logger) zerolog.Logger { 87 | if trace.SpanFromContext(c.Request.Context()).SpanContext().IsValid() { 88 | l = l.With(). 89 | Str("trace_id", trace.SpanFromContext(c.Request.Context()).SpanContext().TraceID().String()). 90 | Str("span_id", trace.SpanFromContext(c.Request.Context()).SpanContext().SpanID().String()). 91 | Logger() 92 | } 93 | 94 | return l.With(). 95 | Str("id", requestid.Get(c)). 96 | Str("foo", "bar"). 97 | Str("path", c.Request.URL.Path). 98 | Logger() 99 | }), 100 | ), func(c *gin.Context) { 101 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 102 | }) 103 | 104 | // Example of JSON format log 105 | r.GET("/json", logger.SetLogger( 106 | logger.WithLogger(func(_ *gin.Context, l zerolog.Logger) zerolog.Logger { 107 | return l.Output(gin.DefaultWriter).With().Logger() 108 | }), 109 | ), func(c *gin.Context) { 110 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 111 | }) 112 | 113 | // Example of a custom message to the log 114 | r.GET("/message", logger.SetLogger( 115 | logger.WithLogger(func(_ *gin.Context, l zerolog.Logger) zerolog.Logger { 116 | return l.Output(gin.DefaultWriter).With().Logger() 117 | }), 118 | logger.WithMessage("Request ended"), 119 | ), func(c *gin.Context) { 120 | c.Error(errors.New("some error has occured here")) 121 | c.Error(errors.New("and some error has occured there")) 122 | c.String(http.StatusBadGateway, "pong "+fmt.Sprint(time.Now().Unix())) 123 | }) 124 | 125 | // Example of specific status levels by http-status-codes 126 | specificLevels := make(map[int]zerolog.Level) 127 | specificLevels[429] = zerolog.TraceLevel 128 | 129 | r.GET("/specific-status-levels", logger.SetLogger( 130 | logger.WithLogger(func(_ *gin.Context, l zerolog.Logger) zerolog.Logger { 131 | return l.Output(gin.DefaultWriter).With().Logger() 132 | }), 133 | logger.WithSpecificLogLevelByStatusCode(specificLevels), 134 | ), func(c *gin.Context) { 135 | //with http-400 StatusBadRequest, it must follow the normal execution, in this case, the log level is warn 136 | //c.String(http.StatusBadRequest, "pong 429 "+fmt.Sprint(time.Now().Unix())) 137 | 138 | //with http-429 StatusTooManyRequests, it must follow the specific log level defined for this status code (trace) 139 | c.String(http.StatusTooManyRequests, "pong 429 "+fmt.Sprint(time.Now().Unix())) 140 | }) 141 | 142 | // Example of skipper usage 143 | r.GET("/health", logger.SetLogger( 144 | logger.WithSkipper(func(c *gin.Context) bool { 145 | return c.Request.URL.Path == "/health" 146 | }), 147 | ), func(c *gin.Context) { 148 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 149 | }) 150 | 151 | // Example of logging data on gin.Context 152 | r.GET("/context", logger.SetLogger( 153 | logger.WithContext(func(c *gin.Context, e *zerolog.Event) *zerolog.Event { 154 | return e.Any("data1", c.MustGet("data1")).Any("data2", c.MustGet("data2")) 155 | }), 156 | ), func(c *gin.Context) { 157 | c.Set("data1", rand.Intn(100)) 158 | c.Set("data2", rand.Intn(100)) 159 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 160 | }) 161 | 162 | // Example of skipper usage 163 | v1 := r.Group("/v1", logger.SetLogger( 164 | logger.WithSkipper(func(c *gin.Context) bool { 165 | return c.Request.Method == "GET" 166 | }))) 167 | { 168 | v1.GET("/ping", func(c *gin.Context) { 169 | c.String(http.StatusOK, "pong01 "+fmt.Sprint(time.Now().Unix())) 170 | }) 171 | v1.POST("/ping", func(c *gin.Context) { 172 | c.String(http.StatusOK, "pong02 "+fmt.Sprint(time.Now().Unix())) 173 | }) 174 | } 175 | 176 | // Listen and Server in 0.0.0.0:8080 177 | if err := r.Run(":8080"); err != nil { 178 | log.Fatal().Msg("can' start server with 8080 port") 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-contrib/logger 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.10.1 7 | github.com/mattn/go-isatty v0.0.20 8 | github.com/rs/zerolog v1.34.0 9 | github.com/stretchr/testify v1.10.0 10 | ) 11 | 12 | require ( 13 | github.com/bytedance/sonic v1.13.2 // indirect 14 | github.com/bytedance/sonic/loader v0.2.4 // indirect 15 | github.com/cloudwego/base64x v0.1.5 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/gabriel-vasile/mimetype v1.4.9 // indirect 18 | github.com/gin-contrib/sse v1.1.0 // indirect 19 | github.com/go-playground/locales v0.14.1 // indirect 20 | github.com/go-playground/universal-translator v0.18.1 // indirect 21 | github.com/go-playground/validator/v10 v10.26.0 // indirect 22 | github.com/goccy/go-json v0.10.5 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 25 | github.com/kr/pretty v0.3.0 // indirect 26 | github.com/leodido/go-urn v1.4.0 // indirect 27 | github.com/mattn/go-colorable v0.1.14 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/rogpeppe/go-internal v1.8.0 // indirect 33 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 34 | github.com/ugorji/go/codec v1.2.12 // indirect 35 | golang.org/x/arch v0.17.0 // indirect 36 | golang.org/x/crypto v0.38.0 // indirect 37 | golang.org/x/net v0.40.0 // indirect 38 | golang.org/x/sys v0.33.0 // indirect 39 | golang.org/x/text v0.25.0 // indirect 40 | google.golang.org/protobuf v1.36.6 // indirect 41 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= 2 | github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= 5 | github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= 7 | github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 10 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= 15 | github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= 16 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 17 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 18 | github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= 19 | github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 20 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 21 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 22 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 23 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 26 | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= 27 | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 28 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 29 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 30 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 31 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 34 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 35 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 36 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 37 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 38 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 39 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 40 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 41 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 42 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 43 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 44 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 45 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 46 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 47 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 48 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 49 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 50 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 51 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 52 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 53 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 54 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 55 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 56 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 57 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 60 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 61 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 62 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 63 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 64 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 65 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 66 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 67 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 68 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 69 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 70 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 71 | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 72 | github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= 73 | github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 74 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 75 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 76 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 77 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 78 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 79 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 80 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 81 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 82 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 83 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 84 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 85 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 86 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 87 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 88 | golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= 89 | golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 90 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 91 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 92 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 93 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 94 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 97 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 98 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 99 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 100 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 101 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 102 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 103 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 104 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 105 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 108 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 109 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 110 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 111 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 112 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 113 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 114 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gin-contrib/logger/c19022ff73a80a919c1560f5aa3b1820c427e155/images/screenshot.png -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "os" 7 | "regexp" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/mattn/go-isatty" 12 | "github.com/rs/zerolog" 13 | ) 14 | 15 | /* 16 | Fn is a function type that takes a gin.Context and a zerolog.Logger as parameters, 17 | and returns a zerolog.Logger. It is typically used to modify or enhance the logger 18 | within the context of a Gin HTTP request. 19 | */ 20 | type Fn func(*gin.Context, zerolog.Logger) zerolog.Logger 21 | 22 | /* 23 | EventFn is a function type that takes a gin.Context and a zerolog.Event as parameters, 24 | and returns a zerolog.Event. It is typically used to modify or enhance the event 25 | within the context of a Gin HTTP request. 26 | */ 27 | type EventFn func(*gin.Context, *zerolog.Event) *zerolog.Event 28 | 29 | /* 30 | Skipper defines a function to skip middleware. It takes a gin.Context as input 31 | and returns a boolean indicating whether to skip the middleware for the given context. 32 | */ 33 | type Skipper func(c *gin.Context) bool 34 | 35 | /* 36 | config holds the configuration for the logger middleware. 37 | */ 38 | type config struct { 39 | /* 40 | logger is a function that defines the logging behavior. 41 | */ 42 | logger Fn 43 | /* 44 | context is a function that defines the logging behavior of gin.Context data 45 | */ 46 | context EventFn 47 | /* 48 | utc is a boolean stating whether to use UTC time zone or local. 49 | */ 50 | utc bool 51 | /* 52 | skipPath is a list of paths to be skipped from logging. 53 | */ 54 | skipPath []string 55 | /* 56 | skipPathRegexps is a list of regular expressions to match paths to be skipped from logging. 57 | */ 58 | skipPathRegexps []*regexp.Regexp 59 | /* 60 | skip is a Skipper that indicates which logs should not be written. Optional. 61 | */ 62 | skip Skipper 63 | /* 64 | output is a writer where logs are written. Optional. Default value is gin.DefaultWriter. 65 | */ 66 | output io.Writer 67 | /* 68 | defaultLevel is the log level used for requests with status code < 400. 69 | */ 70 | defaultLevel zerolog.Level 71 | /* 72 | clientErrorLevel is the log level used for requests with status code between 400 and 499. 73 | */ 74 | clientErrorLevel zerolog.Level 75 | /* 76 | serverErrorLevel is the log level used for requests with status code >= 500. 77 | */ 78 | serverErrorLevel zerolog.Level 79 | /* 80 | pathLevels is a map of specific paths to log levels for requests with status code < 400. 81 | */ 82 | pathLevels map[string]zerolog.Level 83 | /* 84 | message is a custom string that sets a log-message when http-request has finished 85 | */ 86 | message string 87 | /* 88 | specificLevelByStatusCode is a map of specific status codes to log levels every request 89 | */ 90 | specificLevelByStatusCode map[int]zerolog.Level 91 | } 92 | 93 | const loggerKey = "_gin-contrib/logger_" 94 | 95 | var isTerm = isatty.IsTerminal(os.Stdout.Fd()) 96 | 97 | /* 98 | SetLogger returns a gin.HandlerFunc (middleware) that logs requests using zerolog. 99 | It accepts a variadic number of Option functions to customize the logger's behavior. 100 | 101 | The logger configuration includes: 102 | - defaultLevel: the default logging level (default: zerolog.InfoLevel). 103 | - clientErrorLevel: the logging level for client errors (default: zerolog.WarnLevel). 104 | - serverErrorLevel: the logging level for server errors (default: zerolog.ErrorLevel). 105 | - output: the output writer for the logger (default: gin.DefaultWriter). 106 | - skipPath: a list of paths to skip logging. 107 | - skipPathRegexps: a list of regular expressions to skip logging for matching paths. 108 | - logger: a custom logger function to use instead of the default logger. 109 | 110 | The middleware logs the following request details: 111 | - method: the HTTP method of the request. 112 | - path: the URL path of the request. 113 | - ip: the client's IP address. 114 | - user_agent: the User-Agent header of the request. 115 | - status: the HTTP status code of the response. 116 | - latency: the time taken to process the request. 117 | - body_size: the size of the response body. 118 | 119 | The logging level for each request is determined based on the response status code: 120 | - clientErrorLevel for 4xx status codes. 121 | - serverErrorLevel for 5xx status codes. 122 | - defaultLevel for other status codes. 123 | - Custom levels can be set for specific paths using the pathLevels configuration. 124 | */ 125 | func SetLogger(opts ...Option) gin.HandlerFunc { 126 | cfg := &config{ 127 | defaultLevel: zerolog.InfoLevel, 128 | clientErrorLevel: zerolog.WarnLevel, 129 | serverErrorLevel: zerolog.ErrorLevel, 130 | output: os.Stderr, 131 | message: "Request", 132 | } 133 | 134 | // Apply each option to the config 135 | for _, o := range opts { 136 | o.apply(cfg) 137 | } 138 | 139 | // Create a set of paths to skip logging 140 | skip := make(map[string]struct{}, len(cfg.skipPath)) 141 | for _, path := range cfg.skipPath { 142 | skip[path] = struct{}{} 143 | } 144 | 145 | // Initialize the base logger 146 | l := zerolog.New(cfg.output). 147 | Output(zerolog.ConsoleWriter{Out: cfg.output, NoColor: !isTerm}). 148 | With(). 149 | Timestamp(). 150 | Logger() 151 | 152 | return func(c *gin.Context) { 153 | rl := l 154 | if cfg.logger != nil { 155 | rl = cfg.logger(c, l) 156 | } 157 | 158 | start := time.Now() 159 | path := c.Request.URL.Path 160 | if raw := c.Request.URL.RawQuery; raw != "" { 161 | path += "?" + raw 162 | } 163 | 164 | track := !shouldSkipLogging(path, skip, cfg, c) 165 | 166 | contextLogger := rl 167 | if track { 168 | contextLogger = rl.With(). 169 | Str("method", c.Request.Method). 170 | Str("path", path). 171 | Str("ip", c.ClientIP()). 172 | Str("user_agent", c.Request.UserAgent()). 173 | Logger() 174 | } 175 | c.Set(loggerKey, contextLogger) 176 | 177 | c.Next() 178 | 179 | if track { 180 | end := time.Now() 181 | if cfg.utc { 182 | end = end.UTC() 183 | } 184 | latency := end.Sub(start) 185 | 186 | msg := cfg.message 187 | if len(c.Errors) > 0 { 188 | msg += " with errors: " + c.Errors.String() 189 | } 190 | 191 | evt := getLogEvent(rl, cfg, c, path) 192 | 193 | if cfg.context != nil { 194 | evt = cfg.context(c, evt) 195 | } 196 | 197 | evt. 198 | Int("status", c.Writer.Status()). 199 | Str("method", c.Request.Method). 200 | Str("path", path). 201 | Str("ip", c.ClientIP()). 202 | Dur("latency", latency). 203 | Str("user_agent", c.Request.UserAgent()). 204 | Int("body_size", c.Writer.Size()). 205 | Msg(msg) 206 | } 207 | } 208 | } 209 | 210 | /* 211 | ParseLevel parses a string representation of a log level and returns the corresponding zerolog.Level. 212 | It takes a single argument: 213 | - levelStr: a string representing the log level (e.g., "debug", "info", "warn", "error"). 214 | 215 | It returns: 216 | - zerolog.Level: the parsed log level. 217 | - error: an error if the log level string is invalid. 218 | */ 219 | func ParseLevel(levelStr string) (zerolog.Level, error) { 220 | return zerolog.ParseLevel(levelStr) 221 | } 222 | 223 | func shouldSkipLogging(path string, skip map[string]struct{}, cfg *config, c *gin.Context) bool { 224 | if _, ok := skip[path]; ok || (cfg.skip != nil && cfg.skip(c)) { 225 | return true 226 | } 227 | for _, reg := range cfg.skipPathRegexps { 228 | if reg.MatchString(path) { 229 | return true 230 | } 231 | } 232 | return false 233 | } 234 | 235 | func getLogEvent(rl zerolog.Logger, cfg *config, c *gin.Context, path string) *zerolog.Event { 236 | level, hasLevel := cfg.pathLevels[path] 237 | specificLogLevel, hasSpecificLogLevel := cfg.specificLevelByStatusCode[c.Writer.Status()] 238 | 239 | switch { 240 | case hasSpecificLogLevel: 241 | return rl.WithLevel(specificLogLevel).Ctx(c) 242 | case c.Writer.Status() >= http.StatusBadRequest && c.Writer.Status() < http.StatusInternalServerError: 243 | return rl.WithLevel(cfg.clientErrorLevel).Ctx(c) 244 | case c.Writer.Status() >= http.StatusInternalServerError: 245 | return rl.WithLevel(cfg.serverErrorLevel).Ctx(c) 246 | case hasLevel: 247 | return rl.WithLevel(level).Ctx(c) 248 | default: 249 | return rl.WithLevel(cfg.defaultLevel).Ctx(c) 250 | } 251 | } 252 | 253 | /* 254 | GetLogger retrieves the zerolog.Logger instance from the given gin.Context. 255 | It assumes that the logger has been previously set in the context with the key loggerKey. 256 | If the logger is not found, it will panic. 257 | 258 | Parameters: 259 | 260 | c - the gin.Context from which to retrieve the logger. 261 | 262 | Returns: 263 | 264 | zerolog.Logger - the logger instance stored in the context. 265 | */ 266 | func Get(c *gin.Context) zerolog.Logger { 267 | return c.MustGet(loggerKey).(zerolog.Logger) 268 | } 269 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "net/http" 9 | "net/http/httptest" 10 | "regexp" 11 | "strings" 12 | "sync" 13 | "testing" 14 | 15 | "github.com/gin-gonic/gin" 16 | "github.com/rs/zerolog" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | type header struct { 21 | Key string 22 | Value string 23 | } 24 | 25 | func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { 26 | req := httptest.NewRequest(method, path, nil) 27 | for _, h := range headers { 28 | req.Header.Add(h.Key, h.Value) 29 | } 30 | w := httptest.NewRecorder() 31 | r.ServeHTTP(w, req) 32 | return w 33 | } 34 | 35 | func TestLogger(t *testing.T) { 36 | buffer := new(bytes.Buffer) 37 | gin.SetMode(gin.ReleaseMode) 38 | r := gin.New() 39 | r.Use(SetLogger(WithWriter(buffer))) 40 | r.GET("/example", func(c *gin.Context) {}) 41 | r.POST("/example", func(c *gin.Context) { 42 | c.String(http.StatusBadRequest, "ok") 43 | }) 44 | r.PUT("/example", func(c *gin.Context) { 45 | c.String(http.StatusBadGateway, "ok") 46 | }) 47 | r.DELETE("/example", func(c *gin.Context) {}) 48 | r.PATCH("/example", func(c *gin.Context) {}) 49 | r.HEAD("/example", func(c *gin.Context) {}) 50 | r.OPTIONS("/example", func(c *gin.Context) {}) 51 | 52 | resp := performRequest(r, "GET", "/example?a=100", header{"X-Request-Id", "123"}) 53 | assert.Equal(t, 200, resp.Code) 54 | assert.Contains(t, buffer.String(), "200") 55 | assert.Contains(t, buffer.String(), "GET") 56 | assert.Contains(t, buffer.String(), "/example") 57 | assert.Contains(t, buffer.String(), "path=/example?a=100") 58 | 59 | buffer.Reset() 60 | performRequest(r, "POST", "/example?a=100") 61 | assert.Contains(t, buffer.String(), "400") 62 | assert.Contains(t, buffer.String(), "POST") 63 | assert.Contains(t, buffer.String(), "/example") 64 | assert.Contains(t, buffer.String(), "WRN") 65 | assert.Contains(t, buffer.String(), "path=/example?a=100") 66 | 67 | buffer.Reset() 68 | performRequest(r, "PUT", "/example?a=100") 69 | assert.Contains(t, buffer.String(), "502") 70 | assert.Contains(t, buffer.String(), "PUT") 71 | assert.Contains(t, buffer.String(), "/example") 72 | assert.Contains(t, buffer.String(), "ERR") 73 | assert.Contains(t, buffer.String(), "path=/example?a=100") 74 | 75 | buffer.Reset() 76 | r.GET("/example-with-additional-log", func(ctx *gin.Context) { 77 | l := Get(ctx) 78 | l.Info().Msg("additional log") 79 | }) 80 | performRequest(r, "GET", "/example-with-additional-log") 81 | assert.Contains(t, buffer.String(), "200") 82 | assert.Contains(t, buffer.String(), "GET") 83 | assert.Contains(t, buffer.String(), "/example-with-additional-log") 84 | assert.Contains(t, buffer.String(), "additional log") 85 | } 86 | 87 | func TestLoggerWithLogger(t *testing.T) { 88 | buffer := new(bytes.Buffer) 89 | gin.SetMode(gin.ReleaseMode) 90 | r := gin.New() 91 | r.GET("/example", SetLogger( 92 | WithWriter(buffer), 93 | WithUTC(true), 94 | WithLogger(Fn(func(c *gin.Context, l zerolog.Logger) zerolog.Logger { 95 | return l.With(). 96 | Str("foo", "bar"). 97 | Str("path", c.Request.URL.Path). 98 | Logger() 99 | })), 100 | ), func(c *gin.Context) {}) 101 | 102 | r.GET("/example2", SetLogger( 103 | WithWriter(buffer), 104 | WithSkipPath([]string{"/example2"}), 105 | ), func(c *gin.Context) {}) 106 | 107 | rxURL := regexp.MustCompile(`^/regexp\d*`) 108 | 109 | r.GET("/regexp01", SetLogger( 110 | WithWriter(buffer), 111 | WithSkipPathRegexps(rxURL), 112 | ), func(c *gin.Context) {}) 113 | 114 | r.GET("/regexp02", SetLogger( 115 | WithWriter(buffer), 116 | WithSkipPathRegexps(rxURL), 117 | ), func(c *gin.Context) {}) 118 | 119 | performRequest(r, "GET", "/example?a=100") 120 | assert.Contains(t, buffer.String(), "foo") 121 | assert.Contains(t, buffer.String(), "bar") 122 | assert.Contains(t, buffer.String(), "/example") 123 | 124 | buffer.Reset() 125 | performRequest(r, "GET", "/example2") 126 | assert.NotContains(t, buffer.String(), "foo") 127 | assert.NotContains(t, buffer.String(), "bar") 128 | assert.NotContains(t, buffer.String(), "/example2") 129 | 130 | buffer.Reset() 131 | performRequest(r, "GET", "/regexp01") 132 | assert.NotContains(t, buffer.String(), "/regexp01") 133 | 134 | buffer.Reset() 135 | performRequest(r, "GET", "/regexp02") 136 | assert.NotContains(t, buffer.String(), "/regexp02") 137 | } 138 | 139 | func TestLoggerWithLevels(t *testing.T) { 140 | buffer := new(bytes.Buffer) 141 | gin.SetMode(gin.ReleaseMode) 142 | r := gin.New() 143 | r.Use(SetLogger( 144 | WithWriter(buffer), 145 | WithDefaultLevel(zerolog.DebugLevel), 146 | WithClientErrorLevel(zerolog.ErrorLevel), 147 | WithServerErrorLevel(zerolog.FatalLevel), 148 | )) 149 | r.GET("/example", func(c *gin.Context) {}) 150 | r.POST("/example", func(c *gin.Context) { 151 | c.String(http.StatusBadRequest, "ok") 152 | }) 153 | r.PUT("/example", func(c *gin.Context) { 154 | c.String(http.StatusBadGateway, "ok") 155 | }) 156 | 157 | performRequest(r, "GET", "/example?a=100") 158 | assert.Contains(t, buffer.String(), "DBG") 159 | 160 | buffer.Reset() 161 | performRequest(r, "POST", "/example?a=100") 162 | assert.Contains(t, buffer.String(), "ERR") 163 | 164 | buffer.Reset() 165 | performRequest(r, "PUT", "/example?a=100") 166 | assert.Contains(t, buffer.String(), "FTL") 167 | } 168 | 169 | type concurrentBuffer struct { 170 | mu sync.Mutex 171 | b bytes.Buffer 172 | } 173 | 174 | func (b *concurrentBuffer) Write(p []byte) (n int, err error) { 175 | b.mu.Lock() 176 | defer b.mu.Unlock() 177 | return b.b.Write(p) 178 | } 179 | 180 | func TestCustomLoggerIssue68(t *testing.T) { 181 | buffer := new(concurrentBuffer) 182 | gin.SetMode(gin.ReleaseMode) 183 | r := gin.New() 184 | // Use JSON logger as it will explicitly print keys multiple times if they are added multiple times, 185 | // which may happen if there are mutations to the logger. 186 | r.Use(SetLogger( 187 | WithLogger(func(_ *gin.Context, l zerolog.Logger) zerolog.Logger { return l.Output(buffer).With().Logger() }), 188 | WithDefaultLevel(zerolog.DebugLevel), 189 | WithClientErrorLevel(zerolog.ErrorLevel), 190 | WithServerErrorLevel(zerolog.FatalLevel), 191 | )) 192 | r.GET("/example", func(c *gin.Context) {}) 193 | 194 | // concurrent requests should only have their info logged once 195 | var wg sync.WaitGroup 196 | for i := 0; i < 10; i++ { 197 | wg.Add(1) 198 | req := fmt.Sprintf("/example?a=%d", i) 199 | go func() { 200 | defer wg.Done() 201 | performRequest(r, "GET", req) 202 | }() 203 | } 204 | wg.Wait() 205 | 206 | bs := buffer.b.String() 207 | for i := 0; i < 10; i++ { 208 | // should contain each request log exactly once 209 | msg := fmt.Sprintf("/example?a=%d", i) 210 | if assert.Contains(t, bs, msg) { 211 | assert.Equal(t, strings.Index(bs, msg), strings.LastIndex(bs, msg)) 212 | } 213 | } 214 | } 215 | 216 | func TestLoggerParseLevel(t *testing.T) { 217 | type args struct { 218 | levelStr string 219 | } 220 | tests := []struct { 221 | name string 222 | args args 223 | want zerolog.Level 224 | wantErr bool 225 | }{ 226 | {"trace", args{"trace"}, zerolog.TraceLevel, false}, 227 | {"debug", args{"debug"}, zerolog.DebugLevel, false}, 228 | {"info", args{"info"}, zerolog.InfoLevel, false}, 229 | {"warn", args{"warn"}, zerolog.WarnLevel, false}, 230 | {"error", args{"error"}, zerolog.ErrorLevel, false}, 231 | {"fatal", args{"fatal"}, zerolog.FatalLevel, false}, 232 | {"panic", args{"panic"}, zerolog.PanicLevel, false}, 233 | {"disabled", args{"disabled"}, zerolog.Disabled, false}, 234 | {"nolevel", args{""}, zerolog.NoLevel, false}, 235 | {"-1", args{"-1"}, zerolog.TraceLevel, false}, 236 | {"-2", args{"-2"}, zerolog.Level(-2), false}, 237 | {"-3", args{"-3"}, zerolog.Level(-3), false}, 238 | } 239 | for _, tt := range tests { 240 | t.Run(tt.name, func(t *testing.T) { 241 | got, err := ParseLevel(tt.args.levelStr) 242 | if (err != nil) != tt.wantErr { 243 | t.Errorf("ParseLevel() error = %v, wantErr %v", err, tt.wantErr) 244 | return 245 | } 246 | if got != tt.want { 247 | t.Errorf("ParseLevel() got = %v, want %v", got, tt.want) 248 | } 249 | }) 250 | } 251 | } 252 | 253 | func TestLoggerCustomLevel(t *testing.T) { 254 | buffer := new(bytes.Buffer) 255 | gin.SetMode(gin.ReleaseMode) 256 | r := gin.New() 257 | r.Use(SetLogger( 258 | WithWriter(buffer), 259 | WithDefaultLevel(zerolog.InfoLevel), 260 | WithClientErrorLevel(zerolog.ErrorLevel), 261 | WithServerErrorLevel(zerolog.FatalLevel), 262 | WithPathLevel(map[string]zerolog.Level{ 263 | "/example": zerolog.DebugLevel, 264 | }), 265 | )) 266 | r.GET("/example", func(c *gin.Context) {}) 267 | r.POST("/example", func(c *gin.Context) { 268 | c.String(http.StatusBadRequest, "ok") 269 | }) 270 | r.PUT("/example", func(c *gin.Context) { 271 | c.String(http.StatusBadGateway, "ok") 272 | }) 273 | r.GET("/example2", func(c *gin.Context) {}) 274 | 275 | performRequest(r, "GET", "/example") 276 | assert.Contains(t, buffer.String(), "DBG") 277 | 278 | buffer.Reset() 279 | performRequest(r, "GET", "/example2") 280 | assert.Contains(t, buffer.String(), "INF") 281 | 282 | buffer.Reset() 283 | performRequest(r, "POST", "/example") 284 | assert.Contains(t, buffer.String(), "ERR") 285 | 286 | buffer.Reset() 287 | performRequest(r, "PUT", "/example") 288 | assert.Contains(t, buffer.String(), "FTL") 289 | } 290 | 291 | func TestLoggerSkipper(t *testing.T) { 292 | buffer := new(bytes.Buffer) 293 | gin.SetMode(gin.ReleaseMode) 294 | r := gin.New() 295 | r.Use(SetLogger( 296 | WithWriter(buffer), 297 | WithSkipper(func(c *gin.Context) bool { 298 | return c.Request.URL.Path == "/example2" 299 | }), 300 | )) 301 | r.GET("/example", func(c *gin.Context) {}) 302 | r.GET("/example2", func(c *gin.Context) {}) 303 | 304 | performRequest(r, "GET", "/example") 305 | assert.Contains(t, buffer.String(), "GET") 306 | assert.Contains(t, buffer.String(), "/example") 307 | 308 | buffer.Reset() 309 | performRequest(r, "GET", "/example2") 310 | assert.NotContains(t, buffer.String(), "GET") 311 | assert.NotContains(t, buffer.String(), "/example2") 312 | } 313 | 314 | func TestLoggerCustomMessage(t *testing.T) { 315 | buffer := new(bytes.Buffer) 316 | gin.SetMode(gin.ReleaseMode) 317 | r := gin.New() 318 | r.Use(SetLogger( 319 | WithWriter(buffer), 320 | WithMessage("Custom message"), 321 | )) 322 | r.GET("/example", func(c *gin.Context) {}) 323 | 324 | performRequest(r, "GET", "/example") 325 | assert.Contains(t, buffer.String(), "Custom message") 326 | } 327 | 328 | func TestLoggerCustomMessageWithErrors(t *testing.T) { 329 | buffer := new(bytes.Buffer) 330 | gin.SetMode(gin.ReleaseMode) 331 | r := gin.New() 332 | r.Use(SetLogger( 333 | WithWriter(buffer), 334 | WithMessage("Custom message"), 335 | )) 336 | r.GET("/example", func(c *gin.Context) { 337 | _ = c.Error(errors.New("custom error")) 338 | }) 339 | 340 | performRequest(r, "GET", "/example") 341 | assert.Contains(t, buffer.String(), "Custom message with errors: ") 342 | assert.Equal(t, strings.Count(buffer.String(), " with errors: "), 1) 343 | 344 | // Reset and test again to make sure we're not appending to the existing error message 345 | buffer.Reset() 346 | performRequest(r, "GET", "/example") 347 | assert.Contains(t, buffer.String(), "Custom message with errors: ") 348 | assert.Equal(t, strings.Count(buffer.String(), " with errors: "), 1) 349 | } 350 | 351 | func BenchmarkLogger(b *testing.B) { 352 | gin.SetMode(gin.ReleaseMode) 353 | r := gin.New() 354 | r.Use(SetLogger(WithDefaultLevel(zerolog.Disabled))) 355 | r.GET("/", func(ctx *gin.Context) { 356 | ctx.Data(200, "text/plain", []byte("all good")) 357 | }) 358 | 359 | b.ReportAllocs() 360 | b.ResetTimer() 361 | 362 | b.RunParallel(func(pb *testing.PB) { 363 | req, err := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 364 | if err != nil { 365 | b.Errorf("NewRequestWithContext() error = %v", err) 366 | return 367 | } 368 | w := httptest.NewRecorder() 369 | 370 | for pb.Next() { 371 | r.ServeHTTP(w, req) 372 | } 373 | }) 374 | } 375 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "io" 5 | "regexp" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/rs/zerolog" 9 | ) 10 | 11 | /* 12 | Option is an interface that defines a method to apply a configuration 13 | to a given config instance. Implementations of this interface can be 14 | used to modify the configuration settings of the logger. 15 | */ 16 | type Option interface { 17 | apply(*config) 18 | } 19 | 20 | /* 21 | Ensures that optionFunc implements the Option interface at compile time. 22 | If optionFunc does not implement Option, a compile-time error will occur. 23 | */ 24 | var _ Option = (*optionFunc)(nil) 25 | 26 | type optionFunc func(*config) 27 | 28 | func (o optionFunc) apply(c *config) { 29 | o(c) 30 | } 31 | 32 | /* 33 | WithLogger returns an Option that sets the logger function in the config. 34 | The logger function is a function that takes a *gin.Context and a zerolog.Logger, 35 | and returns a zerolog.Logger. This function is typically used to modify or enhance 36 | the logger within the context of a Gin HTTP request. 37 | 38 | Parameters: 39 | 40 | fn - A function that takes a *gin.Context and a zerolog.Logger, and returns a zerolog.Logger. 41 | 42 | Returns: 43 | 44 | Option - An option that sets the logger function in the config. 45 | */ 46 | func WithLogger(fn func(*gin.Context, zerolog.Logger) zerolog.Logger) Option { 47 | return optionFunc(func(c *config) { 48 | c.logger = fn 49 | }) 50 | } 51 | 52 | /* 53 | WithSkipPathRegexps returns an Option that sets the skipPathRegexps field in the config. 54 | The skipPathRegexps field is a list of regular expressions that match paths to be skipped from logging. 55 | 56 | Parameters: 57 | 58 | regs - A list of regular expressions to match paths to be skipped from logging. 59 | 60 | Returns: 61 | 62 | Option - An option that sets the skipPathRegexps field in the config. 63 | */ 64 | func WithSkipPathRegexps(regs ...*regexp.Regexp) Option { 65 | return optionFunc(func(c *config) { 66 | if len(regs) == 0 { 67 | return 68 | } 69 | 70 | c.skipPathRegexps = append(c.skipPathRegexps, regs...) 71 | }) 72 | } 73 | 74 | /* 75 | WithUTC returns an Option that sets the utc field in the config. 76 | The utc field is a boolean that indicates whether to use UTC time zone or local time zone. 77 | 78 | Parameters: 79 | 80 | s - A boolean indicating whether to use UTC time zone. 81 | 82 | Returns: 83 | 84 | Option - An option that sets the utc field in the config. 85 | */ 86 | func WithUTC(s bool) Option { 87 | return optionFunc(func(c *config) { 88 | c.utc = s 89 | }) 90 | } 91 | 92 | /* 93 | WithSkipPath returns an Option that sets the skipPath field in the config. 94 | The skipPath field is a list of URL paths to be skipped from logging. 95 | 96 | Parameters: 97 | 98 | s - A list of URL paths to be skipped from logging. 99 | 100 | Returns: 101 | 102 | Option - An option that sets the skipPath field in the config. 103 | */ 104 | func WithSkipPath(s []string) Option { 105 | return optionFunc(func(c *config) { 106 | c.skipPath = s 107 | }) 108 | } 109 | 110 | /* 111 | WithPathLevel returns an Option that sets the pathLevels field in the config. 112 | The pathLevels field is a map that associates specific URL paths with logging levels. 113 | 114 | Parameters: 115 | 116 | m - A map where the keys are URL paths and the values are zerolog.Level. 117 | 118 | Returns: 119 | 120 | Option - An option that sets the pathLevels field in the config. 121 | */ 122 | func WithPathLevel(m map[string]zerolog.Level) Option { 123 | return optionFunc(func(c *config) { 124 | c.pathLevels = m 125 | }) 126 | } 127 | 128 | /* 129 | WithWriter returns an Option that sets the output field in the config. 130 | The output field is an io.Writer that specifies the destination for log output. 131 | 132 | Parameters: 133 | 134 | s - The writer to be used for log output. 135 | 136 | Returns: 137 | 138 | Option - An option that sets the output field in the config. 139 | */ 140 | func WithWriter(s io.Writer) Option { 141 | return optionFunc(func(c *config) { 142 | c.output = s 143 | }) 144 | } 145 | 146 | /* 147 | WithDefaultLevel returns an Option that sets the defaultLevel field in the config. 148 | The defaultLevel field specifies the logging level for requests with status codes less than 400. 149 | 150 | Parameters: 151 | 152 | lvl - The logging level to be used for requests with status codes less than 400. 153 | 154 | Returns: 155 | 156 | Option - An option that sets the defaultLevel field in the config. 157 | */ 158 | func WithDefaultLevel(lvl zerolog.Level) Option { 159 | return optionFunc(func(c *config) { 160 | c.defaultLevel = lvl 161 | }) 162 | } 163 | 164 | /* 165 | WithClientErrorLevel returns an Option that sets the clientErrorLevel field in the config. 166 | The clientErrorLevel field specifies the logging level for requests with status codes between 400 and 499. 167 | 168 | Parameters: 169 | 170 | lvl - The logging level to be used for requests with status codes between 400 and 499. 171 | 172 | Returns: 173 | 174 | Option - An option that sets the clientErrorLevel field in the config. 175 | */ 176 | func WithClientErrorLevel(lvl zerolog.Level) Option { 177 | return optionFunc(func(c *config) { 178 | c.clientErrorLevel = lvl 179 | }) 180 | } 181 | 182 | /* 183 | WithServerErrorLevel returns an Option that sets the serverErrorLevel field in the config. 184 | The serverErrorLevel field specifies the logging level for server errors. 185 | 186 | Parameters: 187 | 188 | lvl - The logging level to be used for server errors. 189 | 190 | Returns: 191 | 192 | Option - An option that sets the serverErrorLevel field in the config. 193 | */ 194 | func WithServerErrorLevel(lvl zerolog.Level) Option { 195 | return optionFunc(func(c *config) { 196 | c.serverErrorLevel = lvl 197 | }) 198 | } 199 | 200 | /* 201 | WithSkipper returns an Option that sets the Skipper function in the config. 202 | The Skipper function determines whether a request should be skipped for logging. 203 | 204 | Parameters: 205 | 206 | s - A function that takes a gin.Context and returns a boolean indicating whether the request should be skipped. 207 | 208 | Returns: 209 | 210 | Option - An option that sets the Skipper function in the config. 211 | */ 212 | func WithSkipper(s Skipper) Option { 213 | return optionFunc(func(c *config) { 214 | c.skip = s 215 | }) 216 | } 217 | 218 | /* 219 | WithContext returns an Option that sets the context field in the config. 220 | The context field is a function that takes a *gin.Context and a *zerolog.Event, and returns a modified *zerolog.Event. 221 | This allows for custom logging behavior based on the request context. 222 | 223 | Parameters: 224 | 225 | fn - A function that takes a *gin.Context and a *zerolog.Event, and returns a modified *zerolog.Event. 226 | 227 | Returns: 228 | 229 | Option - An option that sets the context field in the config. 230 | */ 231 | func WithContext(fn func(*gin.Context, *zerolog.Event) *zerolog.Event) Option { 232 | return optionFunc(func(c *config) { 233 | c.context = fn 234 | }) 235 | } 236 | 237 | /* 238 | WithMessage returns an Option that sets the message field in the config. 239 | The message field specifies a custom log message to be used when an HTTP request has finished and is logged. 240 | 241 | Parameters: 242 | 243 | message - The custom log message. 244 | 245 | Returns: 246 | 247 | Option - An option that sets the message field in the config. 248 | */ 249 | func WithMessage(message string) Option { 250 | return optionFunc(func(c *config) { 251 | c.message = message 252 | }) 253 | } 254 | 255 | /* 256 | WithSpecificLogLevelByStatusCode returns an Option that sets the specificLevelByStatusCode field in the config. 257 | The specificLevelByStatusCode field is a map that associates specific HTTP status codes with logging levels. 258 | 259 | Parameters: 260 | 261 | statusCodes - A map where the keys are HTTP status codes and the values are zerolog.Level. 262 | 263 | Returns: 264 | 265 | Option - An option that sets the specificLevelByStatusCode field in the config. 266 | */ 267 | func WithSpecificLogLevelByStatusCode(statusCodes map[int]zerolog.Level) Option { 268 | return optionFunc(func(c *config) { 269 | c.specificLevelByStatusCode = statusCodes 270 | }) 271 | } 272 | --------------------------------------------------------------------------------