You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-08-10 22:31:50 +02:00
Move baggage and propagation to separate packages (#1325)
* Move propagation code to propagation package * Move baggage code to baggage package * Update changelog * Make docs of baggage.Set more clear Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
111
propagation/baggage.go
Normal file
111
propagation/baggage.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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 propagation // import "go.opentelemetry.io/otel/propagation"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/baggage"
|
||||
"go.opentelemetry.io/otel/label"
|
||||
)
|
||||
|
||||
const baggageHeader = "baggage"
|
||||
|
||||
// Baggage is a propagator that supports the W3C Baggage format.
|
||||
//
|
||||
// This propagates user-defined baggage associated with a trace. The complete
|
||||
// specification is defined at https://w3c.github.io/baggage/.
|
||||
type Baggage struct{}
|
||||
|
||||
var _ TextMapPropagator = Baggage{}
|
||||
|
||||
// Inject sets baggage key-values from ctx into the carrier.
|
||||
func (b Baggage) Inject(ctx context.Context, carrier TextMapCarrier) {
|
||||
baggageMap := baggage.MapFromContext(ctx)
|
||||
firstIter := true
|
||||
var headerValueBuilder strings.Builder
|
||||
baggageMap.Foreach(func(kv label.KeyValue) bool {
|
||||
if !firstIter {
|
||||
headerValueBuilder.WriteRune(',')
|
||||
}
|
||||
firstIter = false
|
||||
headerValueBuilder.WriteString(url.QueryEscape(strings.TrimSpace((string)(kv.Key))))
|
||||
headerValueBuilder.WriteRune('=')
|
||||
headerValueBuilder.WriteString(url.QueryEscape(strings.TrimSpace(kv.Value.Emit())))
|
||||
return true
|
||||
})
|
||||
if headerValueBuilder.Len() > 0 {
|
||||
headerString := headerValueBuilder.String()
|
||||
carrier.Set(baggageHeader, headerString)
|
||||
}
|
||||
}
|
||||
|
||||
// Extract returns a copy of parent with the baggage from the carrier added.
|
||||
func (b Baggage) Extract(parent context.Context, carrier TextMapCarrier) context.Context {
|
||||
bVal := carrier.Get(baggageHeader)
|
||||
if bVal == "" {
|
||||
return parent
|
||||
}
|
||||
|
||||
baggageValues := strings.Split(bVal, ",")
|
||||
keyValues := make([]label.KeyValue, 0, len(baggageValues))
|
||||
for _, baggageValue := range baggageValues {
|
||||
valueAndProps := strings.Split(baggageValue, ";")
|
||||
if len(valueAndProps) < 1 {
|
||||
continue
|
||||
}
|
||||
nameValue := strings.Split(valueAndProps[0], "=")
|
||||
if len(nameValue) < 2 {
|
||||
continue
|
||||
}
|
||||
name, err := url.QueryUnescape(nameValue[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
trimmedName := strings.TrimSpace(name)
|
||||
value, err := url.QueryUnescape(nameValue[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
trimmedValue := strings.TrimSpace(value)
|
||||
|
||||
// TODO (skaris): properties defiend https://w3c.github.io/correlation-context/, are currently
|
||||
// just put as part of the value.
|
||||
var trimmedValueWithProps strings.Builder
|
||||
trimmedValueWithProps.WriteString(trimmedValue)
|
||||
for _, prop := range valueAndProps[1:] {
|
||||
trimmedValueWithProps.WriteRune(';')
|
||||
trimmedValueWithProps.WriteString(prop)
|
||||
}
|
||||
|
||||
keyValues = append(keyValues, label.String(trimmedName, trimmedValueWithProps.String()))
|
||||
}
|
||||
|
||||
if len(keyValues) > 0 {
|
||||
// Only update the context if valid values were found
|
||||
return baggage.ContextWithMap(parent, baggage.NewMap(baggage.MapUpdate{
|
||||
MultiKV: keyValues,
|
||||
}))
|
||||
}
|
||||
|
||||
return parent
|
||||
}
|
||||
|
||||
// Fields returns the keys who's values are set with Inject.
|
||||
func (b Baggage) Fields() []string {
|
||||
return []string{baggageHeader}
|
||||
}
|
258
propagation/baggage_test.go
Normal file
258
propagation/baggage_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
// 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 propagation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/baggage"
|
||||
"go.opentelemetry.io/otel/label"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
)
|
||||
|
||||
func TestExtractValidBaggageFromHTTPReq(t *testing.T) {
|
||||
prop := propagation.TextMapPropagator(propagation.Baggage{})
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
wantKVs []label.KeyValue
|
||||
}{
|
||||
{
|
||||
name: "valid w3cHeader",
|
||||
header: "key1=val1,key2=val2",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid w3cHeader with spaces",
|
||||
header: "key1 = val1, key2 =val2 ",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid w3cHeader with properties",
|
||||
header: "key1=val1,key2=val2;prop=1",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2;prop=1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid header with url-escaped comma",
|
||||
header: "key1=val1,key2=val2%2Cval3",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2,val3"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid header with an invalid header",
|
||||
header: "key1=val1,key2=val2,a,val3",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid header with no value",
|
||||
header: "key1=,key2=val2",
|
||||
wantKVs: []label.KeyValue{
|
||||
label.String("key1", ""),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("baggage", tt.header)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = prop.Extract(ctx, req.Header)
|
||||
gotBaggage := baggage.MapFromContext(ctx)
|
||||
wantBaggage := baggage.NewMap(baggage.MapUpdate{MultiKV: tt.wantKVs})
|
||||
if gotBaggage.Len() != wantBaggage.Len() {
|
||||
t.Errorf(
|
||||
"Got and Want Baggage are not the same size %d != %d",
|
||||
gotBaggage.Len(),
|
||||
wantBaggage.Len(),
|
||||
)
|
||||
}
|
||||
totalDiff := ""
|
||||
wantBaggage.Foreach(func(keyValue label.KeyValue) bool {
|
||||
val, _ := gotBaggage.Value(keyValue.Key)
|
||||
diff := cmp.Diff(keyValue, label.KeyValue{Key: keyValue.Key, Value: val}, cmp.AllowUnexported(label.Value{}))
|
||||
if diff != "" {
|
||||
totalDiff += diff + "\n"
|
||||
}
|
||||
return true
|
||||
})
|
||||
if totalDiff != "" {
|
||||
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, totalDiff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) {
|
||||
prop := propagation.TextMapPropagator(propagation.Baggage{})
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
hasKVs []label.KeyValue
|
||||
}{
|
||||
{
|
||||
name: "no key values",
|
||||
header: "header1",
|
||||
},
|
||||
{
|
||||
name: "invalid header with existing context",
|
||||
header: "header2",
|
||||
hasKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty header value",
|
||||
header: "",
|
||||
hasKVs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("baggage", tt.header)
|
||||
|
||||
ctx := baggage.NewContext(context.Background(), tt.hasKVs...)
|
||||
wantBaggage := baggage.MapFromContext(ctx)
|
||||
ctx = prop.Extract(ctx, req.Header)
|
||||
gotBaggage := baggage.MapFromContext(ctx)
|
||||
if gotBaggage.Len() != wantBaggage.Len() {
|
||||
t.Errorf(
|
||||
"Got and Want Baggage are not the same size %d != %d",
|
||||
gotBaggage.Len(),
|
||||
wantBaggage.Len(),
|
||||
)
|
||||
}
|
||||
totalDiff := ""
|
||||
wantBaggage.Foreach(func(keyValue label.KeyValue) bool {
|
||||
val, _ := gotBaggage.Value(keyValue.Key)
|
||||
diff := cmp.Diff(keyValue, label.KeyValue{Key: keyValue.Key, Value: val}, cmp.AllowUnexported(label.Value{}))
|
||||
if diff != "" {
|
||||
totalDiff += diff + "\n"
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectBaggageToHTTPReq(t *testing.T) {
|
||||
propagator := propagation.Baggage{}
|
||||
tests := []struct {
|
||||
name string
|
||||
kvs []label.KeyValue
|
||||
wantInHeader []string
|
||||
wantedLen int
|
||||
}{
|
||||
{
|
||||
name: "two simple values",
|
||||
kvs: []label.KeyValue{
|
||||
label.String("key1", "val1"),
|
||||
label.String("key2", "val2"),
|
||||
},
|
||||
wantInHeader: []string{"key1=val1", "key2=val2"},
|
||||
},
|
||||
{
|
||||
name: "two values with escaped chars",
|
||||
kvs: []label.KeyValue{
|
||||
label.String("key1", "val1,val2"),
|
||||
label.String("key2", "val3=4"),
|
||||
},
|
||||
wantInHeader: []string{"key1=val1%2Cval2", "key2=val3%3D4"},
|
||||
},
|
||||
{
|
||||
name: "values of non-string types",
|
||||
kvs: []label.KeyValue{
|
||||
label.Bool("key1", true),
|
||||
label.Int("key2", 123),
|
||||
label.Int64("key3", 123),
|
||||
label.Int32("key4", 123),
|
||||
label.Uint("key5", 123),
|
||||
label.Uint32("key6", 123),
|
||||
label.Uint64("key7", 123),
|
||||
label.Float64("key8", 123.567),
|
||||
label.Float32("key9", 123.567),
|
||||
},
|
||||
wantInHeader: []string{
|
||||
"key1=true",
|
||||
"key2=123",
|
||||
"key3=123",
|
||||
"key4=123",
|
||||
"key5=123",
|
||||
"key6=123",
|
||||
"key7=123",
|
||||
"key8=123.567",
|
||||
"key9=123.567",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
ctx := baggage.ContextWithMap(context.Background(), baggage.NewMap(baggage.MapUpdate{MultiKV: tt.kvs}))
|
||||
propagator.Inject(ctx, req.Header)
|
||||
|
||||
gotHeader := req.Header.Get("baggage")
|
||||
wantedLen := len(strings.Join(tt.wantInHeader, ","))
|
||||
if wantedLen != len(gotHeader) {
|
||||
t.Errorf(
|
||||
"%s: Inject baggage incorrect length %d != %d.", tt.name, tt.wantedLen, len(gotHeader),
|
||||
)
|
||||
}
|
||||
for _, inHeader := range tt.wantInHeader {
|
||||
if !strings.Contains(gotHeader, inHeader) {
|
||||
t.Errorf(
|
||||
"%s: Inject baggage missing part of header: %s in %s", tt.name, inHeader, gotHeader,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaggagePropagatorGetAllKeys(t *testing.T) {
|
||||
var propagator propagation.Baggage
|
||||
want := []string{"baggage"}
|
||||
got := propagator.Fields()
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("GetAllKeys: -got +want %s", diff)
|
||||
}
|
||||
}
|
28
propagation/doc.go
Normal file
28
propagation/doc.go
Normal file
@@ -0,0 +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 propagation contains OpenTelemetry context propagators.
|
||||
|
||||
This package is currently in a pre-GA phase. Backwards incompatible changes
|
||||
may be introduced in subsequent minor version releases as we work to track the
|
||||
evolving OpenTelemetry specification and user feedback.
|
||||
|
||||
OpenTelemetry propagators are used to extract and inject context data from and
|
||||
into messages exchanged by applications. The propagator supported by this
|
||||
package is the W3C Trace Context encoding
|
||||
(https://www.w3.org/TR/trace-context/), and W3C Baggage
|
||||
(https://w3c.github.io/baggage/).
|
||||
*/
|
||||
package propagation // import "go.opentelemetry.io/otel/propagation"
|
78
propagation/propagation.go
Normal file
78
propagation/propagation.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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 propagation // import "go.opentelemetry.io/otel/propagation"
|
||||
|
||||
import "context"
|
||||
|
||||
// TextMapCarrier is the storage medium used by a TextMapPropagator.
|
||||
type TextMapCarrier interface {
|
||||
// Get returns the value associated with the passed key.
|
||||
Get(key string) string
|
||||
// Set stores the key-value pair.
|
||||
Set(key string, value string)
|
||||
}
|
||||
|
||||
// TextMapPropagator propagates cross-cutting concerns as key-value text
|
||||
// pairs within a carrier that travels in-band across process boundaries.
|
||||
type TextMapPropagator interface {
|
||||
// Inject set cross-cutting concerns from the Context into the carrier.
|
||||
Inject(ctx context.Context, carrier TextMapCarrier)
|
||||
// Extract reads cross-cutting concerns from the carrier into a Context.
|
||||
Extract(ctx context.Context, carrier TextMapCarrier) context.Context
|
||||
// Fields returns the keys who's values are set with Inject.
|
||||
Fields() []string
|
||||
}
|
||||
|
||||
type compositeTextMapPropagator []TextMapPropagator
|
||||
|
||||
func (p compositeTextMapPropagator) Inject(ctx context.Context, carrier TextMapCarrier) {
|
||||
for _, i := range p {
|
||||
i.Inject(ctx, carrier)
|
||||
}
|
||||
}
|
||||
|
||||
func (p compositeTextMapPropagator) Extract(ctx context.Context, carrier TextMapCarrier) context.Context {
|
||||
for _, i := range p {
|
||||
ctx = i.Extract(ctx, carrier)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (p compositeTextMapPropagator) Fields() []string {
|
||||
unique := make(map[string]struct{})
|
||||
for _, i := range p {
|
||||
for _, k := range i.Fields() {
|
||||
unique[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
fields := make([]string, 0, len(unique))
|
||||
for k := range unique {
|
||||
fields = append(fields, k)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// NewCompositeTextMapPropagator returns a unified TextMapPropagator from the
|
||||
// group of passed TextMapPropagator. This allows different cross-cutting
|
||||
// concerns to be propagates in a unified manner.
|
||||
//
|
||||
// The returned TextMapPropagator will inject and extract cross-cutting
|
||||
// concerns in the order the TextMapPropagators were provided. Additionally,
|
||||
// the Fields method will return a de-duplicated slice of the keys that are
|
||||
// set with the Inject method.
|
||||
func NewCompositeTextMapPropagator(p ...TextMapPropagator) TextMapPropagator {
|
||||
return compositeTextMapPropagator(p)
|
||||
}
|
102
propagation/propagation_test.go
Normal file
102
propagation/propagation_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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 propagation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
)
|
||||
|
||||
type ctxKeyType uint
|
||||
|
||||
var (
|
||||
ctxKey ctxKeyType = 0
|
||||
)
|
||||
|
||||
type carrier []string
|
||||
|
||||
func (c *carrier) Get(string) string { return "" }
|
||||
|
||||
func (c *carrier) Set(setter, _ string) {
|
||||
*c = append(*c, setter)
|
||||
}
|
||||
|
||||
type propagator struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p propagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
|
||||
carrier.Set(p.Name, "")
|
||||
}
|
||||
|
||||
func (p propagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
|
||||
v := ctx.Value(ctxKey)
|
||||
if v == nil {
|
||||
ctx = context.WithValue(ctx, ctxKey, []string{p.Name})
|
||||
} else {
|
||||
orig := v.([]string)
|
||||
ctx = context.WithValue(ctx, ctxKey, append(orig, p.Name))
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (p propagator) Fields() []string { return []string{p.Name} }
|
||||
|
||||
func TestCompositeTextMapPropagatorFields(t *testing.T) {
|
||||
a, b1, b2 := propagator{"a"}, propagator{"b"}, propagator{"b"}
|
||||
|
||||
want := map[string]struct{}{
|
||||
"a": {},
|
||||
"b": {},
|
||||
}
|
||||
got := propagation.NewCompositeTextMapPropagator(a, b1, b2).Fields()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("invalid fields from composite: %v (want %v)", got, want)
|
||||
}
|
||||
for _, v := range got {
|
||||
if _, ok := want[v]; !ok {
|
||||
t.Errorf("invalid field returned from composite: %q", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeTextMapPropagatorInject(t *testing.T) {
|
||||
a, b := propagator{"a"}, propagator{"b"}
|
||||
|
||||
c := make(carrier, 0, 2)
|
||||
propagation.NewCompositeTextMapPropagator(a, b).Inject(context.Background(), &c)
|
||||
|
||||
if got := strings.Join([]string(c), ","); got != "a,b" {
|
||||
t.Errorf("invalid inject order: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompositeTextMapPropagatorExtract(t *testing.T) {
|
||||
a, b := propagator{"a"}, propagator{"b"}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = propagation.NewCompositeTextMapPropagator(a, b).Extract(ctx, nil)
|
||||
|
||||
v := ctx.Value(ctxKey)
|
||||
if v == nil {
|
||||
t.Fatal("no composite extraction")
|
||||
}
|
||||
if got := strings.Join(v.([]string), ","); got != "a,b" {
|
||||
t.Errorf("invalid extract order: %s", got)
|
||||
}
|
||||
}
|
116
propagation/propagators_test.go
Normal file
116
propagation/propagators_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 propagation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
traceIDStr = "4bf92f3577b34da6a3ce929d0e0e4736"
|
||||
spanIDStr = "00f067aa0ba902b7"
|
||||
)
|
||||
|
||||
var (
|
||||
traceID = mustTraceIDFromHex(traceIDStr)
|
||||
spanID = mustSpanIDFromHex(spanIDStr)
|
||||
)
|
||||
|
||||
func mustTraceIDFromHex(s string) (t trace.TraceID) {
|
||||
var err error
|
||||
t, err = trace.TraceIDFromHex(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mustSpanIDFromHex(s string) (t trace.SpanID) {
|
||||
var err error
|
||||
t, err = trace.SpanIDFromHex(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type outOfThinAirPropagator struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
var _ propagation.TextMapPropagator = outOfThinAirPropagator{}
|
||||
|
||||
func (p outOfThinAirPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
|
||||
sc := trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: 0,
|
||||
}
|
||||
require.True(p.t, sc.IsValid())
|
||||
return trace.ContextWithRemoteSpanContext(ctx, sc)
|
||||
}
|
||||
|
||||
func (outOfThinAirPropagator) Inject(context.Context, propagation.TextMapCarrier) {}
|
||||
|
||||
func (outOfThinAirPropagator) Fields() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type nilCarrier struct{}
|
||||
|
||||
var _ propagation.TextMapCarrier = nilCarrier{}
|
||||
|
||||
func (nilCarrier) Get(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (nilCarrier) Set(key string, value string) {}
|
||||
|
||||
func TestMultiplePropagators(t *testing.T) {
|
||||
ootaProp := outOfThinAirPropagator{t: t}
|
||||
ns := nilCarrier{}
|
||||
testProps := []propagation.TextMapPropagator{
|
||||
propagation.TraceContext{},
|
||||
}
|
||||
bg := context.Background()
|
||||
// sanity check of oota propagator, ensuring that it really
|
||||
// generates the valid span context out of thin air
|
||||
{
|
||||
ctx := ootaProp.Extract(bg, ns)
|
||||
sc := trace.RemoteSpanContextFromContext(ctx)
|
||||
require.True(t, sc.IsValid(), "oota prop failed sanity check")
|
||||
}
|
||||
// sanity check for real propagators, ensuring that they
|
||||
// really are not putting any valid span context into an empty
|
||||
// go context in absence of the HTTP headers.
|
||||
for _, prop := range testProps {
|
||||
ctx := prop.Extract(bg, ns)
|
||||
sc := trace.RemoteSpanContextFromContext(ctx)
|
||||
require.Falsef(t, sc.IsValid(), "%#v failed sanity check", prop)
|
||||
}
|
||||
for _, prop := range testProps {
|
||||
props := propagation.NewCompositeTextMapPropagator(ootaProp, prop)
|
||||
ctx := props.Extract(bg, ns)
|
||||
sc := trace.RemoteSpanContextFromContext(ctx)
|
||||
assert.Truef(t, sc.IsValid(), "%#v clobbers span context", prop)
|
||||
}
|
||||
}
|
156
propagation/trace_context.go
Normal file
156
propagation/trace_context.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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 propagation // import "go.opentelemetry.io/otel/propagation"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
supportedVersion = 0
|
||||
maxVersion = 254
|
||||
traceparentHeader = "traceparent"
|
||||
tracestateHeader = "tracestate"
|
||||
)
|
||||
|
||||
type traceContextPropagatorKeyType uint
|
||||
|
||||
const (
|
||||
tracestateKey traceContextPropagatorKeyType = 0
|
||||
)
|
||||
|
||||
// TraceContext is a propagator that supports the W3C Trace Context format
|
||||
// (https://www.w3.org/TR/trace-context/)
|
||||
//
|
||||
// This propagator will propagate the traceparent and tracestate headers to
|
||||
// guarantee traces are not broken. It is up to the users of this propagator
|
||||
// to choose if they want to participate in a trace by modifying the
|
||||
// traceparent header and relevant parts of the tracestate header containing
|
||||
// their proprietary information.
|
||||
type TraceContext struct{}
|
||||
|
||||
var _ TextMapPropagator = TraceContext{}
|
||||
var traceCtxRegExp = regexp.MustCompile("^(?P<version>[0-9a-f]{2})-(?P<traceID>[a-f0-9]{32})-(?P<spanID>[a-f0-9]{16})-(?P<traceFlags>[a-f0-9]{2})(?:-.*)?$")
|
||||
|
||||
// Inject set tracecontext from the Context into the carrier.
|
||||
func (tc TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) {
|
||||
tracestate := ctx.Value(tracestateKey)
|
||||
if state, ok := tracestate.(string); tracestate != nil && ok {
|
||||
carrier.Set(tracestateHeader, state)
|
||||
}
|
||||
|
||||
sc := trace.SpanContextFromContext(ctx)
|
||||
if !sc.IsValid() {
|
||||
return
|
||||
}
|
||||
h := fmt.Sprintf("%.2x-%s-%s-%.2x",
|
||||
supportedVersion,
|
||||
sc.TraceID,
|
||||
sc.SpanID,
|
||||
sc.TraceFlags&trace.FlagsSampled)
|
||||
carrier.Set(traceparentHeader, h)
|
||||
}
|
||||
|
||||
// Extract reads tracecontext from the carrier into a returned Context.
|
||||
func (tc TraceContext) Extract(ctx context.Context, carrier TextMapCarrier) context.Context {
|
||||
state := carrier.Get(tracestateHeader)
|
||||
if state != "" {
|
||||
ctx = context.WithValue(ctx, tracestateKey, state)
|
||||
}
|
||||
|
||||
sc := tc.extract(carrier)
|
||||
if !sc.IsValid() {
|
||||
return ctx
|
||||
}
|
||||
return trace.ContextWithRemoteSpanContext(ctx, sc)
|
||||
}
|
||||
|
||||
func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
|
||||
h := carrier.Get(traceparentHeader)
|
||||
if h == "" {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
matches := traceCtxRegExp.FindStringSubmatch(h)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if len(matches) < 5 { // four subgroups plus the overall match
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if len(matches[1]) != 2 {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
ver, err := hex.DecodeString(matches[1])
|
||||
if err != nil {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
version := int(ver[0])
|
||||
if version > maxVersion {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if version == 0 && len(matches) != 5 { // four subgroups plus the overall match
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if len(matches[2]) != 32 {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
var sc trace.SpanContext
|
||||
|
||||
sc.TraceID, err = trace.TraceIDFromHex(matches[2][:32])
|
||||
if err != nil {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if len(matches[3]) != 16 {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
sc.SpanID, err = trace.SpanIDFromHex(matches[3])
|
||||
if err != nil {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
if len(matches[4]) != 2 {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
opts, err := hex.DecodeString(matches[4])
|
||||
if err != nil || len(opts) < 1 || (version == 0 && opts[0] > 2) {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
// Clear all flags other than the trace-context supported sampling bit.
|
||||
sc.TraceFlags = opts[0] & trace.FlagsSampled
|
||||
|
||||
if !sc.IsValid() {
|
||||
return trace.SpanContext{}
|
||||
}
|
||||
|
||||
return sc
|
||||
}
|
||||
|
||||
// Fields returns the keys who's values are set with Inject.
|
||||
func (tc TraceContext) Fields() []string {
|
||||
return []string{traceparentHeader, tracestateHeader}
|
||||
}
|
96
propagation/trace_context_benchmark_test.go
Normal file
96
propagation/trace_context_benchmark_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 propagation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func BenchmarkInject(b *testing.B) {
|
||||
var t propagation.TraceContext
|
||||
|
||||
injectSubBenchmarks(b, func(ctx context.Context, b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
t.Inject(ctx, req.Header)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func injectSubBenchmarks(b *testing.B, fn func(context.Context, *testing.B)) {
|
||||
b.Run("SampledSpanContext", func(b *testing.B) {
|
||||
spanID, _ := trace.SpanIDFromHex("00f067aa0ba902b7")
|
||||
traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
|
||||
|
||||
mockTracer := oteltest.DefaultTracer()
|
||||
b.ReportAllocs()
|
||||
sc := trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
}
|
||||
ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc)
|
||||
ctx, _ = mockTracer.Start(ctx, "inject")
|
||||
fn(ctx, b)
|
||||
})
|
||||
|
||||
b.Run("WithoutSpanContext", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
ctx := context.Background()
|
||||
fn(ctx, b)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkExtract(b *testing.B) {
|
||||
extractSubBenchmarks(b, func(b *testing.B, req *http.Request) {
|
||||
var propagator propagation.TraceContext
|
||||
ctx := context.Background()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
propagator.Extract(ctx, req.Header)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func extractSubBenchmarks(b *testing.B, fn func(*testing.B, *http.Request)) {
|
||||
b.Run("Sampled", func(b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01")
|
||||
b.ReportAllocs()
|
||||
|
||||
fn(b, req)
|
||||
})
|
||||
|
||||
b.Run("BogusVersion", func(b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("traceparent", "qw-00000000000000000000000000000000-0000000000000000-01")
|
||||
b.ReportAllocs()
|
||||
fn(b, req)
|
||||
})
|
||||
|
||||
b.Run("FutureAdditionalData", func(b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("traceparent", "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-XYZxsf09")
|
||||
b.ReportAllocs()
|
||||
fn(b, req)
|
||||
})
|
||||
}
|
26
propagation/trace_context_example_test.go
Normal file
26
propagation/trace_context_example_test.go
Normal file
@@ -0,0 +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 propagation_test
|
||||
|
||||
import (
|
||||
"go.opentelemetry.io/otel/global"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
)
|
||||
|
||||
func ExampleTraceContext() {
|
||||
tc := propagation.TraceContext{}
|
||||
// Register the TraceContext propagator globally.
|
||||
global.SetTextMapPropagator(tc)
|
||||
}
|
293
propagation/trace_context_test.go
Normal file
293
propagation/trace_context_test.go
Normal file
@@ -0,0 +1,293 @@
|
||||
// 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 propagation_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
|
||||
prop := propagation.TraceContext{}
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
wantSc trace.SpanContext
|
||||
}{
|
||||
{
|
||||
name: "valid w3cHeader",
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid w3cHeader and sampled",
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "future version",
|
||||
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "future options with sampled bit set",
|
||||
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "future options with sampled bit cleared",
|
||||
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-08",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "future additional data",
|
||||
header: "02-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-XYZxsf09",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid b3Header ending in dash",
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01-",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "future valid b3Header ending in dash",
|
||||
header: "01-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-09-",
|
||||
wantSc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("traceparent", tt.header)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = prop.Extract(ctx, req.Header)
|
||||
gotSc := trace.RemoteSpanContextFromContext(ctx)
|
||||
if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" {
|
||||
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) {
|
||||
wantSc := trace.SpanContext{}
|
||||
prop := propagation.TraceContext{}
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
}{
|
||||
{
|
||||
name: "wrong version length",
|
||||
header: "0000-00000000000000000000000000000000-0000000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "wrong trace ID length",
|
||||
header: "00-ab00000000000000000000000000000000-cd00000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "wrong span ID length",
|
||||
header: "00-ab000000000000000000000000000000-cd0000000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "wrong trace flag length",
|
||||
header: "00-ab000000000000000000000000000000-cd00000000000000-0100",
|
||||
},
|
||||
{
|
||||
name: "bogus version",
|
||||
header: "qw-00000000000000000000000000000000-0000000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "bogus trace ID",
|
||||
header: "00-qw000000000000000000000000000000-cd00000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "bogus span ID",
|
||||
header: "00-ab000000000000000000000000000000-qw00000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "bogus trace flag",
|
||||
header: "00-ab000000000000000000000000000000-cd00000000000000-qw",
|
||||
},
|
||||
{
|
||||
name: "upper case version",
|
||||
header: "A0-00000000000000000000000000000000-0000000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "upper case trace ID",
|
||||
header: "00-AB000000000000000000000000000000-cd00000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "upper case span ID",
|
||||
header: "00-ab000000000000000000000000000000-CD00000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "upper case trace flag",
|
||||
header: "00-ab000000000000000000000000000000-cd00000000000000-A1",
|
||||
},
|
||||
{
|
||||
name: "zero trace ID and span ID",
|
||||
header: "00-00000000000000000000000000000000-0000000000000000-01",
|
||||
},
|
||||
{
|
||||
name: "trace-flag unused bits set",
|
||||
header: "00-ab000000000000000000000000000000-cd00000000000000-09",
|
||||
},
|
||||
{
|
||||
name: "missing options",
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7",
|
||||
},
|
||||
{
|
||||
name: "empty options",
|
||||
header: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("traceparent", tt.header)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = prop.Extract(ctx, req.Header)
|
||||
gotSc := trace.RemoteSpanContextFromContext(ctx)
|
||||
if diff := cmp.Diff(gotSc, wantSc); diff != "" {
|
||||
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectTraceContextToHTTPReq(t *testing.T) {
|
||||
mockTracer := oteltest.DefaultTracer()
|
||||
prop := propagation.TraceContext{}
|
||||
tests := []struct {
|
||||
name string
|
||||
sc trace.SpanContext
|
||||
wantHeader string
|
||||
}{
|
||||
{
|
||||
name: "valid spancontext, sampled",
|
||||
sc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: trace.FlagsSampled,
|
||||
},
|
||||
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000002-01",
|
||||
},
|
||||
{
|
||||
name: "valid spancontext, not sampled",
|
||||
sc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
},
|
||||
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000003-00",
|
||||
},
|
||||
{
|
||||
name: "valid spancontext, with unsupported bit set in traceflags",
|
||||
sc: trace.SpanContext{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
TraceFlags: 0xff,
|
||||
},
|
||||
wantHeader: "00-4bf92f3577b34da6a3ce929d0e0e4736-0000000000000004-01",
|
||||
},
|
||||
{
|
||||
name: "invalid spancontext",
|
||||
sc: trace.SpanContext{},
|
||||
wantHeader: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
ctx := context.Background()
|
||||
if tt.sc.IsValid() {
|
||||
ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc)
|
||||
ctx, _ = mockTracer.Start(ctx, "inject")
|
||||
}
|
||||
prop.Inject(ctx, req.Header)
|
||||
|
||||
gotHeader := req.Header.Get("traceparent")
|
||||
if diff := cmp.Diff(gotHeader, tt.wantHeader); diff != "" {
|
||||
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
|
||||
var propagator propagation.TraceContext
|
||||
want := []string{"traceparent", "tracestate"}
|
||||
got := propagator.Fields()
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("GetAllKeys: -got +want %s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStatePropagation(t *testing.T) {
|
||||
prop := propagation.TraceContext{}
|
||||
want := "opaquevalue"
|
||||
headerName := "tracestate"
|
||||
|
||||
inReq, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
inReq.Header.Add(headerName, want)
|
||||
ctx := prop.Extract(context.Background(), inReq.Header)
|
||||
|
||||
outReq, _ := http.NewRequest(http.MethodGet, "http://www.example.com", nil)
|
||||
prop.Inject(ctx, outReq.Header)
|
||||
|
||||
if diff := cmp.Diff(outReq.Header.Get(headerName), want); diff != "" {
|
||||
t.Errorf("Propagate tracestate: -got +want %s", diff)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user