Repository: open-telemetry/opentelemetry-python

Issue:
Add support for OTEL_PROPAGATORS
The spec describes environment variables that should be supported to configure propagators, this feature request is to add support in the current implementation.

https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md


Discussion:

Resolving patch:
diff --git a/opentelemetry-api/src/opentelemetry/propagators/__init__.py b/opentelemetry-api/src/opentelemetry/propagators/__init__.py
--- a/opentelemetry-api/src/opentelemetry/propagators/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/propagators/__init__.py
@@ -15,6 +15,21 @@
 """
 API for propagation of context.
 
+The propagators for the
+``opentelemetry.propagators.composite.CompositeHTTPPropagator`` can be defined
+via configuration in the ``OTEL_PROPAGATORS`` environment variable. This
+variable should be set to a comma-separated string of names of values for the
+``opentelemetry_propagator`` entry point. For example, setting
+``OTEL_PROPAGATORS`` to ``tracecontext,baggage`` (which is the default value)
+would instantiate
+``opentelemetry.propagators.composite.CompositeHTTPPropagator`` with 2
+propagators, one of type
+``opentelemetry.trace.propagation.tracecontext.TraceContextTextMapPropagator``
+and other of type ``opentelemetry.baggage.propagation.BaggagePropagator``.
+Notice that these propagator classes are defined as
+``opentelemetry_propagator`` entry points in the ``setup.cfg`` file of
+``opentelemetry``.
+
 Example::
 
     import flask
@@ -54,14 +69,16 @@ def example_route():
 """
 
 import typing
+from logging import getLogger
+
+from pkg_resources import iter_entry_points
 
-from opentelemetry.baggage.propagation import BaggagePropagator
+from opentelemetry.configuration import Configuration
 from opentelemetry.context.context import Context
 from opentelemetry.propagators import composite
 from opentelemetry.trace.propagation import textmap
-from opentelemetry.trace.propagation.tracecontext import (
-    TraceContextTextMapPropagator,
-)
+
+logger = getLogger(__name__)
 
 
 def extract(
@@ -104,9 +121,25 @@ def inject(
     get_global_textmap().inject(set_in_carrier, carrier, context)
 
 
-_HTTP_TEXT_FORMAT = composite.CompositeHTTPPropagator(
-    [TraceContextTextMapPropagator(), BaggagePropagator()],
-)  # type: textmap.TextMapPropagator
+try:
+
+    propagators = []
+
+    for propagator in (  # type: ignore
+        Configuration().get("PROPAGATORS", "tracecontext,baggage").split(",")  # type: ignore
+    ):
+
+        propagators.append(  # type: ignore
+            next(  # type: ignore
+                iter_entry_points("opentelemetry_propagator", propagator)  # type: ignore
+            ).load()()
+        )
+
+except Exception:  # pylint: disable=broad-except
+    logger.exception("Failed to load configured propagators")
+    raise
+
+_HTTP_TEXT_FORMAT = composite.CompositeHTTPPropagator(propagators)  # type: ignore
 
 
 def get_global_textmap() -> textmap.TextMapPropagator:
@@ -115,4 +148,4 @@ def get_global_textmap() -> textmap.TextMapPropagator:
 
 def set_global_textmap(http_text_format: textmap.TextMapPropagator,) -> None:
     global _HTTP_TEXT_FORMAT  # pylint:disable=global-statement
-    _HTTP_TEXT_FORMAT = http_text_format
+    _HTTP_TEXT_FORMAT = http_text_format  # type: ignore


Test patch:
diff --git a/opentelemetry-api/tests/configuration/test_configuration.py b/opentelemetry-api/tests/configuration/test_configuration.py
--- a/opentelemetry-api/tests/configuration/test_configuration.py
+++ b/opentelemetry-api/tests/configuration/test_configuration.py
@@ -20,9 +20,10 @@
 
 
 class TestConfiguration(TestCase):
-    def tearDown(self) -> None:
-        # This call resets the attributes of the Configuration class so that
-        # each test is executed in the same conditions.
+
+    # These calls reset the attributes of the Configuration class so that each
+    # test is executed in the same conditions.
+    def setUp(self) -> None:
         Configuration._reset()
 
     def test_singleton(self) -> None:
diff --git a/opentelemetry-api/tests/propagators/test_propagators.py b/opentelemetry-api/tests/propagators/test_propagators.py
new file mode 100644
--- /dev/null
+++ b/opentelemetry-api/tests/propagators/test_propagators.py
@@ -0,0 +1,83 @@
+# 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 importlib import reload
+from os import environ
+from unittest import TestCase
+from unittest.mock import Mock, patch
+
+from opentelemetry.baggage.propagation import BaggagePropagator
+from opentelemetry.configuration import Configuration
+from opentelemetry.trace.propagation.tracecontext import (
+    TraceContextTextMapPropagator,
+)
+
+
+class TestPropagators(TestCase):
+    @patch("opentelemetry.propagators.composite.CompositeHTTPPropagator")
+    def test_default_composite_propagators(self, mock_compositehttppropagator):
+        def test_propagators(propagators):
+
+            propagators = {propagator.__class__ for propagator in propagators}
+
+            self.assertEqual(len(propagators), 2)
+            self.assertEqual(
+                propagators, {TraceContextTextMapPropagator, BaggagePropagator}
+            )
+
+        mock_compositehttppropagator.configure_mock(
+            **{"side_effect": test_propagators}
+        )
+
+        import opentelemetry.propagators
+
+        reload(opentelemetry.propagators)
+
+    @patch.dict(environ, {"OTEL_PROPAGATORS": "a,b,c"})
+    @patch("opentelemetry.propagators.composite.CompositeHTTPPropagator")
+    @patch("pkg_resources.iter_entry_points")
+    def test_non_default_propagators(
+        self, mock_iter_entry_points, mock_compositehttppropagator
+    ):
+
+        Configuration._reset()
+
+        def iter_entry_points_mock(_, propagator):
+            return iter(
+                [
+                    Mock(
+                        **{
+                            "load.side_effect": [
+                                Mock(**{"side_effect": [propagator]})
+                            ]
+                        }
+                    )
+                ]
+            )
+
+        mock_iter_entry_points.configure_mock(
+            **{"side_effect": iter_entry_points_mock}
+        )
+
+        def test_propagators(propagators):
+
+            self.assertEqual(propagators, ["a", "b", "c"])
+
+        mock_compositehttppropagator.configure_mock(
+            **{"side_effect": test_propagators}
+        )
+
+        import opentelemetry.propagators
+
+        reload(opentelemetry.propagators)