Repository: open-telemetry/opentelemetry-python

Issue:
Remove SDK dependency from instrumentation package
The change in https://github.com/open-telemetry/opentelemetry-python/pull/1405 adds the SDK as a dependency of the instrumentation package which was missing from https://github.com/open-telemetry/opentelemetry-python/pull/1036. This should not be the case.


Discussion:

Resolving patch:
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py
--- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py
+++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py
@@ -18,14 +18,15 @@
 
 from pkg_resources import iter_entry_points
 
-from opentelemetry.instrumentation.auto_instrumentation.components import (
-    initialize_components,
-)
-
 logger = getLogger(__file__)
 
 
-def auto_instrument():
+def _load_distros():
+    # will be implemented in a subsequent PR
+    pass
+
+
+def _load_instrumentors():
     for entry_point in iter_entry_points("opentelemetry_instrumentor"):
         try:
             entry_point.load()().instrument()  # type: ignore
@@ -35,10 +36,29 @@ def auto_instrument():
             raise exc
 
 
+def _load_configurators():
+    configured = None
+    for entry_point in iter_entry_points("opentelemetry_configurator"):
+        if configured is not None:
+            logger.warning(
+                "Configuration of %s not loaded, %s already loaded",
+                entry_point.name,
+                configured,
+            )
+            continue
+        try:
+            entry_point.load()().configure()  # type: ignore
+            configured = entry_point.name
+        except Exception as exc:  # pylint: disable=broad-except
+            logger.exception("Configuration of %s failed", entry_point.name)
+            raise exc
+
+
 def initialize():
     try:
-        initialize_components()
-        auto_instrument()
+        _load_distros()
+        _load_configurators()
+        _load_instrumentors()
     except Exception:  # pylint: disable=broad-except
         logger.exception("Failed to auto initialize opentelemetry")
 
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/configurator.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/configurator.py
new file mode 100644
--- /dev/null
+++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/configurator.py
@@ -0,0 +1,53 @@
+# 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.
+# type: ignore
+
+"""
+OpenTelemetry Base Configurator
+"""
+
+from abc import ABC, abstractmethod
+from logging import getLogger
+
+_LOG = getLogger(__name__)
+
+
+class BaseConfigurator(ABC):
+    """An ABC for configurators
+
+    Configurators are used to configure
+    SDKs (i.e. TracerProvider, MeterProvider, Processors...)
+    to reduce the amount of manual configuration required.
+    """
+
+    _instance = None
+    _is_instrumented = False
+
+    def __new__(cls, *args, **kwargs):
+
+        if cls._instance is None:
+            cls._instance = object.__new__(cls, *args, **kwargs)
+
+        return cls._instance
+
+    @abstractmethod
+    def _configure(self, **kwargs):
+        """Configure the SDK"""
+
+    def configure(self, **kwargs):
+        """Configure the SDK"""
+        self._configure(**kwargs)
+
+
+__all__ = ["BaseConfigurator"]
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/components.py b/opentelemetry-sdk/src/opentelemetry/sdk/configuration/__init__.py
similarity index 82%
rename from opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/components.py
rename to opentelemetry-sdk/src/opentelemetry/sdk/configuration/__init__.py
--- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/components.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/configuration/__init__.py
@@ -19,6 +19,7 @@
 
 from opentelemetry import trace
 from opentelemetry.configuration import Configuration
+from opentelemetry.instrumentation.configurator import BaseConfigurator
 from opentelemetry.sdk.metrics.export import MetricsExporter
 from opentelemetry.sdk.resources import Resource
 from opentelemetry.sdk.trace import TracerProvider
@@ -38,15 +39,15 @@
 _DEFAULT_IDS_GENERATOR = RANDOM_IDS_GENERATOR
 
 
-def get_ids_generator() -> str:
+def _get_ids_generator() -> str:
     return Configuration().IDS_GENERATOR or _DEFAULT_IDS_GENERATOR
 
 
-def get_service_name() -> str:
+def _get_service_name() -> str:
     return Configuration().SERVICE_NAME or ""
 
 
-def get_exporter_names() -> Sequence[str]:
+def _get_exporter_names() -> Sequence[str]:
     exporter = Configuration().EXPORTER or _DEFAULT_EXPORTER
     if exporter.lower().strip() == "none":
         return []
@@ -62,10 +63,10 @@ def get_exporter_names() -> Sequence[str]:
     return names
 
 
-def init_tracing(
+def _init_tracing(
     exporters: Sequence[SpanExporter], ids_generator: trace.IdsGenerator
 ):
-    service_name = get_service_name()
+    service_name = _get_service_name()
     provider = TracerProvider(
         resource=Resource.create({"service.name": service_name}),
         ids_generator=ids_generator(),
@@ -85,12 +86,12 @@ def init_tracing(
         )
 
 
-def init_metrics(exporters: Sequence[MetricsExporter]):
+def _init_metrics(exporters: Sequence[MetricsExporter]):
     if exporters:
         logger.warning("automatic metric initialization is not supported yet.")
 
 
-def import_tracer_provider_config_components(
+def _import_tracer_provider_config_components(
     selected_components, entry_point_name
 ) -> Sequence[Tuple[str, object]]:
     component_entry_points = {
@@ -112,7 +113,7 @@ def import_tracer_provider_config_components(
     return component_impls
 
 
-def import_exporters(
+def _import_exporters(
     exporter_names: Sequence[str],
 ) -> Tuple[Sequence[SpanExporter], Sequence[MetricsExporter]]:
     trace_exporters, metric_exporters = {}, {}
@@ -120,7 +121,7 @@ def import_exporters(
     for (
         exporter_name,
         exporter_impl,
-    ) in import_tracer_provider_config_components(
+    ) in _import_tracer_provider_config_components(
         exporter_names, "opentelemetry_exporter"
     ):
         if issubclass(exporter_impl, SpanExporter):
@@ -136,11 +137,11 @@ def import_exporters(
     return trace_exporters, metric_exporters
 
 
-def import_ids_generator(ids_generator_name: str) -> trace.IdsGenerator:
+def _import_ids_generator(ids_generator_name: str) -> trace.IdsGenerator:
     # pylint: disable=unbalanced-tuple-unpacking
     [
         (ids_generator_name, ids_generator_impl)
-    ] = import_tracer_provider_config_components(
+    ] = _import_tracer_provider_config_components(
         [ids_generator_name.strip()], "opentelemetry_ids_generator"
     )
 
@@ -150,14 +151,19 @@ def import_ids_generator(ids_generator_name: str) -> trace.IdsGenerator:
     raise RuntimeError("{0} is not an IdsGenerator".format(ids_generator_name))
 
 
-def initialize_components():
-    exporter_names = get_exporter_names()
-    trace_exporters, metric_exporters = import_exporters(exporter_names)
-    ids_generator_name = get_ids_generator()
-    ids_generator = import_ids_generator(ids_generator_name)
-    init_tracing(trace_exporters, ids_generator)
+def _initialize_components():
+    exporter_names = _get_exporter_names()
+    trace_exporters, metric_exporters = _import_exporters(exporter_names)
+    ids_generator_name = _get_ids_generator()
+    ids_generator = _import_ids_generator(ids_generator_name)
+    _init_tracing(trace_exporters, ids_generator)
 
     # We don't support automatic initialization for metric yet but have added
     # some boilerplate in order to make sure current implementation does not
     # lock us out of supporting metrics later without major surgery.
-    init_metrics(metric_exporters)
+    _init_metrics(metric_exporters)
+
+
+class Configurator(BaseConfigurator):
+    def _configure(self, **kwargs):
+        _initialize_components()


Test patch:
diff --git a/opentelemetry-instrumentation/tests/test_auto_tracing.py b/opentelemetry-sdk/tests/configuration/test_configurator.py
similarity index 83%
rename from opentelemetry-instrumentation/tests/test_auto_tracing.py
rename to opentelemetry-sdk/tests/configuration/test_configurator.py
--- a/opentelemetry-instrumentation/tests/test_auto_tracing.py
+++ b/opentelemetry-sdk/tests/configuration/test_configurator.py
@@ -18,7 +18,11 @@
 from unittest.mock import patch
 
 from opentelemetry.configuration import Configuration
-from opentelemetry.instrumentation.auto_instrumentation import components
+from opentelemetry.sdk.configuration import (
+    _get_ids_generator,
+    _import_ids_generator,
+    _init_tracing,
+)
 from opentelemetry.sdk.resources import Resource
 from opentelemetry.trace.ids_generator import RandomIdsGenerator
 
@@ -71,11 +75,10 @@ class TestTraceInit(TestCase):
     def setUp(self):
         super()
         self.get_provider_patcher = patch(
-            "opentelemetry.instrumentation.auto_instrumentation.components.TracerProvider",
-            Provider,
+            "opentelemetry.sdk.configuration.TracerProvider", Provider,
         )
         self.get_processor_patcher = patch(
-            "opentelemetry.instrumentation.auto_instrumentation.components.BatchExportSpanProcessor",
+            "opentelemetry.sdk.configuration.BatchExportSpanProcessor",
             Processor,
         )
         self.set_provider_patcher = patch(
@@ -96,7 +99,7 @@ def tearDown(self):
     def test_trace_init_default(self):
         environ["OTEL_SERVICE_NAME"] = "my-test-service"
         Configuration._reset()
-        components.init_tracing({"zipkin": Exporter}, RandomIdsGenerator)
+        _init_tracing({"zipkin": Exporter}, RandomIdsGenerator)
 
         self.assertEqual(self.set_provider_mock.call_count, 1)
         provider = self.set_provider_mock.call_args[0][0]
@@ -111,7 +114,7 @@ def test_trace_init_default(self):
     def test_trace_init_otlp(self):
         environ["OTEL_SERVICE_NAME"] = "my-otlp-test-service"
         Configuration._reset()
-        components.init_tracing({"otlp": OTLPExporter}, RandomIdsGenerator)
+        _init_tracing({"otlp": OTLPExporter}, RandomIdsGenerator)
 
         self.assertEqual(self.set_provider_mock.call_count, 1)
         provider = self.set_provider_mock.call_args[0][0]
@@ -128,12 +131,9 @@ def test_trace_init_otlp(self):
 
     @patch.dict(environ, {"OTEL_IDS_GENERATOR": "custom_ids_generator"})
     @patch(
-        "opentelemetry.instrumentation.auto_instrumentation.components.trace.IdsGenerator",
-        new=IdsGenerator,
-    )
-    @patch(
-        "opentelemetry.instrumentation.auto_instrumentation.components.iter_entry_points"
+        "opentelemetry.sdk.configuration.trace.IdsGenerator", new=IdsGenerator,
     )
+    @patch("opentelemetry.sdk.configuration.iter_entry_points")
     def test_trace_init_custom_ids_generator(self, mock_iter_entry_points):
         mock_iter_entry_points.configure_mock(
             return_value=[
@@ -141,8 +141,8 @@ def test_trace_init_custom_ids_generator(self, mock_iter_entry_points):
             ]
         )
         Configuration._reset()
-        ids_generator_name = components.get_ids_generator()
-        ids_generator = components.import_ids_generator(ids_generator_name)
-        components.init_tracing({}, ids_generator)
+        ids_generator_name = _get_ids_generator()
+        ids_generator = _import_ids_generator(ids_generator_name)
+        _init_tracing({}, ids_generator)
         provider = self.set_provider_mock.call_args[0][0]
         self.assertIsInstance(provider.ids_generator, CustomIdsGenerator)