├── .github ├── dependabot.yml └── workflows │ ├── goreleaser.yml │ ├── trivy-scan.yml │ ├── go.yml │ └── codeql.yml ├── .gitignore ├── _example ├── main.go ├── example01 │ └── main.go └── example02 │ └── main.go ├── .goreleaser.yaml ├── options.go ├── .golangci.yml ├── LICENSE ├── requestid.go ├── go.mod ├── README.md ├── requestid_test.go └── go.sum /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-contrib/requestid" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | r := gin.New() 15 | 16 | r.Use(requestid.New()) 17 | 18 | // Example ping request. 19 | r.GET("/ping", func(c *gin.Context) { 20 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 21 | }) 22 | 23 | // Example / request. 24 | r.GET("/", func(c *gin.Context) { 25 | c.String(http.StatusOK, "id:"+requestid.Get(c)) 26 | }) 27 | 28 | // Listen and Server in 0.0.0.0:8080 29 | if err := r.Run(":8080"); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /_example/example01/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-contrib/requestid" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | r := gin.New() 15 | 16 | r.Use(requestid.New()) 17 | 18 | // Example ping request. 19 | r.GET("/ping", func(c *gin.Context) { 20 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 21 | }) 22 | 23 | // Example / request. 24 | r.GET("/", func(c *gin.Context) { 25 | c.String(http.StatusOK, "id:"+requestid.Get(c)) 26 | }) 27 | 28 | // Listen and Server in 0.0.0.0:8080 29 | if err := r.Run(":8080"); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - skip: true 3 | 4 | changelog: 5 | use: github 6 | groups: 7 | - title: Features 8 | regexp: "^.*feat[(\\w)]*:+.*$" 9 | order: 0 10 | - title: "Bug fixes" 11 | regexp: "^.*fix[(\\w)]*:+.*$" 12 | order: 1 13 | - title: "Enhancements" 14 | regexp: "^.*chore[(\\w)]*:+.*$" 15 | order: 2 16 | - title: "Refactor" 17 | regexp: "^.*refactor[(\\w)]*:+.*$" 18 | order: 3 19 | - title: "Build process updates" 20 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 21 | order: 4 22 | - title: "Documentation updates" 23 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 24 | order: 4 25 | - title: Others 26 | order: 999 27 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package requestid 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // Option for queue system 8 | type Option func(*config) 9 | 10 | type ( 11 | Generator func() string 12 | Handler func(c *gin.Context, requestID string) 13 | ) 14 | 15 | type HeaderStrKey string 16 | 17 | // WithGenerator set generator function 18 | func WithGenerator(g Generator) Option { 19 | return func(cfg *config) { 20 | cfg.generator = g 21 | } 22 | } 23 | 24 | // WithCustomHeaderStrKey set custom header key for request id 25 | func WithCustomHeaderStrKey(s HeaderStrKey) Option { 26 | return func(cfg *config) { 27 | cfg.headerKey = s 28 | } 29 | } 30 | 31 | // WithHandler set handler function for request id with context 32 | func WithHandler(handler Handler) Option { 33 | return func(cfg *config) { 34 | cfg.handler = handler 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.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@v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v6 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 | -------------------------------------------------------------------------------- /_example/example02/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-contrib/requestid" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func main() { 14 | r := gin.New() 15 | 16 | r.Use( 17 | requestid.New( 18 | requestid.WithGenerator(func() string { 19 | return "test" 20 | }), 21 | requestid.WithCustomHeaderStrKey("your-customer-key"), 22 | requestid.WithHandler(func(c *gin.Context, requestID string) { 23 | log.Printf("RequestID: %s", requestID) 24 | }), 25 | ), 26 | ) 27 | 28 | // Example ping request. 29 | r.GET("/ping", func(c *gin.Context) { 30 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 31 | }) 32 | 33 | // Example / request. 34 | r.GET("/", func(c *gin.Context) { 35 | c.String(http.StatusOK, "id:"+requestid.Get(c)) 36 | }) 37 | 38 | // Listen and Server in 0.0.0.0:8080 39 | if err := r.Run(":8080"); err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Gin-Gonic 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 | -------------------------------------------------------------------------------- /requestid.go: -------------------------------------------------------------------------------- 1 | package requestid 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/google/uuid" 6 | ) 7 | 8 | var headerXRequestID string 9 | 10 | // Config defines the config for RequestID middleware 11 | type config struct { 12 | // Generator defines a function to generate an ID. 13 | // Optional. Default: func() string { 14 | // return uuid.New().String() 15 | // } 16 | generator Generator 17 | headerKey HeaderStrKey 18 | handler Handler 19 | } 20 | 21 | // New initializes the RequestID middleware. 22 | func New(opts ...Option) gin.HandlerFunc { 23 | cfg := &config{ 24 | generator: func() string { 25 | return uuid.New().String() 26 | }, 27 | headerKey: "X-Request-ID", 28 | } 29 | 30 | for _, opt := range opts { 31 | opt(cfg) 32 | } 33 | 34 | headerXRequestID = string(cfg.headerKey) 35 | 36 | return func(c *gin.Context) { 37 | // Get id from request 38 | rid := c.GetHeader(headerXRequestID) 39 | if rid == "" { 40 | rid = cfg.generator() 41 | c.Request.Header.Add(headerXRequestID, rid) 42 | } 43 | if cfg.handler != nil { 44 | cfg.handler(c, rid) 45 | } 46 | // Set the id to ensure that the requestid is in the response 47 | c.Header(headerXRequestID, rid) 48 | c.Next() 49 | } 50 | } 51 | 52 | // Get returns the request identifier 53 | func Get(c *gin.Context) string { 54 | return c.GetHeader(headerXRequestID) 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | name: Trivy Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | schedule: 11 | # Run daily at 00:00 UTC 12 | - cron: '0 0 * * *' 13 | workflow_dispatch: # Allow manual trigger 14 | 15 | permissions: 16 | contents: read 17 | security-events: write # Required for uploading SARIF results 18 | 19 | jobs: 20 | trivy-scan: 21 | name: Trivy Security Scan 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v6 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: Run Trivy vulnerability scanner (source code) 30 | uses: aquasecurity/trivy-action@0.33.1 31 | with: 32 | scan-type: 'fs' 33 | scan-ref: '.' 34 | scanners: 'vuln,secret,misconfig' 35 | format: 'sarif' 36 | output: 'trivy-results.sarif' 37 | severity: 'CRITICAL,HIGH,MEDIUM' 38 | ignore-unfixed: true 39 | 40 | - name: Upload Trivy results to GitHub Security tab 41 | uses: github/codeql-action/upload-sarif@v4 42 | if: always() 43 | with: 44 | sarif_file: 'trivy-results.sarif' 45 | 46 | - name: Run Trivy scanner (table output for logs) 47 | uses: aquasecurity/trivy-action@0.33.1 48 | if: always() 49 | with: 50 | scan-type: 'fs' 51 | scan-ref: '.' 52 | scanners: 'vuln,secret,misconfig' 53 | format: 'table' 54 | severity: 'CRITICAL,HIGH,MEDIUM' 55 | ignore-unfixed: true 56 | exit-code: '1' 57 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gin-contrib/requestid 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.11.0 7 | github.com/google/uuid v1.6.0 8 | github.com/stretchr/testify v1.11.1 9 | ) 10 | 11 | require ( 12 | github.com/bytedance/sonic v1.14.0 // indirect 13 | github.com/bytedance/sonic/loader v0.3.0 // indirect 14 | github.com/cloudwego/base64x v0.1.6 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 17 | github.com/gin-contrib/sse v1.1.0 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.27.0 // indirect 21 | github.com/goccy/go-json v0.10.5 // indirect 22 | github.com/goccy/go-yaml v1.18.0 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/klauspost/cpuid/v2 v2.3.0 // indirect 25 | github.com/kr/text v0.2.0 // indirect 26 | github.com/leodido/go-urn v1.4.0 // indirect 27 | github.com/mattn/go-isatty v0.0.20 // 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/quic-go/qpack v0.6.0 // indirect 33 | github.com/quic-go/quic-go v0.57.1 // indirect 34 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 35 | github.com/ugorji/go/codec v1.3.0 // indirect 36 | golang.org/x/arch v0.20.0 // indirect 37 | golang.org/x/crypto v0.45.0 // indirect 38 | golang.org/x/net v0.47.0 // indirect 39 | golang.org/x/sys v0.38.0 // indirect 40 | golang.org/x/text v0.31.0 // indirect 41 | google.golang.org/protobuf v1.36.9 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /.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@v6 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v6 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | - name: Setup golangci-lint 26 | uses: golangci/golangci-lint-action@v9 27 | with: 28 | version: v2.6 29 | test: 30 | strategy: 31 | matrix: 32 | os: [ubuntu-latest] 33 | go: [1.24, 1.25] 34 | include: 35 | - os: ubuntu-latest 36 | go-build: ~/.cache/go-build 37 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 38 | runs-on: ${{ matrix.os }} 39 | env: 40 | GO111MODULE: on 41 | GOPROXY: https://proxy.golang.org 42 | steps: 43 | - name: Set up Go ${{ matrix.go }} 44 | uses: actions/setup-go@v6 45 | with: 46 | go-version: ${{ matrix.go }} 47 | 48 | - name: Checkout Code 49 | uses: actions/checkout@v6 50 | with: 51 | ref: ${{ github.ref }} 52 | 53 | - uses: actions/cache@v5 54 | with: 55 | path: | 56 | ${{ matrix.go-build }} 57 | ~/go/pkg/mod 58 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 59 | restore-keys: | 60 | ${{ runner.os }}-go- 61 | - name: Run Tests 62 | run: | 63 | go test -v -covermode=atomic -coverprofile=coverage.out 64 | 65 | - name: Upload coverage to Codecov 66 | uses: codecov/codecov-action@v5 67 | with: 68 | flags: ${{ matrix.os }},go-${{ matrix.go }} 69 | -------------------------------------------------------------------------------- /.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@v6 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 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@v4 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RequestID 2 | 3 | [![Run Tests](https://github.com/gin-contrib/requestid/actions/workflows/go.yml/badge.svg?branch=master)](https://github.com/gin-contrib/requestid/actions/workflows/go.yml) 4 | [![Trivy Security Scan](https://github.com/gin-contrib/requestid/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-contrib/requestid/actions/workflows/trivy-scan.yml) 5 | [![codecov](https://codecov.io/gh/gin-contrib/requestid/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/requestid) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/requestid)](https://goreportcard.com/report/github.com/gin-contrib/requestid) 7 | [![GoDoc](https://godoc.org/github.com/gin-contrib/requestid?status.svg)](https://godoc.org/github.com/gin-contrib/requestid) 8 | 9 | Request ID middleware for Gin Framework. Adds an indentifier to the response using the `X-Request-ID` header. Passes the `X-Request-ID` value back to the caller if it's sent in the request headers. 10 | 11 | ## Usage 12 | 13 | ### Start using it 14 | 15 | Download and install it. 16 | 17 | ```sh 18 | go get github.com/gin-contrib/requestid 19 | ``` 20 | 21 | Import it in your code, then use it: 22 | 23 | ```go 24 | import "github.com/gin-contrib/requestid" 25 | ``` 26 | 27 | ## Config 28 | 29 | define your custom generator function: 30 | 31 | ```go 32 | func main() { 33 | 34 | r := gin.New() 35 | 36 | r.Use( 37 | requestid.New( 38 | requestid.WithGenerator(func() string { 39 | return "test" 40 | }), 41 | requestid.WithCustomHeaderStrKey("your-customer-key"), 42 | ), 43 | ) 44 | 45 | // Example ping request. 46 | r.GET("/ping", func(c *gin.Context) { 47 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 48 | }) 49 | 50 | // Listen and Server in 0.0.0.0:8080 51 | r.Run(":8080") 52 | } 53 | ``` 54 | 55 | ## Example 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "fmt" 62 | "net/http" 63 | "time" 64 | 65 | "github.com/gin-contrib/requestid" 66 | "github.com/gin-gonic/gin" 67 | ) 68 | 69 | func main() { 70 | 71 | r := gin.New() 72 | 73 | r.Use(requestid.New()) 74 | 75 | // Example ping request. 76 | r.GET("/ping", func(c *gin.Context) { 77 | c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix())) 78 | }) 79 | 80 | // Listen and Server in 0.0.0.0:8080 81 | r.Run(":8080") 82 | } 83 | ``` 84 | 85 | How to get the request identifier: 86 | 87 | ```go 88 | // Example / request. 89 | r.GET("/", func(c *gin.Context) { 90 | c.String(http.StatusOK, "id:"+requestid.Get(c)) 91 | }) 92 | ``` 93 | 94 | You can also get the request identifier from response header: 95 | 96 | ```sh 97 | > curl -i "http://127.0.0.1:8080" 98 | 99 | HTTP/1.1 200 OK 100 | Content-Type: text/plain; charset=utf-8 101 | X-Request-ID: 77966910-3912-4193-9b74-267491c51700 102 | Content-Length: 39 103 | 104 | id:77966910-3912-4193-9b74-267491c51700 105 | ``` 106 | 107 | When http request with custom identifier, gin server return the custom identifier in response header. 108 | 109 | ```sh 110 | > curl -i -H "X-Request-ID:test" "http://127.0.0.1:8080" 111 | 112 | HTTP/1.1 200 OK 113 | Content-Type: text/plain; charset=utf-8 114 | X-Request-Id: test 115 | Content-Length: 13 116 | 117 | id:test 118 | ``` 119 | -------------------------------------------------------------------------------- /requestid_test.go: -------------------------------------------------------------------------------- 1 | package requestid 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | const testXRequestID = "test-request-id" 14 | 15 | func emptySuccessResponse(c *gin.Context) { 16 | c.String(http.StatusOK, "") 17 | } 18 | 19 | func Test_RequestID_CreateNew(t *testing.T) { 20 | r := gin.New() 21 | r.Use(New()) 22 | r.GET("/", emptySuccessResponse) 23 | 24 | w := httptest.NewRecorder() 25 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 26 | r.ServeHTTP(w, req) 27 | 28 | assert.Equal(t, http.StatusOK, w.Code) 29 | assert.NotEmpty(t, w.Header().Get(headerXRequestID)) 30 | } 31 | 32 | func Test_RequestID_PassThru(t *testing.T) { 33 | r := gin.New() 34 | r.Use(New()) 35 | r.GET("/", emptySuccessResponse) 36 | 37 | w := httptest.NewRecorder() 38 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 39 | req.Header.Set(headerXRequestID, testXRequestID) 40 | r.ServeHTTP(w, req) 41 | 42 | assert.Equal(t, http.StatusOK, w.Code) 43 | assert.Equal(t, testXRequestID, w.Header().Get(headerXRequestID)) 44 | } 45 | 46 | func TestRequestIDWithCustomID(t *testing.T) { 47 | r := gin.New() 48 | r.Use( 49 | New( 50 | WithGenerator(func() string { 51 | return testXRequestID 52 | }), 53 | ), 54 | ) 55 | r.GET("/", emptySuccessResponse) 56 | 57 | w := httptest.NewRecorder() 58 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 59 | r.ServeHTTP(w, req) 60 | 61 | assert.Equal(t, http.StatusOK, w.Code) 62 | assert.Equal(t, testXRequestID, w.Header().Get(headerXRequestID)) 63 | } 64 | 65 | func TestRequestIDWithCustomHeaderKey(t *testing.T) { 66 | r := gin.New() 67 | r.Use( 68 | New( 69 | WithCustomHeaderStrKey("customKey"), 70 | ), 71 | ) 72 | r.GET("/", emptySuccessResponse) 73 | 74 | w := httptest.NewRecorder() 75 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 76 | req.Header.Set("customKey", testXRequestID) 77 | r.ServeHTTP(w, req) 78 | 79 | assert.Equal(t, http.StatusOK, w.Code) 80 | assert.Equal(t, testXRequestID, w.Header().Get("customKey")) 81 | } 82 | 83 | func TestRequestIDWithHandler(t *testing.T) { 84 | r := gin.New() 85 | called := false 86 | r.Use( 87 | New( 88 | WithHandler(func(c *gin.Context, requestID string) { 89 | called = true 90 | assert.Equal(t, testXRequestID, requestID) 91 | }), 92 | ), 93 | ) 94 | 95 | w := httptest.NewRecorder() 96 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 97 | req.Header.Set("X-Request-ID", testXRequestID) 98 | r.ServeHTTP(w, req) 99 | 100 | assert.True(t, called) 101 | } 102 | 103 | func TestRequestIDIsAttachedToRequestHeaders(t *testing.T) { 104 | r := gin.New() 105 | 106 | r.Use(New()) 107 | 108 | r.GET("/", func(c *gin.Context) { 109 | result := c.GetHeader("X-Request-ID") 110 | assert.NotEmpty(t, result) 111 | }) 112 | 113 | w := httptest.NewRecorder() 114 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 115 | r.ServeHTTP(w, req) 116 | } 117 | 118 | func TestRequestIDNotNilAfterGinCopy(t *testing.T) { 119 | r := gin.New() 120 | r.Use(New()) 121 | 122 | r.GET("/", func(c *gin.Context) { 123 | copy := c.Copy() 124 | result := Get(copy) 125 | assert.NotEmpty(t, result) 126 | }) 127 | 128 | w := httptest.NewRecorder() 129 | req, _ := http.NewRequestWithContext(context.Background(), "GET", "/", nil) 130 | r.ServeHTTP(w, req) 131 | } 132 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= 2 | github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= 3 | github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= 4 | github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 5 | github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= 6 | github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= 7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 12 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 13 | github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= 14 | github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= 15 | github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= 16 | github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= 17 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 18 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 19 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 20 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 21 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 22 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 23 | github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= 24 | github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 25 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 26 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 27 | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 28 | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 29 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 30 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 31 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 32 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 33 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= 37 | github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 38 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 39 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 40 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 41 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 42 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 43 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 44 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 45 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 46 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 49 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 50 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 51 | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 52 | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 53 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= 56 | github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= 57 | github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10= 58 | github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s= 59 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 60 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 62 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 63 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 64 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 65 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 66 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 67 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 68 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 69 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 70 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 71 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 72 | github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= 73 | github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= 74 | go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= 75 | go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= 76 | golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= 77 | golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= 78 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 79 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 80 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 81 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 82 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 83 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 84 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 85 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 86 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 87 | golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 88 | golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 89 | google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= 90 | google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 91 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 92 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 93 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 94 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 95 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 96 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 97 | --------------------------------------------------------------------------------