diff --git a/propagation/benchmark_b3_propagator_test.go b/propagation/benchmark_b3_propagator_test.go new file mode 100644 index 000000000..0eae3eb59 --- /dev/null +++ b/propagation/benchmark_b3_propagator_test.go @@ -0,0 +1,127 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package propagation_test + +import ( + "context" + "net/http" + "testing" + + "go.opentelemetry.io/api/trace" + mocktrace "go.opentelemetry.io/internal/trace" + "go.opentelemetry.io/propagation" +) + +func BenchmarkExtractB3(b *testing.B) { + testGroup := []struct { + singleHeader bool + name string + tests []extractTest + }{ + { + singleHeader: false, + name: "multiple headers", + tests: extractMultipleHeaders, + }, + { + singleHeader: true, + name: "single headers", + tests: extractSingleHeader, + }, + { + singleHeader: false, + name: "invalid multiple headers", + tests: extractInvalidB3MultipleHeaders, + }, + { + singleHeader: true, + name: "invalid single headers", + tests: extractInvalidB3SingleHeader, + }, + } + trace.SetGlobalTracer(&mocktrace.MockTracer{}) + + for _, tg := range testGroup { + propagator := propagation.HttpB3Propagator(tg.singleHeader) + for _, tt := range tg.tests { + traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { + ctx := context.Background() + req, _ := http.NewRequest("GET", "http://example.com", nil) + for h, v := range tt.headers { + req.Header.Set(h, v) + } + + b.ResetTimer() + _ = propagator.Extract(ctx, req.Header) + }) + } + } +} + +func BenchmarkInjectB3(b *testing.B) { + var id uint64 + testGroup := []struct { + singleHeader bool + name string + tests []injectTest + }{ + { + singleHeader: false, + name: "multiple headers", + tests: injectB3MultipleHeader, + }, + { + singleHeader: true, + name: "single headers", + tests: injectB3SingleleHeader, + }, + } + + mockTracer := &mocktrace.MockTracer{ + Sampled: false, + StartSpanId: &id, + } + trace.SetGlobalTracer(mockTracer) + + for _, tg := range testGroup { + id = 0 + propagator := propagation.HttpB3Propagator(tg.singleHeader) + for _, tt := range tg.tests { + traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + ctx := context.Background() + if tt.parentSc.IsValid() { + ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.parentSc)) + } else { + ctx, _ = mockTracer.Start(ctx, "inject") + } + + b.ResetTimer() + propagator.Inject(ctx, req.Header) + }) + } + } +} + +func traceBenchmark(name string, b *testing.B, fn func(*testing.B)) { + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + fn(b) + }) + b.Run(name, func(b *testing.B) { + b.ReportAllocs() + fn(b) + }) +} diff --git a/propagation/http_b3_propagator.go b/propagation/http_b3_propagator.go new file mode 100644 index 000000000..135c83b6e --- /dev/null +++ b/propagation/http_b3_propagator.go @@ -0,0 +1,239 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package propagation + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + "go.opentelemetry.io/api/trace" + + "go.opentelemetry.io/api/core" + apipropagation "go.opentelemetry.io/api/propagation" +) + +const ( + B3SingleHeader = "X-B3" + B3DebugFlagHeader = "X-B3-Flags" + B3TraceIDHeader = "X-B3-TraceId" + B3SpanIDHeader = "X-B3-SpanId" + B3SampledHeader = "X-B3-Sampled" + B3ParentSpanIDHeader = "X-B3-ParentSpanId" +) + +type httpB3Propagator struct { + singleHeader bool +} + +var _ apipropagation.TextFormatPropagator = httpB3Propagator{} + +var hexStr32ByteRegex = regexp.MustCompile("^[a-f0-9]{32}$") +var hexStr16ByteRegex = regexp.MustCompile("^[a-f0-9]{16}$") + +// HttpB3Propagator creates a new text format propagator that facilitates core.SpanContext +// propagation using B3 Headers. +// This propagator supports both version of B3 headers, +// 1. Single Header : +// X-B3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} +// 2. Multiple Headers: +// X-B3-TraceId: {TraceId} +// X-B3-ParentSpanId: {ParentSpanId} +// X-B3-SpanId: {SpanId} +// X-B3-Sampled: {SamplingState} +// X-B3-Flags: {DebugFlag} +// +// If singleHeader is set to true then X-B3 header is used to inject and extract. Otherwise, +// separate headers are used to inject and extract. +func HttpB3Propagator(singleHeader bool) httpB3Propagator { + // [TODO](rghetia): should it automatically look for both versions? which one to pick if + // both are present? What if one is valid and other one is not. + return httpB3Propagator{singleHeader} +} + +func (b3 httpB3Propagator) Inject(ctx context.Context, supplier apipropagation.Supplier) { + sc := trace.CurrentSpan(ctx).SpanContext() + if sc.IsValid() { + if b3.singleHeader { + sampled := sc.TraceFlags & core.TraceFlagsSampled + supplier.Set(B3SingleHeader, + fmt.Sprintf("%.16x%.16x-%.16x-%.1d", sc.TraceID.High, sc.TraceID.Low, sc.SpanID, sampled)) + } else { + supplier.Set(B3TraceIDHeader, + fmt.Sprintf("%.16x%.16x", sc.TraceID.High, sc.TraceID.Low)) + supplier.Set(B3SpanIDHeader, + fmt.Sprintf("%.16x", sc.SpanID)) + + var sampled string + if sc.IsSampled() { + sampled = "1" + } else { + sampled = "0" + } + supplier.Set(B3SampledHeader, sampled) + } + } +} + +// Extract retrieves B3 Headers from the supplier +func (b3 httpB3Propagator) Extract(ctx context.Context, supplier apipropagation.Supplier) core.SpanContext { + if b3.singleHeader { + return b3.extractSingleHeader(supplier) + } + return b3.extract(supplier) +} + +func (b3 httpB3Propagator) GetAllKeys() []string { + if b3.singleHeader { + return []string{B3SingleHeader} + } + return []string{B3TraceIDHeader, B3SpanIDHeader, B3SampledHeader} +} + +func (b3 httpB3Propagator) extract(supplier apipropagation.Supplier) core.SpanContext { + tid, ok := b3.extractTraceID(supplier.Get(B3TraceIDHeader)) + if !ok { + return core.EmptySpanContext() + } + sid, ok := b3.extractSpanID(supplier.Get(B3SpanIDHeader)) + if !ok { + return core.EmptySpanContext() + } + sampled, ok := b3.extractSampledState(supplier.Get(B3SampledHeader)) + if !ok { + return core.EmptySpanContext() + } + + debug, ok := b3.extracDebugFlag(supplier.Get(B3DebugFlagHeader)) + if !ok { + return core.EmptySpanContext() + } + if debug == core.TraceFlagsSampled { + sampled = core.TraceFlagsSampled + } + + sc := core.SpanContext{ + TraceID: tid, + SpanID: sid, + TraceFlags: sampled, + } + + if !sc.IsValid() { + return core.EmptySpanContext() + } + + return sc +} + +func (b3 httpB3Propagator) extractSingleHeader(supplier apipropagation.Supplier) core.SpanContext { + h := supplier.Get(B3SingleHeader) + if h == "" || h == "0" { + core.EmptySpanContext() + } + sc := core.SpanContext{} + parts := strings.Split(h, "-") + l := len(parts) + if l > 4 { + return core.EmptySpanContext() + } + + if l < 2 { + return core.EmptySpanContext() + } else { + var ok bool + sc.TraceID, ok = b3.extractTraceID(parts[0]) + if !ok { + return core.EmptySpanContext() + } + + sc.SpanID, ok = b3.extractSpanID(parts[1]) + if !ok { + return core.EmptySpanContext() + } + + if l > 2 { + sc.TraceFlags, ok = b3.extractSampledState(parts[2]) + if !ok { + return core.EmptySpanContext() + } + } + if l == 4 { + _, ok = b3.extractSpanID(parts[3]) + if !ok { + return core.EmptySpanContext() + } + } + } + + if !sc.IsValid() { + return core.EmptySpanContext() + } + + return sc +} + +// extractTraceID parses the value of the X-B3-TraceId b3Header. +func (b3 httpB3Propagator) extractTraceID(tid string) (traceID core.TraceID, ok bool) { + if hexStr32ByteRegex.MatchString(tid) { + traceID.High, _ = strconv.ParseUint(tid[0:(16)], 16, 64) + traceID.Low, _ = strconv.ParseUint(tid[(16):32], 16, 64) + ok = true + } else if b3.singleHeader && hexStr16ByteRegex.MatchString(tid) { + traceID.Low, _ = strconv.ParseUint(tid[:16], 16, 64) + ok = true + } + return traceID, ok +} + +// extractSpanID parses the value of the X-B3-SpanId or X-B3-ParentSpanId headers. +func (b3 httpB3Propagator) extractSpanID(sid string) (spanID uint64, ok bool) { + if hexStr16ByteRegex.MatchString(sid) { + spanID, _ = strconv.ParseUint(sid, 16, 64) + ok = true + } + return spanID, ok +} + +// extractSampledState parses the value of the X-B3-Sampled b3Header. +func (b3 httpB3Propagator) extractSampledState(sampled string) (flag byte, ok bool) { + switch sampled { + case "", "0": + return 0, true + case "1": + return core.TraceFlagsSampled, true + case "true": + if !b3.singleHeader { + return core.TraceFlagsSampled, true + } + case "d": + if b3.singleHeader { + return core.TraceFlagsSampled, true + } + } + return 0, false +} + +// extracDebugFlag parses the value of the X-B3-Sampled b3Header. +func (b3 httpB3Propagator) extracDebugFlag(debug string) (flag byte, ok bool) { + switch debug { + case "", "0": + return 0, true + case "1": + return core.TraceFlagsSampled, true + } + return 0, false +} diff --git a/propagation/http_b3_propagator_data_test.go b/propagation/http_b3_propagator_data_test.go new file mode 100644 index 000000000..1dab46d2d --- /dev/null +++ b/propagation/http_b3_propagator_data_test.go @@ -0,0 +1,545 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package propagation_test + +import ( + "go.opentelemetry.io/api/core" + "go.opentelemetry.io/propagation" +) + +type extractTest struct { + name string + headers map[string]string + wantSc core.SpanContext +} + +var extractMultipleHeaders = []extractTest{ + { + name: "sampling state defer", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, + { + name: "sampling state deny", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "0", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, + { + name: "sampling state accept", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "1", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "sampling state as a boolean", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "true", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "debug flag set", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3DebugFlagHeader: "1", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + // spec is not clear on the behavior for this case. If debug flag is set + // then sampled state should not be set. From that perspective debug + // takes precedence. Hence, it is sampled. + name: "debug flag set and sampling state is deny", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "0", + propagation.B3DebugFlagHeader: "1", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "with parent span id", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "1", + propagation.B3ParentSpanIDHeader: "00f067aa0ba90200", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "with only sampled state header", + headers: map[string]string{ + propagation.B3SampledHeader: "0", + }, + wantSc: core.EmptySpanContext(), + }, +} + +var extractSingleHeader = []extractTest{ + { + name: "sampling state defer", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, + { + name: "sampling state deny", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-0", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, + { + name: "sampling state accept", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "sampling state debug", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-d", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "with parent span id", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1-00000000000000cd", + }, + wantSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + }, + { + name: "with only sampling state deny", + headers: map[string]string{ + propagation.B3SingleHeader: "0", + }, + wantSc: core.EmptySpanContext(), + }, +} + +var extractInvalidB3MultipleHeaders = []extractTest{ + { + name: "trace ID length > 32", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab00000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "trace ID length >16 and <32", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab0000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "trace ID length <16", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab0000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "wrong span ID length", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd0000000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "wrong sampled flag length", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "10", + }, + }, + { + name: "bogus trace ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "qw000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "bogus span ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "qw00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "bogus sampled flag", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "d", + }, + }, + { + name: "bogus debug flag (string)", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + propagation.B3DebugFlagHeader: "d", + }, + }, + { + name: "bogus debug flag (number)", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + propagation.B3DebugFlagHeader: "10", + }, + }, + { + name: "upper case trace ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "AB000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "upper case span ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "CD00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "zero trace ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "00000000000000000000000000000000", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "zero span ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SpanIDHeader: "0000000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "missing span ID", + headers: map[string]string{ + propagation.B3TraceIDHeader: "ab000000000000000000000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "missing trace ID", + headers: map[string]string{ + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "missing trace ID with valid single header", + headers: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-1", + propagation.B3SpanIDHeader: "cd00000000000000", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "sampled header set to 1 but trace ID and span ID are missing", + headers: map[string]string{ + propagation.B3SampledHeader: "1", + }, + }, +} + +var extractInvalidB3SingleHeader = []extractTest{ + { + name: "wrong trace ID length", + headers: map[string]string{ + propagation.B3SingleHeader: "ab00000000000000000000000000000000-cd00000000000000-1", + }, + }, + { + name: "wrong span ID length", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd0000000000000000-1", + }, + }, + { + name: "wrong sampled state length", + headers: map[string]string{ + propagation.B3SingleHeader: "00-ab000000000000000000000000000000-cd00000000000000-01", + }, + }, + { + name: "wrong parent span ID length", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd00000000000000-1-cd0000000000000000", + }, + }, + { + name: "bogus trace ID", + headers: map[string]string{ + propagation.B3SingleHeader: "qw000000000000000000000000000000-cd00000000000000-1", + }, + }, + { + name: "bogus span ID", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-qw00000000000000-1", + }, + }, + { + name: "bogus sampled flag", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd00000000000000-q", + }, + }, + { + name: "bogus parent span ID", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd00000000000000-1-qw00000000000000", + }, + }, + { + name: "upper case trace ID", + headers: map[string]string{ + propagation.B3SingleHeader: "AB000000000000000000000000000000-cd00000000000000-1", + }, + }, + { + name: "upper case span ID", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-CD00000000000000-1", + }, + }, + { + name: "upper case parent span ID", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd00000000000000-1-EF00000000000000", + }, + }, + { + name: "zero trace ID and span ID", + headers: map[string]string{ + propagation.B3SingleHeader: "00000000000000000000000000000000-0000000000000000-1", + }, + }, + { + name: "missing single header with valid separate headers", + headers: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "upper case span ID with valid separate headers", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-CD00000000000000-1", + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "00f067aa0ba902b7", + propagation.B3SampledHeader: "1", + }, + }, + { + name: "with sampling set to true", + headers: map[string]string{ + propagation.B3SingleHeader: "ab000000000000000000000000000000-cd00000000000000-true", + }, + }, +} + +type injectTest struct { + name string + parentSc core.SpanContext + wantHeaders map[string]string + doNotWantHeaders []string +} + +var injectB3MultipleHeader = []injectTest{ + { + name: "valid spancontext, sampled", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + wantHeaders: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "0000000000000001", + propagation.B3SampledHeader: "1", + }, + doNotWantHeaders: []string{ + propagation.B3ParentSpanIDHeader, + }, + }, + { + name: "valid spancontext, not sampled", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + wantHeaders: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "0000000000000002", + propagation.B3SampledHeader: "0", + }, + doNotWantHeaders: []string{ + propagation.B3ParentSpanIDHeader, + }, + }, + { + name: "valid spancontext, with unsupported bit set in traceflags", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: 0xff, + }, + wantHeaders: map[string]string{ + propagation.B3TraceIDHeader: "4bf92f3577b34da6a3ce929d0e0e4736", + propagation.B3SpanIDHeader: "0000000000000003", + propagation.B3SampledHeader: "1", + }, + doNotWantHeaders: []string{ + propagation.B3ParentSpanIDHeader, + }, + }, +} + +var injectB3SingleleHeader = []injectTest{ + { + name: "valid spancontext, sampled", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: core.TraceFlagsSampled, + }, + wantHeaders: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-0000000000000001-1", + }, + doNotWantHeaders: []string{ + propagation.B3TraceIDHeader, + propagation.B3SpanIDHeader, + propagation.B3SampledHeader, + propagation.B3ParentSpanIDHeader, + }, + }, + { + name: "valid spancontext, not sampled", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + wantHeaders: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-0000000000000002-0", + }, + doNotWantHeaders: []string{ + propagation.B3TraceIDHeader, + propagation.B3SpanIDHeader, + propagation.B3SampledHeader, + propagation.B3ParentSpanIDHeader, + }, + }, + { + name: "valid spancontext, with unsupported bit set in traceflags", + parentSc: core.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: 0xff, + }, + wantHeaders: map[string]string{ + propagation.B3SingleHeader: "4bf92f3577b34da6a3ce929d0e0e4736-0000000000000003-1", + }, + doNotWantHeaders: []string{ + propagation.B3TraceIDHeader, + propagation.B3SpanIDHeader, + propagation.B3SampledHeader, + propagation.B3ParentSpanIDHeader, + }, + }, +} \ No newline at end of file diff --git a/propagation/http_b3_propagator_test.go b/propagation/http_b3_propagator_test.go new file mode 100644 index 000000000..a255c023e --- /dev/null +++ b/propagation/http_b3_propagator_test.go @@ -0,0 +1,157 @@ +// Copyright 2019, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package propagation_test + +import ( + "context" + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" + + "go.opentelemetry.io/api/trace" + mocktrace "go.opentelemetry.io/internal/trace" + "go.opentelemetry.io/propagation" +) + +func TestExtractB3(t *testing.T) { + testGroup := []struct { + singleHeader bool + name string + tests []extractTest + }{ + { + singleHeader: false, + name: "multiple headers", + tests: extractMultipleHeaders, + }, + { + singleHeader: true, + name: "single headers", + tests: extractSingleHeader, + }, + { + singleHeader: false, + name: "invalid multiple headers", + tests: extractInvalidB3MultipleHeaders, + }, + { + singleHeader: true, + name: "invalid single headers", + tests: extractInvalidB3SingleHeader, + }, + } + trace.SetGlobalTracer(&mocktrace.MockTracer{}) + + for _, tg := range testGroup { + propagator := propagation.HttpB3Propagator(tg.singleHeader) + for _, tt := range tg.tests { + t.Run(tt.name, func(t *testing.T) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + for h, v := range tt.headers { + req.Header.Set(h, v) + } + + ctx := context.Background() + gotSc := propagator.Extract(ctx, req.Header) + if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" { + t.Errorf("%s: %s: -got +want %s", tg.name, tt.name, diff) + } + }) + } + } +} + +func TestInjectB3(t *testing.T) { + var id uint64 + testGroup := []struct { + singleHeader bool + name string + tests []injectTest + }{ + { + singleHeader: false, + name: "multiple headers", + tests: injectB3MultipleHeader, + }, + { + singleHeader: true, + name: "single headers", + tests: injectB3SingleleHeader, + }, + } + + mockTracer := &mocktrace.MockTracer{ + Sampled: false, + StartSpanId: &id, + } + trace.SetGlobalTracer(mockTracer) + + for _, tg := range testGroup { + id = 0 + propagator := propagation.HttpB3Propagator(tg.singleHeader) + for _, tt := range tg.tests { + t.Run(tt.name, func(t *testing.T) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + ctx := context.Background() + if tt.parentSc.IsValid() { + ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.parentSc)) + } else { + ctx, _ = mockTracer.Start(ctx, "inject") + } + propagator.Inject(ctx, req.Header) + + for h, v := range tt.wantHeaders { + got, want := req.Header.Get(h), v + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tt.name, h, diff) + } + } + wantOk := false + for _, h := range tt.doNotWantHeaders { + v, gotOk := req.Header[h] + if diff := cmp.Diff(gotOk, wantOk); diff != "" { + t.Errorf("%s: %s, header=%s: -got +want %s, value=%s", tg.name, tt.name, h, diff, v) + } + + } + }) + } + } +} + +func TestHttpB3Propagator_GetAllKeys(t *testing.T) { + propagator := propagation.HttpB3Propagator(false) + want := []string{ + propagation.B3TraceIDHeader, + propagation.B3SpanIDHeader, + propagation.B3SampledHeader, + } + got := propagator.GetAllKeys() + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("GetAllKeys: -got +want %s", diff) + } +} + +func TestHttpB3PropagatorWithSingleHeader_GetAllKeys(t *testing.T) { + propagator := propagation.HttpB3Propagator(true) + want := []string{ + propagation.B3SingleHeader, + } + got := propagator.GetAllKeys() + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("GetAllKeys: -got +want %s", diff) + } +} diff --git a/propagation/http_trace_context_propagator_test.go b/propagation/http_trace_context_propagator_test.go index ff72a2aaf..c6e232588 100644 --- a/propagation/http_trace_context_propagator_test.go +++ b/propagation/http_trace_context_propagator_test.go @@ -42,7 +42,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { wantSc core.SpanContext }{ { - name: "valid header", + name: "valid b3Header", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00", wantSc: core.SpanContext{ TraceID: traceID, @@ -50,7 +50,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { }, }, { - name: "valid header and sampled", + name: "valid b3Header and sampled", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", wantSc: core.SpanContext{ TraceID: traceID, @@ -94,7 +94,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { }, }, { - name: "valid header ending in dash", + name: "valid b3Header ending in dash", header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-", wantSc: core.SpanContext{ TraceID: traceID, @@ -103,7 +103,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { }, }, { - name: "future valid header ending in dash", + name: "future valid b3Header ending in dash", header: "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-", wantSc: core.SpanContext{ TraceID: traceID,