diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index 9cd28551f352..0d41becc71a7 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -3,8 +3,12 @@ ## 1.0.0b53 (Unreleased) ### Features Added + - Read for global feature sdkstats bitmap and add a 15 second delay timer ([#47031](https://github.com/Azure/azure-sdk-for-python/pull/47031)) +- Use `APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE` environment variable in `_is_attach_enabled` to + reliably detect successful auto-instrumentation attach, with fallback to legacy path-based detection + ([#46955](https://github.com/Azure/azure-sdk-for-python/pull/46955)) ### Breaking Changes diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py index 218340e3a13d..074dae72b141 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py @@ -38,6 +38,7 @@ _PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY = "PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY" _AKS_ARM_NAMESPACE_ID = "AKS_ARM_NAMESPACE_ID" _KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST" +_APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE = "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE" # Network diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py index db99edde2c7c..87e07705be8f 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py @@ -23,6 +23,7 @@ from azure.monitor.opentelemetry.exporter._connection_string_parser import ConnectionStringParser from azure.monitor.opentelemetry.exporter._constants import ( _AKS_ARM_NAMESPACE_ID, + _APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE, _AZURE_MONITOR_DISTRO_VERSION, _DEFAULT_AAD_SCOPE, _FUNCTIONS_WORKER_RUNTIME, @@ -68,6 +69,14 @@ def _is_on_aks(): def _is_attach_enabled(): + attach_type = environ.get(_APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE) + if attach_type is not None: + # If the env var is set, attach is only enabled if the value is + # "IntegratedAuto" AND the existing per-RP logic is satisfied. + if attach_type.lower() == "integratedauto": + return True + return False + # Fallback to legacy logic when the env var is not set if _is_on_functions(): return environ.get(_PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY) == "true" if _is_on_app_service(): diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/test_utils.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/test_utils.py index d471e8bca8b5..93ee5b91c273 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/test_utils.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/test_utils.py @@ -731,6 +731,7 @@ def test_get_sdk_version_prefix_aks_windows_attach(self, mock_system): @patch.dict( "azure.monitor.opentelemetry.exporter._utils.environ", {"WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME}, + clear=True, ) def test_attach_enabled(self, mock_isdir): self.assertEqual(_utils._is_attach_enabled(), True) @@ -742,6 +743,7 @@ def test_attach_enabled(self, mock_isdir): @patch.dict( "azure.monitor.opentelemetry.exporter._utils.environ", {"WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME}, + clear=True, ) def test_attach_app_service_disabled(self, mock_isdir): self.assertEqual(_utils._is_attach_enabled(), False) @@ -778,6 +780,198 @@ def test_aks_no_attach(self): # This is not an expected scenario and just tests the default self.assertFalse(_utils._is_attach_enabled()) + # Attach with APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE env var + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_app_service(self): + self.assertTrue(_utils._is_attach_enabled()) + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=True, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_app_service(self, mock_isdir): + self.assertTrue(_utils._is_attach_enabled()) + mock_isdir.assert_not_called() + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=True, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "integratedauto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_app_service_lower(self, mock_isdir): + self.assertTrue(_utils._is_attach_enabled()) + mock_isdir.assert_not_called() + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=True, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "INTEGRATEDAUTO", + }, + clear=True, + ) + def test_attach_type_integrated_auto_app_service_upper(self, mock_isdir): + self.assertTrue(_utils._is_attach_enabled()) + mock_isdir.assert_not_called() + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=True, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "Manual", + }, + clear=True, + ) + def test_attach_type_manual_app_service(self, mock_isdir): + self.assertFalse(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "FUNCTIONS_WORKER_RUNTIME": "python", + "PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY": "true", + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_functions(self): + self.assertTrue(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "FUNCTIONS_WORKER_RUNTIME": "python", + "PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY": "true", + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "Manual", + }, + clear=True, + ) + def test_attach_type_manual_functions(self): + self.assertFalse(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "KUBERNETES_SERVICE_HOST": TEST_KUBERNETES_SERVICE_HOST, + "AKS_ARM_NAMESPACE_ID": TEST_AKS_ARM_NAMESPACE_ID, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_aks(self): + self.assertTrue(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "KUBERNETES_SERVICE_HOST": TEST_KUBERNETES_SERVICE_HOST, + "AKS_ARM_NAMESPACE_ID": TEST_AKS_ARM_NAMESPACE_ID, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "Manual", + }, + clear=True, + ) + def test_attach_type_manual_aks(self): + self.assertFalse(_utils._is_attach_enabled()) + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=False, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + ) + def test_attach_type_integrated_auto_app_service_no_isdir_check(self, mock_isdir): + # APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE overrides the legacy isdir gate. + self.assertTrue(_utils._is_attach_enabled()) + mock_isdir.assert_not_called() + + @patch( + "azure.monitor.opentelemetry.exporter._utils.isdir", + return_value=False, + ) + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "WEBSITE_SITE_NAME": TEST_WEBSITE_SITE_NAME, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "manual", + }, + ) + def test_attach_type_manual_app_service_no_isdir_check(self, mock_isdir): + # APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE overrides the legacy isdir gate. + self.assertFalse(_utils._is_attach_enabled()) + mock_isdir.assert_not_called() + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "FUNCTIONS_WORKER_RUNTIME": "python", + "PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY": "false", + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_functions_enable_telemetry_false(self): + # APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE overrides the legacy PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY check. + self.assertTrue(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "FUNCTIONS_WORKER_RUNTIME": "python", + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_functions_no_enable_telemetry(self): + # IntegratedAuto but ENABLE_TELEMETRY not set. Should be true. Env var overwrites other factors. + self.assertTrue(_utils._is_attach_enabled()) + + @patch.dict( + "azure.monitor.opentelemetry.exporter._utils.environ", + { + "KUBERNETES_SERVICE_HOST": TEST_KUBERNETES_SERVICE_HOST, + "APPLICATIONINSIGHTS_PYTHON_ATTACHTYPE": "IntegratedAuto", + }, + clear=True, + ) + def test_attach_type_integrated_auto_aks_no_arm_namespace(self): + # IntegratedAuto but AKS_ARM_NAMESPACE_ID not set. Should be true. Env var overwrites other factors. + self.assertTrue(_utils._is_attach_enabled()) + # Synthetic def test_is_synthetic_source_bot(self):