Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Request for new component: Baggage Span Processor

**Is your feature request related to a problem?**
The Honeycomb distro currently provides a Span processor that takes items from the baggage and adds those items as attributes onto spans. 

**Describe the solution you'd like**
We'd like to donate this code upstream if there's an interest from the community, the code is pretty minimal:
https://github.com/honeycombio/honeycomb-opentelemetry-python/blob/main/src/honeycomb/opentelemetry/baggage.py

**Describe alternatives you've considered**
Which alternative solutions or features have you considered?

**Additional context**
Add any other context about the feature request here.



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
new file mode 100644
--- /dev/null
+++ b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/__init__.py
@@ -0,0 +1,20 @@
+# 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.
+
+# pylint: disable=import-error
+
+from .processor import BaggageSpanProcessor
+from .version import __version__
+
+__all__ = ["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
new file mode 100644
--- /dev/null
+++ b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/processor.py
@@ -0,0 +1,55 @@
+# 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.
+
+from typing import 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
+
+
+class BaggageSpanProcessor(SpanProcessor):
+    """
+    The BaggageSpanProcessor reads entries stored in Baggage
+    from the parent context and adds the baggage entries' keys and
+    values to the span as attributes on span start.
+
+    Add this span processor to a tracer provider.
+
+    Keys and values added to Baggage will appear on subsequent child
+    spans for a trace within this service *and* be propagated to external
+    services in accordance with any configured propagation formats
+    configured. If the external services also have a Baggage span
+    processor, the keys and values will appear in those child spans as
+    well.
+
+    ⚠ Warning ⚠️
+
+    Do not put sensitive information in Baggage.
+
+    To repeat: a consequence of adding data to Baggage is that the keys and
+    values will appear in all outgoing HTTP headers from the application.
+
+    """
+
+    def __init__(self) -> None:
+        pass
+
+    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)
diff --git a/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/version.py b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/version.py
new file mode 100644
--- /dev/null
+++ b/processor/opentelemetry-processor-baggage/src/opentelemetry/processor/baggage/version.py
@@ -0,0 +1,15 @@
+# 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.
+
+__version__ = "0.46b0.dev"


Test patch:
diff --git a/processor/opentelemetry-processor-baggage/tests/__init__.py b/processor/opentelemetry-processor-baggage/tests/__init__.py
new file mode 100644
--- /dev/null
+++ b/processor/opentelemetry-processor-baggage/tests/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py b/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py
new file mode 100644
--- /dev/null
+++ b/processor/opentelemetry-processor-baggage/tests/test_baggage_processor.py
@@ -0,0 +1,89 @@
+# 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.
+
+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.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SpanProcessor
+from opentelemetry.trace import Span, Tracer
+
+
+class BaggageSpanProcessorTest(unittest.TestCase):
+    def test_check_the_baggage(self):
+        self.assertIsInstance(BaggageSpanProcessor(), 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 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_set_baggage_attaches_to_child_spans_and_detaches_properly_with_token(
+        self,
+    ):
+        tracer_provider = TracerProvider()
+        tracer_provider.add_span_processor(BaggageSpanProcessor())
+
+        # tracer has no baggage to start
+        tracer = tracer_provider.get_tracer("my-tracer")
+        self.assertIsInstance(tracer, Tracer)
+        self.assertEqual(get_all_baggage(), {})
+        # create a context token and set baggage
+        honey_token = attach(set_baggage("bumble", "bee"))
+        self.assertEqual(get_all_baggage(), {"bumble": "bee"})
+        # in a new span, ensure the baggage is there
+        with tracer.start_as_current_span("parent") as span:
+            self.assertEqual(get_all_baggage(), {"bumble": "bee"})
+            self.assertEqual(span._attributes["bumble"], "bee")
+            # create a second context token and set more baggage
+            moar_token = attach(set_baggage("moar", "bee"))
+            self.assertEqual(
+                get_all_baggage(), {"bumble": "bee", "moar": "bee"}
+            )
+            # in a child span, ensure all baggage is there as attributes
+            with tracer.start_as_current_span("child") as child_span:
+                self.assertEqual(
+                    get_all_baggage(), {"bumble": "bee", "moar": "bee"}
+                )
+                self.assertEqual(child_span._attributes["bumble"], "bee")
+                self.assertEqual(child_span._attributes["moar"], "bee")
+            detach(moar_token)
+        detach(honey_token)
+        self.assertEqual(get_all_baggage(), {})