Move B3 propagator from main repo to contrib repo (#344)

* Move B3 context propagator from go.opentelemetry.io/otel repo.

* Add to dependabot.yml

* Update README.  Add doc.go

* Updated CHANGELOG to reflect addition of B3 propagator.

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
ET 2020-09-15 08:07:11 -07:00 committed by GitHub
parent 22ec41426a
commit c5771c8dda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2125 additions and 0 deletions

View File

@ -309,6 +309,15 @@ updates:
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/propagators"
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
interval: "weekly"
day: "sunday"
- package-ecosystem: "gomod"
directory: "/tools"
labels:
- dependencies

View File

@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Benchmark tests for the gRPC instrumentation. (#296)
- Integration testing for the gRPC instrumentation. (#297)
- Allow custom labels to be added to net/http metrics. (#306)
- Added B3 propagator, moving it out of open.telemetry.io/otel repo. (#344)
### Changed

View File

@ -11,6 +11,8 @@ Collection of 3rd-party instrumentation and exporters for [OpenTelemetry-Go](htt
- [Instrumentation](./instrumentation/): Packages providing OpenTelemetry instrumentation for 3rd-party libraries.
- [Exporters](./exporters/): Packages providing OpenTelemetry exporters for 3rd-party telemetry systems.
- [Propagators](./propagators/): Packages providing Opentelemetry context propagators for 3rd-party propagation formats.
- [Detectors](./detectors/): Packages providing OpenTelemetry resource detectors for 3rd-party cloud computing environments.
## Contributing

View File

@ -0,0 +1,103 @@
// 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 b3_test
import (
"context"
"net/http"
"testing"
"go.opentelemetry.io/contrib/propagators/b3"
"go.opentelemetry.io/otel/api/trace"
)
func BenchmarkExtractB3(b *testing.B) {
testGroup := []struct {
name string
tests []extractTest
}{
{
name: "valid headers",
tests: extractHeaders,
},
{
name: "invalid headers",
tests: extractInvalidHeaders,
},
}
for _, tg := range testGroup {
propagator := b3.B3{}
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.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = propagator.Extract(ctx, req.Header)
}
})
}
}
}
func BenchmarkInjectB3(b *testing.B) {
testGroup := []struct {
name string
tests []injectTest
}{
{
name: "valid headers",
tests: injectHeader,
},
{
name: "invalid headers",
tests: injectInvalidHeader,
},
}
for _, tg := range testGroup {
for _, tt := range tg.tests {
propagator := b3.B3{InjectEncoding: tt.encoding}
traceBenchmark(tg.name+"/"+tt.name, b, func(b *testing.B) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := trace.ContextWithSpan(
context.Background(),
testSpan{sc: tt.sc},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
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)
})
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
// 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 b3_test
import (
"go.opentelemetry.io/contrib/propagators/b3"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/propagation"
)
func ExampleB3() {
b3 := b3.B3{}
// Register the B3 propagator globally.
global.SetPropagators(propagation.New(
propagation.WithExtractors(b3),
propagation.WithInjectors(b3),
))
}
func ExampleB3_injectEncoding() {
// Create a B3 propagator configured to inject context with both multiple
// and single header B3 HTTP encoding.
b3 := b3.B3{
InjectEncoding: b3.B3MultipleHeader | b3.B3SingleHeader,
}
global.SetPropagators(propagation.New(
propagation.WithExtractors(b3),
propagation.WithInjectors(b3),
))
}

View File

@ -0,0 +1,178 @@
// 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 b3_test
import (
"context"
"net/http"
"testing"
"github.com/google/go-cmp/cmp"
mocktracer "go.opentelemetry.io/contrib/internal/trace"
"go.opentelemetry.io/contrib/propagators/b3"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace"
)
var (
mockTracer = mocktracer.NewTracer("")
_, mockSpan = mockTracer.Start(context.Background(), "")
)
func TestExtractB3(t *testing.T) {
testGroup := []struct {
name string
tests []extractTest
}{
{
name: "valid extract headers",
tests: extractHeaders,
},
{
name: "invalid extract headers",
tests: extractInvalidHeaders,
},
}
for _, tg := range testGroup {
propagator := b3.B3{}
props := propagation.New(propagation.WithExtractors(propagator))
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()
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
gotSc := trace.RemoteSpanContextFromContext(ctx)
if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" {
t.Errorf("%s: %s: -got +want %s", tg.name, tt.name, diff)
}
})
}
}
}
type testSpan struct {
trace.Span
sc trace.SpanContext
}
func (s testSpan) SpanContext() trace.SpanContext {
return s.sc
}
func TestInjectB3(t *testing.T) {
testGroup := []struct {
name string
tests []injectTest
}{
{
name: "valid inject headers",
tests: injectHeader,
},
{
name: "invalid inject headers",
tests: injectInvalidHeader,
},
}
for _, tg := range testGroup {
for _, tt := range tg.tests {
propagator := b3.B3{InjectEncoding: tt.encoding}
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := trace.ContextWithSpan(
context.Background(),
testSpan{
Span: mockSpan,
sc: tt.sc,
},
)
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)
}
}
for _, h := range tt.doNotWantHeaders {
v, gotOk := req.Header[h]
if diff := cmp.Diff(gotOk, false); diff != "" {
t.Errorf("%s: %s, header=%s: -got +want %s, value=%s", tg.name, tt.name, h, diff, v)
}
}
})
}
}
}
func TestB3Propagator_GetAllKeys(t *testing.T) {
tests := []struct {
name string
propagator b3.B3
want []string
}{
{
name: "no encoding specified",
propagator: b3.B3{},
want: []string{
b3TraceID,
b3SpanID,
b3Sampled,
b3Flags,
},
},
{
name: "B3MultipleHeader encoding specified",
propagator: b3.B3{InjectEncoding: b3.B3MultipleHeader},
want: []string{
b3TraceID,
b3SpanID,
b3Sampled,
b3Flags,
},
},
{
name: "B3SingleHeader encoding specified",
propagator: b3.B3{InjectEncoding: b3.B3SingleHeader},
want: []string{
b3Context,
},
},
{
name: "B3SingleHeader and B3MultipleHeader encoding specified",
propagator: b3.B3{InjectEncoding: b3.B3SingleHeader | b3.B3MultipleHeader},
want: []string{
b3Context,
b3TraceID,
b3SpanID,
b3Sampled,
b3Flags,
},
},
}
for _, test := range tests {
if diff := cmp.Diff(test.propagator.GetAllKeys(), test.want); diff != "" {
t.Errorf("%s: GetAllKeys: -got +want %s", test.name, diff)
}
}
}

View File

@ -0,0 +1,344 @@
// 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 b3
import (
"context"
"errors"
"strings"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace"
)
const (
// Default B3 Header names.
b3ContextHeader = "b3"
b3DebugFlagHeader = "x-b3-flags"
b3TraceIDHeader = "x-b3-traceid"
b3SpanIDHeader = "x-b3-spanid"
b3SampledHeader = "x-b3-sampled"
b3ParentSpanIDHeader = "x-b3-parentspanid"
b3TraceIDPadding = "0000000000000000"
// B3 Single Header encoding widths.
separatorWidth = 1 // Single "-" character.
samplingWidth = 1 // Single hex character.
traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID.
traceID128BitsWidth = 128 / 4 // 32 hex character Trace ID.
spanIDWidth = 16 // 16 hex character ID.
parentSpanIDWidth = 16 // 16 hex character ID.
)
var (
empty = trace.EmptySpanContext()
errInvalidSampledByte = errors.New("invalid B3 Sampled found")
errInvalidSampledHeader = errors.New("invalid B3 Sampled header found")
errInvalidTraceIDHeader = errors.New("invalid B3 traceID header found")
errInvalidSpanIDHeader = errors.New("invalid B3 spanID header found")
errInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found")
errInvalidScope = errors.New("require either both traceID and spanID or none")
errInvalidScopeParent = errors.New("ParentSpanID requires both traceID and spanID to be available")
errInvalidScopeParentSingle = errors.New("ParentSpanID requires traceID, spanID and Sampled to be available")
errEmptyContext = errors.New("empty request context")
errInvalidTraceIDValue = errors.New("invalid B3 traceID value found")
errInvalidSpanIDValue = errors.New("invalid B3 spanID value found")
errInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found")
)
// Encoding is a bitmask representation of the B3 encoding type.
type Encoding uint8
// supports returns if e has o bit(s) set.
func (e Encoding) supports(o Encoding) bool {
return e&o == o
}
const (
// B3MultipleHeader is a B3 encoding that uses multiple headers to
// transmit tracing information all prefixed with `x-b3-`.
B3MultipleHeader Encoding = 1 << iota
// B3SingleHeader is a B3 encoding that uses a single header named `b3`
// to transmit tracing information.
B3SingleHeader
// B3Unspecified is an unspecified B3 encoding.
B3Unspecified Encoding = 0
)
// B3 propagator serializes SpanContext to/from B3 Headers.
// This propagator supports both versions of B3 headers,
// 1. Single Header:
// 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}
type B3 struct {
// InjectEncoding are the B3 encodings used when injecting trace
// information. If no encoding is specified (i.e. `B3Unspecified`)
// `B3MultipleHeader` will be used as the default.
InjectEncoding Encoding
}
var _ propagation.HTTPPropagator = B3{}
// Inject injects a context into the supplier as B3 headers.
// The parent span ID is omitted because it is not tracked in the
// SpanContext.
func (b3 B3) Inject(ctx context.Context, supplier propagation.HTTPSupplier) {
sc := trace.SpanFromContext(ctx).SpanContext()
if b3.InjectEncoding.supports(B3SingleHeader) {
header := []string{}
if sc.TraceID.IsValid() && sc.SpanID.IsValid() {
header = append(header, sc.TraceID.String(), sc.SpanID.String())
}
if sc.TraceFlags&trace.FlagsDebug == trace.FlagsDebug {
header = append(header, "d")
} else if !(sc.TraceFlags&trace.FlagsDeferred == trace.FlagsDeferred) {
if sc.IsSampled() {
header = append(header, "1")
} else {
header = append(header, "0")
}
}
supplier.Set(b3ContextHeader, strings.Join(header, "-"))
}
if b3.InjectEncoding.supports(B3MultipleHeader) || b3.InjectEncoding == B3Unspecified {
if sc.TraceID.IsValid() && sc.SpanID.IsValid() {
supplier.Set(b3TraceIDHeader, sc.TraceID.String())
supplier.Set(b3SpanIDHeader, sc.SpanID.String())
}
if sc.TraceFlags&trace.FlagsDebug == trace.FlagsDebug {
// Since Debug implies deferred, don't also send "X-B3-Sampled".
supplier.Set(b3DebugFlagHeader, "1")
} else if !(sc.TraceFlags&trace.FlagsDeferred == trace.FlagsDeferred) {
if sc.IsSampled() {
supplier.Set(b3SampledHeader, "1")
} else {
supplier.Set(b3SampledHeader, "0")
}
}
}
}
// Extract extracts a context from the supplier if it contains B3 headers.
func (b3 B3) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context {
var (
sc trace.SpanContext
err error
)
// Default to Single Header if a valid value exists.
if h := supplier.Get(b3ContextHeader); h != "" {
sc, err = extractSingle(h)
if err == nil && sc.IsValid() {
return trace.ContextWithRemoteSpanContext(ctx, sc)
}
// The Single Header value was invalid, fallback to Multiple Header.
}
var (
traceID = supplier.Get(b3TraceIDHeader)
spanID = supplier.Get(b3SpanIDHeader)
parentSpanID = supplier.Get(b3ParentSpanIDHeader)
sampled = supplier.Get(b3SampledHeader)
debugFlag = supplier.Get(b3DebugFlagHeader)
)
sc, err = extractMultiple(traceID, spanID, parentSpanID, sampled, debugFlag)
if err != nil || !sc.IsValid() {
return ctx
}
return trace.ContextWithRemoteSpanContext(ctx, sc)
}
func (b3 B3) GetAllKeys() []string {
header := []string{}
if b3.InjectEncoding.supports(B3SingleHeader) {
header = append(header, b3ContextHeader)
}
if b3.InjectEncoding.supports(B3MultipleHeader) || b3.InjectEncoding == B3Unspecified {
header = append(header, b3TraceIDHeader, b3SpanIDHeader, b3SampledHeader, b3DebugFlagHeader)
}
return header
}
// extractMultiple reconstructs a SpanContext from header values based on B3
// Multiple header. It is based on the implementation found here:
// https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go
// and adapted to support a SpanContext.
func extractMultiple(traceID, spanID, parentSpanID, sampled, flags string) (trace.SpanContext, error) {
var (
err error
requiredCount int
sc = trace.SpanContext{}
)
// correct values for an existing sampled header are "0" and "1".
// For legacy support and being lenient to other tracing implementations we
// allow "true" and "false" as inputs for interop purposes.
switch strings.ToLower(sampled) {
case "0", "false":
// Zero value for TraceFlags sample bit is unset.
case "1", "true":
sc.TraceFlags = trace.FlagsSampled
case "":
sc.TraceFlags = trace.FlagsDeferred
default:
return empty, errInvalidSampledHeader
}
// The only accepted value for Flags is "1". This will set Debug to
// true. All other values and omission of header will be ignored.
if flags == "1" {
sc.TraceFlags |= trace.FlagsDebug
}
if traceID != "" {
requiredCount++
id := traceID
if len(traceID) == 16 {
// Pad 64-bit trace IDs.
id = b3TraceIDPadding + traceID
}
if sc.TraceID, err = trace.IDFromHex(id); err != nil {
return empty, errInvalidTraceIDHeader
}
}
if spanID != "" {
requiredCount++
if sc.SpanID, err = trace.SpanIDFromHex(spanID); err != nil {
return empty, errInvalidSpanIDHeader
}
}
if requiredCount != 0 && requiredCount != 2 {
return empty, errInvalidScope
}
if parentSpanID != "" {
if requiredCount == 0 {
return empty, errInvalidScopeParent
}
// Validate parent span ID but we do not use it so do not save it.
if _, err = trace.SpanIDFromHex(parentSpanID); err != nil {
return empty, errInvalidParentSpanIDHeader
}
}
return sc, nil
}
// extractSingle reconstructs a SpanContext from contextHeader based on a B3
// Single header. It is based on the implementation found here:
// https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go
// and adapted to support a SpanContext.
func extractSingle(contextHeader string) (trace.SpanContext, error) {
if contextHeader == "" {
return empty, errEmptyContext
}
var (
sc = trace.SpanContext{}
sampling string
)
headerLen := len(contextHeader)
if headerLen == samplingWidth {
sampling = contextHeader
} else if headerLen == traceID64BitsWidth || headerLen == traceID128BitsWidth {
// Trace ID by itself is invalid.
return empty, errInvalidScope
} else if headerLen >= traceID64BitsWidth+spanIDWidth+separatorWidth {
pos := 0
var traceID string
if string(contextHeader[traceID64BitsWidth]) == "-" {
// traceID must be 64 bits
pos += traceID64BitsWidth // {traceID}
traceID = b3TraceIDPadding + string(contextHeader[0:pos])
} else if string(contextHeader[32]) == "-" {
// traceID must be 128 bits
pos += traceID128BitsWidth // {traceID}
traceID = string(contextHeader[0:pos])
} else {
return empty, errInvalidTraceIDValue
}
var err error
sc.TraceID, err = trace.IDFromHex(traceID)
if err != nil {
return empty, errInvalidTraceIDValue
}
pos += separatorWidth // {traceID}-
sc.SpanID, err = trace.SpanIDFromHex(contextHeader[pos : pos+spanIDWidth])
if err != nil {
return empty, errInvalidSpanIDValue
}
pos += spanIDWidth // {traceID}-{spanID}
if headerLen > pos {
if headerLen == pos+separatorWidth {
// {traceID}-{spanID}- is invalid.
return empty, errInvalidSampledByte
}
pos += separatorWidth // {traceID}-{spanID}-
if headerLen == pos+samplingWidth {
sampling = string(contextHeader[pos])
} else if headerLen == pos+parentSpanIDWidth {
// {traceID}-{spanID}-{parentSpanID} is invalid.
return empty, errInvalidScopeParentSingle
} else if headerLen == pos+samplingWidth+separatorWidth+parentSpanIDWidth {
sampling = string(contextHeader[pos])
pos += samplingWidth + separatorWidth // {traceID}-{spanID}-{sampling}-
// Validate parent span ID but we do not use it so do not
// save it.
_, err = trace.SpanIDFromHex(contextHeader[pos:])
if err != nil {
return empty, errInvalidParentSpanIDValue
}
} else {
return empty, errInvalidParentSpanIDValue
}
}
} else {
return empty, errInvalidTraceIDValue
}
switch sampling {
case "":
sc.TraceFlags = trace.FlagsDeferred
case "d":
sc.TraceFlags = trace.FlagsDebug
case "1":
sc.TraceFlags = trace.FlagsSampled
case "0":
// Zero value for TraceFlags sample bit is unset.
default:
return empty, errInvalidSampledByte
}
return sc, nil
}

View File

@ -0,0 +1,325 @@
// 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 b3
import (
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/api/trace"
)
var (
traceID = trace.ID{0, 0, 0, 0, 0, 0, 0, 0x7b, 0, 0, 0, 0, 0, 0, 0x1, 0xc8}
traceIDStr = "000000000000007b00000000000001c8"
spanID = trace.SpanID{0, 0, 0, 0, 0, 0, 0, 0x7b}
spanIDStr = "000000000000007b"
)
func TestExtractMultiple(t *testing.T) {
tests := []struct {
traceID string
spanID string
parentSpanID string
sampled string
flags string
expected trace.SpanContext
err error
}{
{
"", "", "", "0", "",
trace.SpanContext{},
nil,
},
{
"", "", "", "", "",
trace.SpanContext{TraceFlags: trace.FlagsDeferred},
nil,
},
{
"", "", "", "1", "",
trace.SpanContext{TraceFlags: trace.FlagsSampled},
nil,
},
{
"", "", "", "", "1",
trace.SpanContext{TraceFlags: trace.FlagsDeferred | trace.FlagsDebug},
nil,
},
{
"", "", "", "0", "1",
trace.SpanContext{TraceFlags: trace.FlagsDebug},
nil,
},
{
"", "", "", "1", "1",
trace.SpanContext{TraceFlags: trace.FlagsSampled | trace.FlagsDebug},
nil,
},
{
traceIDStr, spanIDStr, "", "", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsDeferred},
nil,
},
{
traceIDStr, spanIDStr, "", "0", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID},
nil,
},
// Ensure backwards compatibility.
{
traceIDStr, spanIDStr, "", "false", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID},
nil,
},
{
traceIDStr, spanIDStr, "", "1", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled},
nil,
},
// Ensure backwards compatibility.
{
traceIDStr, spanIDStr, "", "true", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled},
nil,
},
{
traceIDStr, spanIDStr, "", "a", "",
empty,
errInvalidSampledHeader,
},
{
traceIDStr, spanIDStr, "", "1", "1",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled | trace.FlagsDebug},
nil,
},
// Invalid flags are discarded.
{
traceIDStr, spanIDStr, "", "1", "invalid",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled},
nil,
},
// Support short trace IDs.
{
"00000000000001c8", spanIDStr, "", "0", "",
trace.SpanContext{
TraceID: trace.ID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8},
SpanID: spanID,
},
nil,
},
{
"00000000000001c", spanIDStr, "", "0", "",
empty,
errInvalidTraceIDHeader,
},
{
"00000000000001c80", spanIDStr, "", "0", "",
empty,
errInvalidTraceIDHeader,
},
{
traceIDStr[:len(traceIDStr)-2], spanIDStr, "", "0", "",
empty,
errInvalidTraceIDHeader,
},
{
traceIDStr + "0", spanIDStr, "", "0", "",
empty,
errInvalidTraceIDHeader,
},
{
traceIDStr, "00000000000001c", "", "0", "",
empty,
errInvalidSpanIDHeader,
},
{
traceIDStr, "00000000000001c80", "", "0", "",
empty,
errInvalidSpanIDHeader,
},
{
traceIDStr, "", "", "0", "",
empty,
errInvalidScope,
},
{
"", spanIDStr, "", "0", "",
empty,
errInvalidScope,
},
{
"", "", spanIDStr, "0", "",
empty,
errInvalidScopeParent,
},
{
traceIDStr, spanIDStr, "00000000000001c8", "0", "",
trace.SpanContext{TraceID: traceID, SpanID: spanID},
nil,
},
{
traceIDStr, spanIDStr, "00000000000001c", "0", "",
empty,
errInvalidParentSpanIDHeader,
},
{
traceIDStr, spanIDStr, "00000000000001c80", "0", "",
empty,
errInvalidParentSpanIDHeader,
},
}
for _, test := range tests {
actual, err := extractMultiple(
test.traceID,
test.spanID,
test.parentSpanID,
test.sampled,
test.flags,
)
info := []interface{}{
"trace ID: %q, span ID: %q, parent span ID: %q, sampled: %q, flags: %q",
test.traceID,
test.spanID,
test.parentSpanID,
test.sampled,
test.flags,
}
if !assert.Equal(t, test.err, err, info...) {
continue
}
assert.Equal(t, test.expected, actual, info...)
}
}
func TestExtractSingle(t *testing.T) {
tests := []struct {
header string
expected trace.SpanContext
err error
}{
{"0", trace.SpanContext{}, nil},
{"1", trace.SpanContext{TraceFlags: trace.FlagsSampled}, nil},
{"d", trace.SpanContext{TraceFlags: trace.FlagsDebug}, nil},
{"a", empty, errInvalidSampledByte},
{"3", empty, errInvalidSampledByte},
{"000000000000007b", empty, errInvalidScope},
{"000000000000007b00000000000001c8", empty, errInvalidScope},
// Support short trace IDs.
{
"00000000000001c8-000000000000007b",
trace.SpanContext{
TraceID: trace.ID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0xc8},
SpanID: spanID,
TraceFlags: trace.FlagsDeferred,
},
nil,
},
{
"000000000000007b00000000000001c8-000000000000007b",
trace.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceFlags: trace.FlagsDeferred,
},
nil,
},
{
"000000000000007b00000000000001c8-000000000000007b-",
empty,
errInvalidSampledByte,
},
{
"000000000000007b00000000000001c8-000000000000007b-3",
empty,
errInvalidSampledByte,
},
{
"000000000000007b00000000000001c8-000000000000007b-00000000000001c8",
empty,
errInvalidScopeParentSingle,
},
{
"000000000000007b00000000000001c8-000000000000007b-1",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled},
nil,
},
// ParentSpanID is discarded, but should still result in a parsable header.
{
"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c8",
trace.SpanContext{TraceID: traceID, SpanID: spanID, TraceFlags: trace.FlagsSampled},
nil,
},
{
"000000000000007b00000000000001c8-000000000000007b-1-00000000000001c",
empty,
errInvalidParentSpanIDValue,
},
{"", empty, errEmptyContext},
}
for _, test := range tests {
actual, err := extractSingle(test.header)
if !assert.Equal(t, test.err, err, "header: %s", test.header) {
continue
}
assert.Equal(t, test.expected, actual, "header: %s", test.header)
}
}
func TestB3EncodingOperations(t *testing.T) {
encodings := []Encoding{
B3MultipleHeader,
B3SingleHeader,
B3Unspecified,
}
// Test for overflow (or something really unexpected).
for i, e := range encodings {
for j := i + 1; j < i+len(encodings); j++ {
o := encodings[j%len(encodings)]
assert.False(t, e == o, "%v == %v", e, o)
}
}
// B3Unspecified is a special case, it supports only itself, but is
// supported by everything.
assert.True(t, B3Unspecified.supports(B3Unspecified))
for _, e := range encodings[:len(encodings)-1] {
assert.False(t, B3Unspecified.supports(e), e)
assert.True(t, e.supports(B3Unspecified), e)
}
// Skip the special case for B3Unspecified.
for i, e := range encodings[:len(encodings)-1] {
// Everything should support itself.
assert.True(t, e.supports(e))
for j := i + 1; j < i+len(encodings); j++ {
o := encodings[j%len(encodings)]
// Any "or" combination should be supportive of an operand.
assert.True(t, (e | o).supports(e), "(%[0]v|%[1]v).supports(%[0]v)", e, o)
// Bitmasks should be unique.
assert.False(t, o.supports(e), "%v.supports(%v)", o, e)
}
}
// Encoding.supports should be more inclusive than equality.
all := ^B3Unspecified
for _, e := range encodings {
assert.True(t, all.supports(e))
}
}

17
propagators/b3/doc.go Normal file
View File

@ -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 b3 // import "go.opentelemetry.io/contrib/propagators/b3"

19
propagators/doc.go Normal file
View File

@ -0,0 +1,19 @@
// 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 module contains all of its functionality in its subpackages.
// This top-level package is to allow clients to pull in all propagator
// implementations at once using
// require go.opentelemetry.io/contrib/propagators
package propagators

12
propagators/go.mod Normal file
View File

@ -0,0 +1,12 @@
module go.opentelemetry.io/contrib/propagators
go 1.14
replace go.opentelemetry.io/contrib => ./..
require (
github.com/google/go-cmp v0.5.1
github.com/stretchr/testify v1.6.1
go.opentelemetry.io/contrib v0.11.0
go.opentelemetry.io/otel v0.11.0
)

26
propagators/go.sum Normal file
View File

@ -0,0 +1,26 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v0.11.0 h1:IN2tzQa9Gc4ZVKnTaMbPVcHjvzOdg5n9QfnmlqiET7E=
go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=