Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Baggage span processor - key predicate
This issue is to track adding a method of selecting what baggage key entries should be copied.

Feedback in the JS contrib PR was to allow a user-provided predicate function. This puts the responsibility on the user to ensure sensitive baggage keys are not copied while also not prescribing how that is determined.
- https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2166


We had a similar feedback in the .NET contrib project but thought it was more complicated than just using a set of prefixes so created an issue to continue the discussion. The plain processor that copies all baggage entries (like using `*` in your example) is likely to be accepted first.
- https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/1695



Discussion:

Resolving patch:
diff --git a/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/__init__.py b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/__init__.py
--- a/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/__init__.py
+++ b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/__init__.py
@@ -14,7 +14,7 @@
 
 # pylint: disable=import-error
 
-from .processor import BaggageSpanProcessor
+from .processor import ALLOW_ALL_BAGGAGE_KEYS, BaggageSpanProcessor
 from .version import __version__
 
-__all__ = ["BaggageSpanProcessor", "__version__"]
+__all__ = ["ALLOW_ALL_BAGGAGE_KEYS", "BaggageSpanProcessor", "__version__"]
diff --git a/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/processor.py b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/processor.py
--- a/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/processor.py
+++ b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/processor.py
@@ -12,13 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import Optional
+from typing import Callable, Optional
 
 from opentelemetry.baggage import get_all as get_all_baggage
 from opentelemetry.context import Context
 from opentelemetry.sdk.trace.export import SpanProcessor
 from opentelemetry.trace import Span
 
+# A BaggageKeyPredicate is a function that takes a baggage key and returns a boolean
+BaggageKeyPredicateT = Callable[[str], bool]
+
+# A BaggageKeyPredicate that always returns True, allowing all baggage keys to be added to spans
+ALLOW_ALL_BAGGAGE_KEYS: BaggageKeyPredicateT = lambda _: True
+
 
 class BaggageSpanProcessor(SpanProcessor):
     """
@@ -44,12 +50,13 @@ class BaggageSpanProcessor(SpanProcessor):
 
     """
 
-    def __init__(self) -> None:
-        pass
+    def __init__(self, baggage_key_predicate: BaggageKeyPredicateT) -> None:
+        self._baggage_key_predicate = baggage_key_predicate
 
     def on_start(
         self, span: "Span", parent_context: Optional[Context] = None
     ) -> None:
         baggage = get_all_baggage(parent_context)
         for key, value in baggage.items():
-            span.set_attribute(key, value)
+            if self._baggage_key_predicate(key):
+                span.set_attribute(key, value)


Test patch:
diff --git a/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py b/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py
--- a/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py
+++ b/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py
@@ -12,12 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import re
 import unittest
 
 from opentelemetry.baggage import get_all as get_all_baggage
 from opentelemetry.baggage import set_baggage
 from opentelemetry.context import attach, detach
-from opentelemetry.processor.baggage import BaggageSpanProcessor
+from opentelemetry.processor.baggage import (
+    ALLOW_ALL_BAGGAGE_KEYS,
+    BaggageSpanProcessor,
+)
 from opentelemetry.sdk.trace import TracerProvider
 from opentelemetry.sdk.trace.export import SpanProcessor
 from opentelemetry.trace import Span, Tracer
@@ -25,13 +29,77 @@
 
 class BaggageSpanProcessorTest(unittest.TestCase):
     def test_check_the_baggage(self):
-        self.assertIsInstance(BaggageSpanProcessor(), SpanProcessor)
+        self.assertIsInstance(
+            BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS), SpanProcessor
+        )
 
     def test_set_baggage_attaches_to_child_spans_and_detaches_properly_with_context(
         self,
     ):
         tracer_provider = TracerProvider()
-        tracer_provider.add_span_processor(BaggageSpanProcessor())
+        tracer_provider.add_span_processor(
+            BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS)
+        )
+
+        # tracer has no baggage to start
+        tracer = tracer_provider.get_tracer("my-tracer")
+        self.assertIsInstance(tracer, Tracer)
+        self.assertEqual(get_all_baggage(), {})
+        # set baggage in context
+        ctx = set_baggage("queen", "bee")
+        with tracer.start_as_current_span(
+            name="bumble", context=ctx
+        ) as bumble_span:
+            # span should have baggage key-value pair in context
+            self.assertEqual(get_all_baggage(ctx), {"queen": "bee"})
+            # span should have baggage key-value pair in attribute
+            self.assertEqual(bumble_span._attributes["queen"], "bee")
+            with tracer.start_as_current_span(
+                name="child_span", context=ctx
+            ) as child_span:
+                self.assertIsInstance(child_span, Span)
+                # child span should have baggage key-value pair in context
+                self.assertEqual(get_all_baggage(ctx), {"queen": "bee"})
+                # child span should have baggage key-value pair in attribute
+                self.assertEqual(child_span._attributes["queen"], "bee")
+
+    def test_baggage_span_processor_with_string_prefix(
+        self,
+    ):
+        tracer_provider = TracerProvider()
+        tracer_provider.add_span_processor(
+            BaggageSpanProcessor(self.has_prefix)
+        )
+
+        # tracer has no baggage to start
+        tracer = tracer_provider.get_tracer("my-tracer")
+        self.assertIsInstance(tracer, Tracer)
+        self.assertEqual(get_all_baggage(), {})
+        # set baggage in context
+        ctx = set_baggage("queen", "bee")
+        with tracer.start_as_current_span(
+            name="bumble", context=ctx
+        ) as bumble_span:
+            # span should have baggage key-value pair in context
+            self.assertEqual(get_all_baggage(ctx), {"queen": "bee"})
+            # span should have baggage key-value pair in attribute
+            self.assertEqual(bumble_span._attributes["queen"], "bee")
+            with tracer.start_as_current_span(
+                name="child_span", context=ctx
+            ) as child_span:
+                self.assertIsInstance(child_span, Span)
+                # child span should have baggage key-value pair in context
+                self.assertEqual(get_all_baggage(ctx), {"queen": "bee"})
+                # child span should have baggage key-value pair in attribute
+                self.assertEqual(child_span._attributes["queen"], "bee")
+
+    def test_baggage_span_processor_with_regex(
+        self,
+    ):
+        tracer_provider = TracerProvider()
+        tracer_provider.add_span_processor(
+            BaggageSpanProcessor(self.matches_regex)
+        )
 
         # tracer has no baggage to start
         tracer = tracer_provider.get_tracer("my-tracer")
@@ -59,7 +127,9 @@ def test_set_baggage_attaches_to_child_spans_and_detaches_properly_with_token(
         self,
     ):
         tracer_provider = TracerProvider()
-        tracer_provider.add_span_processor(BaggageSpanProcessor())
+        tracer_provider.add_span_processor(
+            BaggageSpanProcessor(ALLOW_ALL_BAGGAGE_KEYS)
+        )
 
         # tracer has no baggage to start
         tracer = tracer_provider.get_tracer("my-tracer")
@@ -87,3 +157,11 @@ def test_set_baggage_attaches_to_child_spans_and_detaches_properly_with_token(
             detach(moar_token)
         detach(honey_token)
         self.assertEqual(get_all_baggage(), {})
+
+    @staticmethod
+    def has_prefix(baggage_key: str) -> bool:
+        return baggage_key.startswith("que")
+
+    @staticmethod
+    def matches_regex(baggage_key: str) -> bool:
+        return re.match(r"que.*", baggage_key) is not None