Add disable_instrumentation() with sticky user-intent semantics

Add a public disable_instrumentation() entry point so users can explicitly opt
out of Agent Framework telemetry, with a sticky-disable flag that makes the
user's intent "leading" — no framework code path (foundry's
configure_azure_monitor, configure_otel_providers, enable_instrumentation,
enable_sensitive_telemetry, or direct OBSERVABILITY_SETTINGS.enable_*
writes) can re-enable instrumentation until the user explicitly clears the
disable with enable_instrumentation(force=True) /
enable_sensitive_telemetry(force=True).

Also addresses the two remaining unresolved review threads on the PR:
1. test_observability_settings_defaults_instrumentation_true pins the new
   "ENABLE_INSTRUMENTATION defaults to True when env unset" behavior.
2. test_enable_instrumentation_reads_env_sensitive_data restores coverage
   for the post-import load_dotenv() fallback path.

Implementation:
- ObservabilitySettings.enable_instrumentation / enable_sensitive_data become
  properties backed by _enable_*. While _user_disabled is True, the getters
  return False and the setters drop True writes (defense in depth so third-
  party writes can't subvert the disable).
- Public is_user_disabled read-only property lets integrations (e.g. foundry's
  configure_azure_monitor) cheaply check the disable state without poking at
  privates.
- enable_instrumentation() and enable_sensitive_telemetry() short-circuit with
  an info log when disabled; gain a force=True kwarg that clears the disable.
- configure_otel_providers() still creates providers / exporters / views so a
  later force-enable can use them, but logs an info message when called while
  disabled.
- Foundry's FoundryChatClient.configure_azure_monitor and
  FoundryAgent.configure_azure_monitor early-return when the user has
  disabled, so Azure Monitor's global providers aren't installed unnecessarily.

Tests: 11 new tests covering default-on, env re-read at call time, sticky
behavior against each re-enable surface (enable_instrumentation,
enable_sensitive_telemetry, configure_otel_providers, direct attribute
writes), force=True override, re-arming the disable, and the __all__ export.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-05-19 09:30:41 +02:00
Unverified
parent 1d1e58e12a
commit 6a23dcd555
5 changed files with 338 additions and 9 deletions
@@ -793,8 +793,22 @@ class RawFoundryAgent( # type: ignore[misc]
Raises:
ImportError: If azure-monitor-opentelemetry-exporter is not installed.
"""
from agent_framework.observability import (
OBSERVABILITY_SETTINGS,
create_metric_views,
create_resource,
enable_instrumentation,
)
from azure.core.exceptions import ResourceNotFoundError
if OBSERVABILITY_SETTINGS.is_user_disabled:
logger.info(
"FoundryAgent.configure_azure_monitor(): Skipping setup because instrumentation was "
"explicitly disabled via disable_instrumentation(). Call enable_instrumentation(force=True) "
"to re-enable, then re-invoke configure_azure_monitor()."
)
return
client = self.client
if not isinstance(client, RawFoundryAgentChatClient):
raise TypeError("configure_azure_monitor requires a RawFoundryAgentChatClient-based client.")
@@ -817,8 +831,6 @@ class RawFoundryAgent( # type: ignore[misc]
"Install it with: pip install azure-monitor-opentelemetry"
) from exc
from agent_framework.observability import create_metric_views, create_resource, enable_instrumentation
if "resource" not in kwargs:
kwargs["resource"] = create_resource()
@@ -271,8 +271,22 @@ class RawFoundryChatClient( # type: ignore[misc]
Raises:
ImportError: If azure-monitor-opentelemetry-exporter is not installed.
"""
from agent_framework.observability import (
OBSERVABILITY_SETTINGS,
create_metric_views,
create_resource,
enable_instrumentation,
)
from azure.core.exceptions import ResourceNotFoundError
if OBSERVABILITY_SETTINGS.is_user_disabled:
logger.info(
"FoundryChatClient.configure_azure_monitor(): Skipping setup because instrumentation was "
"explicitly disabled via disable_instrumentation(). Call enable_instrumentation(force=True) "
"to re-enable, then re-invoke configure_azure_monitor()."
)
return
try:
conn_string = await self.project_client.telemetry.get_application_insights_connection_string()
except ResourceNotFoundError:
@@ -291,8 +305,6 @@ class RawFoundryChatClient( # type: ignore[misc]
"Install it with: pip install azure-monitor-opentelemetry"
) from exc
from agent_framework.observability import create_metric_views, create_resource, enable_instrumentation
if "resource" not in kwargs:
kwargs["resource"] = create_resource()