diff --git a/CHANGELOG.md b/CHANGELOG.md index af5011de4..6b1f76163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `ErrInvalidHexID`, `ErrInvalidTraceIDLength`, `ErrInvalidSpanIDLength`, `ErrInvalidSpanIDLength`, or `ErrNilSpanID` from the `go.opentelemetry.io/otel` package are unexported now. (#1243) +### Fixed + +- The `go.opentelemetry.io/otel/api/global` packages global TextMapPropagator now delegates functionality to a globally set delegate for all previously returned propagators. (#1258) + ## [0.13.0] - 2020-10-08 ### Added diff --git a/api/global/internal/propagator.go b/api/global/internal/propagator.go new file mode 100644 index 000000000..706e024a6 --- /dev/null +++ b/api/global/internal/propagator.go @@ -0,0 +1,86 @@ +// 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 internal + +import ( + "context" + "sync" + + "go.opentelemetry.io/otel" +) + +// textMapPropagator is a default TextMapPropagator that delegates calls to a +// registered delegate if one is set, otherwise it defaults to delegating the +// calls to a the default no-op otel.TextMapPropagator. +type textMapPropagator struct { + mtx sync.Mutex + once sync.Once + delegate otel.TextMapPropagator + noop otel.TextMapPropagator +} + +// Compile-time guarantee that textMapPropagator implements the +// otel.TextMapPropagator interface. +var _ otel.TextMapPropagator = (*textMapPropagator)(nil) + +func newTextMapPropagator() *textMapPropagator { + return &textMapPropagator{ + noop: otel.NewCompositeTextMapPropagator(), + } +} + +// SetDelegate sets a delegate otel.TextMapPropagator that all calls are +// forwarded to. Delegation can only be performed once, all subsequent calls +// perform no delegation. +func (p *textMapPropagator) SetDelegate(delegate otel.TextMapPropagator) { + if delegate == nil { + return + } + + p.mtx.Lock() + p.once.Do(func() { p.delegate = delegate }) + p.mtx.Unlock() +} + +// HasDelegate returns if a delegate is set for p. +func (p *textMapPropagator) HasDelegate() bool { + p.mtx.Lock() + defer p.mtx.Unlock() + return p.delegate != nil +} + +// Inject set cross-cutting concerns from the Context into the carrier. +func (p *textMapPropagator) Inject(ctx context.Context, carrier otel.TextMapCarrier) { + if p.HasDelegate() { + p.delegate.Inject(ctx, carrier) + } + p.noop.Inject(ctx, carrier) +} + +// Extract reads cross-cutting concerns from the carrier into a Context. +func (p *textMapPropagator) Extract(ctx context.Context, carrier otel.TextMapCarrier) context.Context { + if p.HasDelegate() { + return p.delegate.Extract(ctx, carrier) + } + return p.noop.Extract(ctx, carrier) +} + +// Fields returns the keys who's values are set with Inject. +func (p *textMapPropagator) Fields() []string { + if p.HasDelegate() { + return p.delegate.Fields() + } + return p.noop.Fields() +} diff --git a/api/global/internal/state.go b/api/global/internal/state.go index e2ded6eb1..9542e60bf 100644 --- a/api/global/internal/state.go +++ b/api/global/internal/state.go @@ -41,8 +41,9 @@ var ( globalMeter = defaultMeterValue() globalPropagators = defaultPropagatorsValue() - delegateMeterOnce sync.Once - delegateTraceOnce sync.Once + delegateMeterOnce sync.Once + delegateTraceOnce sync.Once + delegateTextMapPropagatorOnce sync.Once ) // TracerProvider is the internal implementation for global.TracerProvider. @@ -96,6 +97,19 @@ func TextMapPropagator() otel.TextMapPropagator { // SetTextMapPropagator is the internal implementation for global.SetTextMapPropagator. func SetTextMapPropagator(p otel.TextMapPropagator) { + // For the textMapPropagator already returned by TextMapPropagator + // delegate to p. + delegateTextMapPropagatorOnce.Do(func() { + if current := TextMapPropagator(); current == p { + // Setting the provider to the prior default is nonsense, panic. + // Panic is acceptable because we are likely still early in the + // process lifetime. + panic("invalid TextMapPropagator, the global instance cannot be reinstalled") + } else if def, ok := current.(*textMapPropagator); ok { + def.SetDelegate(p) + } + }) + // Return p when subsequent calls to TextMapPropagator are made. globalPropagators.Store(propagatorsHolder{tm: p}) } @@ -113,16 +127,10 @@ func defaultMeterValue() *atomic.Value { func defaultPropagatorsValue() *atomic.Value { v := &atomic.Value{} - v.Store(propagatorsHolder{tm: getDefaultTextMapPropagator()}) + v.Store(propagatorsHolder{tm: newTextMapPropagator()}) return v } -// getDefaultTextMapPropagator returns the default TextMapPropagator, -// configured with W3C trace and baggage propagation. -func getDefaultTextMapPropagator() otel.TextMapPropagator { - return otel.NewCompositeTextMapPropagator() -} - // ResetForTest restores the initial global state, for testing purposes. func ResetForTest() { globalTracer = defaultTracerValue()