From b7ed0144fd52e2214a5d2f9eb16b9adc8211820a Mon Sep 17 00:00:00 2001 From: Bas van Beek Date: Wed, 9 Sep 2020 11:46:35 +0200 Subject: [PATCH 1/2] add b3:0 to http reporter and added test for custom headers using HTTPDoer --- reporter/http/http.go | 16 +++++++ reporter/http/http_test.go | 89 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/reporter/http/http.go b/reporter/http/http.go index 16b0609..fbbab8a 100644 --- a/reporter/http/http.go +++ b/reporter/http/http.go @@ -60,6 +60,7 @@ type httpReporter struct { reqCallback RequestCallbackFn reqTimeout time.Duration serializer reporter.SpanSerializer + doNotSample bool } // Send implements reporter @@ -152,6 +153,9 @@ func (r *httpReporter) sendBatch() error { r.logger.Printf("failed when creating the request: %s\n", err.Error()) return err } + if r.doNotSample { + req.Header.Set("b3", "0") + } req.Header.Set("Content-Type", r.serializer.ContentType()) if r.reqCallback != nil { r.reqCallback(req) @@ -237,6 +241,17 @@ func Serializer(serializer reporter.SpanSerializer) ReporterOption { } } +// AllowSamplingReporterCalls if set to true will remove the b3:0 header on +// outgoing calls to the Zipkin collector. +// By default we send b3:0 header to mitigate trace reporting amplification in +// service mesh environments where the sidecar proxies might trace the call +// we do here towards the Zipkin collector. +func AllowSamplingReporterCalls(allow bool) ReporterOption { + return func(r *httpReporter) { + r.doNotSample = !allow + } +} + // NewReporter returns a new HTTP Reporter. // url should be the endpoint to send the spans to, e.g. // http://localhost:9411/api/v2/spans @@ -256,6 +271,7 @@ func NewReporter(url string, opts ...ReporterOption) reporter.Reporter { batchMtx: &sync.Mutex{}, serializer: reporter.JSONSerializer{}, reqTimeout: defaultTimeout, + doNotSample: true, } for _, opt := range opts { diff --git a/reporter/http/http_test.go b/reporter/http/http_test.go index 22cd3ba..1392763 100644 --- a/reporter/http/http_test.go +++ b/reporter/http/http_test.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "reflect" "sync/atomic" "testing" "time" @@ -167,3 +168,91 @@ func TestSpanIsReportedAfterBatchSize(t *testing.T) { t.Errorf("unexpected number of spans received\nhave: %d, want: %d", aNumSpans, eNumSpans) } } + +func TestSpanCustomHeaders(t *testing.T) { + serializer := reporter.JSONSerializer{} + + hc := headerClient{ + headers: http.Header{ + "Key1": []string{"val1a", "val1b"}, + "Key2": []string{"val2"}, + }, + } + var haveHeaders http.Header + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + haveHeaders = r.Header + })) + defer ts.Close() + + spans := generateSpans(1) + + rep := zipkinhttp.NewReporter( + ts.URL, + zipkinhttp.Serializer(serializer), + zipkinhttp.Client(hc), + ) + for _, span := range spans { + rep.Send(*span) + } + rep.Close() + + for _, key := range []string{"Key1", "Key2"} { + if want, have := hc.headers.Values(key), haveHeaders.Values(key); !reflect.DeepEqual(want, have) { + t.Errorf("header %s: want: %v, have: %v\n", key, want, have) + } + } +} + +func TestB3SamplingHeader(t *testing.T) { + serializer := reporter.JSONSerializer{} + + var haveHeaders http.Header + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + haveHeaders = r.Header + })) + defer ts.Close() + + spans := generateSpans(1) + + rep := zipkinhttp.NewReporter( + ts.URL, + zipkinhttp.Serializer(serializer), + zipkinhttp.AllowSamplingReporterCalls(true), + ) + for _, span := range spans { + rep.Send(*span) + } + rep.Close() + + if len(haveHeaders.Values("B3")) > 0 { + t.Errorf("Expected B3 header to not exist, got %v", haveHeaders.Values("B3")) + } + + rep = zipkinhttp.NewReporter( + ts.URL, + zipkinhttp.Serializer(serializer), + ) + for _, span := range spans { + rep.Send(*span) + } + rep.Close() + + if want, have := []string{"0"}, haveHeaders.Values("B3"); !reflect.DeepEqual(want, have) { + t.Errorf("B3 header: want: %v, have %v", want, have) + } + +} + +type headerClient struct { + client http.Client + headers http.Header +} + +func (h headerClient) Do(req *http.Request) (*http.Response, error) { + for key, item := range h.headers { + for _, val := range item { + req.Header.Add(key, val) + } + } + return h.client.Do(req) +} From a4c38a5ce1fe8bd78612eb3b89eeaef4a066ceb1 Mon Sep 17 00:00:00 2001 From: Bas van Beek Date: Wed, 9 Sep 2020 12:01:36 +0200 Subject: [PATCH 2/2] Fix: Go < 1.14 does not support http.Header.Values() --- reporter/http/http_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/reporter/http/http_test.go b/reporter/http/http_test.go index 1392763..cef83c2 100644 --- a/reporter/http/http_test.go +++ b/reporter/http/http_test.go @@ -197,7 +197,7 @@ func TestSpanCustomHeaders(t *testing.T) { rep.Close() for _, key := range []string{"Key1", "Key2"} { - if want, have := hc.headers.Values(key), haveHeaders.Values(key); !reflect.DeepEqual(want, have) { + if want, have := hc.headers[key], haveHeaders[key]; !reflect.DeepEqual(want, have) { t.Errorf("header %s: want: %v, have: %v\n", key, want, have) } } @@ -206,7 +206,7 @@ func TestSpanCustomHeaders(t *testing.T) { func TestB3SamplingHeader(t *testing.T) { serializer := reporter.JSONSerializer{} - var haveHeaders http.Header + var haveHeaders map[string][]string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { haveHeaders = r.Header })) @@ -224,8 +224,8 @@ func TestB3SamplingHeader(t *testing.T) { } rep.Close() - if len(haveHeaders.Values("B3")) > 0 { - t.Errorf("Expected B3 header to not exist, got %v", haveHeaders.Values("B3")) + if len(haveHeaders["B3"]) > 0 { + t.Errorf("Expected B3 header to not exist, got %v", haveHeaders["B3"]) } rep = zipkinhttp.NewReporter( @@ -237,7 +237,7 @@ func TestB3SamplingHeader(t *testing.T) { } rep.Close() - if want, have := []string{"0"}, haveHeaders.Values("B3"); !reflect.DeepEqual(want, have) { + if want, have := []string{"0"}, haveHeaders["B3"]; !reflect.DeepEqual(want, have) { t.Errorf("B3 header: want: %v, have %v", want, have) } @@ -245,7 +245,7 @@ func TestB3SamplingHeader(t *testing.T) { type headerClient struct { client http.Client - headers http.Header + headers map[string][]string } func (h headerClient) Do(req *http.Request) (*http.Response, error) {