diff --git a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md index 9e1cec31c322..0d375d70f845 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md @@ -2,20 +2,18 @@ ## 1.6.8 (Unreleased) -### Features Added - -### Breaking Changes - ### Bugs Fixed +- Pin OTel before breaking change. + ([#40529](https://github.com/Azure/azure-sdk-for-python/pull/40529)) + ### Other Changes ## 1.6.7 (2025-04-10) ### Bugs Fixed -- Adapt to OpenTelemetry dependency resolver change. - ([#40463](https://github.com/Azure/azure-sdk-for-python/pull/40463)) +### Other Changes ## 1.6.6 (2025-04-07) diff --git a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index f1a48d0a63b5..ae14fdda2788 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -3,13 +3,14 @@ # Licensed under the MIT License. See License in the project root for # license information. # -------------------------------------------------------------------------- +from functools import cached_property from logging import getLogger, Formatter from typing import Dict, List, cast from opentelemetry._events import _set_event_logger_provider from opentelemetry._logs import set_logger_provider from opentelemetry.instrumentation.dependencies import ( - DependencyConflictError, + get_dist_dependency_conflicts, ) from opentelemetry.instrumentation.instrumentor import ( # type: ignore BaseInstrumentor, @@ -25,8 +26,11 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.trace import set_tracer_provider -from opentelemetry.util._importlib_metadata import entry_points - +from opentelemetry.util._importlib_metadata import ( + EntryPoint, + distributions, + entry_points, +) from azure.core.settings import settings from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan @@ -207,8 +211,25 @@ def _setup_live_metrics(configurations): enable_live_metrics(**configurations) +class _EntryPointDistFinder: + @cached_property + def _mapping(self): + return {self._key_for(ep): dist for dist in distributions() for ep in dist.entry_points} + + def dist_for(self, entry_point: EntryPoint): + dist = getattr(entry_point, "dist", None) + if dist: + return dist + + return self._mapping.get(self._key_for(entry_point)) + + @staticmethod + def _key_for(entry_point: EntryPoint): + return f"{entry_point.group}:{entry_point.name}:{entry_point.value}" + + def _setup_instrumentations(configurations: Dict[str, ConfigurationValue]): - # entry_point_finder = _EntryPointDistFinder() + entry_point_finder = _EntryPointDistFinder() # use pkg_resources for now until https://github.com/open-telemetry/opentelemetry-python/pull/3168 is merged for entry_point in entry_points(group="opentelemetry_instrumentor"): lib_name = entry_point.name @@ -218,16 +239,20 @@ def _setup_instrumentations(configurations: Dict[str, ConfigurationValue]): _logger.debug("Instrumentation skipped for library %s", entry_point.name) continue try: + # Check if dependent libraries/version are installed + entry_point_dist = entry_point_finder.dist_for(entry_point) # type: ignore + conflict = get_dist_dependency_conflicts(entry_point_dist) # type: ignore + if conflict: + _logger.debug( + "Skipping instrumentation %s: %s", + entry_point.name, + conflict, + ) + continue # Load the instrumentor via entrypoint instrumentor: BaseInstrumentor = entry_point.load() - instrumentor().instrument(raise_exception_on_conflict=True) - except DependencyConflictError as exc: - _logger.debug( - "Skipping instrumentation %s: %s", - entry_point.name, - exc.conflict, - ) - continue + # tell instrumentation to not run dep checks again as we already did it above + instrumentor().instrument(skip_dep_check=True) except Exception as ex: # pylint: disable=broad-except _logger.warning( "Exception occurred when instrumenting: %s.", diff --git a/sdk/monitor/azure-monitor-opentelemetry/setup.py b/sdk/monitor/azure-monitor-opentelemetry/setup.py index 9e519edaf9cc..c377627e65c8 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/setup.py +++ b/sdk/monitor/azure-monitor-opentelemetry/setup.py @@ -84,15 +84,16 @@ "azure-core<2.0.0,>=1.28.0", "azure-core-tracing-opentelemetry~=1.0.0b11", "azure-monitor-opentelemetry-exporter~=1.0.0b31", - "opentelemetry-instrumentation-django~=0.53b0", - "opentelemetry-instrumentation-fastapi~=0.53b0", - "opentelemetry-instrumentation-flask~=0.53b0", - "opentelemetry-instrumentation-psycopg2~=0.53b0", - "opentelemetry-instrumentation-requests~=0.53b0", - "opentelemetry-instrumentation-urllib~=0.53b0", - "opentelemetry-instrumentation-urllib3~=0.53b0", + # TODO: Unpin once breaking change in 1.32.0/0.53b0 is resolved. + "opentelemetry-instrumentation-django<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-fastapi<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-flask<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-psycopg2<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-requests<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-urllib<0.53b0,>=0.49b0", + "opentelemetry-instrumentation-urllib3<0.53b0,>=0.49b0", "opentelemetry-resource-detector-azure~=0.1.4", - "opentelemetry-sdk~=1.32", + "opentelemetry-sdk<1.32,>=1.28.0", ], entry_points={ "opentelemetry_distro": [ diff --git a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py index 7db014acd841..a6cb1dae37ea 100644 --- a/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py +++ b/sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py @@ -14,10 +14,6 @@ import unittest from unittest.mock import Mock, call, patch -from opentelemetry.instrumentation.dependencies import ( - DependencyConflict, - DependencyConflictError, -) from opentelemetry.sdk._logs import LoggingHandler from opentelemetry.sdk.resources import Resource @@ -537,10 +533,12 @@ def test_setup_live_metrics( @patch("azure.monitor.opentelemetry._configure._ALL_SUPPORTED_INSTRUMENTED_LIBRARIES", ("test_instr2")) @patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") + @patch("azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.entry_points") def test_setup_instrumentations_lib_not_supported( self, iter_mock, + dep_mock, enabled_mock, ): ep_mock = Mock() @@ -552,8 +550,10 @@ def test_setup_instrumentations_lib_not_supported( ep_mock.name = "test_instr1" ep2_mock.name = "test_instr2" ep2_mock.load.return_value = instr_class_mock + dep_mock.return_value = None enabled_mock.return_value = True _setup_instrumentations({}) + dep_mock.assert_called_with(ep2_mock.dist) ep_mock.load.assert_not_called() ep2_mock.load.assert_called_once() instrumentor_mock.instrument.assert_called_once() @@ -579,10 +579,12 @@ def test_setup_instrumentations_additional_azure( @patch("azure.monitor.opentelemetry._configure._ALL_SUPPORTED_INSTRUMENTED_LIBRARIES", ("test_instr")) @patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") @patch("azure.monitor.opentelemetry._configure._logger") + @patch("azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.entry_points") def test_setup_instrumentations_conflict( self, iter_mock, + dep_mock, logger_mock, enabled_mock, ): @@ -593,24 +595,23 @@ def test_setup_instrumentations_conflict( instr_class_mock.return_value = instrumentor_mock ep_mock.name = "test_instr" ep_mock.load.return_value = instr_class_mock - instrumentor_mock.instrument.side_effect = DependencyConflictError( - DependencyConflict( - required="test_instr" - ) - ) + dep_mock.return_value = True enabled_mock.return_value = True _setup_instrumentations({}) - ep_mock.load.assert_called_once() - instrumentor_mock.instrument.assert_called_once() + dep_mock.assert_called_with(ep_mock.dist) + ep_mock.load.assert_not_called() + instrumentor_mock.instrument.assert_not_called() logger_mock.debug.assert_called_once() @patch("azure.monitor.opentelemetry._configure._ALL_SUPPORTED_INSTRUMENTED_LIBRARIES", ("test_instr")) @patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") @patch("azure.monitor.opentelemetry._configure._logger") + @patch("azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.entry_points") def test_setup_instrumentations_exception( self, iter_mock, + dep_mock, logger_mock, enabled_mock, ): @@ -621,8 +622,10 @@ def test_setup_instrumentations_exception( instr_class_mock.return_value = instrumentor_mock ep_mock.name = "test_instr" ep_mock.load.side_effect = Exception() + dep_mock.return_value = None enabled_mock.return_value = True _setup_instrumentations({}) + dep_mock.assert_called_with(ep_mock.dist) ep_mock.load.assert_called_once() instrumentor_mock.instrument.assert_not_called() logger_mock.warning.assert_called_once() @@ -632,10 +635,12 @@ def test_setup_instrumentations_exception( ) @patch("azure.monitor.opentelemetry._configure._is_instrumentation_enabled") @patch("azure.monitor.opentelemetry._configure._logger") + @patch("azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts") @patch("azure.monitor.opentelemetry._configure.entry_points") def test_setup_instrumentations_disabled( self, iter_mock, + dep_mock, logger_mock, enabled_mock, ): @@ -648,8 +653,10 @@ def test_setup_instrumentations_disabled( ep_mock.name = "test_instr1" ep2_mock.name = "test_instr2" ep2_mock.load.return_value = instr_class_mock + dep_mock.return_value = None enabled_mock.side_effect = [False, True] _setup_instrumentations({}) + dep_mock.assert_called_with(ep2_mock.dist) ep_mock.load.assert_not_called() ep2_mock.load.assert_called_once() instrumentor_mock.instrument.assert_called_once()