-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Python: Fix ENABLE_SENSITIVE_DATA env var not respected after load_dotenv() #4120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1848a7b
4f56ded
c7c79a4
03be169
99f7cc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -880,8 +880,7 @@ def get_meter( | |
| return metrics.get_meter(name=name, version=version, schema_url=schema_url) | ||
|
|
||
|
|
||
| global OBSERVABILITY_SETTINGS | ||
| OBSERVABILITY_SETTINGS: ObservabilitySettings = ObservabilitySettings() | ||
| OBSERVABILITY_SETTINGS: ObservabilitySettings | None = None | ||
|
|
||
|
|
||
| def enable_instrumentation( | ||
|
|
@@ -900,8 +899,11 @@ def enable_instrumentation( | |
| Keyword Args: | ||
| enable_sensitive_data: Enable OpenTelemetry sensitive events. Overrides | ||
| the environment variable ENABLE_SENSITIVE_DATA if set. Default is None. | ||
| When not provided, falls back to the ENABLE_SENSITIVE_DATA environment variable. | ||
| """ | ||
| global OBSERVABILITY_SETTINGS | ||
| if OBSERVABILITY_SETTINGS is None: | ||
| OBSERVABILITY_SETTINGS = ObservabilitySettings() | ||
| OBSERVABILITY_SETTINGS.enable_instrumentation = True | ||
| if enable_sensitive_data is not None: | ||
| OBSERVABILITY_SETTINGS.enable_sensitive_data = enable_sensitive_data | ||
|
|
@@ -1026,27 +1028,22 @@ def configure_otel_providers( | |
| - https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/ | ||
| """ | ||
| global OBSERVABILITY_SETTINGS | ||
| if env_file_path: | ||
| # Build kwargs, excluding None values | ||
| settings_kwargs: dict[str, Any] = { | ||
| "enable_instrumentation": True, | ||
| "env_file_path": env_file_path, | ||
| } | ||
| if env_file_encoding is not None: | ||
| settings_kwargs["env_file_encoding"] = env_file_encoding | ||
| if enable_sensitive_data is not None: | ||
| settings_kwargs["enable_sensitive_data"] = enable_sensitive_data | ||
| if vs_code_extension_port is not None: | ||
| settings_kwargs["vs_code_extension_port"] = vs_code_extension_port | ||
|
|
||
| OBSERVABILITY_SETTINGS = ObservabilitySettings(**settings_kwargs) | ||
| else: | ||
| # Update the observability settings with the provided values | ||
| OBSERVABILITY_SETTINGS.enable_instrumentation = True | ||
| if enable_sensitive_data is not None: | ||
| OBSERVABILITY_SETTINGS.enable_sensitive_data = enable_sensitive_data | ||
| if vs_code_extension_port is not None: | ||
| OBSERVABILITY_SETTINGS.vs_code_extension_port = vs_code_extension_port | ||
| # Build kwargs for a fresh ObservabilitySettings, excluding None values. | ||
| # Creating the settings here (instead of at import time) ensures that | ||
| # environment variables populated by the caller's load_dotenv() are picked up. | ||
| settings_kwargs: dict[str, Any] = { | ||
| "enable_instrumentation": True, | ||
| } | ||
| if env_file_path is not None: | ||
| settings_kwargs["env_file_path"] = env_file_path | ||
| if env_file_encoding is not None: | ||
| settings_kwargs["env_file_encoding"] = env_file_encoding | ||
| if enable_sensitive_data is not None: | ||
| settings_kwargs["enable_sensitive_data"] = enable_sensitive_data | ||
| if vs_code_extension_port is not None: | ||
| settings_kwargs["vs_code_extension_port"] = vs_code_extension_port | ||
|
|
||
| OBSERVABILITY_SETTINGS = ObservabilitySettings(**settings_kwargs) | ||
|
|
||
| OBSERVABILITY_SETTINGS._configure( # type: ignore[reportPrivateUsage] | ||
|
Comment on lines
+1031
to
1048
|
||
| additional_exporters=exporters, | ||
|
|
@@ -1132,10 +1129,10 @@ def get_response( | |
| **kwargs: Any, | ||
| ) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: | ||
| """Trace chat responses with OpenTelemetry spans and metrics.""" | ||
| global OBSERVABILITY_SETTINGS | ||
| settings = OBSERVABILITY_SETTINGS | ||
| super_get_response = super().get_response # type: ignore[misc] | ||
|
|
||
| if not OBSERVABILITY_SETTINGS.ENABLED: | ||
| if settings is None or not settings.ENABLED: | ||
| return super_get_response(messages=messages, stream=stream, options=options, **kwargs) # type: ignore[no-any-return] | ||
|
Comment on lines
+1132
to
1136
|
||
|
|
||
| opts: dict[str, Any] = options or {} # type: ignore[assignment] | ||
|
|
@@ -1170,7 +1167,7 @@ def get_response( | |
| span_name = attributes.get(SpanAttributes.LLM_REQUEST_MODEL, "unknown") | ||
| span = get_tracer().start_span(f"{operation} {span_name}") | ||
| span.set_attributes(attributes) | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1205,11 +1202,7 @@ async def _finalize_stream() -> None: | |
| operation_duration_histogram=self.duration_histogram, | ||
| duration=duration, | ||
| ) | ||
| if ( | ||
| OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED | ||
| and isinstance(response, ChatResponse) | ||
| and response.messages | ||
| ): | ||
| if settings.SENSITIVE_DATA_ENABLED and isinstance(response, ChatResponse) and response.messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1230,7 +1223,7 @@ async def _finalize_stream() -> None: | |
|
|
||
| async def _get_response() -> ChatResponse: | ||
| with _get_span(attributes=attributes, span_name_attribute=SpanAttributes.LLM_REQUEST_MODEL) as span: | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1252,7 +1245,7 @@ async def _get_response() -> ChatResponse: | |
| operation_duration_histogram=self.duration_histogram, | ||
| duration=duration, | ||
| ) | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and response.messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1312,12 +1305,12 @@ def run( | |
| **kwargs: Any, | ||
| ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: | ||
| """Trace agent runs with OpenTelemetry spans and metrics.""" | ||
| global OBSERVABILITY_SETTINGS | ||
| settings = OBSERVABILITY_SETTINGS | ||
| super_run = super().run # type: ignore[misc] | ||
| provider_name = str(self.otel_provider_name) | ||
| capture_usage = bool(getattr(self, "_otel_capture_usage", True)) | ||
|
|
||
| if not OBSERVABILITY_SETTINGS.ENABLED: | ||
| if settings is None or not settings.ENABLED: | ||
| return super_run( # type: ignore[no-any-return] | ||
| messages=messages, | ||
| stream=stream, | ||
|
|
@@ -1363,7 +1356,7 @@ def run( | |
| span_name = attributes.get(OtelAttr.AGENT_NAME, "unknown") | ||
| span = get_tracer().start_span(f"{operation} {span_name}") | ||
| span.set_attributes(attributes) | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1396,11 +1389,7 @@ async def _finalize_stream() -> None: | |
| capture_usage=capture_usage, | ||
| ) | ||
| _capture_response(span=span, attributes=response_attributes, duration=duration) | ||
| if ( | ||
| OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED | ||
| and isinstance(response, AgentResponse) | ||
| and response.messages | ||
| ): | ||
| if settings.SENSITIVE_DATA_ENABLED and isinstance(response, AgentResponse) and response.messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1420,7 +1409,7 @@ async def _finalize_stream() -> None: | |
|
|
||
| async def _run() -> AgentResponse: | ||
| with _get_span(attributes=attributes, span_name_attribute=OtelAttr.AGENT_NAME) as span: | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1442,7 +1431,7 @@ async def _run() -> AgentResponse: | |
| if response: | ||
| response_attributes = _get_response_attributes(attributes, response, capture_usage=capture_usage) | ||
| _capture_response(span=span, attributes=response_attributes, duration=duration) | ||
| if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED and response.messages: | ||
| if settings.SENSITIVE_DATA_ENABLED and response.messages: | ||
| _capture_messages( | ||
| span=span, | ||
| provider_name=provider_name, | ||
|
|
@@ -1780,8 +1769,7 @@ def __repr__(self) -> str: | |
|
|
||
| def workflow_tracer() -> Tracer: | ||
| """Get a workflow tracer or a no-op tracer if not enabled.""" | ||
| global OBSERVABILITY_SETTINGS | ||
| return get_tracer() if OBSERVABILITY_SETTINGS.ENABLED else trace.NoOpTracer() | ||
| return get_tracer() if OBSERVABILITY_SETTINGS is not None and OBSERVABILITY_SETTINGS.ENABLED else trace.NoOpTracer() | ||
|
|
||
|
|
||
| def create_workflow_span( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With OBSERVABILITY_SETTINGS now defaulting to None, setting ENABLE_INSTRUMENTATION=true in the process environment no longer automatically enables instrumentation until some code explicitly calls enable_instrumentation()/configure_otel_providers(). This appears to regress the documented “zero-code via env vars” behavior (e.g., python/samples/02-agents/observability/README.md:156 says ENABLE_INSTRUMENTATION=true should cause the framework to emit observability data). Consider lazily initializing OBSERVABILITY_SETTINGS on first telemetry check (or at import time when env vars are already present) so env-only enablement continues to work while still supporting load_dotenv-after-import timing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every sample in the repo explicitly calls
configure_otel_providers()orenable_instrumentation()— no sample relies on env-var-only enablement. The README's "zero-code" pattern (Pattern 5) refers to theopentelemetry-instrumentCLI, which doesn't depend onOBSERVABILITY_SETTINGS. Deferring creation to call time is intentional so that env vars set viaload_dotenv()after import are correctly picked up.