You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-25 22:41:46 +02:00
Move correlation context propagation to correlation package (#479)
Correlation context propagation shouldn't be a part of the trace package - it is a different aspect of the propagation cross-cutting concern. This commit also adds a DefaultHTTPPropagator function for correlation context propagation and makes the plugins use it. Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
This commit is contained in:
99
api/correlation/correlation_context_propagator.go
Normal file
99
api/correlation/correlation_context_propagator.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package correlation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/core"
|
||||||
|
"go.opentelemetry.io/otel/api/key"
|
||||||
|
"go.opentelemetry.io/otel/api/propagation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CorrelationContextHeader is specified by W3C.
|
||||||
|
//nolint:golint
|
||||||
|
var CorrelationContextHeader = "Correlation-Context"
|
||||||
|
|
||||||
|
// CorrelationContext propagates Key:Values in W3C CorrelationContext
|
||||||
|
// format.
|
||||||
|
// nolint:golint
|
||||||
|
type CorrelationContext struct{}
|
||||||
|
|
||||||
|
var _ propagation.HTTPPropagator = CorrelationContext{}
|
||||||
|
|
||||||
|
// DefaultHTTPPropagator returns the default context correlation HTTP
|
||||||
|
// propagator.
|
||||||
|
func DefaultHTTPPropagator() propagation.HTTPPropagator {
|
||||||
|
return CorrelationContext{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject implements HTTPInjector.
|
||||||
|
func (CorrelationContext) Inject(ctx context.Context, supplier propagation.HTTPSupplier) {
|
||||||
|
correlationCtx := FromContext(ctx)
|
||||||
|
firstIter := true
|
||||||
|
var headerValueBuilder strings.Builder
|
||||||
|
correlationCtx.Foreach(func(kv core.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()
|
||||||
|
supplier.Set(CorrelationContextHeader, headerString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract implements HTTPExtractor.
|
||||||
|
func (CorrelationContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context {
|
||||||
|
correlationContext := supplier.Get(CorrelationContextHeader)
|
||||||
|
if correlationContext == "" {
|
||||||
|
return WithMap(ctx, NewEmptyMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
contextValues := strings.Split(correlationContext, ",")
|
||||||
|
keyValues := make([]core.KeyValue, 0, len(contextValues))
|
||||||
|
for _, contextValue := range contextValues {
|
||||||
|
valueAndProps := strings.Split(contextValue, ";")
|
||||||
|
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, key.New(trimmedName).String(trimmedValueWithProps.String()))
|
||||||
|
}
|
||||||
|
return WithMap(ctx, NewMap(MapUpdate{
|
||||||
|
MultiKV: keyValues,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllKeys implements HTTPPropagator.
|
||||||
|
func (CorrelationContext) GetAllKeys() []string {
|
||||||
|
return []string{CorrelationContextHeader}
|
||||||
|
}
|
||||||
215
api/correlation/correlation_context_propagator_test.go
Normal file
215
api/correlation/correlation_context_propagator_test.go
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package correlation_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/core"
|
||||||
|
"go.opentelemetry.io/otel/api/correlation"
|
||||||
|
"go.opentelemetry.io/otel/api/key"
|
||||||
|
"go.opentelemetry.io/otel/api/propagation"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) {
|
||||||
|
props := propagation.New(propagation.WithExtractors(correlation.CorrelationContext{}))
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header string
|
||||||
|
wantKVs []core.KeyValue
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid w3cHeader",
|
||||||
|
header: "key1=val1,key2=val2",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid w3cHeader with spaces",
|
||||||
|
header: "key1 = val1, key2 =val2 ",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid w3cHeader with properties",
|
||||||
|
header: "key1=val1,key2=val2;prop=1",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2;prop=1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid header with url-escaped comma",
|
||||||
|
header: "key1=val1,key2=val2%2Cval3",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2,val3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid header with an invalid header",
|
||||||
|
header: "key1=val1,key2=val2,a,val3",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid header with no value",
|
||||||
|
header: "key1=,key2=val2",
|
||||||
|
wantKVs: []core.KeyValue{
|
||||||
|
key.New("key1").String(""),
|
||||||
|
key.New("key2").String("val2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
req.Header.Set("Correlation-Context", tt.header)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
||||||
|
gotCorCtx := correlation.FromContext(ctx)
|
||||||
|
wantCorCtx := correlation.NewMap(correlation.MapUpdate{MultiKV: tt.wantKVs})
|
||||||
|
if gotCorCtx.Len() != wantCorCtx.Len() {
|
||||||
|
t.Errorf(
|
||||||
|
"Got and Want CorCtx are not the same size %d != %d",
|
||||||
|
gotCorCtx.Len(),
|
||||||
|
wantCorCtx.Len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
totalDiff := ""
|
||||||
|
wantCorCtx.Foreach(func(kv core.KeyValue) bool {
|
||||||
|
val, _ := gotCorCtx.Value(kv.Key)
|
||||||
|
diff := cmp.Diff(kv, core.KeyValue{Key: kv.Key, Value: val}, cmp.AllowUnexported(core.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) {
|
||||||
|
props := propagation.New(propagation.WithExtractors(correlation.CorrelationContext{}))
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
header string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no key values",
|
||||||
|
header: "header1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||||
|
req.Header.Set("Correlation-Context", tt.header)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
||||||
|
gotCorCtx := correlation.FromContext(ctx)
|
||||||
|
if gotCorCtx.Len() != 0 {
|
||||||
|
t.Errorf("Got and Want CorCtx are not the same size %d != %d", gotCorCtx.Len(), 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInjectCorrelationContextToHTTPReq(t *testing.T) {
|
||||||
|
propagator := correlation.CorrelationContext{}
|
||||||
|
props := propagation.New(propagation.WithInjectors(propagator))
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kvs []core.KeyValue
|
||||||
|
wantInHeader []string
|
||||||
|
wantedLen int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "two simple values",
|
||||||
|
kvs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1"),
|
||||||
|
key.New("key2").String("val2"),
|
||||||
|
},
|
||||||
|
wantInHeader: []string{"key1=val1", "key2=val2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two values with escaped chars",
|
||||||
|
kvs: []core.KeyValue{
|
||||||
|
key.New("key1").String("val1,val2"),
|
||||||
|
key.New("key2").String("val3=4"),
|
||||||
|
},
|
||||||
|
wantInHeader: []string{"key1=val1%2Cval2", "key2=val3%3D4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "values of non-string types",
|
||||||
|
kvs: []core.KeyValue{
|
||||||
|
key.New("key1").Bool(true),
|
||||||
|
key.New("key2").Int(123),
|
||||||
|
key.New("key3").Int64(123),
|
||||||
|
key.New("key4").Int32(123),
|
||||||
|
key.New("key5").Uint(123),
|
||||||
|
key.New("key6").Uint32(123),
|
||||||
|
key.New("key7").Uint64(123),
|
||||||
|
key.New("key8").Float64(123.567),
|
||||||
|
key.New("key9").Float32(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 := correlation.WithMap(context.Background(), correlation.NewMap(correlation.MapUpdate{MultiKV: tt.kvs}))
|
||||||
|
propagation.InjectHTTP(ctx, props, req.Header)
|
||||||
|
|
||||||
|
gotHeader := req.Header.Get("Correlation-Context")
|
||||||
|
wantedLen := len(strings.Join(tt.wantInHeader, ","))
|
||||||
|
if wantedLen != len(gotHeader) {
|
||||||
|
t.Errorf(
|
||||||
|
"%s: Inject Correlation-Context incorrect length %d != %d.", tt.name, tt.wantedLen, len(gotHeader),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, inHeader := range tt.wantInHeader {
|
||||||
|
if !strings.Contains(gotHeader, inHeader) {
|
||||||
|
t.Errorf(
|
||||||
|
"%s: Inject Correlation-Context missing part of header: %s in %s", tt.name, inHeader, gotHeader,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
|
||||||
|
var propagator correlation.CorrelationContext
|
||||||
|
want := []string{"Correlation-Context"}
|
||||||
|
got := propagator.GetAllKeys()
|
||||||
|
if diff := cmp.Diff(got, want); diff != "" {
|
||||||
|
t.Errorf("GetAllKeys: -got +want %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,11 @@ package testtrace_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/core"
|
"go.opentelemetry.io/otel/api/core"
|
||||||
"go.opentelemetry.io/otel/api/correlation"
|
|
||||||
"go.opentelemetry.io/otel/api/key"
|
|
||||||
"go.opentelemetry.io/otel/api/propagation"
|
"go.opentelemetry.io/otel/api/propagation"
|
||||||
"go.opentelemetry.io/otel/api/trace"
|
"go.opentelemetry.io/otel/api/trace"
|
||||||
mocktrace "go.opentelemetry.io/otel/internal/trace"
|
mocktrace "go.opentelemetry.io/otel/internal/trace"
|
||||||
@@ -289,202 +286,9 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) {
|
|
||||||
propagator := trace.TraceContext{}
|
|
||||||
props := propagation.New(propagation.WithExtractors(propagator))
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
header string
|
|
||||||
wantKVs []core.KeyValue
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid w3cHeader",
|
|
||||||
header: "key1=val1,key2=val2",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid w3cHeader with spaces",
|
|
||||||
header: "key1 = val1, key2 =val2 ",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid w3cHeader with properties",
|
|
||||||
header: "key1=val1,key2=val2;prop=1",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2;prop=1"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid header with url-escaped comma",
|
|
||||||
header: "key1=val1,key2=val2%2Cval3",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2,val3"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid header with an invalid header",
|
|
||||||
header: "key1=val1,key2=val2,a,val3",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid header with no value",
|
|
||||||
header: "key1=,key2=val2",
|
|
||||||
wantKVs: []core.KeyValue{
|
|
||||||
key.New("key1").String(""),
|
|
||||||
key.New("key2").String("val2"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
|
||||||
req.Header.Set("Correlation-Context", tt.header)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
|
||||||
gotCorCtx := correlation.FromContext(ctx)
|
|
||||||
wantCorCtx := correlation.NewMap(correlation.MapUpdate{MultiKV: tt.wantKVs})
|
|
||||||
if gotCorCtx.Len() != wantCorCtx.Len() {
|
|
||||||
t.Errorf(
|
|
||||||
"Got and Want CorCtx are not the same size %d != %d",
|
|
||||||
gotCorCtx.Len(),
|
|
||||||
wantCorCtx.Len(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
totalDiff := ""
|
|
||||||
wantCorCtx.Foreach(func(kv core.KeyValue) bool {
|
|
||||||
val, _ := gotCorCtx.Value(kv.Key)
|
|
||||||
diff := cmp.Diff(kv, core.KeyValue{Key: kv.Key, Value: val}, cmp.AllowUnexported(core.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) {
|
|
||||||
propagator := trace.TraceContext{}
|
|
||||||
props := propagation.New(propagation.WithExtractors(propagator))
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
header string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no key values",
|
|
||||||
header: "header1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
|
||||||
req.Header.Set("Correlation-Context", tt.header)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
|
||||||
gotCorCtx := correlation.FromContext(ctx)
|
|
||||||
if gotCorCtx.Len() != 0 {
|
|
||||||
t.Errorf("Got and Want CorCtx are not the same size %d != %d", gotCorCtx.Len(), 0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInjectCorrelationContextToHTTPReq(t *testing.T) {
|
|
||||||
propagator := trace.TraceContext{}
|
|
||||||
props := propagation.New(propagation.WithInjectors(propagator))
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
kvs []core.KeyValue
|
|
||||||
wantInHeader []string
|
|
||||||
wantedLen int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "two simple values",
|
|
||||||
kvs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1"),
|
|
||||||
key.New("key2").String("val2"),
|
|
||||||
},
|
|
||||||
wantInHeader: []string{"key1=val1", "key2=val2"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two values with escaped chars",
|
|
||||||
kvs: []core.KeyValue{
|
|
||||||
key.New("key1").String("val1,val2"),
|
|
||||||
key.New("key2").String("val3=4"),
|
|
||||||
},
|
|
||||||
wantInHeader: []string{"key1=val1%2Cval2", "key2=val3%3D4"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "values of non-string types",
|
|
||||||
kvs: []core.KeyValue{
|
|
||||||
key.New("key1").Bool(true),
|
|
||||||
key.New("key2").Int(123),
|
|
||||||
key.New("key3").Int64(123),
|
|
||||||
key.New("key4").Int32(123),
|
|
||||||
key.New("key5").Uint(123),
|
|
||||||
key.New("key6").Uint32(123),
|
|
||||||
key.New("key7").Uint64(123),
|
|
||||||
key.New("key8").Float64(123.567),
|
|
||||||
key.New("key9").Float32(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 := correlation.WithMap(context.Background(), correlation.NewMap(correlation.MapUpdate{MultiKV: tt.kvs}))
|
|
||||||
propagation.InjectHTTP(ctx, props, req.Header)
|
|
||||||
|
|
||||||
gotHeader := req.Header.Get("Correlation-Context")
|
|
||||||
wantedLen := len(strings.Join(tt.wantInHeader, ","))
|
|
||||||
if wantedLen != len(gotHeader) {
|
|
||||||
t.Errorf(
|
|
||||||
"%s: Inject Correlation-Context incorrect length %d != %d.", tt.name, tt.wantedLen, len(gotHeader),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for _, inHeader := range tt.wantInHeader {
|
|
||||||
if !strings.Contains(gotHeader, inHeader) {
|
|
||||||
t.Errorf(
|
|
||||||
"%s: Inject Correlation-Context missing part of header: %s in %s", tt.name, inHeader, gotHeader,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
|
func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
|
||||||
var propagator trace.TraceContext
|
var propagator trace.TraceContext
|
||||||
want := []string{"Traceparent", "Correlation-Context"}
|
want := []string{"Traceparent"}
|
||||||
got := propagator.GetAllKeys()
|
got := propagator.GetAllKeys()
|
||||||
if diff := cmp.Diff(got, want); diff != "" {
|
if diff := cmp.Diff(got, want); diff != "" {
|
||||||
t.Errorf("GetAllKeys: -got +want %s", diff)
|
t.Errorf("GetAllKeys: -got +want %s", diff)
|
||||||
|
|||||||
@@ -18,21 +18,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/core"
|
"go.opentelemetry.io/otel/api/core"
|
||||||
"go.opentelemetry.io/otel/api/correlation"
|
|
||||||
"go.opentelemetry.io/otel/api/key"
|
|
||||||
"go.opentelemetry.io/otel/api/propagation"
|
"go.opentelemetry.io/otel/api/propagation"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
supportedVersion = 0
|
supportedVersion = 0
|
||||||
maxVersion = 254
|
maxVersion = 254
|
||||||
TraceparentHeader = "Traceparent"
|
TraceparentHeader = "Traceparent"
|
||||||
CorrelationContextHeader = "Correlation-Context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TraceContext propagates SpanContext in W3C TraceContext format.
|
// TraceContext propagates SpanContext in W3C TraceContext format.
|
||||||
@@ -57,31 +53,13 @@ func (TraceContext) Inject(ctx context.Context, supplier propagation.HTTPSupplie
|
|||||||
sc.TraceFlags&core.TraceFlagsSampled)
|
sc.TraceFlags&core.TraceFlagsSampled)
|
||||||
supplier.Set(TraceparentHeader, h)
|
supplier.Set(TraceparentHeader, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
correlationCtx := correlation.FromContext(ctx)
|
|
||||||
firstIter := true
|
|
||||||
var headerValueBuilder strings.Builder
|
|
||||||
correlationCtx.Foreach(func(kv core.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()
|
|
||||||
supplier.Set(CorrelationContextHeader, headerString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc TraceContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context {
|
func (tc TraceContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context {
|
||||||
return correlation.WithMap(ContextWithRemoteSpanContext(ctx, tc.extractSpanContext(supplier)), tc.extractCorrelationCtx(supplier))
|
return ContextWithRemoteSpanContext(ctx, tc.extract(supplier))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (TraceContext) extractSpanContext(supplier propagation.HTTPSupplier) core.SpanContext {
|
func (TraceContext) extract(supplier propagation.HTTPSupplier) core.SpanContext {
|
||||||
h := supplier.Get(TraceparentHeader)
|
h := supplier.Get(TraceparentHeader)
|
||||||
if h == "" {
|
if h == "" {
|
||||||
return core.EmptySpanContext()
|
return core.EmptySpanContext()
|
||||||
@@ -148,50 +126,6 @@ func (TraceContext) extractSpanContext(supplier propagation.HTTPSupplier) core.S
|
|||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (TraceContext) extractCorrelationCtx(supplier propagation.HTTPSupplier) correlation.Map {
|
|
||||||
correlationContext := supplier.Get(CorrelationContextHeader)
|
|
||||||
if correlationContext == "" {
|
|
||||||
return correlation.NewEmptyMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
contextValues := strings.Split(correlationContext, ",")
|
|
||||||
keyValues := make([]core.KeyValue, 0, len(contextValues))
|
|
||||||
for _, contextValue := range contextValues {
|
|
||||||
valueAndProps := strings.Split(contextValue, ";")
|
|
||||||
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, key.New(trimmedName).String(trimmedValueWithProps.String()))
|
|
||||||
}
|
|
||||||
return correlation.NewMap(correlation.MapUpdate{
|
|
||||||
MultiKV: keyValues,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (TraceContext) GetAllKeys() []string {
|
func (TraceContext) GetAllKeys() []string {
|
||||||
return []string{TraceparentHeader, CorrelationContextHeader}
|
return []string{TraceparentHeader}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
propagator = trace.DefaultHTTPPropagator()
|
tcPropagator = trace.DefaultHTTPPropagator()
|
||||||
propagators = propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator))
|
ccPropagator = correlation.DefaultHTTPPropagator()
|
||||||
|
propagators = propagation.New(
|
||||||
|
propagation.WithInjectors(tcPropagator, ccPropagator),
|
||||||
|
propagation.WithExtractors(tcPropagator, ccPropagator),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
type metadataSupplier struct {
|
type metadataSupplier struct {
|
||||||
|
|||||||
@@ -29,8 +29,12 @@ var (
|
|||||||
HostKey = key.New("http.host")
|
HostKey = key.New("http.host")
|
||||||
URLKey = key.New("http.url")
|
URLKey = key.New("http.url")
|
||||||
|
|
||||||
propagator = trace.DefaultHTTPPropagator()
|
tcPropagator = trace.DefaultHTTPPropagator()
|
||||||
propagators = propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator))
|
ccPropagator = correlation.DefaultHTTPPropagator()
|
||||||
|
propagators = propagation.New(
|
||||||
|
propagation.WithInjectors(tcPropagator, ccPropagator),
|
||||||
|
propagation.WithExtractors(tcPropagator, ccPropagator),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the Attributes, Context Entries, and SpanContext that were encoded by Inject.
|
// Returns the Attributes, Context Entries, and SpanContext that were encoded by Inject.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/core"
|
"go.opentelemetry.io/otel/api/core"
|
||||||
|
"go.opentelemetry.io/otel/api/correlation"
|
||||||
"go.opentelemetry.io/otel/api/global"
|
"go.opentelemetry.io/otel/api/global"
|
||||||
"go.opentelemetry.io/otel/api/propagation"
|
"go.opentelemetry.io/otel/api/propagation"
|
||||||
"go.opentelemetry.io/otel/api/trace"
|
"go.opentelemetry.io/otel/api/trace"
|
||||||
@@ -128,10 +129,15 @@ func WithMessageEvents(events ...event) Option {
|
|||||||
// named after the operation and with any provided HandlerOptions.
|
// named after the operation and with any provided HandlerOptions.
|
||||||
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
|
||||||
h := Handler{handler: handler, operation: operation}
|
h := Handler{handler: handler, operation: operation}
|
||||||
propagator := trace.DefaultHTTPPropagator()
|
tcPropagator := trace.DefaultHTTPPropagator()
|
||||||
|
ccPropagator := correlation.DefaultHTTPPropagator()
|
||||||
|
props := propagation.New(
|
||||||
|
propagation.WithInjectors(tcPropagator, ccPropagator),
|
||||||
|
propagation.WithExtractors(tcPropagator, ccPropagator),
|
||||||
|
)
|
||||||
defaultOpts := []Option{
|
defaultOpts := []Option{
|
||||||
WithTracer(global.TraceProvider().Tracer("go.opentelemetry.io/plugin/othttp")),
|
WithTracer(global.TraceProvider().Tracer("go.opentelemetry.io/plugin/othttp")),
|
||||||
WithPropagators(propagation.New(propagation.WithInjectors(propagator), propagation.WithExtractors(propagator))),
|
WithPropagators(props),
|
||||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user