From 5e6634059427be53d366d29070c8209e005bc7d4 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Mon, 5 Oct 2020 08:25:09 -0700 Subject: [PATCH] Add Baggage API and move Baggage propagator (#1217) * Move api/baggage to the propagators package * Create Baggage API to match specification * Update CHANGELOG.md * Baggage API unit tests * Rename and add unit test * Update unit test value checking * Update TODO with issue tracking work. --- CHANGELOG.md | 3 + api/baggage/doc.go | 16 -- api/baggage/map.go | 176 ------------------ baggage.go | 67 +++++++ baggage_test.go | 86 +++++++++ bridge/opentracing/bridge.go | 24 +-- bridge/opentracing/internal/mock.go | 22 +-- bridge/opentracing/mix_test.go | 2 +- example/basic/main.go | 13 +- example/namedtracer/main.go | 8 +- .../context.go => internal/baggage/baggage.go | 166 +++++++++++++++++ .../baggage/baggage_test.go | 0 .../baggage.go | 29 +-- .../baggage_test.go | 15 +- propagators/doc.go | 4 +- 15 files changed, 380 insertions(+), 251 deletions(-) delete mode 100644 api/baggage/doc.go delete mode 100644 api/baggage/map.go create mode 100644 baggage.go create mode 100644 baggage_test.go rename api/baggage/context.go => internal/baggage/baggage.go (57%) rename api/baggage/map_test.go => internal/baggage/baggage_test.go (100%) rename api/baggage/baggage_propagator.go => propagators/baggage.go (79%) rename api/baggage/baggage_propagator_test.go => propagators/baggage_test.go (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d62a7137268..06b0c8f4b3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added - OTLP Metric exporter supports Histogram aggregation. (#1209) +- A Baggage API to implement the OpenTelemetry specification. (#1217) ### Changed - 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) +- Move the `go.opentelemetry.io/otel/api/baggage` package into `go.opentelemetry.io/otel/propagators`. (#1217) ### Removed @@ -24,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - 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) - The `SetAttribute` method of the `Span` from the `go.opentelemetry.io/otel/api/trace` package was removed given its redundancy with the `SetAttributes` method. (#1216) +- The internal implementation of Baggage storage is removed in favor of using the new Baggage API functionality. (#1217) - Remove duplicate hostname key `HostHostNameKey` in Resource semantic conventions. (#1219) ## [0.12.0] - 2020-09-24 diff --git a/api/baggage/doc.go b/api/baggage/doc.go deleted file mode 100644 index 9404811e2ecc..000000000000 --- a/api/baggage/doc.go +++ /dev/null @@ -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 baggage provides types and utilities for baggage features. -package baggage // import "go.opentelemetry.io/otel/api/baggage" diff --git a/api/baggage/map.go b/api/baggage/map.go deleted file mode 100644 index b01bd919364f..000000000000 --- a/api/baggage/map.go +++ /dev/null @@ -1,176 +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 baggage - -import "go.opentelemetry.io/otel/label" - -type rawMap map[label.Key]label.Value -type keySet map[label.Key]struct{} - -// Map is an immutable storage for correlations. -type Map struct { - m rawMap -} - -// MapUpdate contains information about correlation changes to be -// made. -type MapUpdate struct { - // DropSingleK contains a single key to be dropped from - // correlations. Use this to avoid an overhead of a slice - // allocation if there is only one key to drop. - DropSingleK label.Key - // DropMultiK contains all the keys to be dropped from - // correlations. - DropMultiK []label.Key - - // SingleKV contains a single key-value pair to be added to - // correlations. Use this to avoid an overhead of a slice - // allocation if there is only one key-value pair to add. - SingleKV label.KeyValue - // MultiKV contains all the key-value pairs to be added to - // correlations. - MultiKV []label.KeyValue -} - -func newMap(raw rawMap) Map { - return Map{ - m: raw, - } -} - -// NewEmptyMap creates an empty correlations map. -func NewEmptyMap() Map { - return newMap(nil) -} - -// NewMap creates a map with the contents of the update applied. In -// this function, having an update with DropSingleK or DropMultiK -// makes no sense - those fields are effectively ignored. -func NewMap(update MapUpdate) Map { - return NewEmptyMap().Apply(update) -} - -// Apply creates a copy of the map with the contents of the update -// applied. Apply will first drop the keys from DropSingleK and -// DropMultiK, then add key-value pairs from SingleKV and MultiKV. -func (m Map) Apply(update MapUpdate) Map { - delSet, addSet := getModificationSets(update) - mapSize := getNewMapSize(m.m, delSet, addSet) - - r := make(rawMap, mapSize) - for k, v := range m.m { - // do not copy items we want to drop - if _, ok := delSet[k]; ok { - continue - } - // do not copy items we would overwrite - if _, ok := addSet[k]; ok { - continue - } - r[k] = v - } - if update.SingleKV.Key.Defined() { - r[update.SingleKV.Key] = update.SingleKV.Value - } - for _, kv := range update.MultiKV { - r[kv.Key] = kv.Value - } - if len(r) == 0 { - r = nil - } - return newMap(r) -} - -func getModificationSets(update MapUpdate) (delSet, addSet keySet) { - deletionsCount := len(update.DropMultiK) - if update.DropSingleK.Defined() { - deletionsCount++ - } - if deletionsCount > 0 { - delSet = make(map[label.Key]struct{}, deletionsCount) - for _, k := range update.DropMultiK { - delSet[k] = struct{}{} - } - if update.DropSingleK.Defined() { - delSet[update.DropSingleK] = struct{}{} - } - } - - additionsCount := len(update.MultiKV) - if update.SingleKV.Key.Defined() { - additionsCount++ - } - if additionsCount > 0 { - addSet = make(map[label.Key]struct{}, additionsCount) - for _, k := range update.MultiKV { - addSet[k.Key] = struct{}{} - } - if update.SingleKV.Key.Defined() { - addSet[update.SingleKV.Key] = struct{}{} - } - } - - return -} - -func getNewMapSize(m rawMap, delSet, addSet keySet) int { - mapSizeDiff := 0 - for k := range addSet { - if _, ok := m[k]; !ok { - mapSizeDiff++ - } - } - for k := range delSet { - if _, ok := m[k]; ok { - if _, inAddSet := addSet[k]; !inAddSet { - mapSizeDiff-- - } - } - } - return len(m) + mapSizeDiff -} - -// Value gets a value from correlations map and returns a boolean -// value indicating whether the key exist in the map. -func (m Map) Value(k label.Key) (label.Value, bool) { - value, ok := m.m[k] - return value, ok -} - -// HasValue returns a boolean value indicating whether the key exist -// in the map. -func (m Map) HasValue(k label.Key) bool { - _, has := m.Value(k) - return has -} - -// Len returns a length of the map. -func (m Map) Len() int { - return len(m.m) -} - -// Foreach calls a passed callback once on each key-value pair until -// all the key-value pairs of the map were iterated or the callback -// returns false, whichever happens first. -func (m Map) Foreach(f func(label.KeyValue) bool) { - for k, v := range m.m { - if !f(label.KeyValue{ - Key: k, - Value: v, - }) { - return - } - } -} diff --git a/baggage.go b/baggage.go new file mode 100644 index 000000000000..bf0f1f6f8a14 --- /dev/null +++ b/baggage.go @@ -0,0 +1,67 @@ +// 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" + + "go.opentelemetry.io/otel/internal/baggage" + "go.opentelemetry.io/otel/label" +) + +// Baggage returns a copy of the baggage in ctx. +func Baggage(ctx context.Context) label.Set { + // TODO (MrAlias, #1222): The underlying storage, the Map, shares many of + // the functional elements of the label.Set. These should be unified so + // this conversion is unnecessary and there is no performance hit calling + // this. + m := baggage.MapFromContext(ctx) + values := make([]label.KeyValue, 0, m.Len()) + m.Foreach(func(kv label.KeyValue) bool { + values = append(values, kv) + return true + }) + return label.NewSet(values...) +} + +// BaggageValue returns the value related to key in the baggage of ctx. If no +// value is set, the returned label.Value will be an uninitialized zero-value +// with type INVALID. +func BaggageValue(ctx context.Context, key label.Key) label.Value { + v, _ := baggage.MapFromContext(ctx).Value(key) + return v +} + +// ContextWithBaggageValues returns a copy of parent with pairs updated in the baggage. +func ContextWithBaggageValues(parent context.Context, pairs ...label.KeyValue) context.Context { + m := baggage.MapFromContext(parent).Apply(baggage.MapUpdate{ + MultiKV: pairs, + }) + return baggage.ContextWithMap(parent, m) +} + +// ContextWithoutBaggageValues returns a copy of parent in which the values related +// to keys have been removed from the baggage. +func ContextWithoutBaggageValues(parent context.Context, keys ...label.Key) context.Context { + m := baggage.MapFromContext(parent).Apply(baggage.MapUpdate{ + DropMultiK: keys, + }) + return baggage.ContextWithMap(parent, m) +} + +// ContextWithoutBaggage returns a copy of parent without baggage. +func ContextWithoutBaggage(parent context.Context) context.Context { + return baggage.ContextWithNoCorrelationData(parent) +} diff --git a/baggage_test.go b/baggage_test.go new file mode 100644 index 000000000000..3d53b24da2e8 --- /dev/null +++ b/baggage_test.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 otel + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel/internal/baggage" + "go.opentelemetry.io/otel/label" +) + +func TestBaggage(t *testing.T) { + ctx := context.Background() + ctx = baggage.ContextWithMap(ctx, baggage.NewEmptyMap()) + + b := Baggage(ctx) + if b.Len() != 0 { + t.Fatalf("empty baggage returned a set with %d elements", b.Len()) + } + + first, second, third := label.Key("first"), label.Key("second"), label.Key("third") + ctx = ContextWithBaggageValues(ctx, first.Bool(true), second.String("2")) + m := baggage.MapFromContext(ctx) + v, ok := m.Value(first) + if !ok { + t.Fatal("WithBaggageValues failed to set first value") + } + if !v.AsBool() { + t.Fatal("WithBaggageValues failed to set first correct value") + } + v, ok = m.Value(second) + if !ok { + t.Fatal("WithBaggageValues failed to set second value") + } + if v.AsString() != "2" { + t.Fatal("WithBaggageValues failed to set second correct value") + } + _, ok = m.Value(third) + if ok { + t.Fatal("WithBaggageValues set an unexpected third value") + } + + b = Baggage(ctx) + if b.Len() != 2 { + t.Fatalf("Baggage returned a set with %d elements, want 2", b.Len()) + } + + v = BaggageValue(ctx, first) + if v.Type() != label.BOOL || !v.AsBool() { + t.Fatal("BaggageValue failed to get correct first value") + } + v = BaggageValue(ctx, second) + if v.Type() != label.STRING || v.AsString() != "2" { + t.Fatal("BaggageValue failed to get correct second value") + } + + ctx = ContextWithoutBaggageValues(ctx, first) + m = baggage.MapFromContext(ctx) + _, ok = m.Value(first) + if ok { + t.Fatal("WithoutBaggageValues failed to remove a baggage value") + } + _, ok = m.Value(second) + if !ok { + t.Fatal("WithoutBaggageValues removed incorrect value") + } + + ctx = ContextWithoutBaggage(ctx) + m = baggage.MapFromContext(ctx) + if m.Len() != 0 { + t.Fatal("WithoutBaggage failed to clear baggage") + } +} diff --git a/bridge/opentracing/bridge.go b/bridge/opentracing/bridge.go index 3a0d2a3bf02f..d9dd7d0c829a 100644 --- a/bridge/opentracing/bridge.go +++ b/bridge/opentracing/bridge.go @@ -26,10 +26,10 @@ import ( otlog "github.com/opentracing/opentracing-go/log" "go.opentelemetry.io/otel" - otelbaggage "go.opentelemetry.io/otel/api/baggage" otelglobal "go.opentelemetry.io/otel/api/global" oteltrace "go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/internal/baggage" "go.opentelemetry.io/otel/internal/trace/noop" otelparent "go.opentelemetry.io/otel/internal/trace/parent" "go.opentelemetry.io/otel/label" @@ -38,7 +38,7 @@ import ( ) type bridgeSpanContext struct { - baggageItems otelbaggage.Map + baggageItems baggage.Map otelSpanContext oteltrace.SpanContext } @@ -46,7 +46,7 @@ var _ ot.SpanContext = &bridgeSpanContext{} func newBridgeSpanContext(otelSpanContext oteltrace.SpanContext, parentOtSpanContext ot.SpanContext) *bridgeSpanContext { bCtx := &bridgeSpanContext{ - baggageItems: otelbaggage.NewEmptyMap(), + baggageItems: baggage.NewEmptyMap(), otelSpanContext: otelSpanContext, } if parentOtSpanContext != nil { @@ -66,7 +66,7 @@ func (c *bridgeSpanContext) ForeachBaggageItem(handler func(k, v string) bool) { func (c *bridgeSpanContext) setBaggageItem(restrictedKey, value string) { crk := http.CanonicalHeaderKey(restrictedKey) - c.baggageItems = c.baggageItems.Apply(otelbaggage.MapUpdate{SingleKV: label.String(crk, value)}) + c.baggageItems = c.baggageItems.Apply(baggage.MapUpdate{SingleKV: label.String(crk, value)}) } func (c *bridgeSpanContext) baggageItem(restrictedKey string) string { @@ -327,8 +327,8 @@ func (t *BridgeTracer) SetTextMapPropagator(propagator otel.TextMapPropagator) { } func (t *BridgeTracer) NewHookedContext(ctx context.Context) context.Context { - ctx = otelbaggage.ContextWithSetHook(ctx, t.baggageSetHook) - ctx = otelbaggage.ContextWithGetHook(ctx, t.baggageGetHook) + ctx = baggage.ContextWithSetHook(ctx, t.baggageSetHook) + ctx = baggage.ContextWithGetHook(ctx, t.baggageGetHook) return ctx } @@ -346,8 +346,8 @@ func (t *BridgeTracer) baggageSetHook(ctx context.Context) context.Context { // we clear the context only to avoid calling a get hook // during MapFromContext, but otherwise we don't change the // context, so we don't care about the old hooks. - clearCtx, _, _ := otelbaggage.ContextWithNoHooks(ctx) - m := otelbaggage.MapFromContext(clearCtx) + clearCtx, _, _ := baggage.ContextWithNoHooks(ctx) + m := baggage.MapFromContext(clearCtx) m.Foreach(func(kv label.KeyValue) bool { bSpan.setBaggageItemOnly(string(kv.Key), kv.Value.Emit()) return true @@ -355,7 +355,7 @@ func (t *BridgeTracer) baggageSetHook(ctx context.Context) context.Context { return ctx } -func (t *BridgeTracer) baggageGetHook(ctx context.Context, m otelbaggage.Map) otelbaggage.Map { +func (t *BridgeTracer) baggageGetHook(ctx context.Context, m baggage.Map) baggage.Map { span := ot.SpanFromContext(ctx) if span == nil { t.warningHandler("No active OpenTracing span, can not propagate the baggage items from OpenTracing span context\n") @@ -374,7 +374,7 @@ func (t *BridgeTracer) baggageGetHook(ctx context.Context, m otelbaggage.Map) ot for k, v := range items { kv = append(kv, label.String(k, v)) } - return m.Apply(otelbaggage.MapUpdate{MultiKV: kv}) + return m.Apply(baggage.MapUpdate{MultiKV: kv}) } // StartSpan is a part of the implementation of the OpenTracing Tracer @@ -613,7 +613,7 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int sc: bridgeSC.otelSpanContext, } ctx := oteltrace.ContextWithSpan(context.Background(), fs) - ctx = otelbaggage.ContextWithMap(ctx, bridgeSC.baggageItems) + ctx = baggage.ContextWithMap(ctx, bridgeSC.baggageItems) t.getPropagator().Inject(ctx, header) return nil } @@ -632,7 +632,7 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span } header := http.Header(hhcarrier) ctx := t.getPropagator().Extract(context.Background(), header) - baggage := otelbaggage.MapFromContext(ctx) + baggage := baggage.MapFromContext(ctx) otelSC, _, _ := otelparent.GetSpanContextAndLinks(ctx, false) bridgeSC := &bridgeSpanContext{ baggageItems: baggage, diff --git a/bridge/opentracing/internal/mock.go b/bridge/opentracing/internal/mock.go index d115b821876d..4f506d70d030 100644 --- a/bridge/opentracing/internal/mock.go +++ b/bridge/opentracing/internal/mock.go @@ -21,9 +21,9 @@ import ( "sync" "time" - otelbaggage "go.opentelemetry.io/otel/api/baggage" oteltrace "go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/internal/baggage" otelparent "go.opentelemetry.io/otel/internal/trace/parent" "go.opentelemetry.io/otel/label" @@ -45,7 +45,7 @@ type MockContextKeyValue struct { } type MockTracer struct { - Resources otelbaggage.Map + Resources baggage.Map FinishedSpans []*MockSpan SpareTraceIDs []oteltrace.ID SpareSpanIDs []oteltrace.SpanID @@ -60,7 +60,7 @@ var _ migration.DeferredContextSetupTracerExtension = &MockTracer{} func NewMockTracer() *MockTracer { return &MockTracer{ - Resources: otelbaggage.NewEmptyMap(), + Resources: baggage.NewEmptyMap(), FinishedSpans: nil, SpareTraceIDs: nil, SpareSpanIDs: nil, @@ -86,7 +86,7 @@ func (t *MockTracer) Start(ctx context.Context, name string, opts ...oteltrace.S officialTracer: t, spanContext: spanContext, recording: config.Record, - Attributes: otelbaggage.NewMap(otelbaggage.MapUpdate{ + Attributes: baggage.NewMap(baggage.MapUpdate{ MultiKV: config.Attributes, }), StartTime: startTime, @@ -179,10 +179,10 @@ func (t *MockTracer) DeferredContextSetupHook(ctx context.Context, span oteltrac } type MockEvent struct { - CtxAttributes otelbaggage.Map + CtxAttributes baggage.Map Timestamp time.Time Name string - Attributes otelbaggage.Map + Attributes baggage.Map } type MockSpan struct { @@ -192,7 +192,7 @@ type MockSpan struct { SpanKind oteltrace.SpanKind recording bool - Attributes otelbaggage.Map + Attributes baggage.Map StartTime time.Time EndTime time.Time ParentSpanID oteltrace.SpanID @@ -223,12 +223,12 @@ func (s *MockSpan) SetError(v bool) { } func (s *MockSpan) SetAttributes(attributes ...label.KeyValue) { - s.applyUpdate(otelbaggage.MapUpdate{ + s.applyUpdate(baggage.MapUpdate{ MultiKV: attributes, }) } -func (s *MockSpan) applyUpdate(update otelbaggage.MapUpdate) { +func (s *MockSpan) applyUpdate(update baggage.MapUpdate) { s.Attributes = s.Attributes.Apply(update) } @@ -283,10 +283,10 @@ func (s *MockSpan) AddEvent(ctx context.Context, name string, attrs ...label.Key func (s *MockSpan) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) { s.Events = append(s.Events, MockEvent{ - CtxAttributes: otelbaggage.MapFromContext(ctx), + CtxAttributes: baggage.MapFromContext(ctx), Timestamp: timestamp, Name: name, - Attributes: otelbaggage.NewMap(otelbaggage.MapUpdate{ + Attributes: baggage.NewMap(baggage.MapUpdate{ MultiKV: attrs, }), }) diff --git a/bridge/opentracing/mix_test.go b/bridge/opentracing/mix_test.go index 8e3a433d8183..9d3eacdb2844 100644 --- a/bridge/opentracing/mix_test.go +++ b/bridge/opentracing/mix_test.go @@ -21,9 +21,9 @@ import ( ot "github.com/opentracing/opentracing-go" - otelbaggage "go.opentelemetry.io/otel/api/baggage" otelglobal "go.opentelemetry.io/otel/api/global" oteltrace "go.opentelemetry.io/otel/api/trace" + otelbaggage "go.opentelemetry.io/otel/internal/baggage" "go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/bridge/opentracing/internal" diff --git a/example/basic/main.go b/example/basic/main.go index 3de912b612f7..aa6b45444236 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -18,12 +18,13 @@ import ( "context" "log" - "go.opentelemetry.io/otel/api/baggage" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/metric" "go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/exporters/stdout" "go.opentelemetry.io/otel/label" + "go.opentelemetry.io/otel/propagators" "go.opentelemetry.io/otel/sdk/metric/controller/push" "go.opentelemetry.io/otel/sdk/metric/processor/basic" "go.opentelemetry.io/otel/sdk/metric/selector/simple" @@ -62,7 +63,7 @@ func main() { global.SetMeterProvider(pusher.MeterProvider()) // set global propagator to baggage (the default is no-op). - global.SetTextMapPropagator(baggage.Baggage{}) + global.SetTextMapPropagator(propagators.Baggage{}) tracer := global.Tracer("ex.com/basic") meter := global.Meter("ex.com/basic") @@ -78,11 +79,7 @@ func main() { valuerecorderTwo := metric.Must(meter).NewFloat64ValueRecorder("ex.com.two") ctx := context.Background() - - ctx = baggage.NewContext(ctx, - fooKey.String("foo1"), - barKey.String("bar1"), - ) + ctx = otel.ContextWithBaggageValues(ctx, fooKey.String("foo1"), barKey.String("bar1")) valuerecorder := valuerecorderTwo.Bind(commonLabels...) defer valuerecorder.Unbind() @@ -97,7 +94,7 @@ func main() { meter.RecordBatch( // Note: call-site variables added as context Entries: - baggage.NewContext(ctx, anotherKey.String("xyz")), + otel.ContextWithBaggageValues(ctx, anotherKey.String("xyz")), commonLabels, valuerecorderTwo.Measurement(2.0), diff --git a/example/namedtracer/main.go b/example/namedtracer/main.go index cb15699ad8cc..ae9c284e076b 100644 --- a/example/namedtracer/main.go +++ b/example/namedtracer/main.go @@ -18,7 +18,7 @@ import ( "context" "log" - "go.opentelemetry.io/otel/api/baggage" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/trace" "go.opentelemetry.io/otel/example/namedtracer/foo" @@ -64,11 +64,7 @@ func main() { // Create a named tracer with package path as its name. tracer := tp.Tracer("example/namedtracer/main") ctx := context.Background() - - ctx = baggage.NewContext(ctx, - fooKey.String("foo1"), - barKey.String("bar1"), - ) + ctx = otel.ContextWithBaggageValues(ctx, fooKey.String("foo1"), barKey.String("bar1")) var span trace.Span ctx, span = tracer.Start(ctx, "operation") diff --git a/api/baggage/context.go b/internal/baggage/baggage.go similarity index 57% rename from api/baggage/context.go rename to internal/baggage/baggage.go index 46e62cca55de..6b5c0c2d9335 100644 --- a/api/baggage/context.go +++ b/internal/baggage/baggage.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package baggage provides types and functions to manage W3C Baggage. package baggage import ( @@ -20,6 +21,165 @@ import ( "go.opentelemetry.io/otel/label" ) +type rawMap map[label.Key]label.Value +type keySet map[label.Key]struct{} + +// Map is an immutable storage for correlations. +type Map struct { + m rawMap +} + +// MapUpdate contains information about correlation changes to be +// made. +type MapUpdate struct { + // DropSingleK contains a single key to be dropped from + // correlations. Use this to avoid an overhead of a slice + // allocation if there is only one key to drop. + DropSingleK label.Key + // DropMultiK contains all the keys to be dropped from + // correlations. + DropMultiK []label.Key + + // SingleKV contains a single key-value pair to be added to + // correlations. Use this to avoid an overhead of a slice + // allocation if there is only one key-value pair to add. + SingleKV label.KeyValue + // MultiKV contains all the key-value pairs to be added to + // correlations. + MultiKV []label.KeyValue +} + +func newMap(raw rawMap) Map { + return Map{ + m: raw, + } +} + +// NewEmptyMap creates an empty correlations map. +func NewEmptyMap() Map { + return newMap(nil) +} + +// NewMap creates a map with the contents of the update applied. In +// this function, having an update with DropSingleK or DropMultiK +// makes no sense - those fields are effectively ignored. +func NewMap(update MapUpdate) Map { + return NewEmptyMap().Apply(update) +} + +// Apply creates a copy of the map with the contents of the update +// applied. Apply will first drop the keys from DropSingleK and +// DropMultiK, then add key-value pairs from SingleKV and MultiKV. +func (m Map) Apply(update MapUpdate) Map { + delSet, addSet := getModificationSets(update) + mapSize := getNewMapSize(m.m, delSet, addSet) + + r := make(rawMap, mapSize) + for k, v := range m.m { + // do not copy items we want to drop + if _, ok := delSet[k]; ok { + continue + } + // do not copy items we would overwrite + if _, ok := addSet[k]; ok { + continue + } + r[k] = v + } + if update.SingleKV.Key.Defined() { + r[update.SingleKV.Key] = update.SingleKV.Value + } + for _, kv := range update.MultiKV { + r[kv.Key] = kv.Value + } + if len(r) == 0 { + r = nil + } + return newMap(r) +} + +func getModificationSets(update MapUpdate) (delSet, addSet keySet) { + deletionsCount := len(update.DropMultiK) + if update.DropSingleK.Defined() { + deletionsCount++ + } + if deletionsCount > 0 { + delSet = make(map[label.Key]struct{}, deletionsCount) + for _, k := range update.DropMultiK { + delSet[k] = struct{}{} + } + if update.DropSingleK.Defined() { + delSet[update.DropSingleK] = struct{}{} + } + } + + additionsCount := len(update.MultiKV) + if update.SingleKV.Key.Defined() { + additionsCount++ + } + if additionsCount > 0 { + addSet = make(map[label.Key]struct{}, additionsCount) + for _, k := range update.MultiKV { + addSet[k.Key] = struct{}{} + } + if update.SingleKV.Key.Defined() { + addSet[update.SingleKV.Key] = struct{}{} + } + } + + return +} + +func getNewMapSize(m rawMap, delSet, addSet keySet) int { + mapSizeDiff := 0 + for k := range addSet { + if _, ok := m[k]; !ok { + mapSizeDiff++ + } + } + for k := range delSet { + if _, ok := m[k]; ok { + if _, inAddSet := addSet[k]; !inAddSet { + mapSizeDiff-- + } + } + } + return len(m) + mapSizeDiff +} + +// Value gets a value from correlations map and returns a boolean +// value indicating whether the key exist in the map. +func (m Map) Value(k label.Key) (label.Value, bool) { + value, ok := m.m[k] + return value, ok +} + +// HasValue returns a boolean value indicating whether the key exist +// in the map. +func (m Map) HasValue(k label.Key) bool { + _, has := m.Value(k) + return has +} + +// Len returns a length of the map. +func (m Map) Len() int { + return len(m.m) +} + +// Foreach calls a passed callback once on each key-value pair until +// all the key-value pairs of the map were iterated or the callback +// returns false, whichever happens first. +func (m Map) Foreach(f func(label.KeyValue) bool) { + for k, v := range m.m { + if !f(label.KeyValue{ + Key: k, + Value: v, + }) { + return + } + } +} + type correlationsType struct{} // SetHookFunc describes a type of a callback that is called when @@ -148,6 +308,12 @@ func ContextWithMap(ctx context.Context, m Map) context.Context { } } +// ContextWithNoCorrelationData returns a context stripped of correlation +// data. +func ContextWithNoCorrelationData(ctx context.Context) context.Context { + return context.WithValue(ctx, correlationsKey, nil) +} + // NewContext returns a context with the map from passed context // updated with the passed key-value pairs. func NewContext(ctx context.Context, keyvalues ...label.KeyValue) context.Context { diff --git a/api/baggage/map_test.go b/internal/baggage/baggage_test.go similarity index 100% rename from api/baggage/map_test.go rename to internal/baggage/baggage_test.go diff --git a/api/baggage/baggage_propagator.go b/propagators/baggage.go similarity index 79% rename from api/baggage/baggage_propagator.go rename to propagators/baggage.go index 5824b9236d34..6ff01286db2f 100644 --- a/api/baggage/baggage_propagator.go +++ b/propagators/baggage.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package baggage +package propagators import ( "context" @@ -20,6 +20,7 @@ import ( "strings" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/internal/baggage" "go.opentelemetry.io/otel/label" ) @@ -27,15 +28,17 @@ import ( // https://github.com/open-telemetry/opentelemetry-specification/blob/18b2752ebe6c7f0cdd8c7b2bcbdceb0ae3f5ad95/specification/correlationcontext/api.md#header-name const baggageHeader = "otcorrelations" -// Baggage propagates Key:Values in W3C CorrelationContext format. -// nolint:golint +// 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 _ otel.TextMapPropagator = Baggage{} -// Inject set baggage key-values from the Context into the carrier. +// Inject sets baggage key-values from ctx into the carrier. func (b Baggage) Inject(ctx context.Context, carrier otel.TextMapCarrier) { - baggageMap := MapFromContext(ctx) + baggageMap := baggage.MapFromContext(ctx) firstIter := true var headerValueBuilder strings.Builder baggageMap.Foreach(func(kv label.KeyValue) bool { @@ -54,14 +57,14 @@ func (b Baggage) Inject(ctx context.Context, carrier otel.TextMapCarrier) { } } -// Extract reads baggage key-values from the carrier into a returned Context. -func (b Baggage) Extract(ctx context.Context, carrier otel.TextMapCarrier) context.Context { - baggage := carrier.Get(baggageHeader) - if baggage == "" { - return ctx +// Extract returns a copy of parent with the baggage from the carrier added. +func (b Baggage) Extract(parent context.Context, carrier otel.TextMapCarrier) context.Context { + bVal := carrier.Get(baggageHeader) + if bVal == "" { + return parent } - baggageValues := strings.Split(baggage, ",") + baggageValues := strings.Split(bVal, ",") keyValues := make([]label.KeyValue, 0, len(baggageValues)) for _, baggageValue := range baggageValues { valueAndProps := strings.Split(baggageValue, ";") @@ -97,12 +100,12 @@ func (b Baggage) Extract(ctx context.Context, carrier otel.TextMapCarrier) conte if len(keyValues) > 0 { // Only update the context if valid values were found - return ContextWithMap(ctx, NewMap(MapUpdate{ + return baggage.ContextWithMap(parent, baggage.NewMap(baggage.MapUpdate{ MultiKV: keyValues, })) } - return ctx + return parent } // Fields returns the keys who's values are set with Inject. diff --git a/api/baggage/baggage_propagator_test.go b/propagators/baggage_test.go similarity index 94% rename from api/baggage/baggage_propagator_test.go rename to propagators/baggage_test.go index 319ca2a12143..4473e5528445 100644 --- a/api/baggage/baggage_propagator_test.go +++ b/propagators/baggage_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package baggage_test +package propagators_test import ( "context" @@ -23,12 +23,13 @@ import ( "github.com/google/go-cmp/cmp" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/api/baggage" + "go.opentelemetry.io/otel/internal/baggage" "go.opentelemetry.io/otel/label" + "go.opentelemetry.io/otel/propagators" ) func TestExtractValidBaggageFromHTTPReq(t *testing.T) { - prop := otel.TextMapPropagator(baggage.Baggage{}) + prop := otel.TextMapPropagator(propagators.Baggage{}) tests := []struct { name string header string @@ -117,7 +118,7 @@ func TestExtractValidBaggageFromHTTPReq(t *testing.T) { } func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { - prop := otel.TextMapPropagator(baggage.Baggage{}) + prop := otel.TextMapPropagator(propagators.Baggage{}) tests := []struct { name string header string @@ -175,7 +176,7 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) { } func TestInjectBaggageToHTTPReq(t *testing.T) { - propagator := baggage.Baggage{} + propagator := propagators.Baggage{} tests := []struct { name string kvs []label.KeyValue @@ -248,8 +249,8 @@ func TestInjectBaggageToHTTPReq(t *testing.T) { } } -func TestTraceContextPropagator_GetAllKeys(t *testing.T) { - var propagator baggage.Baggage +func TestBaggagePropagatorGetAllKeys(t *testing.T) { + var propagator propagators.Baggage want := []string{"otcorrelations"} got := propagator.Fields() if diff := cmp.Diff(got, want); diff != "" { diff --git a/propagators/doc.go b/propagators/doc.go index 5672a9887acc..a9595634e219 100644 --- a/propagators/doc.go +++ b/propagators/doc.go @@ -17,6 +17,8 @@ Package propagators contains OpenTelemetry context propagators. 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/). +package is the W3C Trace Context encoding +(https://www.w3.org/TR/trace-context/), and W3C Baggage +(https://w3c.github.io/baggage/). */ package propagators // import "go.opentelemetry.io/otel/propagators"