Repository: open-telemetry/opentelemetry-python

Issue:
TracerProvider can be overridden
When `TracerProvider` is overridden we raise a warning. This is good but actually we should also stop it from being overridden. If a `TracerProvider` is set in any step of auto instrumentation, then it may be easily overridden in application code causing unexpected side effects like span processors being lost.


Discussion:

Resolving patch:
diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py
--- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py
@@ -459,7 +459,8 @@ def set_meter_provider(meter_provider: MeterProvider) -> None:
     global _METER_PROVIDER  # pylint: disable=global-statement
 
     if _METER_PROVIDER is not None:
-        logger.warning("Overriding current MeterProvider")
+        logger.warning("Overriding of current MeterProvider is not allowed")
+        return
 
     _METER_PROVIDER = meter_provider
 
diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py
--- a/opentelemetry-api/src/opentelemetry/trace/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py
@@ -70,44 +70,13 @@
     `set_tracer_provider`.
 """
 
-__all__ = [
-    "DEFAULT_TRACE_OPTIONS",
-    "DEFAULT_TRACE_STATE",
-    "INVALID_SPAN",
-    "INVALID_SPAN_CONTEXT",
-    "INVALID_SPAN_ID",
-    "INVALID_TRACE_ID",
-    "DefaultSpan",
-    "DefaultTracer",
-    "DefaultTracerProvider",
-    "LazyLink",
-    "Link",
-    "LinkBase",
-    "ParentSpan",
-    "Span",
-    "SpanContext",
-    "SpanKind",
-    "TraceFlags",
-    "TraceState",
-    "TracerProvider",
-    "Tracer",
-    "format_span_id",
-    "format_trace_id",
-    "get_current_span",
-    "get_tracer",
-    "get_tracer_provider",
-    "set_tracer_provider",
-    "set_span_in_context",
-]
 
 import abc
 import enum
-import types as python_types
 import typing
 from contextlib import contextmanager
 from logging import getLogger
 
-from opentelemetry.configuration import Configuration
 from opentelemetry.trace.propagation import (
     get_current_span,
     set_span_in_context,
@@ -461,11 +430,16 @@ def get_tracer(
 
 
 def set_tracer_provider(tracer_provider: TracerProvider) -> None:
-    """Sets the current global :class:`~.TracerProvider` object."""
+    """Sets the current global :class:`~.TracerProvider` object.
+
+    This can only be done once, a warning will be logged if any furter attempt
+    is made.
+    """
     global _TRACER_PROVIDER  # pylint: disable=global-statement
 
     if _TRACER_PROVIDER is not None:
-        logger.warning("Overriding current TracerProvider")
+        logger.warning("Overriding of current TracerProvider is not allowed")
+        return
 
     _TRACER_PROVIDER = tracer_provider
 
@@ -478,3 +452,35 @@ def get_tracer_provider() -> TracerProvider:
         _TRACER_PROVIDER = _load_trace_provider("tracer_provider")
 
     return _TRACER_PROVIDER
+
+
+__all__ = [
+    "DEFAULT_TRACE_OPTIONS",
+    "DEFAULT_TRACE_STATE",
+    "INVALID_SPAN",
+    "INVALID_SPAN_CONTEXT",
+    "INVALID_SPAN_ID",
+    "INVALID_TRACE_ID",
+    "DefaultSpan",
+    "DefaultTracer",
+    "DefaultTracerProvider",
+    "LazyLink",
+    "Link",
+    "LinkBase",
+    "ParentSpan",
+    "Span",
+    "SpanContext",
+    "SpanKind",
+    "TraceFlags",
+    "TraceState",
+    "TracerProvider",
+    "Tracer",
+    "format_span_id",
+    "format_trace_id",
+    "get_current_span",
+    "get_tracer",
+    "get_tracer_provider",
+    "set_tracer_provider",
+    "set_span_in_context",
+    "Status",
+]


Test patch:
diff --git a/opentelemetry-api/tests/metrics/test_globals.py b/opentelemetry-api/tests/metrics/test_globals.py
--- a/opentelemetry-api/tests/metrics/test_globals.py
+++ b/opentelemetry-api/tests/metrics/test_globals.py
@@ -16,8 +16,8 @@ def test_meter_provider_override_warning(self):
                 test.output,
                 [
                     (
-                        "WARNING:opentelemetry.metrics:Overriding current "
-                        "MeterProvider"
+                        "WARNING:opentelemetry.metrics:Overriding of current "
+                        "MeterProvider is not allowed"
                     )
                 ],
             )
diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py
--- a/opentelemetry-api/tests/trace/test_globals.py
+++ b/opentelemetry-api/tests/trace/test_globals.py
@@ -25,17 +25,19 @@ def test_get_tracer(self):
     def test_tracer_provider_override_warning(self):
         """trace.set_tracer_provider should throw a warning when overridden"""
         trace.set_tracer_provider(TracerProvider())
+        tracer_provider = trace.get_tracer_provider()
         with self.assertLogs(level=WARNING) as test:
             trace.set_tracer_provider(TracerProvider())
             self.assertEqual(
                 test.output,
                 [
                     (
-                        "WARNING:opentelemetry.trace:Overriding current "
-                        "TracerProvider"
+                        "WARNING:opentelemetry.trace:Overriding of current "
+                        "TracerProvider is not allowed"
                     )
                 ],
             )
+        self.assertIs(tracer_provider, trace.get_tracer_provider())
 
 
 class TestTracer(unittest.TestCase):
diff --git a/tests/util/src/opentelemetry/test/test_base.py b/tests/util/src/opentelemetry/test/test_base.py
--- a/tests/util/src/opentelemetry/test/test_base.py
+++ b/tests/util/src/opentelemetry/test/test_base.py
@@ -34,15 +34,27 @@ def setUpClass(cls):
         cls.original_tracer_provider = trace_api.get_tracer_provider()
         result = cls.create_tracer_provider()
         cls.tracer_provider, cls.memory_exporter = result
+        # This is done because set_tracer_provider cannot override the
+        # current tracer provider.
+        trace_api._TRACER_PROVIDER = None  # pylint: disable=protected-access
         trace_api.set_tracer_provider(cls.tracer_provider)
         cls.original_meter_provider = metrics_api.get_meter_provider()
         result = cls.create_meter_provider()
         cls.meter_provider, cls.memory_metrics_exporter = result
+        # This is done because set_meter_provider cannot override the
+        # current meter provider.
+        metrics_api._METER_PROVIDER = None  # pylint: disable=protected-access
         metrics_api.set_meter_provider(cls.meter_provider)
 
     @classmethod
     def tearDownClass(cls):
+        # This is done because set_tracer_provider cannot override the
+        # current tracer provider.
+        trace_api._TRACER_PROVIDER = None  # pylint: disable=protected-access
         trace_api.set_tracer_provider(cls.original_tracer_provider)
+        # This is done because set_meter_provider cannot override the
+        # current meter provider.
+        metrics_api._METER_PROVIDER = None  # pylint: disable=protected-access
         metrics_api.set_meter_provider(cls.original_meter_provider)
 
     def setUp(self):