diff --git a/nethttp/client.go b/nethttp/client.go index 375105d..bfb305f 100644 --- a/nethttp/client.go +++ b/nethttp/client.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "net/http/httptrace" + "net/url" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" @@ -33,6 +34,7 @@ type Transport struct { type clientOptions struct { operationName string componentName string + urlTagFunc func(u *url.URL) string disableClientTrace bool disableInjectSpanContext bool spanObserver func(span opentracing.Span, r *http.Request) @@ -49,6 +51,15 @@ func OperationName(operationName string) ClientOption { } } +// URLTagFunc returns a ClientOption that uses given function f +// to set the span's http.url tag. Can be used to change the default +// http.url tag, eg to redact sensitive information. +func URLTagFunc(f func(u *url.URL) string) ClientOption { + return func(options *clientOptions) { + options.urlTagFunc = f + } +} + // ComponentName returns a ClientOption that sets the component // name for the client-side span. func ComponentName(componentName string) ClientOption { @@ -109,6 +120,9 @@ func ClientSpanObserver(f func(span opentracing.Span, r *http.Request)) ClientOp // } func TraceRequest(tr opentracing.Tracer, req *http.Request, options ...ClientOption) (*http.Request, *Tracer) { opts := &clientOptions{ + urlTagFunc: func(u *url.URL) string { + return u.String() + }, spanObserver: func(_ opentracing.Span, _ *http.Request) {}, } for _, opt := range options { @@ -159,7 +173,7 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { tracer.start(req) ext.HTTPMethod.Set(tracer.sp, req.Method) - ext.HTTPUrl.Set(tracer.sp, req.URL.String()) + ext.HTTPUrl.Set(tracer.sp, tracer.opts.urlTagFunc(req.URL)) tracer.opts.spanObserver(tracer.sp, req) if !tracer.opts.disableInjectSpanContext { diff --git a/nethttp/client_test.go b/nethttp/client_test.go index 679e4ef..7128f1e 100644 --- a/nethttp/client_test.go +++ b/nethttp/client_test.go @@ -3,6 +3,7 @@ package nethttp import ( "net/http" "net/http/httptest" + "net/url" "testing" opentracing "github.com/opentracing/opentracing-go" @@ -208,3 +209,47 @@ func makeTags(keyVals ...interface{}) map[string]interface{} { } return result } + +func TestClientCustomURL(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/ok", func(w http.ResponseWriter, r *http.Request) {}) + srv := httptest.NewServer(mux) + defer srv.Close() + + fn := func(u *url.URL) string { + // Simulate redacting token + return srv.URL + u.Path + "?token=*" + } + + tests := []struct { + opts []ClientOption + url string + tag string + }{ + // These first cases fail early + {[]ClientOption{}, "/ok?token=a", srv.Listener.Addr().String()}, + {[]ClientOption{URLTagFunc(fn)}, "/ok?token=c", srv.Listener.Addr().String()}, + // Disable ClientTrace to fire RoundTrip + {[]ClientOption{ClientTrace(false)}, "/ok?token=b", srv.URL + "/ok?token=b"}, + {[]ClientOption{ClientTrace(false), URLTagFunc(fn)}, "/ok?token=c", srv.URL + "/ok?token=*"}, + } + + for _, tt := range tests { + var clientSpan *mocktracer.MockSpan + + spans := makeRequest(t, srv.URL+tt.url, tt.opts...) + for _, span := range spans { + if span.OperationName == "HTTP GET" { + clientSpan = span + break + } + } + if clientSpan == nil { + t.Fatal("cannot find client span") + } + tag := clientSpan.Tags()["http.url"] + if got, want := tag, tt.tag; got != want { + t.Fatalf("got %s tag name, expected %s", got, want) + } + } +} \ No newline at end of file