Skip to content

Commit 9a04fc8

Browse files
committed
trace: respond with trace context to report the sampling options
For cases where incoming request doesn't contain sampling options, client determines whether the request should be traced. Determined sampling option needs to be returned back to the source of the incoming request in the response back. Change-Id: I3ccce25d1e0d653a2d865fd4434e5a34d2eef61c Reviewed-on: https://code-review.googlesource.com/12211 Reviewed-by: kokoro <[email protected]> Reviewed-by: Ross Light <[email protected]>
1 parent e8b5f2c commit 9a04fc8

File tree

4 files changed

+86
-13
lines changed

4 files changed

+86
-13
lines changed

trace/http.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,28 @@ type handler struct {
8989
}
9090

9191
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
92-
span := h.traceClient.SpanFromRequest(r)
92+
traceID, parentSpanID, options, optionsOk, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
93+
if !ok {
94+
traceID = nextTraceID()
95+
}
96+
t := &trace{
97+
traceID: traceID,
98+
client: h.traceClient,
99+
globalOptions: options,
100+
localOptions: options,
101+
}
102+
span := startNewChildWithRequest(r, t, parentSpanID)
103+
span.span.Kind = spanKindServer
104+
span.rootSpan = true
105+
configureSpanFromPolicy(span, h.traceClient.policy, ok)
93106
defer span.Finish()
94107

95108
r = r.WithContext(NewContext(r.Context(), span))
109+
if ok && !optionsOk {
110+
// Inject the trace context back to the response with the sampling options.
111+
// TODO(jbd): Remove when there is a better way to report the client's sampling.
112+
w.Header().Set(httpHeader, spanHeader(traceID, parentSpanID, span.trace.localOptions))
113+
}
96114
h.handler.ServeHTTP(w, r)
115+
97116
}

trace/http_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,52 @@ func TestHTTPHandlerNoTrace(t *testing.T) {
107107
t.Fatal(err)
108108
}
109109
}
110+
111+
func TestHTTPHandler_response(t *testing.T) {
112+
tc := newTestClient(&noopTransport{})
113+
p, _ := NewLimitedSampler(1, 1<<32) // all
114+
tc.SetSamplingPolicy(p)
115+
handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
116+
ts := httptest.NewServer(handler)
117+
defer ts.Close()
118+
119+
tests := []struct {
120+
name string
121+
traceHeader string
122+
wantTraceHeader string
123+
}{
124+
{
125+
name: "no global",
126+
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123",
127+
wantTraceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
128+
},
129+
{
130+
name: "global=1",
131+
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1",
132+
wantTraceHeader: "",
133+
},
134+
{
135+
name: "global=0",
136+
traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=0",
137+
wantTraceHeader: "",
138+
},
139+
{
140+
name: "no trace context",
141+
traceHeader: "",
142+
wantTraceHeader: "",
143+
},
144+
}
145+
146+
for _, tt := range tests {
147+
req, _ := http.NewRequest("GET", ts.URL, nil)
148+
req.Header.Set(httpHeader, tt.traceHeader)
149+
150+
res, err := http.DefaultClient.Do(req)
151+
if err != nil {
152+
t.Errorf("failed to request: %v", err)
153+
}
154+
if got, want := res.Header.Get(httpHeader), tt.wantTraceHeader; got != want {
155+
t.Errorf("%v: response context header = %q; want %q", tt.name, got, want)
156+
}
157+
}
158+
}

trace/trace.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ func (c *Client) SpanFromHeader(name string, header string) *Span {
335335
if c == nil {
336336
return nil
337337
}
338-
traceID, parentSpanID, options, ok := traceInfoFromHeader(header)
338+
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(header)
339339
if !ok {
340340
traceID = nextTraceID()
341341
}
@@ -373,7 +373,7 @@ func (c *Client) SpanFromRequest(r *http.Request) *Span {
373373
if c == nil {
374374
return nil
375375
}
376-
traceID, parentSpanID, options, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
376+
traceID, parentSpanID, options, _, ok := traceInfoFromHeader(r.Header.Get(httpHeader))
377377
if !ok {
378378
traceID = nextTraceID()
379379
}
@@ -446,20 +446,22 @@ func FromContext(ctx context.Context) *Span {
446446
return s
447447
}
448448

449-
func traceInfoFromHeader(h string) (string, uint64, optionFlags, bool) {
449+
func traceInfoFromHeader(h string) (traceID string, spanID uint64, options optionFlags, optionsOk bool, ok bool) {
450450
// See https://cloud.google.com/trace/docs/faq for the header format.
451451
// Return if the header is empty or missing, or if the header is unreasonably
452452
// large, to avoid making unnecessary copies of a large string.
453453
if h == "" || len(h) > 200 {
454-
return "", 0, 0, false
454+
return "", 0, 0, false, false
455+
455456
}
456457

457458
// Parse the trace id field.
458459
slash := strings.Index(h, `/`)
459460
if slash == -1 {
460-
return "", 0, 0, false
461+
return "", 0, 0, false, false
462+
461463
}
462-
traceID, h := h[:slash], h[slash+1:]
464+
traceID, h = h[:slash], h[slash+1:]
463465

464466
// Parse the span id field.
465467
spanstr := h
@@ -469,19 +471,22 @@ func traceInfoFromHeader(h string) (string, uint64, optionFlags, bool) {
469471
}
470472
spanID, err := strconv.ParseUint(spanstr, 10, 64)
471473
if err != nil {
472-
return "", 0, 0, false
474+
return "", 0, 0, false, false
475+
473476
}
474477

475478
// Parse the options field, options field is optional.
476479
if !strings.HasPrefix(h, "o=") {
477-
return traceID, spanID, 0, true
480+
return traceID, spanID, 0, false, true
481+
478482
}
479483
o, err := strconv.ParseUint(h[2:], 10, 64)
480484
if err != nil {
481-
return "", 0, 0, false
485+
return "", 0, 0, false, false
486+
482487
}
483-
options := optionFlags(o)
484-
return traceID, spanID, options, true
488+
options = optionFlags(o)
489+
return traceID, spanID, options, true, true
485490
}
486491

487492
type optionFlags uint32

trace/trace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func TestHeader(t *testing.T) {
231231
},
232232
}
233233
for _, tt := range tests {
234-
traceID, parentSpanID, opts, ok := traceInfoFromHeader(tt.header)
234+
traceID, parentSpanID, opts, _, ok := traceInfoFromHeader(tt.header)
235235
if got, want := traceID, tt.wantTraceID; got != want {
236236
t.Errorf("TraceID(%v) = %q; want %q", tt.header, got, want)
237237
}

0 commit comments

Comments
 (0)