Skip to content

Commit

Permalink
baggagetrace: Add baggage key predicate (#5619)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?
- Closes #5437 

## Short description of the changes
- Add `BaggageKeyPredicate` type and `AllowAllBaggageKeys`
implementation that allows all keys
- Add baggage key predicate as constructor parameter to processor
- Add unit tests to verify behaviour for prefix and regex
- Update README with examples of setting a filter

---------

Co-authored-by: Alex Boten <[email protected]>
  • Loading branch information
MikeGoldsmith and codeboten authored Jun 6, 2024
1 parent 0430e80 commit 85969a3
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
This parameter is used as a replacement of `WithInstrumentationScope` to specify the name of the logger backing the returned `Handler`. (#5588)
- Upgrade all dependencies of `go.opentelemetry.io/otel/semconv/v1.24.0` to `go.opentelemetry.io/otel/semconv/v1.25.0`. (#5605)

- Update the span processor in `go.opentelemetry.io/contrib/processors/baggage/baggagetrace` to require a baggage key predicate. (#5619)

### Removed

- The `WithInstrumentationScope` option function in `go.opentelemetry.io/contrib/bridges/otelslog` is removed.
Expand Down
24 changes: 23 additions & 1 deletion processors/baggage/baggagetrace/doc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package baggagetrace implements a baggage span processor.
// Package baggagetrace is an OpenTelemetry [Span Processor] that reads key/values
// stored in [Baggage] in the starting span's parent context and adds them as
// attributes to the span.
//
// Keys and values added to Baggage will appear on all subsequent child spans for
// a trace within this service and will be propagated to external services via
// propagation headers.
// If the external services also have a Baggage span processor, the keys and
// values will appear in those child spans as well.
//
// Do not put sensitive information in Baggage.
//
// # Usage
//
// Add the span processor when configuring the tracer provider.
//
// The convenience function [AllowAllBaggageKeys] is provided to
// allow all baggage keys to be copied to the span. Alternatively, you can
// provide a custom baggage key predicate to select which baggage keys you want
// to copy.
//
// [Span Processor]: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
// [Baggage]: https://opentelemetry.io/docs/specs/otel/api/baggage
package baggagetrace // import "go.opentelemetry.io/contrib/processors/baggage/baggagetrace"
43 changes: 43 additions & 0 deletions processors/baggage/baggagetrace/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package baggagetrace_test

import (
"regexp"
"strings"

"go.opentelemetry.io/contrib/processors/baggage/baggagetrace"
"go.opentelemetry.io/otel/sdk/trace"
)

func ExampleNew_allKeys() {
trace.NewTracerProvider(
trace.WithSpanProcessor(baggagetrace.New(baggagetrace.AllowAllBaggageKeys)),
)
}

func ExampleNew_keysWithPrefix() {
trace.NewTracerProvider(
trace.WithSpanProcessor(
baggagetrace.New(
func(baggageKey string) bool {
return strings.HasPrefix(baggageKey, "my-key")
},
),
),
)
}

func ExampleNew_keysMatchingRegex() {
expr := regexp.MustCompile(`^key.+`)
trace.NewTracerProvider(
trace.WithSpanProcessor(
baggagetrace.New(
func(baggageKey string) bool {
return expr.MatchString(baggageKey)
},
),
),
)
}
21 changes: 17 additions & 4 deletions processors/baggage/baggagetrace/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,36 @@ import (
"go.opentelemetry.io/otel/sdk/trace"
)

// BaggageKeyPredicate is a function that returns true if the baggage key should be added to the span.
type BaggageKeyPredicate func(baggageKey string) bool

// AllowAllBaggageKeys is a BaggageKeyPredicate that allows all baggage keys.
var AllowAllBaggageKeys = func(string) bool { return true }

// SpanProcessor is a processing pipeline for spans in the trace signal.
type SpanProcessor struct{}
type SpanProcessor struct {
baggageKeyPredicate BaggageKeyPredicate
}

var _ trace.SpanProcessor = (*SpanProcessor)(nil)

// New returns a new SpanProcessor.
//
// The Baggage span processor duplicates onto a span the attributes found
// in Baggage in the parent context at the moment the span is started.
func New() trace.SpanProcessor {
return &SpanProcessor{}
// The predicate function is used to filter which baggage keys are added to the span.
func New(baggageKeyPredicate BaggageKeyPredicate) trace.SpanProcessor {
return &SpanProcessor{
baggageKeyPredicate: baggageKeyPredicate,
}
}

// OnStart is called when a span is started and adds span attributes for baggage contents.
func (processor SpanProcessor) OnStart(ctx context.Context, span trace.ReadWriteSpan) {
for _, entry := range otelbaggage.FromContext(ctx).Members() {
span.SetAttributes(attribute.String(entry.Key(), entry.Value()))
if processor.baggageKeyPredicate(entry.Key()) {
span.SetAttributes(attribute.String(entry.Key(), entry.Value()))
}
}
}

Expand Down
117 changes: 104 additions & 13 deletions processors/baggage/baggagetrace/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ package baggagetrace

import (
"context"
"regexp"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/attribute"
Expand All @@ -33,19 +34,15 @@ func NewTestExporter() *testExporter {
return &testExporter{}
}

func TestSpanProcessorAppendsBaggageAttributes(t *testing.T) {
suitcase, err := otelbaggage.New()
require.NoError(t, err)
packingCube, err := otelbaggage.NewMemberRaw("baggage.test", "baggage value")
require.NoError(t, err)
suitcase, err = suitcase.SetMember(packingCube)
require.NoError(t, err)
ctx := otelbaggage.ContextWithBaggage(context.Background(), suitcase)
func TestSpanProcessorAppendsAllBaggageAttributes(t *testing.T) {
baggage, _ := otelbaggage.New()
baggage = addEntryToBaggage(t, baggage, "baggage.test", "baggage value")
ctx := otelbaggage.ContextWithBaggage(context.Background(), baggage)

// create trace provider with baggage processor and test exporter
exporter := NewTestExporter()
tp := trace.NewTracerProvider(
trace.WithSpanProcessor(New()),
trace.WithSpanProcessor(New(AllowAllBaggageKeys)),
trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)),
)

Expand All @@ -54,9 +51,103 @@ func TestSpanProcessorAppendsBaggageAttributes(t *testing.T) {
_, span := tracer.Start(ctx, "test")
span.End()

assert.Len(t, exporter.spans, 1)
assert.Len(t, exporter.spans[0].Attributes(), 1)
require.Len(t, exporter.spans, 1)
require.Len(t, exporter.spans[0].Attributes(), 1)

want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")}
assert.Equal(t, want, exporter.spans[0].Attributes())
require.Equal(t, want, exporter.spans[0].Attributes())
}

func TestSpanProcessorAppendsBaggageAttributesWithHaPrefixPredicate(t *testing.T) {
baggage, _ := otelbaggage.New()
baggage = addEntryToBaggage(t, baggage, "baggage.test", "baggage value")
ctx := otelbaggage.ContextWithBaggage(context.Background(), baggage)

baggageKeyPredicate := func(key string) bool {
return strings.HasPrefix(key, "baggage.")
}

// create trace provider with baggage processor and test exporter
exporter := NewTestExporter()
tp := trace.NewTracerProvider(
trace.WithSpanProcessor(New(baggageKeyPredicate)),
trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)),
)

// create tracer and start/end span
tracer := tp.Tracer("test")
_, span := tracer.Start(ctx, "test")
span.End()

require.Len(t, exporter.spans, 1)
require.Len(t, exporter.spans[0].Attributes(), 1)

want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")}
require.Equal(t, want, exporter.spans[0].Attributes())
}

func TestSpanProcessorAppendsBaggageAttributesWithRegexPredicate(t *testing.T) {
baggage, _ := otelbaggage.New()
baggage = addEntryToBaggage(t, baggage, "baggage.test", "baggage value")
ctx := otelbaggage.ContextWithBaggage(context.Background(), baggage)

expr := regexp.MustCompile(`^baggage\..*`)
baggageKeyPredicate := func(key string) bool {
return expr.MatchString(key)
}

// create trace provider with baggage processor and test exporter
exporter := NewTestExporter()
tp := trace.NewTracerProvider(
trace.WithSpanProcessor(New(baggageKeyPredicate)),
trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)),
)

// create tracer and start/end span
tracer := tp.Tracer("test")
_, span := tracer.Start(ctx, "test")
span.End()

require.Len(t, exporter.spans, 1)
require.Len(t, exporter.spans[0].Attributes(), 1)

want := []attribute.KeyValue{attribute.String("baggage.test", "baggage value")}
require.Equal(t, want, exporter.spans[0].Attributes())
}

func TestOnlyAddsBaggageEntriesThatMatchPredicate(t *testing.T) {
baggage, _ := otelbaggage.New()
baggage = addEntryToBaggage(t, baggage, "baggage.test", "baggage value")
baggage = addEntryToBaggage(t, baggage, "foo", "bar")
ctx := otelbaggage.ContextWithBaggage(context.Background(), baggage)

baggageKeyPredicate := func(key string) bool {
return key == "baggage.test"
}

// create trace provider with baggage processor and test exporter
exporter := NewTestExporter()
tp := trace.NewTracerProvider(
trace.WithSpanProcessor(New(baggageKeyPredicate)),
trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)),
)

// create tracer and start/end span
tracer := tp.Tracer("test")
_, span := tracer.Start(ctx, "test")
span.End()

require.Len(t, exporter.spans, 1)
require.Len(t, exporter.spans[0].Attributes(), 1)

want := attribute.String("baggage.test", "baggage value")
require.Equal(t, want, exporter.spans[0].Attributes()[0])
}

func addEntryToBaggage(t *testing.T, baggage otelbaggage.Baggage, key, value string) otelbaggage.Baggage {
member, err := otelbaggage.NewMemberRaw(key, value)
require.NoError(t, err)
baggage, err = baggage.SetMember(member)
require.NoError(t, err)
return baggage
}

0 comments on commit 85969a3

Please sign in to comment.