Skip to content

Commit cc0dd84

Browse files
jcchavezsbasvanbeek
andcommitted
feat(#128): adds support for request sampler in client.
tests: improves sample override tests chore: uses tribool decision in request sampler. Co-authored-by: Bas van Beek <[email protected]>
1 parent 55065cd commit cc0dd84

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

middleware/http/transport.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type transport struct {
5757
errHandler ErrHandler
5858
errResponseReader *ErrResponseReader
5959
logger *log.Logger
60+
requestSampler func(r *http.Request) *bool
6061
}
6162

6263
// TransportOption allows one to configure optional transport configuration.
@@ -106,6 +107,17 @@ func TransportLogger(l *log.Logger) TransportOption {
106107
}
107108
}
108109

110+
// TransportRequestSampler allows one to set the sampling decision based on
111+
// the details found in the http.Request. It has preference over the existing
112+
// sampling decision contained in the context. The function returns a *bool,
113+
// if returning nil, sampling decision is not being changed whereas returning
114+
// something else than nil is being used as sampling decision.
115+
func TransportRequestSampler(sampleFunc func(r *http.Request) *bool) TransportOption {
116+
return func(t *transport) {
117+
t.requestSampler = sampleFunc
118+
}
119+
}
120+
109121
// NewTransport returns a new Zipkin instrumented http RoundTripper which can be
110122
// used with a standard library http Client.
111123
func NewTransport(tracer *zipkin.Tracer, options ...TransportOption) (http.RoundTripper, error) {
@@ -167,7 +179,14 @@ func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error)
167179
zipkin.TagHTTPMethod.Set(sp, req.Method)
168180
zipkin.TagHTTPPath.Set(sp, req.URL.Path)
169181

170-
_ = b3.InjectHTTP(req)(sp.Context())
182+
spCtx := sp.Context()
183+
if t.requestSampler != nil {
184+
if shouldSample := t.requestSampler(req); shouldSample != nil {
185+
spCtx.Sampled = shouldSample
186+
}
187+
}
188+
189+
_ = b3.InjectHTTP(req)(spCtx)
171190

172191
res, err = t.rt.RoundTrip(req)
173192
if err != nil {

middleware/http/transport_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package http
22

33
import (
4+
"context"
45
"errors"
56
"io"
67
"io/ioutil"
@@ -9,6 +10,7 @@ import (
910
"testing"
1011

1112
zipkin "github.com/openzipkin/zipkin-go"
13+
"github.com/openzipkin/zipkin-go/reporter/recorder"
1214
)
1315

1416
type errRoundTripper struct {
@@ -125,3 +127,90 @@ func TestRoundTripErrResponseReadingSuccess(t *testing.T) {
125127
t.Errorf("unexpected body: want %s, have %s", want, have)
126128
}
127129
}
130+
131+
func boolToPtr(b bool) *bool {
132+
return &b
133+
}
134+
135+
func TestTransportRequestSamplerOverridesSamplingFromContext(t *testing.T) {
136+
cases := []struct {
137+
Sampler func(uint64) bool
138+
RequestSampler func(*http.Request) *bool
139+
ExpectedSampling string
140+
}{
141+
// Test proper handling when there is no RequestSampler
142+
{
143+
Sampler: zipkin.AlwaysSample,
144+
RequestSampler: nil,
145+
ExpectedSampling: "1",
146+
},
147+
// Test proper handling when there is no RequestSampler
148+
{
149+
Sampler: zipkin.NeverSample,
150+
RequestSampler: nil,
151+
ExpectedSampling: "0",
152+
},
153+
// Test RequestSampler override sample -> no sample
154+
{
155+
Sampler: zipkin.AlwaysSample,
156+
RequestSampler: func(_ *http.Request) *bool { return boolToPtr(false) },
157+
ExpectedSampling: "0",
158+
},
159+
// Test RequestSampler override no sample -> sample
160+
{
161+
Sampler: zipkin.NeverSample,
162+
RequestSampler: func(_ *http.Request) *bool { return boolToPtr(true) },
163+
ExpectedSampling: "1",
164+
},
165+
// Test RequestSampler pass through of sampled decision
166+
{
167+
Sampler: zipkin.AlwaysSample,
168+
RequestSampler: func(r *http.Request) *bool {
169+
return nil
170+
},
171+
ExpectedSampling: "1",
172+
},
173+
// Test RequestSampler pass through of not sampled decision
174+
{
175+
Sampler: zipkin.NeverSample,
176+
RequestSampler: func(r *http.Request) *bool {
177+
return nil
178+
},
179+
ExpectedSampling: "0",
180+
},
181+
}
182+
183+
for i, c := range cases {
184+
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
185+
if want, have := c.ExpectedSampling, r.Header.Get("x-b3-sampled"); want != have {
186+
t.Errorf("unexpected sampling decision in case #%d, want %q, have %q", i, want, have)
187+
}
188+
}))
189+
190+
// we need to use a valid reporter or Tracer will implement noop mode which makes this test invalid
191+
rep := recorder.NewReporter()
192+
193+
tracer, err := zipkin.NewTracer(rep, zipkin.WithSampler(c.Sampler))
194+
if err != nil {
195+
t.Fatalf("unexpected error when creating tracer: %v", err)
196+
}
197+
198+
sp := tracer.StartSpan("op1")
199+
defer sp.Finish()
200+
ctx := zipkin.NewContext(context.Background(), sp)
201+
202+
req, _ := http.NewRequest("GET", srv.URL, nil)
203+
transport, _ := NewTransport(
204+
tracer,
205+
TransportRequestSampler(c.RequestSampler),
206+
)
207+
208+
_, err = transport.RoundTrip(req.WithContext(ctx))
209+
if err != nil {
210+
t.Fatalf("unexpected error: %v", err)
211+
}
212+
213+
rep.Close()
214+
srv.Close()
215+
}
216+
}

0 commit comments

Comments
 (0)