Skip to content

Commit

Permalink
Implement traceid.Transport & add example usage (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
VojtechVitek authored Mar 6, 2024
1 parent f15203d commit 4bdfc81
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 29 deletions.
49 changes: 37 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,45 @@ func main() {
See [example/main.go](./example/main.go)
## Example - making HTTP requests
## Example: Send HTTP request with TraceId header
```go
import (
"github.com/go-chi/traceid"
)

func main() {
// Set TraceId in context, if not set from parent ctx yet.
ctx := traceid.NewContext(context.Background())

// Make a request with TraceId header.
req, _ := http.NewRequestWithContext(ctx, "GET", "http://localhost:3333/proxy", nil)
traceid.SetHeader(ctx, req)

resp, err := http.DefaultClient.Do(req)
//...
}
```
## Example: Set TraceId header in all outgoing HTTP requests globally
```go
import (
"github.com/go-chi/traceid"
"github.com/go-chi/transport"
)

func main() {
// Set TraceId in context, if not set from parent ctx yet.
ctx := traceid.NewContext(context.Background())

// Make a request with TraceId header.
req, _ := http.NewRequest("GET", "http://localhost:3333/proxy", nil)
req.WithContext(ctx)
traceid.SetHeader(ctx, req)
resp, err := resp.Do(req)
//...
// Send TraceId in all outgoing HTTP requests.
http.DefaultTransport = transport.Chain(
http.DefaultTransport,
transport.SetHeader("User-Agent", "my-app/v1.0.0"),
traceid.Transport,
)
// This will automatically send TraceId header.
req, _ := http.NewRequest("GET", "http://localhost:3333/proxy", nil)
_, _ = http.DefaultClient.Do(req)
}
```
Expand All @@ -65,7 +90,7 @@ $ go run github.com/go-chi/traceid/cmd/traceid 018e0ee7-3605-7d75-b344-01062c6fd
2024-03-05 14:56:57.477 +0100 CET
```
### Create new UUIDv7:
You can also create a new UUIDv7:
```
$ go run github.com/go-chi/traceid/cmd/traceid
018e0ee7-3605-7d75-b344-01062c6fd8bc
Expand Down
2 changes: 2 additions & 0 deletions example/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ require (
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/go-chi/httplog v0.3.2 // indirect
github.com/go-chi/httplog/v2 v2.0.9 // indirect
github.com/go-chi/transport v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/rs/zerolog v1.29.1 // indirect
golang.org/x/sys v0.10.0 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect
)
38 changes: 38 additions & 0 deletions example/go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/httplog v0.3.2 h1:WjXmBLaJU7kEMkvKpwFXG1m/Z6DcD7JkztvTsKtJ5EY=
github.com/go-chi/httplog v0.3.2/go.mod h1:UoiQQ/MTZH5V6JbNB2FzF0DynTh5okpXxlhsyxoP5m8=
github.com/go-chi/httplog/v2 v2.0.9 h1:RK1TBETd4SSwu075tcfm0KKxR/k98RUfzmOWxLaocGg=
github.com/go-chi/httplog/v2 v2.0.9/go.mod h1:/XXdxicJsp4BA5fapgIC3VuTD+z0Z/VzukoB3VDc1YE=
github.com/go-chi/transport v0.0.0-20240306121026-6c51581757e6 h1:ZSAmz1eNSyrm2dOY7pIwPTf3pxqK7uOUpmg/9u70gBU=
github.com/go-chi/transport v0.0.0-20240306121026-6c51581757e6/go.mod h1:/6vqZkTndiNlc6pN3uPZgyt4diOlrzgM2Y4bXCaRzpM=
github.com/go-chi/transport v0.1.0 h1:Fip309BDSwa2UBTqcXqAusceOdHg4FMLO3Nx9IR4sYI=
github.com/go-chi/transport v0.1.0/go.mod h1:/6vqZkTndiNlc6pN3uPZgyt4diOlrzgM2Y4bXCaRzpM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand All @@ -14,13 +19,46 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
19 changes: 18 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"log/slog"
"math/rand"
"net/http"
"net/http/httputil"
"net/url"
Expand All @@ -12,11 +13,27 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httplog/v2"
"github.com/go-chi/traceid"
"github.com/go-chi/transport"
)

func main() {
r := chi.NewRouter()
// Set TraceId in all outgoing HTTP requests globally.
http.DefaultTransport = transport.Chain(
http.DefaultTransport,
transport.SetHeader("User-Agent", "my-app/v1.0.0"),
traceid.Transport,
)

// Send test request.
go func() {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(800)+200))

req, _ := http.NewRequest("GET", "http://localhost:3333/proxy/proxy", nil)
_, _ = http.DefaultClient.Do(req)
}()

// HTTP server with TraceId middleware + logging.
r := chi.NewRouter()
r.Use(traceid.Middleware)
r.Use(httplog.RequestLogger(logger()))
r.Use(middleware.Recoverer)
Expand Down
23 changes: 23 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package traceid

import (
"context"
"net/http"

"github.com/google/uuid"
)

func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

traceID := r.Header.Get(Header)
if _, err := uuid.Parse(traceID); err != nil {
traceID = uuidV7()
}

ctx = context.WithValue(ctx, ctxKey{}, traceID)
w.Header().Set(Header, traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
17 changes: 1 addition & 16 deletions traceid.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,6 @@ import (

var Header = http.CanonicalHeaderKey("TraceId")

func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

traceID := r.Header.Get(Header)
if _, err := uuid.Parse(traceID); err != nil {
traceID = uuidV7()
}

ctx = context.WithValue(ctx, ctxKey{}, traceID)
w.Header().Set(Header, traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

type ctxKey struct{}

func FromContext(ctx context.Context) string {
Expand All @@ -32,7 +17,7 @@ func FromContext(ctx context.Context) string {
}

func NewContext(ctx context.Context) context.Context {
if v, ok := ctx.Value(ctxKey{}).(string); ok && v != "" {
if traceID, ok := ctx.Value(ctxKey{}).(string); ok && traceID != "" {
return ctx
}

Expand Down
47 changes: 47 additions & 0 deletions transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package traceid

import (
"net/http"
)

func Transport(next http.RoundTripper) http.RoundTripper {
return RoundTripFunc(func(req *http.Request) (resp *http.Response, err error) {
r := cloneRequest(req)

traceID, _ := r.Context().Value(ctxKey{}).(string)
if traceID == "" {
traceID = uuidV7()
}
r.Header.Set(http.CanonicalHeaderKey(Header), traceID)

return next.RoundTrip(r)
})
}

// RoundTripFunc, similar to http.HandlerFunc, is an adapter
// to allow the use of ordinary functions as http.RoundTrippers.
type RoundTripFunc func(r *http.Request) (*http.Response, error)

func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}

// cloneRequest creates a shallow copy of a given request
// to comply with stdlib's http.RoundTripper contract:
//
// RoundTrip should not modify the request, except for
// consuming and closing the Request's Body. RoundTrip may
// read fields of the request in a separate goroutine. Callers
// should not mutate or reuse the request until the Response's
// Body has been closed.
func cloneRequest(orig *http.Request) *http.Request {
clone := &http.Request{}
*clone = *orig

clone.Header = make(http.Header, len(orig.Header))
for key, value := range orig.Header {
clone.Header[key] = append([]string{}, value...)
}

return clone
}

0 comments on commit 4bdfc81

Please sign in to comment.