From d676c688f8e3ba51c68e6c84a9e8580e21d097dc Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Thu, 16 Jul 2026 11:32:02 -0700 Subject: [PATCH 1/7] Use system-assigned managed identity for Entra Azure Monitor auth When APPLICATIONINSIGHTS_CONNECTION_STRING is set and APPLICATIONINSIGHTS_AUTH_MODE is Entra, pass a ManagedIdentityCredential (no client id) as the Azure Monitor exporter credential to the microsoft-opentelemetry distro. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure/ai/agentserver/core/_constants.py | 1 + .../azure/ai/agentserver/core/_tracing.py | 31 +++++++++++ .../tests/test_tracing.py | 54 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_constants.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_constants.py index 21f1286390cd..5a766389be95 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_constants.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_constants.py @@ -19,6 +19,7 @@ class Constants: # Tracing APPLICATIONINSIGHTS_CONNECTION_STRING = "APPLICATIONINSIGHTS_CONNECTION_STRING" + APPLICATIONINSIGHTS_AUTH_MODE = "APPLICATIONINSIGHTS_AUTH_MODE" OTEL_EXPORTER_OTLP_ENDPOINT = "OTEL_EXPORTER_OTLP_ENDPOINT" FOUNDRY_AGENT365_TRACING_ENABLED = "FOUNDRY_AGENT365_TRACING_ENABLED" OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index 1266d751e4ec..5236bb18ed8d 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -246,6 +246,15 @@ def _setup_distro_export( kwargs["enable_azure_monitor"] = True kwargs["azure_monitor_connection_string"] = connection_string + # When Entra-based auth is requested, export to Azure Monitor using a + # system-assigned managed identity (no client id) rather than the + # connection string's instrumentation key alone. + auth_mode = os.environ.get("APPLICATIONINSIGHTS_AUTH_MODE", "") + if auth_mode.strip().lower() == "entra": + credential = _create_managed_identity_credential() + if credential is not None: + kwargs["azure_monitor_exporter_credential"] = credential + # A365 tracing export — enabled only in hosted environments. if ( os.environ.get("FOUNDRY_HOSTING_ENVIRONMENT", "") @@ -259,6 +268,28 @@ def _setup_distro_export( use_microsoft_opentelemetry(**kwargs) +def _create_managed_identity_credential() -> Optional[Any]: + """Create a system-assigned :class:`ManagedIdentityCredential`. + + Instantiated without a ``client_id`` so the platform-assigned (system) + managed identity is used for Entra-based Azure Monitor export. Returns + ``None`` when ``azure-identity`` is not installed so tracing setup can + continue without export credentials. + + :return: A managed identity credential, or ``None`` if unavailable. + :rtype: Any or None + """ + try: + from azure.identity import ManagedIdentityCredential + except ImportError: + logger.warning( + "APPLICATIONINSIGHTS_AUTH_MODE=Entra requires azure-identity, " + "which is not installed — Azure Monitor export credential disabled." + ) + return None + return ManagedIdentityCredential() + + # ====================================================================== # Public API: span operations # ====================================================================== diff --git a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py index bacf0f4d6dea..40a3a35258a3 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py @@ -148,6 +148,60 @@ def test_distro_called_without_conn_str(self) -> None: assert kwargs["connection_string"] is None +# ------------------------------------------------------------------ # +# Entra-based Azure Monitor export credential +# ------------------------------------------------------------------ # + + +class TestEntraAuthMode: + """Verify _setup_distro_export wires a managed identity credential for Entra auth.""" + + def _run(self, env: dict) -> dict: + from azure.ai.agentserver.core import _tracing + with mock.patch("microsoft.opentelemetry.use_microsoft_opentelemetry") as mock_use, \ + mock.patch.dict(os.environ, env, clear=False): + _tracing._setup_distro_export( + resource=Resource.create({}), + span_processors=[], + log_record_processors=[], + connection_string="InstrumentationKey=00000000-0000-0000-0000-000000000000", + ) + mock_use.assert_called_once() + return mock_use.call_args[1] + + def test_entra_auth_mode_passes_managed_identity_credential(self) -> None: + sentinel = object() + with mock.patch( + "azure.ai.agentserver.core._tracing._create_managed_identity_credential", + return_value=sentinel, + ): + kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) + assert kwargs["enable_azure_monitor"] is True + assert kwargs["azure_monitor_exporter_credential"] is sentinel + + def test_entra_auth_mode_case_insensitive(self) -> None: + sentinel = object() + with mock.patch( + "azure.ai.agentserver.core._tracing._create_managed_identity_credential", + return_value=sentinel, + ): + kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "entra"}) + assert kwargs["azure_monitor_exporter_credential"] is sentinel + + def test_no_credential_when_auth_mode_not_entra(self) -> None: + env = {"APPLICATIONINSIGHTS_AUTH_MODE": ""} + with mock.patch.dict(os.environ, {}, clear=False): + os.environ.pop("APPLICATIONINSIGHTS_AUTH_MODE", None) + kwargs = self._run(env) + assert "azure_monitor_exporter_credential" not in kwargs + + def test_managed_identity_credential_has_no_client_id(self) -> None: + from azure.ai.agentserver.core import _tracing + with mock.patch("azure.identity.ManagedIdentityCredential") as mock_cred: + _tracing._create_managed_identity_credential() + mock_cred.assert_called_once_with() + + # ------------------------------------------------------------------ # # Constructor passes / skips connection string # ------------------------------------------------------------------ # From bc1f2d6b39b1c9500dd94de9b2458e905826b743 Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Tue, 21 Jul 2026 11:46:26 -0700 Subject: [PATCH 2/7] Disable Azure Monitor export when Entra requested but no credential available Avoids silent fallback to instrumentation-key auth when azure-identity is missing; other exporters (A365, OTLP) are unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure/ai/agentserver/core/_tracing.py | 21 ++++++++++++++----- .../tests/test_tracing.py | 13 ++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index 5236bb18ed8d..ee448d59d002 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -243,15 +243,26 @@ def _setup_distro_export( # Azure Monitor export is off by default in the distro — enable it # when a connection string is available. if connection_string: - kwargs["enable_azure_monitor"] = True - kwargs["azure_monitor_connection_string"] = connection_string - # When Entra-based auth is requested, export to Azure Monitor using a # system-assigned managed identity (no client id) rather than the # connection string's instrumentation key alone. auth_mode = os.environ.get("APPLICATIONINSIGHTS_AUTH_MODE", "") - if auth_mode.strip().lower() == "entra": - credential = _create_managed_identity_credential() + entra_auth = auth_mode.strip().lower() == "entra" + credential = _create_managed_identity_credential() if entra_auth else None + + if entra_auth and credential is None: + # Entra was explicitly requested but no credential is available + # (azure-identity missing). Do NOT enable Azure Monitor, since the + # distro would otherwise silently fall back to instrumentation-key + # auth and bypass the requested Entra-only mode. Other exporters + # (A365, OTLP) below are unaffected. + logger.warning( + "APPLICATIONINSIGHTS_AUTH_MODE=Entra requested but no managed identity " + "credential is available — Azure Monitor export disabled." + ) + else: + kwargs["enable_azure_monitor"] = True + kwargs["azure_monitor_connection_string"] = connection_string if credential is not None: kwargs["azure_monitor_exporter_credential"] = credential diff --git a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py index 40a3a35258a3..7d2f029183c6 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py @@ -195,6 +195,19 @@ def test_no_credential_when_auth_mode_not_entra(self) -> None: kwargs = self._run(env) assert "azure_monitor_exporter_credential" not in kwargs + def test_entra_auth_disables_azure_monitor_when_credential_unavailable(self) -> None: + # Entra explicitly requested but azure-identity is missing: Azure Monitor + # must NOT be enabled (avoid silent instrumentation-key fallback), while + # other exporters remain unaffected. + with mock.patch( + "azure.ai.agentserver.core._tracing._create_managed_identity_credential", + return_value=None, + ): + kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) + assert "enable_azure_monitor" not in kwargs + assert "azure_monitor_connection_string" not in kwargs + assert "azure_monitor_exporter_credential" not in kwargs + def test_managed_identity_credential_has_no_client_id(self) -> None: from azure.ai.agentserver.core import _tracing with mock.patch("azure.identity.ManagedIdentityCredential") as mock_cred: From 4f4487f44b6a2e75c7caa3c79cd1357fa522f5e9 Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Tue, 21 Jul 2026 12:04:47 -0700 Subject: [PATCH 3/7] Address review: single Entra warning + document APPLICATIONINSIGHTS_AUTH_MODE - Make _create_managed_identity_credential return None quietly so a missing azure-identity produces one actionable warning from the caller, not two. - Document APPLICATIONINSIGHTS_AUTH_MODE (Entra), system-assigned identity behavior, and the azure-identity requirement in README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure-ai-agentserver-core/README.md | 20 +++++++++++++++++++ .../azure/ai/agentserver/core/_tracing.py | 12 +++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/README.md b/sdk/agentserver/azure-ai-agentserver-core/README.md index 9dc0a8da465d..db5f0a5f8041 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/README.md +++ b/sdk/agentserver/azure-ai-agentserver-core/README.md @@ -40,6 +40,7 @@ Protocol packages (e.g. `azure-ai-agentserver-invocations`) subclass `AgentServe | `FOUNDRY_PROJECT_ARM_ID` | Foundry project ARM resource ID (used in tracing) | `""` | | `FOUNDRY_AGENT_SESSION_ID` | Default session ID when not provided per-request | `""` | | `APPLICATIONINSIGHTS_CONNECTION_STRING` | Azure Monitor connection string | — | +| `APPLICATIONINSIGHTS_AUTH_MODE` | Azure Monitor authentication mode. Set to `Entra` to authenticate with a system-assigned managed identity instead of the connection string's instrumentation key. Requires `azure-identity`. | — | | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector endpoint | — | ## Examples @@ -176,6 +177,25 @@ export APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=..." python my_agent.py ``` +#### Entra-based authentication + +By default the Azure Monitor exporter authenticates with the instrumentation key +embedded in the connection string. To authenticate with Microsoft Entra ID +instead, set `APPLICATIONINSIGHTS_AUTH_MODE` to `Entra`. In this mode the exporter +uses a **system-assigned managed identity** (no client ID) to obtain tokens: + +```bash +export APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=..." +export APPLICATIONINSIGHTS_AUTH_MODE="Entra" +python my_agent.py +``` + +Entra mode requires the [`azure-identity`](https://pypi.org/project/azure-identity/) +package. It is normally present transitively, but if it is unavailable the Azure +Monitor exporter is disabled (after a warning) rather than silently falling back +to instrumentation-key authentication. Other exporters (OTLP, Agent 365) are +unaffected. + ## Troubleshooting ### Logging diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index ee448d59d002..6e6a84787566 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -258,7 +258,8 @@ def _setup_distro_export( # (A365, OTLP) below are unaffected. logger.warning( "APPLICATIONINSIGHTS_AUTH_MODE=Entra requested but no managed identity " - "credential is available — Azure Monitor export disabled." + "credential is available (ensure azure-identity is installed) — " + "Azure Monitor export disabled." ) else: kwargs["enable_azure_monitor"] = True @@ -284,8 +285,9 @@ def _create_managed_identity_credential() -> Optional[Any]: Instantiated without a ``client_id`` so the platform-assigned (system) managed identity is used for Entra-based Azure Monitor export. Returns - ``None`` when ``azure-identity`` is not installed so tracing setup can - continue without export credentials. + ``None`` quietly when ``azure-identity`` is not installed; the caller is + responsible for emitting a single actionable warning and disabling Azure + Monitor export on that path. :return: A managed identity credential, or ``None`` if unavailable. :rtype: Any or None @@ -293,10 +295,6 @@ def _create_managed_identity_credential() -> Optional[Any]: try: from azure.identity import ManagedIdentityCredential except ImportError: - logger.warning( - "APPLICATIONINSIGHTS_AUTH_MODE=Entra requires azure-identity, " - "which is not installed — Azure Monitor export credential disabled." - ) return None return ManagedIdentityCredential() From 1d521d63f3795e2a9e13482c576368b235b0c3f4 Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Tue, 21 Jul 2026 12:10:25 -0700 Subject: [PATCH 4/7] Revert README documentation changes for APPLICATIONINSIGHTS_AUTH_MODE Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure-ai-agentserver-core/README.md | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/README.md b/sdk/agentserver/azure-ai-agentserver-core/README.md index db5f0a5f8041..9dc0a8da465d 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/README.md +++ b/sdk/agentserver/azure-ai-agentserver-core/README.md @@ -40,7 +40,6 @@ Protocol packages (e.g. `azure-ai-agentserver-invocations`) subclass `AgentServe | `FOUNDRY_PROJECT_ARM_ID` | Foundry project ARM resource ID (used in tracing) | `""` | | `FOUNDRY_AGENT_SESSION_ID` | Default session ID when not provided per-request | `""` | | `APPLICATIONINSIGHTS_CONNECTION_STRING` | Azure Monitor connection string | — | -| `APPLICATIONINSIGHTS_AUTH_MODE` | Azure Monitor authentication mode. Set to `Entra` to authenticate with a system-assigned managed identity instead of the connection string's instrumentation key. Requires `azure-identity`. | — | | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector endpoint | — | ## Examples @@ -177,25 +176,6 @@ export APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=..." python my_agent.py ``` -#### Entra-based authentication - -By default the Azure Monitor exporter authenticates with the instrumentation key -embedded in the connection string. To authenticate with Microsoft Entra ID -instead, set `APPLICATIONINSIGHTS_AUTH_MODE` to `Entra`. In this mode the exporter -uses a **system-assigned managed identity** (no client ID) to obtain tokens: - -```bash -export APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=..." -export APPLICATIONINSIGHTS_AUTH_MODE="Entra" -python my_agent.py -``` - -Entra mode requires the [`azure-identity`](https://pypi.org/project/azure-identity/) -package. It is normally present transitively, but if it is unavailable the Azure -Monitor exporter is disabled (after a warning) rather than silently falling back -to instrumentation-key authentication. Other exporters (OTLP, Agent 365) are -unaffected. - ## Troubleshooting ### Logging From 99d4c0ea831b6c56c91a0de2a5351023e094593e Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Tue, 21 Jul 2026 12:17:30 -0700 Subject: [PATCH 5/7] Simplify Entra auth: azure-identity is always available via distro Remove the defensive missing-azure-identity handling and the helper wrapper; azure-identity is pulled in transitively by microsoft-opentelemetry, so create the ManagedIdentityCredential directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure/ai/agentserver/core/_tracing.py | 48 ++++--------------- .../tests/test_tracing.py | 21 ++------ 2 files changed, 13 insertions(+), 56 deletions(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index 6e6a84787566..3087e9d6bc4f 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -243,29 +243,18 @@ def _setup_distro_export( # Azure Monitor export is off by default in the distro — enable it # when a connection string is available. if connection_string: + kwargs["enable_azure_monitor"] = True + kwargs["azure_monitor_connection_string"] = connection_string + # When Entra-based auth is requested, export to Azure Monitor using a # system-assigned managed identity (no client id) rather than the - # connection string's instrumentation key alone. + # connection string's instrumentation key alone. azure-identity is + # always available transitively via microsoft-opentelemetry. auth_mode = os.environ.get("APPLICATIONINSIGHTS_AUTH_MODE", "") - entra_auth = auth_mode.strip().lower() == "entra" - credential = _create_managed_identity_credential() if entra_auth else None - - if entra_auth and credential is None: - # Entra was explicitly requested but no credential is available - # (azure-identity missing). Do NOT enable Azure Monitor, since the - # distro would otherwise silently fall back to instrumentation-key - # auth and bypass the requested Entra-only mode. Other exporters - # (A365, OTLP) below are unaffected. - logger.warning( - "APPLICATIONINSIGHTS_AUTH_MODE=Entra requested but no managed identity " - "credential is available (ensure azure-identity is installed) — " - "Azure Monitor export disabled." - ) - else: - kwargs["enable_azure_monitor"] = True - kwargs["azure_monitor_connection_string"] = connection_string - if credential is not None: - kwargs["azure_monitor_exporter_credential"] = credential + if auth_mode.strip().lower() == "entra": + from azure.identity import ManagedIdentityCredential + + kwargs["azure_monitor_exporter_credential"] = ManagedIdentityCredential() # A365 tracing export — enabled only in hosted environments. if ( @@ -280,25 +269,6 @@ def _setup_distro_export( use_microsoft_opentelemetry(**kwargs) -def _create_managed_identity_credential() -> Optional[Any]: - """Create a system-assigned :class:`ManagedIdentityCredential`. - - Instantiated without a ``client_id`` so the platform-assigned (system) - managed identity is used for Entra-based Azure Monitor export. Returns - ``None`` quietly when ``azure-identity`` is not installed; the caller is - responsible for emitting a single actionable warning and disabling Azure - Monitor export on that path. - - :return: A managed identity credential, or ``None`` if unavailable. - :rtype: Any or None - """ - try: - from azure.identity import ManagedIdentityCredential - except ImportError: - return None - return ManagedIdentityCredential() - - # ====================================================================== # Public API: span operations # ====================================================================== diff --git a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py index 7d2f029183c6..51a6125db339 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py @@ -172,7 +172,7 @@ def _run(self, env: dict) -> dict: def test_entra_auth_mode_passes_managed_identity_credential(self) -> None: sentinel = object() with mock.patch( - "azure.ai.agentserver.core._tracing._create_managed_identity_credential", + "azure.identity.ManagedIdentityCredential", return_value=sentinel, ): kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) @@ -182,7 +182,7 @@ def test_entra_auth_mode_passes_managed_identity_credential(self) -> None: def test_entra_auth_mode_case_insensitive(self) -> None: sentinel = object() with mock.patch( - "azure.ai.agentserver.core._tracing._create_managed_identity_credential", + "azure.identity.ManagedIdentityCredential", return_value=sentinel, ): kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "entra"}) @@ -193,25 +193,12 @@ def test_no_credential_when_auth_mode_not_entra(self) -> None: with mock.patch.dict(os.environ, {}, clear=False): os.environ.pop("APPLICATIONINSIGHTS_AUTH_MODE", None) kwargs = self._run(env) - assert "azure_monitor_exporter_credential" not in kwargs - - def test_entra_auth_disables_azure_monitor_when_credential_unavailable(self) -> None: - # Entra explicitly requested but azure-identity is missing: Azure Monitor - # must NOT be enabled (avoid silent instrumentation-key fallback), while - # other exporters remain unaffected. - with mock.patch( - "azure.ai.agentserver.core._tracing._create_managed_identity_credential", - return_value=None, - ): - kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) - assert "enable_azure_monitor" not in kwargs - assert "azure_monitor_connection_string" not in kwargs + assert kwargs["enable_azure_monitor"] is True assert "azure_monitor_exporter_credential" not in kwargs def test_managed_identity_credential_has_no_client_id(self) -> None: - from azure.ai.agentserver.core import _tracing with mock.patch("azure.identity.ManagedIdentityCredential") as mock_cred: - _tracing._create_managed_identity_credential() + self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) mock_cred.assert_called_once_with() From 2a9040dcc96d663bdcdcfc83e86b5d8ed36b1063 Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Tue, 21 Jul 2026 12:23:14 -0700 Subject: [PATCH 6/7] Never fail tracing setup when azure-identity is unavailable If azure-identity cannot be imported for Entra auth, log a warning and continue with connection-string authentication instead of raising, so tracing setup never breaks a hosted agent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure/ai/agentserver/core/_tracing.py | 15 +++++++++++---- .../tests/test_tracing.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index 3087e9d6bc4f..bd0c097551af 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -249,12 +249,19 @@ def _setup_distro_export( # When Entra-based auth is requested, export to Azure Monitor using a # system-assigned managed identity (no client id) rather than the # connection string's instrumentation key alone. azure-identity is - # always available transitively via microsoft-opentelemetry. + # normally available transitively via microsoft-opentelemetry; if it + # is missing we log and continue rather than failing tracing setup. auth_mode = os.environ.get("APPLICATIONINSIGHTS_AUTH_MODE", "") if auth_mode.strip().lower() == "entra": - from azure.identity import ManagedIdentityCredential - - kwargs["azure_monitor_exporter_credential"] = ManagedIdentityCredential() + try: + from azure.identity import ManagedIdentityCredential + + kwargs["azure_monitor_exporter_credential"] = ManagedIdentityCredential() + except ImportError: + logger.warning( + "APPLICATIONINSIGHTS_AUTH_MODE=Entra requested but azure-identity is " + "not installed — continuing with connection-string authentication." + ) # A365 tracing export — enabled only in hosted environments. if ( diff --git a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py index 51a6125db339..cc52c6006151 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py @@ -196,6 +196,24 @@ def test_no_credential_when_auth_mode_not_entra(self) -> None: assert kwargs["enable_azure_monitor"] is True assert "azure_monitor_exporter_credential" not in kwargs + def test_entra_auth_continues_when_azure_identity_missing(self) -> None: + # If azure-identity cannot be imported, tracing setup must not fail: + # Azure Monitor stays enabled (connection-string auth) and no credential + # is passed. + import builtins + + real_import = builtins.__import__ + + def _fake_import(name, *args, **kwargs): + if name == "azure.identity": + raise ImportError("No module named 'azure.identity'") + return real_import(name, *args, **kwargs) + + with mock.patch("builtins.__import__", side_effect=_fake_import): + kwargs = self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) + assert kwargs["enable_azure_monitor"] is True + assert "azure_monitor_exporter_credential" not in kwargs + def test_managed_identity_credential_has_no_client_id(self) -> None: with mock.patch("azure.identity.ManagedIdentityCredential") as mock_cred: self._run({"APPLICATIONINSIGHTS_AUTH_MODE": "Entra"}) From f5d91232306619ab33f0b4755f41c52903812b78 Mon Sep 17 00:00:00 2001 From: Ankit Singhal Date: Wed, 22 Jul 2026 10:58:48 -0700 Subject: [PATCH 7/7] Use Constants.APPLICATIONINSIGHTS_AUTH_MODE instead of string literal Consume the constant from _constants.py in _tracing.py so there is a single source of truth for the env var name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3c15f03b-e6c3-49d7-ae4b-c0bef594022e --- .../azure/ai/agentserver/core/_tracing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py index bd0c097551af..9ef0bcb6eea1 100644 --- a/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py +++ b/sdk/agentserver/azure-ai-agentserver-core/azure/ai/agentserver/core/_tracing.py @@ -41,6 +41,7 @@ from opentelemetry import baggage as _otel_baggage, context as _otel_context, trace from . import _config +from ._constants import Constants _Content = Union[str, bytes, memoryview] @@ -251,7 +252,7 @@ def _setup_distro_export( # connection string's instrumentation key alone. azure-identity is # normally available transitively via microsoft-opentelemetry; if it # is missing we log and continue rather than failing tracing setup. - auth_mode = os.environ.get("APPLICATIONINSIGHTS_AUTH_MODE", "") + auth_mode = os.environ.get(Constants.APPLICATIONINSIGHTS_AUTH_MODE, "") if auth_mode.strip().lower() == "entra": try: from azure.identity import ManagedIdentityCredential