Skip to content

Commit d80a9f3

Browse files
feat!: Add baggage key predicate func to baggage span processor (#990)
* update test to add multiple baggage items * add keyfilter constructor parameter * rework into regex * add regex example and test * move new setup & assertions to #on_start test block Co-authored-by: Mike Goldsmith <[email protected]> * consistent start_with; no "starts" to confuse readers Co-authored-by: Mike Goldsmith <[email protected]> * spec the new new method Co-authored-by: Mike Goldsmith <[email protected]> * update doc comments with new usage * update README with new usage * bonus: convenience docker compose service for testing baggage processor * update description to indicate new baggage entries are not added to active span * fix whitespace --------- Co-authored-by: Robb Kidd <[email protected]>
1 parent 99c3859 commit d80a9f3

File tree

4 files changed

+144
-21
lines changed

4 files changed

+144
-21
lines changed

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ services:
162162
command: ./start_server.sh
163163
working_dir: /app/instrumentation/sinatra/example
164164

165+
processor-baggage-test:
166+
<<: *base
167+
working_dir: /app/processor/baggage
168+
command: |
169+
bash -c "bundle install && rake"
170+
165171
mongo:
166172
image: mongo:4.4
167173
expose:

processor/baggage/README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This is an OpenTelemetry [span processor](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor) that reads key/values stored in [Baggage](https://opentelemetry.io/docs/specs/otel/baggage/api/) in the starting span's parent context and adds them as attributes to the span.
44

5-
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.
5+
Keys and values added to Baggage will appear on all subsequent child spans, not the current active span, for a trace within this service *and* will be propagated to external services via propagation headers.
66
If the external services also have a Baggage span processor, the keys and values will appear in those child spans as well.
77

88
⚠️ Waning ⚠️
@@ -31,7 +31,7 @@ To install the instrumentation, add the gem to your Gemfile:
3131
gem 'opentelemetry-processor-baggage'
3232
```
3333

34-
Then add the processor to an SDK's configuration:
34+
Then configure the span processor to copy all baggage entries:
3535

3636
```ruby
3737
require 'rubygems'
@@ -40,8 +40,11 @@ require 'bundler/setup'
4040
Bundler.require
4141

4242
OpenTelemetry::SDK.configure do |c|
43-
# Add the BaggageSpanProcessor to the collection of span processors
44-
c.add_span_processor(OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new)
43+
# Add the BaggageSpanProcessor to the collection of span processors and
44+
# copy all baggage entries
45+
c.add_span_processor(OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
46+
OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS
47+
))
4548

4649
# Because the span processor list is no longer empty, the SDK will not use the
4750
# values in OTEL_TRACES_EXPORTER to instantiate exporters.
@@ -57,6 +60,27 @@ OpenTelemetry::SDK.configure do |c|
5760
end
5861
```
5962

63+
Alternatively, you can provide a custom baggage key predicate to select which baggage keys you want to copy.
64+
65+
For example, to only copy baggage entries that start with `myapp.`:
66+
67+
```ruby
68+
OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze
69+
OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
70+
# a constant here improves performance
71+
->(baggage_key) { baggage_key.start_with?(OUR_BAGGAGE_KEY_PREFIX) }
72+
)
73+
```
74+
75+
For example, to only copy baggage entries that match `myapp.`, `myapp1.` and `myapp42.`:
76+
77+
```ruby
78+
OUR_BAGGAGE_KEY_MATCHER = /\Amyapp\d*\./
79+
OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
80+
->(baggage_key) { OUR_BAGGAGE_KEY_MATCHER.match?(baggage_key) }
81+
)
82+
```
83+
6084
## How can I get involved?
6185

6286
The `opentelemetry-processor-baggage` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`.

processor/baggage/lib/opentelemetry/processor/baggage/baggage_span_processor.rb

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,36 @@
1010
module OpenTelemetry
1111
module Processor
1212
module Baggage
13+
# A baggage key predicate that allows all keys to be added to the span as attributes.
14+
ALLOW_ALL_BAGGAGE_KEYS = ->(_) { true }
15+
1316
# The BaggageSpanProcessor reads key/values stored in Baggage in the
14-
# starting span's parent context and adds them as attributes to the span.
17+
# starting span's parent context and adds them as attributes to the span,
18+
# if a key matches a provided predicate lambda.
19+
#
20+
# Keys and values added to Baggage will appear on all subsequent child spans,
21+
# not the current active span, for a trace within this service *and* will be
22+
# propagated to external services via propagation headers.
1523
#
16-
# Keys and values added to Baggage will appear on all subsequent child spans
17-
# for a trace within this service *and* will be propagated to external services
18-
# via propagation headers. If the external services also have a Baggage span
19-
# processor, the keys and values will appear in those child spans as well.
24+
# If the external services also have a Baggage span processor, the keys and
25+
# values will appear in those child spans as well.
2026
#
2127
# ⚠️
2228
# To repeat: a consequence of adding data to Baggage is that the keys and
2329
# values will appear in all outgoing HTTP headers from the application.
2430
# Do not put sensitive information in Baggage.
2531
# ⚠️
2632
#
27-
# @example
33+
# @example Adding the BaggageSpanProcessor to the SDK, only add attributes for keys that start with 'myapp.'
34+
# OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze
35+
#
2836
# OpenTelemetry::SDK.configure do |c|
2937
# # Add the BaggageSpanProcessor to the collection of span processors
30-
# c.add_span_processor(OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new)
38+
# c.add_span_processor(
39+
# OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
40+
# ->(key) { key.start_with?(OUR_BAGGAGE_KEY_PREFIX) } # a constant here improves performance
41+
# )
42+
# )
3143
#
3244
# # Because the span processor list is no longer empty, the SDK will not use the
3345
# # values in OTEL_TRACES_EXPORTER to instantiate exporters.
@@ -41,7 +53,31 @@ module Baggage
4153
# )
4254
# )
4355
# end
56+
#
57+
# @example Allow all Baggage keys to be added to the span as attributes
58+
# OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
59+
# # This processor provides a convenience predicate that allows all keys to be added as attributes.
60+
# OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS
61+
# )
4462
class BaggageSpanProcessor < OpenTelemetry::SDK::Trace::SpanProcessor
63+
# Create a new BaggageSpanProcessor that reads Baggage keys and values from the parent context
64+
# and adds them as attributes to the span.
65+
#
66+
# @param [lambda] baggage_key_predicate A lambda that takes a baggage key [String] and returns true if
67+
# the key should be added to the span as an attribute, false otherwise.
68+
#
69+
# @example Only add attributes for keys that start with a specific prefix
70+
# OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze
71+
# OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
72+
# ->(key) { key.start_with?(OUR_BAGGAGE_KEY_PREFIX) } # a constant here improves performance
73+
# )
74+
def initialize(baggage_key_predicate)
75+
raise ArgumentError, 'baggage_key_predicate must respond to :call (lambda/Proc)' unless baggage_key_predicate.respond_to?(:call)
76+
77+
@baggage_key_predicate = baggage_key_predicate
78+
super()
79+
end
80+
4581
# Called when a `Span` is started, adds Baggage keys/values to the span as attributes.
4682
#
4783
# @param [Span] span the `Span` that just started, expected to conform
@@ -51,7 +87,11 @@ class BaggageSpanProcessor < OpenTelemetry::SDK::Trace::SpanProcessor
5187
def on_start(span, parent_context)
5288
return unless span.respond_to?(:add_attributes) && parent_context.is_a?(::OpenTelemetry::Context)
5389

54-
span.add_attributes(::OpenTelemetry::Baggage.values(context: parent_context))
90+
span.add_attributes(
91+
::OpenTelemetry::Baggage
92+
.values(context: parent_context)
93+
.select { |k, _v| @baggage_key_predicate.call(k) }
94+
)
5595
end
5696

5797
# Called when a Span is ended, does nothing.

processor/baggage/test/opentelemetry/processor/baggage/baggage_span_processor_test.rb

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
TEST_EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
1212

1313
OpenTelemetry::SDK.configure do |c|
14-
# the baggage processor getting wired in for testing
15-
c.add_span_processor OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new
14+
# the baggage processor getting wired in for integration testing
15+
c.add_span_processor OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
16+
OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS
17+
)
1618

1719
# use a simple processor and in-memory export for testing sent spans
1820
c.add_span_processor(
@@ -24,17 +26,68 @@
2426
end
2527

2628
describe OpenTelemetry::Processor::Baggage::BaggageSpanProcessor do
27-
let(:processor) { OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new }
29+
let(:processor) do
30+
OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
31+
OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS
32+
)
33+
end
2834
let(:span) { Minitest::Mock.new }
29-
let(:context_with_baggage) { OpenTelemetry::Baggage.set_value('a_key', 'a_value') }
35+
let(:context_with_baggage) do
36+
OpenTelemetry::Baggage.build(context: OpenTelemetry::Context.empty) do |baggage|
37+
baggage.set_value('a_key', 'a_value')
38+
baggage.set_value('b_key', 'b_value')
39+
end
40+
end
41+
42+
describe '#new' do
43+
it 'requires a callable baggage_key_predicate' do
44+
_(-> { OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new }).must_raise(ArgumentError)
45+
err = _(-> { OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(:not_a_callable) }).must_raise(ArgumentError)
46+
_(err.message).must_match(/must respond to :call/)
47+
end
48+
end
3049

3150
describe '#on_start' do
32-
it 'adds current baggage keys/values as attributes when a span starts' do
33-
span.expect(:add_attributes, span, [{ 'a_key' => 'a_value' }])
51+
describe 'with the ALLOW_ALL_BAGGAGE_KEYS predicate' do
52+
it 'adds current baggage keys/values as attributes when a span starts' do
53+
span.expect(:add_attributes, span, [{ 'a_key' => 'a_value', 'b_key' => 'b_value' }])
54+
55+
processor.on_start(span, context_with_baggage)
56+
57+
span.verify
58+
end
59+
end
60+
61+
describe 'with a start_with? key predicate' do
62+
let(:start_with_processor) do
63+
OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
64+
->(baggage_key) { baggage_key.start_with?('a') }
65+
)
66+
end
67+
68+
it 'only adds attributes that pass the keyfilter' do
69+
span.expect(:add_attributes, span, [{ 'a_key' => 'a_value' }])
70+
71+
start_with_processor.on_start(span, context_with_baggage)
72+
73+
span.verify
74+
end
75+
end
76+
77+
describe 'with a regex key predicate' do
78+
let(:regex_processor) do
79+
OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(
80+
->(baggage_key) { baggage_key.match?(/^b_ke.+/) }
81+
)
82+
end
83+
84+
it 'only adds attributes that pass the keyfilter' do
85+
span.expect(:add_attributes, span, [{ 'b_key' => 'b_value' }])
3486

35-
processor.on_start(span, context_with_baggage)
87+
regex_processor.on_start(span, context_with_baggage)
3688

37-
span.verify
89+
span.verify
90+
end
3891
end
3992

4093
it 'does not blow up when given nil context' do
@@ -89,7 +142,7 @@
89142

90143
_(exporter.finished_spans.size).must_equal(1)
91144
_(exporter.finished_spans.first.name).must_equal('integration test span')
92-
_(exporter.finished_spans.first.attributes).must_equal('a_key' => 'a_value')
145+
_(exporter.finished_spans.first.attributes).must_equal('a_key' => 'a_value', 'b_key' => 'b_value')
93146
end
94147
end
95148
end

0 commit comments

Comments
 (0)