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

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.
This commit is contained in:
Tyler Yahn
2020-10-05 08:25:09 -07:00
committed by GitHub
parent de50711fcc
commit 5e66340594
15 changed files with 380 additions and 251 deletions

View File

@ -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

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 baggage provides types and utilities for baggage features.
package baggage // import "go.opentelemetry.io/otel/api/baggage"

View File

@ -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
}
}
}

67
baggage.go Normal file
View File

@ -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)
}

86
baggage_test.go Normal file
View File

@ -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")
}
}

View File

@ -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,

View File

@ -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,
}),
})

View File

@ -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"

View File

@ -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),

View File

@ -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")

View File

@ -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 {

View File

@ -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.

View File

@ -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 != "" {

View File

@ -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"