Enable instrumentation by default

This commit is contained in:
Tao Chen
2026-05-14 13:42:00 -07:00
Unverified
parent 7432105ebe
commit 0219e17be2
23 changed files with 104 additions and 205 deletions
@@ -3,7 +3,7 @@
"""Observability and OpenTelemetry helpers for Agent Framework.
Commonly used exports:
- enable_instrumentation
- enable_sensitive_telemetry
- configure_otel_providers
- AgentTelemetryLayer
- ChatTelemetryLayer
@@ -80,7 +80,7 @@ __all__ = [
"configure_otel_providers",
"create_metric_views",
"create_resource",
"enable_instrumentation",
"enable_sensitive_telemetry",
"get_meter",
"get_tracer",
]
@@ -643,8 +643,8 @@ class ObservabilitySettings:
Sensitive events should only be enabled on test and development environments.
Keyword Args:
enable_instrumentation: Enable OpenTelemetry diagnostics. Default is False.
Can be set via environment variable ENABLE_INSTRUMENTATION.
enable_instrumentation: Enable OpenTelemetry diagnostics. Default is True.
Can be disabled by setting environment variable ENABLE_INSTRUMENTATION=false.
enable_sensitive_data: Enable OpenTelemetry sensitive events. Default is False.
Can be set via environment variable ENABLE_SENSITIVE_DATA.
enable_console_exporters: Enable console exporters for traces, logs, and metrics.
@@ -659,12 +659,12 @@ class ObservabilitySettings:
from agent_framework import ObservabilitySettings
# Using environment variables
# Set ENABLE_INSTRUMENTATION=true
# Instrumentation is enabled by default; set ENABLE_INSTRUMENTATION=false to disable.
# Set ENABLE_CONSOLE_EXPORTERS=true
settings = ObservabilitySettings()
# Or passing parameters directly
settings = ObservabilitySettings(enable_instrumentation=True, enable_console_exporters=True)
settings = ObservabilitySettings(enable_console_exporters=True)
"""
def __init__(self, **kwargs: Any) -> None:
@@ -677,7 +677,10 @@ class ObservabilitySettings:
env_file_encoding=env_file_encoding,
**kwargs,
)
self.enable_instrumentation: bool = data.get("enable_instrumentation") or False
# `enable_instrumentation` is defaulted to True if not set
instrumentation_value = data.get("enable_instrumentation")
self.enable_instrumentation: bool = True if instrumentation_value is None else instrumentation_value
self.enable_sensitive_data: bool = data.get("enable_sensitive_data") or False
self.enable_console_exporters: bool = data.get("enable_console_exporters") or False
self.vs_code_extension_port: int | None = data.get("vs_code_extension_port")
@@ -951,30 +954,22 @@ def _read_int_env(name: str, *, default: int | None = None) -> int | None:
return default
def enable_instrumentation(
*,
enable_sensitive_data: bool | None = None,
) -> None:
"""Enable instrumentation for your application.
def enable_sensitive_telemetry() -> None:
"""Enable capture of sensitive data in telemetry for your application.
Calling this method implies you want to enable observability in your application.
Instrumentation is enabled by default; this method exists to opt-in to capturing
sensitive event payloads (e.g., chat messages, tool arguments).
This method does not configure exporters or providers.
It only updates the global variables that trigger the instrumentation code.
If you have already set the environment variable ENABLE_INSTRUMENTATION=true,
calling this method has no effect, unless you want to enable or disable sensitive data events.
This method does not configure exporters or providers. It also ensures that
instrumentation is enabled (in case it was explicitly disabled via the
ENABLE_INSTRUMENTATION environment variable).
Keyword Args:
enable_sensitive_data: Enable OpenTelemetry sensitive events. Overrides
the environment variable ENABLE_SENSITIVE_DATA if set. Default is None.
Warning:
Sensitive events should only be enabled on test and development environments.
"""
global OBSERVABILITY_SETTINGS
OBSERVABILITY_SETTINGS.enable_instrumentation = True
if enable_sensitive_data is not None:
OBSERVABILITY_SETTINGS.enable_sensitive_data = enable_sensitive_data
else:
# Re-read from current environment in case env vars were set after import (e.g. load_dotenv())
OBSERVABILITY_SETTINGS.enable_sensitive_data = _read_bool_env("ENABLE_SENSITIVE_DATA")
OBSERVABILITY_SETTINGS.enable_sensitive_data = True
def configure_otel_providers(
@@ -1008,7 +1003,7 @@ def configure_otel_providers(
Since you can only setup one provider per signal type (logs, traces, metrics),
you can choose to use this method and take the exporter and provider that we created.
Alternatively, you can setup the providers yourself, or through another library
(e.g., Azure Monitor) and just call `enable_instrumentation()` to enable instrumentation.
(e.g., Azure Monitor) and just call `enable_sensitive_telemetry()` to opt-in to sensitive data capture.
Note:
By default, the Agent Framework emits metrics with the prefixes `agent_framework`
@@ -1042,7 +1037,6 @@ def configure_otel_providers(
from agent_framework.observability import configure_otel_providers
# Using environment variables (recommended)
# Set ENABLE_INSTRUMENTATION=true
# Set OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
configure_otel_providers()
@@ -1087,12 +1081,13 @@ def configure_otel_providers(
.. code-block:: python
# when azure monitor is installed
from agent_framework.observability import enable_instrumentation
from agent_framework.observability import enable_sensitive_telemetry
from azure.monitor.opentelemetry import configure_azure_monitor
connection_string = "InstrumentationKey=your_instrumentation_key_here;..."
configure_azure_monitor(connection_string=connection_string)
enable_instrumentation()
# Optional: opt into capturing sensitive data (dev/test only)
enable_sensitive_telemetry()
References:
- https://opentelemetry.io/docs/languages/sdk-configuration/general/
@@ -1015,11 +1015,8 @@ def test_observability_settings_is_setup_initial(monkeypatch):
assert settings.is_setup is False
# region Test enable_instrumentation function
def test_enable_instrumentation_function(monkeypatch):
"""Test enable_instrumentation function enables instrumentation."""
def test_enable_sensitive_telemetry_function(monkeypatch):
"""Test enable_sensitive_telemetry function enables instrumentation."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
@@ -1030,41 +1027,7 @@ def test_enable_instrumentation_function(monkeypatch):
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is False
observability.enable_instrumentation()
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
def test_enable_instrumentation_with_sensitive_data(monkeypatch):
"""Test enable_instrumentation function with sensitive_data parameter."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "false")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
observability.enable_instrumentation(enable_sensitive_data=True)
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is True
def test_enable_instrumentation_reads_env_sensitive_data(monkeypatch):
"""Test enable_instrumentation re-reads ENABLE_SENSITIVE_DATA from os.environ when not explicitly passed."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "false")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is False
# Simulate load_dotenv() setting env var after import
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "true")
observability.enable_instrumentation()
observability.enable_sensitive_telemetry()
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is True
@@ -1154,24 +1117,8 @@ def test_configure_otel_providers_explicit_param_overrides_env(monkeypatch):
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is False
def test_enable_instrumentation_explicit_param_overrides_env(monkeypatch):
"""Test that explicit enable_sensitive_data parameter to enable_instrumentation overrides env var."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.setenv("ENABLE_SENSITIVE_DATA", "true")
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
# Explicit False should override the env var True
observability.enable_instrumentation(enable_sensitive_data=False)
assert observability.OBSERVABILITY_SETTINGS.enable_instrumentation is True
assert observability.OBSERVABILITY_SETTINGS.enable_sensitive_data is False
def test_enable_instrumentation_does_not_touch_console_exporters(monkeypatch):
"""Test enable_instrumentation does not modify enable_console_exporters (it is an exporter concern)."""
def test_enable_sensitive_telemetry_does_not_touch_console_exporters(monkeypatch):
"""Test enable_sensitive_telemetry does not modify enable_console_exporters (it is an exporter concern)."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
@@ -1185,14 +1132,14 @@ def test_enable_instrumentation_does_not_touch_console_exporters(monkeypatch):
# Simulate load_dotenv() setting env var after import
monkeypatch.setenv("ENABLE_CONSOLE_EXPORTERS", "true")
observability.enable_instrumentation()
# enable_console_exporters is not managed by enable_instrumentation;
observability.enable_sensitive_telemetry()
# enable_console_exporters is not managed by enable_sensitive_telemetry;
# it is only read by configure_otel_providers.
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is False
def test_enable_instrumentation_does_not_clobber_console_exporters(monkeypatch):
"""Test enable_instrumentation does not reset enable_console_exporters set by prior configure call."""
def test_enable_sensitive_telemetry_does_not_clobber_console_exporters(monkeypatch):
"""Test enable_sensitive_telemetry does not reset enable_console_exporters set by prior configure call."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
@@ -1215,42 +1162,13 @@ def test_enable_instrumentation_does_not_clobber_console_exporters(monkeypatch):
observability.configure_otel_providers(enable_console_exporters=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
# Calling enable_instrumentation should not clobber the value
observability.enable_instrumentation()
# Calling enable_sensitive_telemetry should not clobber the value
observability.enable_sensitive_telemetry()
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
def test_enable_instrumentation_with_sensitive_data_does_not_touch_console_exporters(monkeypatch):
"""Test enable_console_exporters is untouched even when enable_sensitive_data is explicitly passed."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
monkeypatch.delenv("ENABLE_SENSITIVE_DATA", raising=False)
monkeypatch.delenv("VS_CODE_EXTENSION_PORT", raising=False)
for key in [
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
]:
monkeypatch.delenv(key, raising=False)
observability = importlib.import_module("agent_framework.observability")
importlib.reload(observability)
# Set console exporters via configure_otel_providers
with patch.object(observability.OBSERVABILITY_SETTINGS, "_configure"):
observability.configure_otel_providers(enable_console_exporters=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
# Calling enable_instrumentation with explicit sensitive_data should not clobber console exporters
observability.enable_instrumentation(enable_sensitive_data=True)
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
def test_enable_instrumentation_preserves_console_exporters_after_env_removed(monkeypatch):
"""Test enable_instrumentation preserves enable_console_exporters when env var is removed after reload."""
def test_enable_sensitive_telemetry_preserves_console_exporters_after_env_removed(monkeypatch):
"""Test enable_sensitive_telemetry preserves enable_console_exporters when env var is removed after reload."""
import importlib
monkeypatch.setenv("ENABLE_INSTRUMENTATION", "false")
@@ -1264,8 +1182,8 @@ def test_enable_instrumentation_preserves_console_exporters_after_env_removed(mo
# Remove the env var after reload
monkeypatch.delenv("ENABLE_CONSOLE_EXPORTERS", raising=False)
# enable_instrumentation should not reset the value
observability.enable_instrumentation()
# enable_sensitive_telemetry should not reset the value
observability.enable_sensitive_telemetry()
assert observability.OBSERVABILITY_SETTINGS.enable_console_exporters is True
@@ -94,7 +94,6 @@ def serve(
auto_open: bool = False,
cors_origins: list[str] | None = None,
ui_enabled: bool = True,
instrumentation_enabled: bool = False,
mode: str = "developer",
auth_enabled: bool = True,
auth_token: str | None = None,
@@ -109,7 +108,6 @@ def serve(
auto_open: Whether to automatically open browser
cors_origins: List of allowed CORS origins
ui_enabled: Whether to enable the UI
instrumentation_enabled: Whether to enable OpenTelemetry instrumentation
mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)
auth_enabled: Whether to enable Bearer token authentication
auth_token: Custom authentication token (auto-generated if not provided with auth_enabled=True)
@@ -126,13 +124,6 @@ def serve(
if not isinstance(port, int) or not (1 <= port <= 65535):
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
# Enable instrumentation if requested
if instrumentation_enabled:
from agent_framework.observability import enable_instrumentation
enable_instrumentation(enable_sensitive_data=True)
logger.info("Enabled Agent Framework instrumentation with sensitive data")
# Create server with direct parameters
server = DevServer(
entities_dir=entities_dir,
@@ -98,7 +98,7 @@ class AgentFrameworkExecutor:
try:
from agent_framework.observability import OBSERVABILITY_SETTINGS, configure_otel_providers
# Configure if instrumentation is enabled (via enable_instrumentation() or env var)
# Configure if instrumentation is enabled (on by default, via env var, or enable_sensitive_telemetry())
if OBSERVABILITY_SETTINGS.ENABLED:
# Call configure_otel_providers to set up exporters.
# If OTEL_EXPORTER_OTLP_ENDPOINT is set, exporters will be created automatically.
@@ -518,7 +518,7 @@ class DevServer:
framework="agent_framework",
runtime="python", # Python DevUI backend
capabilities={
"instrumentation": os.getenv("ENABLE_INSTRUMENTATION") == "true",
"instrumentation": os.getenv("ENABLE_INSTRUMENTATION", "true").lower() != "false",
"openai_proxy": openai_executor.is_configured,
"deployment": True, # Deployment feature is available
},
@@ -248,8 +248,8 @@ services:
- AZURE_OPENAI_API_KEY=\${AZURE_OPENAI_API_KEY}
- AZURE_OPENAI_ENDPOINT=\${AZURE_OPENAI_ENDPOINT}
- AZURE_OPENAI_MODEL=\${AZURE_OPENAI_MODEL}
# Optional: Enable instrumentation
- ENABLE_INSTRUMENTATION=\${ENABLE_INSTRUMENTATION:-false}
# Optional: Disable instrumentation (enabled by default)
- ENABLE_INSTRUMENTATION=\${ENABLE_INSTRUMENTATION:-true}
ports:
- "8080:8080"
restart: unless-stopped
@@ -817,7 +817,7 @@ 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
from agent_framework.observability import create_metric_views, create_resource, enable_sensitive_telemetry
if "resource" not in kwargs:
kwargs["resource"] = create_resource()
@@ -828,7 +828,8 @@ class RawFoundryAgent( # type: ignore[misc]
**kwargs,
)
enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
if enable_sensitive_data:
enable_sensitive_telemetry()
class FoundryAgent( # type: ignore[misc]
@@ -291,7 +291,7 @@ 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
from agent_framework.observability import create_metric_views, create_resource, enable_sensitive_telemetry
if "resource" not in kwargs:
kwargs["resource"] = create_resource()
@@ -302,7 +302,8 @@ class RawFoundryChatClient( # type: ignore[misc]
**kwargs,
)
enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
if enable_sensitive_data:
enable_sensitive_telemetry()
# region Tool factory methods (override OpenAI defaults with Foundry versions)
@@ -699,7 +699,7 @@ async def test_foundry_agent_configure_azure_monitor() -> None:
),
patch("agent_framework.observability.create_metric_views", mock_views),
patch("agent_framework.observability.create_resource", return_value=mock_resource),
patch("agent_framework.observability.enable_instrumentation", mock_enable),
patch("agent_framework.observability.enable_sensitive_telemetry", mock_enable),
):
await agent.configure_azure_monitor(enable_sensitive_data=True)
@@ -708,7 +708,7 @@ async def test_foundry_agent_configure_azure_monitor() -> None:
assert call_kwargs["connection_string"] == "InstrumentationKey=test-key;IngestionEndpoint=https://test.endpoint"
assert call_kwargs["views"] == []
assert call_kwargs["resource"] is mock_resource
mock_enable.assert_called_once_with(enable_sensitive_data=True)
mock_enable.assert_called_once()
async def test_foundry_agent_configure_azure_monitor_resource_not_found() -> None:
@@ -249,7 +249,7 @@ async def test_configure_azure_monitor() -> None:
),
patch("agent_framework.observability.create_metric_views", mock_views),
patch("agent_framework.observability.create_resource", return_value=mock_resource),
patch("agent_framework.observability.enable_instrumentation", mock_enable),
patch("agent_framework.observability.enable_sensitive_telemetry", mock_enable),
):
await client.configure_azure_monitor(enable_sensitive_data=True)
@@ -259,7 +259,7 @@ async def test_configure_azure_monitor() -> None:
assert call_kwargs["connection_string"] == "InstrumentationKey=test-key;IngestionEndpoint=https://test.endpoint"
assert call_kwargs["views"] == []
assert call_kwargs["resource"] is mock_resource
mock_enable.assert_called_once_with(enable_sensitive_data=True)
mock_enable.assert_called_once()
async def test_configure_azure_monitor_resource_not_found() -> None:
@@ -324,7 +324,7 @@ async def test_configure_azure_monitor_with_custom_resource() -> None:
),
patch("agent_framework.observability.create_metric_views", return_value=[]),
patch("agent_framework.observability.create_resource") as mock_create_resource,
patch("agent_framework.observability.enable_instrumentation"),
patch("agent_framework.observability.enable_sensitive_telemetry"),
):
await client.configure_azure_monitor(resource=custom_resource)
@@ -6,7 +6,6 @@ from __future__ import annotations
import importlib.metadata
from agent_framework.observability import enable_instrumentation
from agentlightning.tracer import ( # type: ignore[reportMissingImports]
AgentOpsTracer, # type: ignore[reportMissingImports, import-not-found]
)
@@ -26,7 +25,6 @@ class AgentFrameworkTracer(AgentOpsTracer): # type: ignore
def init(self) -> None:
"""Initialize the agent-framework-lab-lightning for training."""
enable_instrumentation()
super().init() # pyright: ignore[reportUnknownMemberType]
def teardown(self) -> None: