1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-15 01:04:25 +02:00

Update propagation to conform with OpenTelemetry specification (#1212)

* Move propagation package contents to the otel package

* Implement package relocation

* Update propagation to OTel spec

* Add changes to changelog

* Add propagation tests
This commit is contained in:
Tyler Yahn
2020-10-02 12:27:16 -07:00
committed by GitHub
parent dc79f7fe25
commit 6e184cd16f
18 changed files with 280 additions and 311 deletions

View File

@ -15,6 +15,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Changed ### Changed
- Set default propagator to no-op propagator. (#1184) - Set default propagator to no-op propagator. (#1184)
- The `HTTPSupplier`, `HTTPExtractor`, `HTTPInjector`, and `HTTPPropagator` from the `go.opentelemetry.io/otel/api/propagation` package were replaced with unified `TextMapCarrier` and `TextMapPropagator` in the `go.opentelemetry.io/otel` package. (#1212)
- The `New` function from the `go.opentelemetry.io/otel/api/propagation` package was replaced with `NewCompositeTextMapPropagator` in the `go.opentelemetry.io/otel` package. (#1212)
### Removed
- The `ExtractHTTP` and `InjectHTTP` fuctions from the `go.opentelemetry.io/otel/api/propagation` package were removed. (#1212)
- The `Propagators` interface from the `go.opentelemetry.io/otel/api/propagation` package was removed to conform to the OpenTelemetry specification.
The explicit `TextMapPropagator` type can be used in its place as this is the `Propagator` type the specification defines. (#1212)
### Removed ### Removed

View File

@ -19,7 +19,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
) )
@ -27,21 +27,14 @@ import (
// https://github.com/open-telemetry/opentelemetry-specification/blob/18b2752ebe6c7f0cdd8c7b2bcbdceb0ae3f5ad95/specification/correlationcontext/api.md#header-name // https://github.com/open-telemetry/opentelemetry-specification/blob/18b2752ebe6c7f0cdd8c7b2bcbdceb0ae3f5ad95/specification/correlationcontext/api.md#header-name
const baggageHeader = "otcorrelations" const baggageHeader = "otcorrelations"
// Baggage propagates Key:Values in W3C CorrelationContext // Baggage propagates Key:Values in W3C CorrelationContext format.
// format.
// nolint:golint // nolint:golint
type Baggage struct{} type Baggage struct{}
var _ propagation.HTTPPropagator = Baggage{} var _ otel.TextMapPropagator = Baggage{}
// DefaultHTTPPropagator returns the default context correlation HTTP // Inject set baggage key-values from the Context into the carrier.
// propagator. func (b Baggage) Inject(ctx context.Context, carrier otel.TextMapCarrier) {
func DefaultHTTPPropagator() propagation.HTTPPropagator {
return Baggage{}
}
// Inject implements HTTPInjector.
func (b Baggage) Inject(ctx context.Context, supplier propagation.HTTPSupplier) {
baggageMap := MapFromContext(ctx) baggageMap := MapFromContext(ctx)
firstIter := true firstIter := true
var headerValueBuilder strings.Builder var headerValueBuilder strings.Builder
@ -57,13 +50,13 @@ func (b Baggage) Inject(ctx context.Context, supplier propagation.HTTPSupplier)
}) })
if headerValueBuilder.Len() > 0 { if headerValueBuilder.Len() > 0 {
headerString := headerValueBuilder.String() headerString := headerValueBuilder.String()
supplier.Set(baggageHeader, headerString) carrier.Set(baggageHeader, headerString)
} }
} }
// Extract implements HTTPExtractor. // Extract reads baggage key-values from the carrier into a returned Context.
func (b Baggage) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { func (b Baggage) Extract(ctx context.Context, carrier otel.TextMapCarrier) context.Context {
baggage := supplier.Get(baggageHeader) baggage := carrier.Get(baggageHeader)
if baggage == "" { if baggage == "" {
return ctx return ctx
} }
@ -112,7 +105,7 @@ func (b Baggage) Extract(ctx context.Context, supplier propagation.HTTPSupplier)
return ctx return ctx
} }
// GetAllKeys implements HTTPPropagator. // Fields returns the keys who's values are set with Inject.
func (b Baggage) GetAllKeys() []string { func (b Baggage) Fields() []string {
return []string{baggageHeader} return []string{baggageHeader}
} }

View File

@ -22,13 +22,13 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/api/baggage" "go.opentelemetry.io/otel/api/baggage"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
) )
func TestExtractValidBaggageFromHTTPReq(t *testing.T) { func TestExtractValidBaggageFromHTTPReq(t *testing.T) {
props := propagation.New(propagation.WithExtractors(baggage.Baggage{})) prop := otel.TextMapPropagator(baggage.Baggage{})
tests := []struct { tests := []struct {
name string name string
header string header string
@ -90,7 +90,7 @@ func TestExtractValidBaggageFromHTTPReq(t *testing.T) {
req.Header.Set("otcorrelations", tt.header) req.Header.Set("otcorrelations", tt.header)
ctx := context.Background() ctx := context.Background()
ctx = propagation.ExtractHTTP(ctx, props, req.Header) ctx = prop.Extract(ctx, req.Header)
gotBaggage := baggage.MapFromContext(ctx) gotBaggage := baggage.MapFromContext(ctx)
wantBaggage := baggage.NewMap(baggage.MapUpdate{MultiKV: tt.wantKVs}) wantBaggage := baggage.NewMap(baggage.MapUpdate{MultiKV: tt.wantKVs})
if gotBaggage.Len() != wantBaggage.Len() { if gotBaggage.Len() != wantBaggage.Len() {
@ -117,7 +117,7 @@ func TestExtractValidBaggageFromHTTPReq(t *testing.T) {
} }
func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) {
props := propagation.New(propagation.WithExtractors(baggage.Baggage{})) prop := otel.TextMapPropagator(baggage.Baggage{})
tests := []struct { tests := []struct {
name string name string
header string header string
@ -152,7 +152,7 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) {
ctx := baggage.NewContext(context.Background(), tt.hasKVs...) ctx := baggage.NewContext(context.Background(), tt.hasKVs...)
wantBaggage := baggage.MapFromContext(ctx) wantBaggage := baggage.MapFromContext(ctx)
ctx = propagation.ExtractHTTP(ctx, props, req.Header) ctx = prop.Extract(ctx, req.Header)
gotBaggage := baggage.MapFromContext(ctx) gotBaggage := baggage.MapFromContext(ctx)
if gotBaggage.Len() != wantBaggage.Len() { if gotBaggage.Len() != wantBaggage.Len() {
t.Errorf( t.Errorf(
@ -176,7 +176,6 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) {
func TestInjectBaggageToHTTPReq(t *testing.T) { func TestInjectBaggageToHTTPReq(t *testing.T) {
propagator := baggage.Baggage{} propagator := baggage.Baggage{}
props := propagation.New(propagation.WithInjectors(propagator))
tests := []struct { tests := []struct {
name string name string
kvs []label.KeyValue kvs []label.KeyValue
@ -229,7 +228,7 @@ func TestInjectBaggageToHTTPReq(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil) req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := baggage.ContextWithMap(context.Background(), baggage.NewMap(baggage.MapUpdate{MultiKV: tt.kvs})) ctx := baggage.ContextWithMap(context.Background(), baggage.NewMap(baggage.MapUpdate{MultiKV: tt.kvs}))
propagation.InjectHTTP(ctx, props, req.Header) propagator.Inject(ctx, req.Header)
gotHeader := req.Header.Get("otcorrelations") gotHeader := req.Header.Get("otcorrelations")
wantedLen := len(strings.Join(tt.wantInHeader, ",")) wantedLen := len(strings.Join(tt.wantInHeader, ","))
@ -252,7 +251,7 @@ func TestInjectBaggageToHTTPReq(t *testing.T) {
func TestTraceContextPropagator_GetAllKeys(t *testing.T) { func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
var propagator baggage.Baggage var propagator baggage.Baggage
want := []string{"otcorrelations"} want := []string{"otcorrelations"}
got := propagator.GetAllKeys() got := propagator.Fields()
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)
} }

View File

@ -18,8 +18,8 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/api/metric" "go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
) )
@ -33,7 +33,7 @@ type (
} }
propagatorsHolder struct { propagatorsHolder struct {
pr propagation.Propagators tm otel.TextMapPropagator
} }
) )
@ -90,14 +90,14 @@ func SetMeterProvider(mp metric.MeterProvider) {
globalMeter.Store(meterProviderHolder{mp: mp}) globalMeter.Store(meterProviderHolder{mp: mp})
} }
// Propagators is the internal implementation for global.Propagators. // TextMapPropagator is the internal implementation for global.TextMapPropagator.
func Propagators() propagation.Propagators { func TextMapPropagator() otel.TextMapPropagator {
return globalPropagators.Load().(propagatorsHolder).pr return globalPropagators.Load().(propagatorsHolder).tm
} }
// SetPropagators is the internal implementation for global.SetPropagators. // SetTextMapPropagator is the internal implementation for global.SetTextMapPropagator.
func SetPropagators(pr propagation.Propagators) { func SetTextMapPropagator(p otel.TextMapPropagator) {
globalPropagators.Store(propagatorsHolder{pr: pr}) globalPropagators.Store(propagatorsHolder{tm: p})
} }
func defaultTracerValue() *atomic.Value { func defaultTracerValue() *atomic.Value {
@ -114,13 +114,14 @@ func defaultMeterValue() *atomic.Value {
func defaultPropagatorsValue() *atomic.Value { func defaultPropagatorsValue() *atomic.Value {
v := &atomic.Value{} v := &atomic.Value{}
v.Store(propagatorsHolder{pr: getDefaultPropagators()}) v.Store(propagatorsHolder{tm: getDefaultTextMapPropagator()})
return v return v
} }
// getDefaultPropagators returns a default noop Propagators // getDefaultTextMapPropagator returns the default TextMapPropagator,
func getDefaultPropagators() propagation.Propagators { // configured with W3C trace and baggage propagation.
return propagation.New() func getDefaultTextMapPropagator() otel.TextMapPropagator {
return otel.NewCompositeTextMapPropagator()
} }
// ResetForTest restores the initial global state, for testing purposes. // ResetForTest restores the initial global state, for testing purposes.

View File

@ -15,18 +15,17 @@
package global package global
import ( import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/api/global/internal" "go.opentelemetry.io/otel/api/global/internal"
"go.opentelemetry.io/otel/api/propagation"
) )
// Propagators returns the registered global propagators instance. If // TextMapPropagator returns the global TextMapPropagator. If none has been
// none is registered then an instance of propagators.NoopPropagators // set, a No-Op TextMapPropagator is returned.
// is returned. func TextMapPropagator() otel.TextMapPropagator {
func Propagators() propagation.Propagators { return internal.TextMapPropagator()
return internal.Propagators()
} }
// SetPropagators registers `p` as the global propagators instance. // SetTextMapPropagator sets propagator as the global TSetTextMapPropagator.
func SetPropagators(p propagation.Propagators) { func SetTextMapPropagator(propagator otel.TextMapPropagator) {
internal.SetPropagators(p) internal.SetTextMapPropagator(propagator)
} }

View File

@ -1,16 +0,0 @@
// 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 provides support for propagating context over HTTP.
package propagation // import "go.opentelemetry.io/otel/api/propagation"

View File

@ -1,143 +0,0 @@
// 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 (
"context"
)
// HTTPSupplier is an interface that specifies methods to retrieve and
// store a single value for a key to an associated carrier. It is
// implemented by http.Headers.
type HTTPSupplier interface {
// Get method retrieves a single value for a given key.
Get(key string) string
// Set method stores a single value for a given key. Note that
// this should not be appending a value to some array, but
// rather overwrite the old value.
Set(key string, value string)
}
// HTTPExtractor extracts information from a HTTPSupplier into a
// context.
type HTTPExtractor interface {
// Extract method retrieves encoded information using supplier
// from the associated carrier, decodes it and creates a new
// context containing the decoded information.
//
// Information can be a correlation context or a remote span
// context. In case of span context, the propagator should
// store it in the context using
// trace.ContextWithRemoteSpanContext. In case of correlation
// context, the propagator should use correlation.WithMap to
// store it in the context.
Extract(ctx context.Context, supplier HTTPSupplier) context.Context
}
// HTTPInjector injects information into a HTTPSupplier.
type HTTPInjector interface {
// Inject method retrieves information from the context,
// encodes it into propagator specific format and then injects
// the encoded information using supplier into an associated
// carrier.
Inject(ctx context.Context, supplier HTTPSupplier)
}
// Config contains the current set of extractors and injectors.
type Config struct {
httpEx []HTTPExtractor
httpIn []HTTPInjector
}
// Propagators is the interface to a set of injectors and extractors
// for all supported carrier formats. It can be used to chain multiple
// propagators into a single entity.
type Propagators interface {
// HTTPExtractors returns the configured extractors.
HTTPExtractors() []HTTPExtractor
// HTTPInjectors returns the configured injectors.
HTTPInjectors() []HTTPInjector
}
// HTTPPropagator is the interface to inject to and extract from
// HTTPSupplier.
type HTTPPropagator interface {
HTTPInjector
HTTPExtractor
// GetAllKeys returns the HTTP header names used.
GetAllKeys() []string
}
// Option support passing configuration parameters to New().
type Option func(*Config)
// propagators is the default Propagators implementation.
type propagators struct {
config Config
}
// New returns a standard Propagators implementation.
func New(options ...Option) Propagators {
config := Config{}
for _, opt := range options {
opt(&config)
}
return &propagators{
config: config,
}
}
// WithInjectors appends to the optional injector set.
func WithInjectors(inj ...HTTPInjector) Option {
return func(config *Config) {
config.httpIn = append(config.httpIn, inj...)
}
}
// WithExtractors appends to the optional extractor set.
func WithExtractors(ext ...HTTPExtractor) Option {
return func(config *Config) {
config.httpEx = append(config.httpEx, ext...)
}
}
// HTTPExtractors implements Propagators.
func (p *propagators) HTTPExtractors() []HTTPExtractor {
return p.config.httpEx
}
// HTTPInjectors implements Propagators.
func (p *propagators) HTTPInjectors() []HTTPInjector {
return p.config.httpIn
}
// ExtractHTTP applies props.HTTPExtractors() to the passed context
// and the supplier and returns the combined result context.
func ExtractHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) context.Context {
for _, ex := range props.HTTPExtractors() {
ctx = ex.Extract(ctx, supplier)
}
return ctx
}
// InjectHTTP applies props.HTTPInjectors() to the passed context and
// the supplier.
func InjectHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) {
for _, in := range props.HTTPInjectors() {
in.Inject(ctx, supplier)
}
}

View File

@ -25,9 +25,9 @@ import (
otext "github.com/opentracing/opentracing-go/ext" otext "github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log" otlog "github.com/opentracing/opentracing-go/log"
"go.opentelemetry.io/otel"
otelbaggage "go.opentelemetry.io/otel/api/baggage" otelbaggage "go.opentelemetry.io/otel/api/baggage"
otelglobal "go.opentelemetry.io/otel/api/global" otelglobal "go.opentelemetry.io/otel/api/global"
otelpropagation "go.opentelemetry.io/otel/api/propagation"
oteltrace "go.opentelemetry.io/otel/api/trace" oteltrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/internal/trace/noop" "go.opentelemetry.io/otel/internal/trace/noop"
@ -287,7 +287,7 @@ type BridgeTracer struct {
warningHandler BridgeWarningHandler warningHandler BridgeWarningHandler
warnOnce sync.Once warnOnce sync.Once
propagators otelpropagation.Propagators propagator otel.TextMapPropagator
} }
var _ ot.Tracer = &BridgeTracer{} var _ ot.Tracer = &BridgeTracer{}
@ -304,7 +304,7 @@ func NewBridgeTracer() *BridgeTracer {
otelTracer: noop.Tracer, otelTracer: noop.Tracer,
}, },
warningHandler: func(msg string) {}, warningHandler: func(msg string) {},
propagators: nil, propagator: nil,
} }
} }
@ -322,8 +322,8 @@ func (t *BridgeTracer) SetOpenTelemetryTracer(tracer oteltrace.Tracer) {
t.setTracer.isSet = true t.setTracer.isSet = true
} }
func (t *BridgeTracer) SetPropagators(propagators otelpropagation.Propagators) { func (t *BridgeTracer) SetTextMapPropagator(propagator otel.TextMapPropagator) {
t.propagators = propagators t.propagator = propagator
} }
func (t *BridgeTracer) NewHookedContext(ctx context.Context) context.Context { func (t *BridgeTracer) NewHookedContext(ctx context.Context) context.Context {
@ -614,7 +614,7 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
} }
ctx := oteltrace.ContextWithSpan(context.Background(), fs) ctx := oteltrace.ContextWithSpan(context.Background(), fs)
ctx = otelbaggage.ContextWithMap(ctx, bridgeSC.baggageItems) ctx = otelbaggage.ContextWithMap(ctx, bridgeSC.baggageItems)
otelpropagation.InjectHTTP(ctx, t.getPropagators(), header) t.getPropagator().Inject(ctx, header)
return nil return nil
} }
@ -631,7 +631,7 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
return nil, ot.ErrInvalidCarrier return nil, ot.ErrInvalidCarrier
} }
header := http.Header(hhcarrier) header := http.Header(hhcarrier)
ctx := otelpropagation.ExtractHTTP(context.Background(), t.getPropagators(), header) ctx := t.getPropagator().Extract(context.Background(), header)
baggage := otelbaggage.MapFromContext(ctx) baggage := otelbaggage.MapFromContext(ctx)
otelSC, _, _ := otelparent.GetSpanContextAndLinks(ctx, false) otelSC, _, _ := otelparent.GetSpanContextAndLinks(ctx, false)
bridgeSC := &bridgeSpanContext{ bridgeSC := &bridgeSpanContext{
@ -644,9 +644,9 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
return bridgeSC, nil return bridgeSC, nil
} }
func (t *BridgeTracer) getPropagators() otelpropagation.Propagators { func (t *BridgeTracer) getPropagator() otel.TextMapPropagator {
if t.propagators != nil { if t.propagator != nil {
return t.propagators return t.propagator
} }
return otelglobal.Propagators() return otelglobal.TextMapPropagator()
} }

View File

@ -21,7 +21,6 @@ import (
"go.opentelemetry.io/otel/api/baggage" "go.opentelemetry.io/otel/api/baggage"
"go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/metric" "go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/exporters/stdout" "go.opentelemetry.io/otel/exporters/stdout"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -62,12 +61,8 @@ func main() {
global.SetTracerProvider(tp) global.SetTracerProvider(tp)
global.SetMeterProvider(pusher.MeterProvider()) global.SetMeterProvider(pusher.MeterProvider())
// set propagator to baggage since the default is no-op // set global propagator to baggage (the default is no-op).
bagPropagator := baggage.DefaultHTTPPropagator() global.SetTextMapPropagator(baggage.Baggage{})
props := propagation.New(propagation.WithExtractors(bagPropagator),
propagation.WithInjectors(bagPropagator))
global.SetPropagators(props)
tracer := global.Tracer("ex.com/basic") tracer := global.Tracer("ex.com/basic")
meter := global.Meter("ex.com/basic") meter := global.Meter("ex.com/basic")

View File

@ -27,7 +27,6 @@ import (
"go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/metric" "go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/propagation"
apitrace "go.opentelemetry.io/otel/api/trace" apitrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/exporters/otlp" "go.opentelemetry.io/otel/exporters/otlp"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -75,10 +74,8 @@ func initProvider() func() {
push.WithPeriod(2*time.Second), push.WithPeriod(2*time.Second),
) )
tcPropagator := propagators.TraceContext{} // set global propagator to tracecontext (the default is no-op).
props := propagation.New(propagation.WithExtractors(tcPropagator), global.SetTextMapPropagator(propagators.TraceContext{})
propagation.WithInjectors(tcPropagator))
global.SetPropagators(props)
global.SetTracerProvider(tracerProvider) global.SetTracerProvider(tracerProvider)
global.SetMeterProvider(pusher.MeterProvider()) global.SetMeterProvider(pusher.MeterProvider())
pusher.Start() pusher.Start()

View File

@ -16,7 +16,6 @@ package otel
import ( import (
"go.opentelemetry.io/otel/api/metric" "go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
) )
@ -24,8 +23,6 @@ type Tracer = trace.Tracer
type Meter = metric.Meter type Meter = metric.Meter
type Propagators = propagation.Propagators
// ErrorHandler handles irremediable events. // ErrorHandler handles irremediable events.
type ErrorHandler interface { type ErrorHandler interface {
// Handle handles any error deemed irremediable by an OpenTelemetry // Handle handles any error deemed irremediable by an OpenTelemetry

78
propagation.go Normal file
View 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 otel
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)
}

100
propagation_test.go Normal file
View File

@ -0,0 +1,100 @@
// 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 otel
import (
"context"
"strings"
"testing"
)
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 TextMapCarrier) {
carrier.Set(p.Name, "")
}
func (p propagator) Extract(ctx context.Context, carrier 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 := 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)
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 = 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)
}
}

View File

@ -1,25 +0,0 @@
// 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 propagators
import "go.opentelemetry.io/otel/api/propagation"
// DefaultHTTPPropagator returns the default OpenTelemetry HTTP propagator,
// the W3C Trace Context propagator.
func DefaultHTTPPropagator() propagation.HTTPPropagator {
// As specified here:
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/api-propagators.md#global-propagators
return TraceContext{}
}

View File

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/propagators" "go.opentelemetry.io/otel/propagators"
) )
@ -58,9 +58,9 @@ type outOfThinAirPropagator struct {
t *testing.T t *testing.T
} }
var _ propagation.HTTPPropagator = outOfThinAirPropagator{} var _ otel.TextMapPropagator = outOfThinAirPropagator{}
func (p outOfThinAirPropagator) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { func (p outOfThinAirPropagator) Extract(ctx context.Context, carrier otel.TextMapCarrier) context.Context {
sc := trace.SpanContext{ sc := trace.SpanContext{
TraceID: traceID, TraceID: traceID,
SpanID: spanID, SpanID: spanID,
@ -70,34 +70,33 @@ func (p outOfThinAirPropagator) Extract(ctx context.Context, supplier propagatio
return trace.ContextWithRemoteSpanContext(ctx, sc) return trace.ContextWithRemoteSpanContext(ctx, sc)
} }
func (outOfThinAirPropagator) Inject(context.Context, propagation.HTTPSupplier) {} func (outOfThinAirPropagator) Inject(context.Context, otel.TextMapCarrier) {}
func (outOfThinAirPropagator) GetAllKeys() []string { func (outOfThinAirPropagator) Fields() []string {
return nil return nil
} }
type nilSupplier struct{} type nilCarrier struct{}
var _ propagation.HTTPSupplier = nilSupplier{} var _ otel.TextMapCarrier = nilCarrier{}
func (nilSupplier) Get(key string) string { func (nilCarrier) Get(key string) string {
return "" return ""
} }
func (nilSupplier) Set(key string, value string) {} func (nilCarrier) Set(key string, value string) {}
func TestMultiplePropagators(t *testing.T) { func TestMultiplePropagators(t *testing.T) {
ootaProp := outOfThinAirPropagator{t: t} ootaProp := outOfThinAirPropagator{t: t}
ns := nilSupplier{} ns := nilCarrier{}
testProps := []propagation.HTTPPropagator{ testProps := []otel.TextMapPropagator{
propagators.TraceContext{}, propagators.TraceContext{},
} }
bg := context.Background() bg := context.Background()
// sanity check of oota propagator, ensuring that it really // sanity check of oota propagator, ensuring that it really
// generates the valid span context out of thin air // generates the valid span context out of thin air
{ {
props := propagation.New(propagation.WithExtractors(ootaProp)) ctx := ootaProp.Extract(bg, ns)
ctx := propagation.ExtractHTTP(bg, props, ns)
sc := trace.RemoteSpanContextFromContext(ctx) sc := trace.RemoteSpanContextFromContext(ctx)
require.True(t, sc.IsValid(), "oota prop failed sanity check") require.True(t, sc.IsValid(), "oota prop failed sanity check")
} }
@ -105,19 +104,14 @@ func TestMultiplePropagators(t *testing.T) {
// really are not putting any valid span context into an empty // really are not putting any valid span context into an empty
// go context in absence of the HTTP headers. // go context in absence of the HTTP headers.
for _, prop := range testProps { for _, prop := range testProps {
props := propagation.New(propagation.WithExtractors(prop)) ctx := prop.Extract(bg, ns)
ctx := propagation.ExtractHTTP(bg, props, ns)
sc := trace.RemoteSpanContextFromContext(ctx) sc := trace.RemoteSpanContextFromContext(ctx)
require.Falsef(t, sc.IsValid(), "%#v failed sanity check", prop) require.Falsef(t, sc.IsValid(), "%#v failed sanity check", prop)
} }
for _, prop := range testProps { for _, prop := range testProps {
props := propagation.New(propagation.WithExtractors(ootaProp, prop)) props := otel.NewCompositeTextMapPropagator(ootaProp, prop)
ctx := propagation.ExtractHTTP(bg, props, ns) ctx := props.Extract(bg, ns)
sc := trace.RemoteSpanContextFromContext(ctx) sc := trace.RemoteSpanContextFromContext(ctx)
assert.Truef(t, sc.IsValid(), "%#v clobbers span context", prop) assert.Truef(t, sc.IsValid(), "%#v clobbers span context", prop)
} }
} }
func TestDefaultHTTPPropagator(t *testing.T) {
assert.IsType(t, propagators.TraceContext{}, propagators.DefaultHTTPPropagator())
}

View File

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
) )
@ -47,15 +47,14 @@ const (
// their proprietary information. // their proprietary information.
type TraceContext struct{} type TraceContext struct{}
var _ propagation.HTTPPropagator = TraceContext{} var _ otel.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})(?:-.*)?$") 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 injects a context into the supplier as W3C Trace Context HTTP // Inject set tracecontext from the Context into the carrier.
// headers. func (tc TraceContext) Inject(ctx context.Context, carrier otel.TextMapCarrier) {
func (tc TraceContext) Inject(ctx context.Context, supplier propagation.HTTPSupplier) {
tracestate := ctx.Value(tracestateKey) tracestate := ctx.Value(tracestateKey)
if state, ok := tracestate.(string); tracestate != nil && ok { if state, ok := tracestate.(string); tracestate != nil && ok {
supplier.Set(tracestateHeader, state) carrier.Set(tracestateHeader, state)
} }
sc := trace.SpanFromContext(ctx).SpanContext() sc := trace.SpanFromContext(ctx).SpanContext()
@ -67,26 +66,25 @@ func (tc TraceContext) Inject(ctx context.Context, supplier propagation.HTTPSupp
sc.TraceID, sc.TraceID,
sc.SpanID, sc.SpanID,
sc.TraceFlags&trace.FlagsSampled) sc.TraceFlags&trace.FlagsSampled)
supplier.Set(traceparentHeader, h) carrier.Set(traceparentHeader, h)
} }
// Extract extracts a context from the supplier if it contains W3C Trace // Extract reads tracecontext from the carrier into a returned Context.
// Context headers. func (tc TraceContext) Extract(ctx context.Context, carrier otel.TextMapCarrier) context.Context {
func (tc TraceContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { state := carrier.Get(tracestateHeader)
state := supplier.Get(tracestateHeader)
if state != "" { if state != "" {
ctx = context.WithValue(ctx, tracestateKey, state) ctx = context.WithValue(ctx, tracestateKey, state)
} }
sc := tc.extract(supplier) sc := tc.extract(carrier)
if !sc.IsValid() { if !sc.IsValid() {
return ctx return ctx
} }
return trace.ContextWithRemoteSpanContext(ctx, sc) return trace.ContextWithRemoteSpanContext(ctx, sc)
} }
func (tc TraceContext) extract(supplier propagation.HTTPSupplier) trace.SpanContext { func (tc TraceContext) extract(carrier otel.TextMapCarrier) trace.SpanContext {
h := supplier.Get(traceparentHeader) h := carrier.Get(traceparentHeader)
if h == "" { if h == "" {
return trace.EmptySpanContext() return trace.EmptySpanContext()
} }
@ -153,8 +151,7 @@ func (tc TraceContext) extract(supplier propagation.HTTPSupplier) trace.SpanCont
return sc return sc
} }
// GetAllKeys returns the HTTP header names this propagator will use when // Fields returns the keys who's values are set with Inject.
// injecting. func (tc TraceContext) Fields() []string {
func (tc TraceContext) GetAllKeys() []string {
return []string{traceparentHeader, tracestateHeader} return []string{traceparentHeader, tracestateHeader}
} }

View File

@ -16,15 +16,11 @@ package propagators_test
import ( import (
"go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/propagators" "go.opentelemetry.io/otel/propagators"
) )
func ExampleTraceContext() { func ExampleTraceContext() {
tc := propagators.TraceContext{} tc := propagators.TraceContext{}
// Register the TraceContext propagator globally. // Register the TraceContext propagator globally.
global.SetPropagators(propagation.New( global.SetTextMapPropagator(tc)
propagation.WithExtractors(tc),
propagation.WithInjectors(tc),
))
} }

View File

@ -21,14 +21,13 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/api/trace/tracetest" "go.opentelemetry.io/otel/api/trace/tracetest"
"go.opentelemetry.io/otel/propagators" "go.opentelemetry.io/otel/propagators"
) )
func TestExtractValidTraceContextFromHTTPReq(t *testing.T) { func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
props := propagation.New(propagation.WithExtractors(propagators.TraceContext{})) prop := propagators.TraceContext{}
tests := []struct { tests := []struct {
name string name string
header string header string
@ -112,7 +111,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
req.Header.Set("traceparent", tt.header) req.Header.Set("traceparent", tt.header)
ctx := context.Background() ctx := context.Background()
ctx = propagation.ExtractHTTP(ctx, props, req.Header) ctx = prop.Extract(ctx, req.Header)
gotSc := trace.RemoteSpanContextFromContext(ctx) gotSc := trace.RemoteSpanContextFromContext(ctx)
if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" { if diff := cmp.Diff(gotSc, tt.wantSc); diff != "" {
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff) t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
@ -123,7 +122,7 @@ func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) { func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) {
wantSc := trace.EmptySpanContext() wantSc := trace.EmptySpanContext()
props := propagation.New(propagation.WithExtractors(propagators.TraceContext{})) prop := propagators.TraceContext{}
tests := []struct { tests := []struct {
name string name string
header string header string
@ -200,7 +199,7 @@ func TestExtractInvalidTraceContextFromHTTPReq(t *testing.T) {
req.Header.Set("traceparent", tt.header) req.Header.Set("traceparent", tt.header)
ctx := context.Background() ctx := context.Background()
ctx = propagation.ExtractHTTP(ctx, props, req.Header) ctx = prop.Extract(ctx, req.Header)
gotSc := trace.RemoteSpanContextFromContext(ctx) gotSc := trace.RemoteSpanContextFromContext(ctx)
if diff := cmp.Diff(gotSc, wantSc); diff != "" { if diff := cmp.Diff(gotSc, wantSc); diff != "" {
t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff) t.Errorf("Extract Tracecontext: %s: -got +want %s", tt.name, diff)
@ -215,7 +214,7 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) {
Sampled: false, Sampled: false,
StartSpanID: &id, StartSpanID: &id,
} }
props := propagation.New(propagation.WithInjectors(propagators.TraceContext{})) prop := propagators.TraceContext{}
tests := []struct { tests := []struct {
name string name string
sc trace.SpanContext sc trace.SpanContext
@ -261,7 +260,7 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) {
ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc) ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc)
ctx, _ = mockTracer.Start(ctx, "inject") ctx, _ = mockTracer.Start(ctx, "inject")
} }
propagation.InjectHTTP(ctx, props, req.Header) prop.Inject(ctx, req.Header)
gotHeader := req.Header.Get("traceparent") gotHeader := req.Header.Get("traceparent")
if diff := cmp.Diff(gotHeader, tt.wantHeader); diff != "" { if diff := cmp.Diff(gotHeader, tt.wantHeader); diff != "" {
@ -274,23 +273,23 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) {
func TestTraceContextPropagator_GetAllKeys(t *testing.T) { func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
var propagator propagators.TraceContext var propagator propagators.TraceContext
want := []string{"traceparent", "tracestate"} want := []string{"traceparent", "tracestate"}
got := propagator.GetAllKeys() got := propagator.Fields()
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)
} }
} }
func TestTraceStatePropagation(t *testing.T) { func TestTraceStatePropagation(t *testing.T) {
props := propagation.New(propagation.WithInjectors(propagators.TraceContext{}), propagation.WithExtractors(propagators.TraceContext{})) prop := propagators.TraceContext{}
want := "opaquevalue" want := "opaquevalue"
headerName := "tracestate" headerName := "tracestate"
inReq, _ := http.NewRequest(http.MethodGet, "http://example.com", nil) inReq, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
inReq.Header.Add(headerName, want) inReq.Header.Add(headerName, want)
ctx := propagation.ExtractHTTP(context.Background(), props, inReq.Header) ctx := prop.Extract(context.Background(), inReq.Header)
outReq, _ := http.NewRequest(http.MethodGet, "http://www.example.com", nil) outReq, _ := http.NewRequest(http.MethodGet, "http://www.example.com", nil)
propagation.InjectHTTP(ctx, props, outReq.Header) prop.Inject(ctx, outReq.Header)
if diff := cmp.Diff(outReq.Header.Get(headerName), want); diff != "" { if diff := cmp.Diff(outReq.Header.Get(headerName), want); diff != "" {
t.Errorf("Propagate tracestate: -got +want %s", diff) t.Errorf("Propagate tracestate: -got +want %s", diff)