From 4d26807005aabc5e8a15ac71ade951e2b294e86c Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Sun, 27 Sep 2020 16:18:11 -0400 Subject: [PATCH 1/6] [propagator] Initial commit of jaeger propagator. --- propagators/jaeger/doc.go | 17 ++ propagators/jaeger/jaeger_data_test.go | 75 +++++++++ propagators/jaeger/jaeger_example_test.go | 16 ++ propagators/jaeger/jaeger_integration_test.go | 48 ++++++ propagators/jaeger/jaeger_propagator.go | 143 +++++++++++++++++ propagators/jaeger/jaeger_propagator_test.go | 148 ++++++++++++++++++ 6 files changed, 447 insertions(+) create mode 100644 propagators/jaeger/doc.go create mode 100644 propagators/jaeger/jaeger_data_test.go create mode 100644 propagators/jaeger/jaeger_example_test.go create mode 100644 propagators/jaeger/jaeger_integration_test.go create mode 100644 propagators/jaeger/jaeger_propagator.go create mode 100644 propagators/jaeger/jaeger_propagator_test.go diff --git a/propagators/jaeger/doc.go b/propagators/jaeger/doc.go new file mode 100644 index 00000000000..ca1e6938b76 --- /dev/null +++ b/propagators/jaeger/doc.go @@ -0,0 +1,17 @@ +// Copyright The 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. + +// This package implements the B3 propagator specification as defined +// at https://github.com/openzipkin/b3-propagation +package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" diff --git a/propagators/jaeger/jaeger_data_test.go b/propagators/jaeger/jaeger_data_test.go new file mode 100644 index 00000000000..0be5fb08126 --- /dev/null +++ b/propagators/jaeger/jaeger_data_test.go @@ -0,0 +1,75 @@ +package jaeger_test + +import ( + "fmt" + "go.opentelemetry.io/otel/api/trace" +) + +const ( + traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" + spanIDStr = "00f067aa0ba902b7" + jaegerHeader = "uber-trace-id" +) + +var ( + traceID = trace.ID{0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} + spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} +) + +type extractTest struct { + name string + headers map[string]string + expected trace.SpanContext +} + +var extractHeaders = []extractTest{ + { + "empty", + map[string]string{}, + trace.SpanContext{}, + }, + { + "sampling state not sample", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceIDStr, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, + { + "sampling state sampled", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceIDStr, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + "sampling state debug", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceIDStr, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled | trace.FlagsDebug, + }, + }, + { + "sampling state debug but sampled bit didn't set, result in not sampled decision", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceIDStr, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + }, +} + +var invalidExtractHeaders = []extractTest{} diff --git a/propagators/jaeger/jaeger_example_test.go b/propagators/jaeger/jaeger_example_test.go new file mode 100644 index 00000000000..266b7ca49b7 --- /dev/null +++ b/propagators/jaeger/jaeger_example_test.go @@ -0,0 +1,16 @@ +package jaeger_test + +import ( + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/api/propagation" +) + +func ExampleJaeger() { + jaeger := jaeger.Jaeger{} + // register jaeger propagator + global.SetPropagators(propagation.New( + propagation.WithExtractors(jaeger), + propagation.WithInjectors(jaeger), + )) +} diff --git a/propagators/jaeger/jaeger_integration_test.go b/propagators/jaeger/jaeger_integration_test.go new file mode 100644 index 00000000000..d0f0c7cab07 --- /dev/null +++ b/propagators/jaeger/jaeger_integration_test.go @@ -0,0 +1,48 @@ +package jaeger_test + +import ( + "context" + "github.com/google/go-cmp/cmp" + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/trace" + "net/http" + "testing" +) + +func TestExtractJaeger(t *testing.T) { + testGroup := []struct { + name string + testcases []extractTest + }{ + { + name: "valid test case", + testcases: extractHeaders, + }, + { + name: "invalid test case", + testcases: invalidExtractHeaders, + }, + } + + for _, tg := range testGroup { + propagator := jaeger.Jaeger{} + props := propagation.New(propagation.WithExtractors(propagator)) + + for _, tc := range tg.testcases { + t.Run(tc.name, func(t *testing.T) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + for k, v := range tc.headers { + req.Header.Set(k, v) + } + + ctx := context.Background() + ctx = propagation.ExtractHTTP(ctx, props, req.Header) + resSc := trace.RemoteSpanContextFromContext(ctx) + if diff := cmp.Diff(resSc, tc.expected); diff != "" { + t.Errorf("%s: %s: -got +want %s", tg.name, tc.name, diff) + } + }) + } + } +} diff --git a/propagators/jaeger/jaeger_propagator.go b/propagators/jaeger/jaeger_propagator.go new file mode 100644 index 00000000000..5a6dbb91949 --- /dev/null +++ b/propagators/jaeger/jaeger_propagator.go @@ -0,0 +1,143 @@ +package jaeger + +import ( + "context" + "errors" + "fmt" + "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/trace" + "strconv" + "strings" +) + +const ( + jaegerHeader = "uber-trace-id" + separator = ":" + traceID64bitsWidth = 64 / 4 + traceID128bitsWidth = 128 / 4 + spanIDWidth = 64 / 4 + + traceIDPadding = "0000000000000000" + + flagsDebug = 0x02 + flagsSampled = 0x01 + flagsNotSampled = 0x00 + + deprecatedParentSpanID = "0" +) + +var ( + empty = trace.EmptySpanContext() + + errEmptyContext = errors.New("empty context found in Jaeger propagation") + errMalformedTraceContextVal = errors.New("header value of uber-trace-id should contain four different part separated by : ") + errInvalidTraceIDLength = errors.New("invalid trace id length, must be either 16 or 32") + errMalformedTraceID = errors.New("cannot decode trace id from header, should be a string of hex, lowercase trace id can't be all zero") + errInvalidSpanIDLength = errors.New("invalid span id length, must be 16") + errMalformedSpanID = errors.New("cannot decode span id from header, should be a string of hex, lowercase span id can't be all zero") + errMalformedFlag = errors.New("cannot decode flag") +) + +type Jaeger struct { +} + +var _ propagation.HTTPPropagator = &Jaeger{} + +func (jaeger Jaeger) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { + sc := trace.SpanFromContext(ctx).SpanContext() + headers := []string{} + if !sc.TraceID.IsValid() || !sc.SpanID.IsValid() { + return + } + headers = append(headers, sc.TraceID.String(), sc.SpanID.String(), deprecatedParentSpanID) + if sc.IsDebug() { + headers = append(headers, fmt.Sprintf("%x", flagsDebug|flagsSampled)) + } else if sc.IsSampled() { + headers = append(headers, fmt.Sprintf("%x", flagsSampled)) + } else { + headers = append(headers, fmt.Sprintf("%x", flagsNotSampled)) + } + + supplier.Set(jaegerHeader, strings.Join(headers, separator)) +} + +func (jaeger Jaeger) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { + // extract tracing information + if h := supplier.Get(jaegerHeader); h != "" { + sc, err := extract(h) + if err == nil && sc.IsValid() { + return trace.ContextWithRemoteSpanContext(ctx, sc) + } + } + + return ctx +} + +func extract(headerVal string) (trace.SpanContext, error) { + if headerVal == "" { + return empty, errEmptyContext + } + + var ( + sc = trace.SpanContext{} + err error + ) + + parts := strings.Split(headerVal, separator) + if len(parts) != 4 { + return empty, errMalformedTraceContextVal + } + + // extract trace ID + if parts[0] != "" { + id := parts[0] + if len(id) != traceID128bitsWidth && len(id) != traceID64bitsWidth { + return empty, errInvalidTraceIDLength + } + // padding when length is 16 + if len(id) == traceID64bitsWidth { + id = traceIDPadding + id + } + sc.TraceID, err = trace.IDFromHex(id) + if err != nil { + return empty, errMalformedTraceID + } + } + + // extract span ID + if parts[1] != "" { + id := parts[1] + if len(id) != spanIDWidth { + return empty, errInvalidSpanIDLength + } + sc.SpanID, err = trace.SpanIDFromHex(id) + if err != nil { + return empty, errMalformedSpanID + } + } + + // skip third part as it is desecrated + + // extract flag + if parts[3] != "" { + flagStr := parts[3] + flag, err := strconv.ParseInt(flagStr, 16, 64) + if err != nil { + return empty, errMalformedFlag + } + if flag&flagsSampled == flagsSampled { + // if sample bit is set, we check if debug bit is also set + if flag&flagsDebug == flagsDebug { + sc.TraceFlags |= trace.FlagsSampled | trace.FlagsDebug + } else { + sc.TraceFlags |= trace.FlagsSampled + } + } + // ignore other bit, including firehose since we don't have corresponding flag in trace context. + } + return sc, nil +} + +func (jaeger Jaeger) GetAllKeys() []string { + return []string{jaegerHeader} +} diff --git a/propagators/jaeger/jaeger_propagator_test.go b/propagators/jaeger/jaeger_propagator_test.go new file mode 100644 index 00000000000..1786769fa2c --- /dev/null +++ b/propagators/jaeger/jaeger_propagator_test.go @@ -0,0 +1,148 @@ +package jaeger + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "strings" + "testing" + + "go.opentelemetry.io/otel/api/trace" +) + +var ( + traceID = trace.ID{0, 0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0x1, 0xc8} + traceID128Str = "00000000000000007b000000000001c8" + zeroTraceIDStr = "00000000000000000000000000000000" + traceID64Str = "7b000000000001c8" + spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b} + zeroSpanIDStr = "0000000000000000" + spanIDStr = "000000000000007b" +) + +func TestJaeger_Extract(t *testing.T) { + testData := []struct { + traceID string + spanID string + parentSpanID string + flags string + expected trace.SpanContext + err error + }{ + { + traceID128Str, spanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + nil, + }, + { + traceID64Str, spanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + nil, + }, + { + traceID128Str, spanIDStr, deprecatedParentSpanID, "3", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled | trace.FlagsDebug, + }, + nil, + }, + { + // if we didn't set sampled bit when debug bit is 1, then assuming it's not sampled + traceID128Str, spanIDStr, deprecatedParentSpanID, "2", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: 0x00, + }, + nil, + }, + { + // ignore firehose bit since we don't really have this feature in otel span context + traceID128Str, spanIDStr, deprecatedParentSpanID, "8", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: 0x00, + }, + nil, + }, + { + traceID128Str, spanIDStr, deprecatedParentSpanID, "9", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + nil, + }, + { + traceID128Str, spanIDStr, "wired stuff", "1", + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + nil, + }, + { + fmt.Sprintf("%32s", "This_is_a_string_len_64"), spanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{}, + errMalformedTraceID, + }, + { + "000000000007b00000000000001c8", spanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{}, + errInvalidTraceIDLength, + }, + { + traceID128Str, fmt.Sprintf("%16s", "wiredspanid"), deprecatedParentSpanID, "1", + trace.SpanContext{}, + errMalformedSpanID, + }, + { + traceID128Str, "0000000000010", deprecatedParentSpanID, "1", + trace.SpanContext{}, + errInvalidSpanIDLength, + }, + { + // reject invalid traceID(0) and spanID(0) + zeroTraceIDStr, zeroSpanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{}, + errMalformedTraceID, + }, + { + // reject invalid traceID(0) and spanID(0) + traceID128Str, zeroSpanIDStr, deprecatedParentSpanID, "1", + trace.SpanContext{}, + errMalformedSpanID, + }, + } + + for _, test := range testData { + headerVal := strings.Join([]string{test.traceID, test.spanID, test.parentSpanID, test.flags}, separator) + sc, err := extract(headerVal) + + info := []interface{}{ + "trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q", + test.traceID, + test.spanID, + test.parentSpanID, + test.flags, + } + + if !assert.Equal(t, test.err, err, info...) { + continue + } + + assert.Equal(t, test.expected, sc, info...) + } +} From dccda7c9714e4a132c2f12dce0f1c6ebf88c679b Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Mon, 28 Sep 2020 21:32:54 -0400 Subject: [PATCH 2/6] [propagator] Add more test & copyright header. --- propagators/jaeger/jaeger_data_test.go | 134 +++++++++++++++++- propagators/jaeger/jaeger_example_test.go | 14 ++ propagators/jaeger/jaeger_integration_test.go | 20 ++- propagators/jaeger/jaeger_propagator.go | 19 ++- propagators/jaeger/jaeger_propagator_test.go | 17 ++- 5 files changed, 192 insertions(+), 12 deletions(-) diff --git a/propagators/jaeger/jaeger_data_test.go b/propagators/jaeger/jaeger_data_test.go index 0be5fb08126..10f5a864f9e 100644 --- a/propagators/jaeger/jaeger_data_test.go +++ b/propagators/jaeger/jaeger_data_test.go @@ -1,18 +1,34 @@ +// Copyright The 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 jaeger_test import ( "fmt" + "go.opentelemetry.io/otel/api/trace" ) const ( - traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736" + traceID16Str = "0000000000000000a3ce929d0e0e4736" + traceID32Str = "0000000000000000a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" jaegerHeader = "uber-trace-id" ) var ( - traceID = trace.ID{0x4b, 0xf9, 0x2f, 0x35, 0x77, 0xb3, 0x4d, 0xa6, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} + traceID = trace.ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} ) @@ -31,7 +47,7 @@ var extractHeaders = []extractTest{ { "sampling state not sample", map[string]string{ - jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceIDStr, spanIDStr), + jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, trace.SpanContext{ TraceID: traceID, @@ -41,7 +57,7 @@ var extractHeaders = []extractTest{ { "sampling state sampled", map[string]string{ - jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceIDStr, spanIDStr), + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContext{ TraceID: traceID, @@ -52,7 +68,7 @@ var extractHeaders = []extractTest{ { "sampling state debug", map[string]string{ - jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceIDStr, spanIDStr), + jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, trace.SpanContext{ TraceID: traceID, @@ -63,13 +79,117 @@ var extractHeaders = []extractTest{ { "sampling state debug but sampled bit didn't set, result in not sampled decision", map[string]string{ - jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceIDStr, spanIDStr), + jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceID32Str, spanIDStr), }, trace.SpanContext{ TraceID: traceID, SpanID: spanID, }, }, + { + "flag can be various length", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:00001", traceID32Str, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + "flag can be hex numbers", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:ff", traceID32Str, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsDebug | trace.FlagsSampled, + }, + }, + { + "left padding 64 bit trace ID", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID16Str, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + "128 bit trace ID", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + "ignore parent span id", + map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:whatever:1", traceID32Str, spanIDStr), + }, + trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, } -var invalidExtractHeaders = []extractTest{} +var invalidExtractHeaders = []extractTest{ + { + name: "trace ID length > 32", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str+"0000", spanIDStr), + }, + }, + { + name: "trace ID length is not 32 or 16", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", "1234567890abcd01234", spanIDStr), + }, + }, + { + name: "span ID length is not 16 or 32", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr+"0000"), + }, + }, + { + name: "invalid trace ID", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", "zcd00v0000000000a3ce929d0e0e4736", spanIDStr), + }, + }, + { + name: "invalid span ID", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, "00f0wiredba902b7"), + }, + }, + { + name: "invalid flags", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:wired", traceID32Str, spanIDStr), + }, + }, + { + name: "invalid separator", + headers: map[string]string{ + jaegerHeader: fmt.Sprintf("%s-%s-0-1", traceID32Str, spanIDStr), + }, + }, + { + name: "missing jaeger header", + headers: map[string]string{ + jaegerHeader + "not": fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), + }, + }, +} diff --git a/propagators/jaeger/jaeger_example_test.go b/propagators/jaeger/jaeger_example_test.go index 266b7ca49b7..b67b23721ba 100644 --- a/propagators/jaeger/jaeger_example_test.go +++ b/propagators/jaeger/jaeger_example_test.go @@ -1,3 +1,17 @@ +// Copyright The 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 jaeger_test import ( diff --git a/propagators/jaeger/jaeger_integration_test.go b/propagators/jaeger/jaeger_integration_test.go index d0f0c7cab07..020dbd0011c 100644 --- a/propagators/jaeger/jaeger_integration_test.go +++ b/propagators/jaeger/jaeger_integration_test.go @@ -1,13 +1,29 @@ +// Copyright The 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 jaeger_test import ( "context" + "net/http" + "testing" + "github.com/google/go-cmp/cmp" + "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" - "net/http" - "testing" ) func TestExtractJaeger(t *testing.T) { diff --git a/propagators/jaeger/jaeger_propagator.go b/propagators/jaeger/jaeger_propagator.go index 5a6dbb91949..7acf09659d1 100644 --- a/propagators/jaeger/jaeger_propagator.go +++ b/propagators/jaeger/jaeger_propagator.go @@ -1,13 +1,28 @@ +// Copyright The 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 jaeger import ( "context" "errors" "fmt" - "go.opentelemetry.io/otel/api/propagation" - "go.opentelemetry.io/otel/api/trace" "strconv" "strings" + + "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/trace" ) const ( diff --git a/propagators/jaeger/jaeger_propagator_test.go b/propagators/jaeger/jaeger_propagator_test.go index 1786769fa2c..7bbcc74ab07 100644 --- a/propagators/jaeger/jaeger_propagator_test.go +++ b/propagators/jaeger/jaeger_propagator_test.go @@ -1,11 +1,26 @@ +// Copyright The 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 jaeger import ( "fmt" - "github.com/stretchr/testify/assert" "strings" "testing" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/api/trace" ) From 7685e9228eee5fa5a459a444cb8426d588ad295c Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Tue, 29 Sep 2020 22:09:49 -0400 Subject: [PATCH 3/6] [propagator] Add injection integration test. --- propagators/jaeger/jaeger_data_test.go | 74 +++++++++++++++++++ propagators/jaeger/jaeger_integration_test.go | 55 ++++++++++++++ propagators/jaeger/jaeger_propagator.go | 9 +-- 3 files changed, 132 insertions(+), 6 deletions(-) diff --git a/propagators/jaeger/jaeger_data_test.go b/propagators/jaeger/jaeger_data_test.go index 10f5a864f9e..998af1726c4 100644 --- a/propagators/jaeger/jaeger_data_test.go +++ b/propagators/jaeger/jaeger_data_test.go @@ -192,4 +192,78 @@ var invalidExtractHeaders = []extractTest{ jaegerHeader + "not": fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, }, + { + name: "empty header value", + headers: map[string]string{ + jaegerHeader: "", + }, + }, +} + +type injectTest struct { + name string + sc trace.SpanContext + wantHeaders map[string]string +} + +var injectHeaders = []injectTest{ + { + name: "sampled", + sc: trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + wantHeaders: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), + }, + }, + { + name: "debug", + sc: trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled | trace.FlagsDebug, + }, + wantHeaders: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), + }, + }, + { + name: "not sampled", + sc: trace.SpanContext{ + TraceID: traceID, + SpanID: spanID, + }, + wantHeaders: map[string]string{ + jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), + }, + }, +} + +var invalidInjectHeaders = []injectTest{ + { + name: "empty", + sc: trace.SpanContext{}, + }, + { + name: "missing traceID", + sc: trace.SpanContext{ + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + name: "missing spanID", + sc: trace.SpanContext{ + TraceID: traceID, + TraceFlags: trace.FlagsSampled, + }, + }, + { + name: "missing both traceID and spanID", + sc: trace.SpanContext{ + TraceFlags: trace.FlagsSampled, + }, + }, } diff --git a/propagators/jaeger/jaeger_integration_test.go b/propagators/jaeger/jaeger_integration_test.go index 020dbd0011c..cb55d15a8cf 100644 --- a/propagators/jaeger/jaeger_integration_test.go +++ b/propagators/jaeger/jaeger_integration_test.go @@ -21,11 +21,17 @@ import ( "github.com/google/go-cmp/cmp" + mocktracer "go.opentelemetry.io/contrib/internal/trace" "go.opentelemetry.io/contrib/propagators/jaeger" "go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/trace" ) +var ( + mockTracer = mocktracer.NewTracer("") + _, mockSpan = mockTracer.Start(context.Background(), "") +) + func TestExtractJaeger(t *testing.T) { testGroup := []struct { name string @@ -62,3 +68,52 @@ func TestExtractJaeger(t *testing.T) { } } } + +type testSpan struct { + trace.Span + sc trace.SpanContext +} + +func (s testSpan) SpanContext() trace.SpanContext { + return s.sc +} + +func TestInjectJaeger(t *testing.T) { + testGroup := []struct { + name string + testcases []injectTest + }{ + { + name: "valid test case", + testcases: injectHeaders, + }, + { + name: "invalid test case", + testcases: invalidInjectHeaders, + }, + } + + for _, tg := range testGroup { + for _, tc := range tg.testcases { + propagator := jaeger.Jaeger{} + t.Run(tc.name, func(t *testing.T) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + ctx := trace.ContextWithSpan( + context.Background(), + testSpan{ + Span: mockSpan, + sc: tc.sc, + }, + ) + propagator.Inject(ctx, req.Header) + + for h, v := range tc.wantHeaders { + result, want := req.Header.Get(h), v + if diff := cmp.Diff(result, want); diff != "" { + t.Errorf("%s: %s, header=%s: -got +want %s", tg.name, tc.name, h, diff) + } + } + }) + } + } +} diff --git a/propagators/jaeger/jaeger_propagator.go b/propagators/jaeger/jaeger_propagator.go index 7acf09659d1..438d7f53e9b 100644 --- a/propagators/jaeger/jaeger_propagator.go +++ b/propagators/jaeger/jaeger_propagator.go @@ -44,7 +44,6 @@ const ( var ( empty = trace.EmptySpanContext() - errEmptyContext = errors.New("empty context found in Jaeger propagation") errMalformedTraceContextVal = errors.New("header value of uber-trace-id should contain four different part separated by : ") errInvalidTraceIDLength = errors.New("invalid trace id length, must be either 16 or 32") errMalformedTraceID = errors.New("cannot decode trace id from header, should be a string of hex, lowercase trace id can't be all zero") @@ -73,7 +72,9 @@ func (jaeger Jaeger) Inject(ctx context.Context, supplier propagation.HTTPSuppli headers = append(headers, fmt.Sprintf("%x", flagsNotSampled)) } - supplier.Set(jaegerHeader, strings.Join(headers, separator)) + if len(headers) == 4 { + supplier.Set(jaegerHeader, strings.Join(headers, separator)) + } } func (jaeger Jaeger) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { @@ -89,10 +90,6 @@ func (jaeger Jaeger) Extract(ctx context.Context, supplier propagation.HTTPSuppl } func extract(headerVal string) (trace.SpanContext, error) { - if headerVal == "" { - return empty, errEmptyContext - } - var ( sc = trace.SpanContext{} err error From ca684d57177049d1614f64cf17919a630d6b4703 Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Mon, 5 Oct 2020 20:46:09 -0400 Subject: [PATCH 4/6] [propagator] Address comments. --- propagators/jaeger/doc.go | 4 ++-- propagators/jaeger/jaeger_data_test.go | 2 +- propagators/jaeger/jaeger_propagator.go | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/propagators/jaeger/doc.go b/propagators/jaeger/doc.go index ca1e6938b76..29f5bbd3439 100644 --- a/propagators/jaeger/doc.go +++ b/propagators/jaeger/doc.go @@ -12,6 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This package implements the B3 propagator specification as defined -// at https://github.com/openzipkin/b3-propagation +// This package implements the Jaeger propagator specification as defined +// at https://www.jaegertracing.io/docs/1.18/client-libraries/#propagation-format package jaeger // import "go.opentelemetry.io/contrib/propagators/jaeger" diff --git a/propagators/jaeger/jaeger_data_test.go b/propagators/jaeger/jaeger_data_test.go index 998af1726c4..f1cb3d01d4a 100644 --- a/propagators/jaeger/jaeger_data_test.go +++ b/propagators/jaeger/jaeger_data_test.go @@ -21,7 +21,7 @@ import ( ) const ( - traceID16Str = "0000000000000000a3ce929d0e0e4736" + traceID16Str = "a3ce929d0e0e4736" traceID32Str = "0000000000000000a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" jaegerHeader = "uber-trace-id" diff --git a/propagators/jaeger/jaeger_propagator.go b/propagators/jaeger/jaeger_propagator.go index 438d7f53e9b..ece36d7bac6 100644 --- a/propagators/jaeger/jaeger_propagator.go +++ b/propagators/jaeger/jaeger_propagator.go @@ -52,8 +52,7 @@ var ( errMalformedFlag = errors.New("cannot decode flag") ) -type Jaeger struct { -} +type Jaeger struct{} var _ propagation.HTTPPropagator = &Jaeger{} @@ -72,9 +71,7 @@ func (jaeger Jaeger) Inject(ctx context.Context, supplier propagation.HTTPSuppli headers = append(headers, fmt.Sprintf("%x", flagsNotSampled)) } - if len(headers) == 4 { - supplier.Set(jaegerHeader, strings.Join(headers, separator)) - } + supplier.Set(jaegerHeader, strings.Join(headers, separator)) } func (jaeger Jaeger) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { @@ -128,7 +125,7 @@ func extract(headerVal string) (trace.SpanContext, error) { } } - // skip third part as it is desecrated + // skip third part as it is deprecated // extract flag if parts[3] != "" { From 480e3c468570001f128201a04f699505195865df Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Tue, 6 Oct 2020 20:24:21 -0400 Subject: [PATCH 5/6] [propagator] Address the comment. Change traceID so that we can verify the propagator will not just take the low 64 bits. --- propagators/jaeger/jaeger_data_test.go | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/propagators/jaeger/jaeger_data_test.go b/propagators/jaeger/jaeger_data_test.go index f1cb3d01d4a..f5f7946feaf 100644 --- a/propagators/jaeger/jaeger_data_test.go +++ b/propagators/jaeger/jaeger_data_test.go @@ -22,14 +22,15 @@ import ( const ( traceID16Str = "a3ce929d0e0e4736" - traceID32Str = "0000000000000000a3ce929d0e0e4736" + traceID32Str = "a1ce929d0e0e4736a3ce929d0e0e4736" spanIDStr = "00f067aa0ba902b7" jaegerHeader = "uber-trace-id" ) var ( - traceID = trace.ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} - spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} + traceID16 = trace.ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} + traceID32 = trace.ID{0xa1, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36, 0xa3, 0xce, 0x92, 0x9d, 0x0e, 0x0e, 0x47, 0x36} + spanID = trace.SpanID{0x00, 0xf0, 0x67, 0xaa, 0x0b, 0xa9, 0x02, 0xb7} ) type extractTest struct { @@ -50,7 +51,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:0", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, }, }, @@ -60,7 +61,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -71,7 +72,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:3", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled | trace.FlagsDebug, }, @@ -82,7 +83,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:2", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, }, }, @@ -92,7 +93,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:00001", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -103,7 +104,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:ff", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsDebug | trace.FlagsSampled, }, @@ -114,7 +115,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID16Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID16, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -125,7 +126,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:0:1", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -136,7 +137,7 @@ var extractHeaders = []extractTest{ jaegerHeader: fmt.Sprintf("%s:%s:whatever:1", traceID32Str, spanIDStr), }, trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -210,7 +211,7 @@ var injectHeaders = []injectTest{ { name: "sampled", sc: trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled, }, @@ -221,7 +222,7 @@ var injectHeaders = []injectTest{ { name: "debug", sc: trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, TraceFlags: trace.FlagsSampled | trace.FlagsDebug, }, @@ -232,7 +233,7 @@ var injectHeaders = []injectTest{ { name: "not sampled", sc: trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, SpanID: spanID, }, wantHeaders: map[string]string{ @@ -256,7 +257,7 @@ var invalidInjectHeaders = []injectTest{ { name: "missing spanID", sc: trace.SpanContext{ - TraceID: traceID, + TraceID: traceID32, TraceFlags: trace.FlagsSampled, }, }, From b1d98e4fbcf2cbd102d0c768aee60288880efb9c Mon Sep 17 00:00:00 2001 From: "zhongyang.wu" Date: Thu, 8 Oct 2020 21:49:19 -0400 Subject: [PATCH 6/6] [propagator] Add necessary doc for functions and types --- propagators/jaeger/jaeger_propagator.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/propagators/jaeger/jaeger_propagator.go b/propagators/jaeger/jaeger_propagator.go index ece36d7bac6..0291526f430 100644 --- a/propagators/jaeger/jaeger_propagator.go +++ b/propagators/jaeger/jaeger_propagator.go @@ -52,10 +52,17 @@ var ( errMalformedFlag = errors.New("cannot decode flag") ) +// Jaeger propagator serializes SpanContext to/from Jaeger Headers +// +// Jaeger format: +// +// uber-trace-id: {trace-id}:{span-id}:{parent-span-id}:{flags} type Jaeger struct{} var _ propagation.HTTPPropagator = &Jaeger{} +// Inject injects a context to the supplier following jaeger format. +// The parent span ID is set to an dummy parent span id as the most implementations do. func (jaeger Jaeger) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { sc := trace.SpanFromContext(ctx).SpanContext() headers := []string{} @@ -74,6 +81,7 @@ func (jaeger Jaeger) Inject(ctx context.Context, supplier propagation.HTTPSuppli supplier.Set(jaegerHeader, strings.Join(headers, separator)) } +// Extract extracts a context from the supplier if it contains Jaeger headers. func (jaeger Jaeger) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { // extract tracing information if h := supplier.Get(jaegerHeader); h != "" {