Python: Fix ENABLE_SENSITIVE_DATA env var ignored when set after module import#4743
Conversation
…umentation (microsoft#4119) Fix ENABLE_SENSITIVE_DATA and VS_CODE_EXTENSION_PORT env vars being ignored when load_dotenv() runs after module import. The module-level OBSERVABILITY_SETTINGS singleton cached env state at import time, and configure_otel_providers() / enable_instrumentation() never re-read from os.environ when parameters were None. Both functions now construct a fresh ObservabilitySettings() to pick up current env vars when explicit parameters are not provided, matching the existing behavior of the env_file_path branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Fixes a bug in the Python core observability module where ENABLE_SENSITIVE_DATA (and related env-driven settings) could be ignored if environment variables were set after agent_framework.observability was imported (e.g., via load_dotenv()), by refreshing env-derived defaults at call time.
Changes:
- Update
enable_instrumentation()to re-readENABLE_SENSITIVE_DATAfrom the current environment when no explicit argument is provided. - Update
configure_otel_providers()to re-read env-derived defaults (notably sensitive data and VS Code port) when no explicit arguments are provided. - Add regression tests covering env re-read behavior and explicit-parameter precedence.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/observability.py |
Refresh env-derived defaults at call time instead of relying solely on import-time singleton values. |
python/packages/core/tests/core/test_observability.py |
Add tests verifying env re-read behavior for sensitive data and VS Code extension port. |
You can also share your feedback on Copilot code review. Take the survey.
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 88%
✓ Correctness
The changes correctly implement re-reading environment variables when
enable_instrumentation()andconfigure_otel_providers()are called without explicit parameters, addressing the use case whereload_dotenv()sets env vars after module import. The logic for explicit-parameter-overrides-env is sound, and the new tests cover the key scenarios. No correctness issues found.
✓ Security Reliability
The changes allow
enable_instrumentation()andconfigure_otel_providers()to re-read environment variables at call time instead of relying only on import-time values, supportingload_dotenv()workflows. The security-sensitiveenable_sensitive_dataflag correctly preserves explicit-parameter-takes-precedence semantics. No injection risks, resource leaks, or unsafe deserialization found. One minor behavioral change—enable_console_exportersis now unconditionally refreshed from the environment in theelsebranch ofconfigure_otel_providers, which was not previously updated there—is consistent with theenv_file_pathbranch but lacks test coverage.
✗ Test Coverage
The new tests cover the main happy-path scenarios for the env-var re-read behaviour in both
enable_instrumentationandconfigure_otel_providers. However, the newly addedenable_console_exportersrefresh line inconfigure_otel_providershas no test coverage at all, and there is no test verifying thatenable_instrumentation(enable_sensitive_data=False)still takes precedence over a truthy env var (the symmetric explicit-override case for that function).
✗ Design Approach
The change fixes a real problem —
OBSERVABILITY_SETTINGSis constructed at module import time (line 899), so env vars set later viaload_dotenv()are invisible to it. The fix is a symptom-level workaround: it instantiates a freshObservabilitySettings()inside each public API function to re-read env vars. This works only for the two code paths covered, butObservabilitySettingsis a public global, so any caller accessing it directly still sees the stale values. Additionally,ObservabilitySettings()construction callscreate_resource(), which builds an OTelResourceobject that is immediately discarded; the fix uses heavy object creation as a mechanism for what is essentially an env-var read. The root cause — eager construction of the singleton at import time — is not addressed. A cleaner fix would be to make the env-var-backed fields lazy properties onObservabilitySettings(reading fromos.environon access), or to delay singleton construction to first use. There is also a minor inconsistency:enable_instrumentation()refreshesenable_sensitive_databut notenable_console_exporters, whereasconfigure_otel_providers()refreshes both; if the user setsENABLE_CONSOLE_EXPORTERSafter import and only callsenable_instrumentation(), that flag is silently ignored.
Flagged Issues
- No test covers the new
OBSERVABILITY_SETTINGS.enable_console_exporters = refreshed.enable_console_exportersline (~line 1077 in observability.py). If this line were deleted or regressed, no test would fail. - The fix patches only the two public API code paths;
OBSERVABILITY_SETTINGSremains a module-level public global whose env-var-backed fields are stale for any direct caller. The root cause—eager singleton construction at import time—is not addressed. Consider making these fields lazy properties that reados.environon access, or lazily constructing the singleton on first use. - Each call to
enable_instrumentation()orconfigure_otel_providers()now instantiates a fullObservabilitySettings()whose constructor also callscreate_resource(), building an OTelResourcethat is immediately discarded. Settings parsing should be separated from resource creation so the refresh pattern does not carry unnecessary side effects.
Suggestions
- Add a test for
enable_instrumentation(enable_sensitive_data=False)whenENABLE_SENSITIVE_DATA=trueis in the environment, to verify the explicit parameter still wins (mirrors the existingtest_configure_otel_providers_explicit_param_overrides_envbut for the simpler function). - In
enable_instrumentation(),enable_console_exportersis not refreshed from the environment (onlyenable_sensitive_datais), whereasconfigure_otel_providers()refreshes both. If a user setsENABLE_CONSOLE_EXPORTERSafter import and calls onlyenable_instrumentation(), the env var is silently ignored. Either document this intentional asymmetry or refreshenable_console_exportersin both functions. - The tests use
importlib.reload(observability), which re-runs module-level code but can leave stale references in other already-imported modules. This fragility should be noted if the module gains cross-module references.
Automated review by eavanvalkenburg's agents
…abilitySettings - Add _read_bool_env/_read_int_env helpers to read env vars without constructing a full ObservabilitySettings (which calls create_resource()) - Replace ObservabilitySettings() in enable_instrumentation() and configure_otel_providers() else-branch with direct env reads - Add enable_console_exporters parameter to configure_otel_providers() for override parity with enable_sensitive_data and vs_code_extension_port - Propagate _resource and _executed_setup in the non-env_file_path branch - Make existing tests hermetic (clear VS_CODE_EXTENSION_PORT and ENABLE_CONSOLE_EXPORTERS env vars) - Add tests: enable_console_exporters env refresh, explicit param overrides for both enable_instrumentation() and configure_otel_providers() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Refresh enable_console_exporters in enable_instrumentation() for consistency with configure_otel_providers(), so env var changes after import are picked up by both public API functions - Make test_configure_otel_providers_reads_env_vs_code_port hermetic by clearing ENABLE_CONSOLE_EXPORTERS from the environment - Add test_enable_instrumentation_reads_env_console_exporters to cover the new refresh behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
The change correctly adds a re-read of ENABLE_CONSOLE_EXPORTERS from the environment inside enable_instrumentation(), mirroring the existing pattern for ENABLE_SENSITIVE_DATA. The new line in the existing test (delenv for ENABLE_CONSOLE_EXPORTERS) prevents test pollution from the new behavior. The new test is well-structured and follows the same pattern as the surrounding tests. No correctness issues found.
✓ Security Reliability
The change adds a single line to
enable_instrumentation()that refreshes theenable_console_exporterssetting from the environment, matching the existing pattern used forenable_sensitive_data. The helper_read_bool_envperforms a safeos.getenvfollowed by a lowercase string comparison—no injection, deserialization, or resource-leak risk. The new test properly isolates environment variables viamonkeypatchand validates the round-trip. The existing test gains a defensivedelenvfor the new variable. No security or reliability issues found.
✓ Test Coverage
The diff adds a one-line production fix to refresh
enable_console_exportersfrom the environment insideenable_instrumentation(), plus a well-structured test that covers the primary scenario (env var absent at import, set before callingenable_instrumentation). The existingtest_configure_otel_providers_reads_env_vs_code_porttest also gets a minor cleanup to deleteENABLE_CONSOLE_EXPORTERSto avoid cross-contamination. The new test follows the same reload-and-assert pattern used by sibling tests and its assertions are meaningful.
✗ Design Approach
The fix unconditionally overwrites
OBSERVABILITY_SETTINGS.enable_console_exportersfrom the environment every timeenable_instrumentation()is called. This is problematic for two reasons. First, it is a leaky abstraction:enable_instrumentation()is documented as a function that only flips the instrumentation flag and does not configure exporters or providers; folding console-exporter state management into it bleeds a provider-configuration concern across a documented layer boundary. Second,configure_otel_providers()already correctly re-readsENABLE_CONSOLE_EXPORTERSfrom the environment at call time (lines 1102–1106), so for the primary load_dotenv use case the fix is redundant on that path. On theenable_instrumentation()-only path (users who wire their own providers, e.g. Azure Monitor), the value written is never read by any exporter-creation code—enable_console_exportersis only consumed inside_configure(), whichenable_instrumentation()never calls. The unconditional overwrite also introduces an asymmetry:enable_sensitive_datacan be protected by passing it explicitly, but there is no correspondingenable_console_exportersparameter toenable_instrumentation(), so any value previously set programmatically (e.g., via a priorconfigure_otel_providers(enable_console_exporters=True)call) can be silently clobbered by a subsequentenable_instrumentation()call when the env var is absent.
Flagged Issues
- Unconditional overwrite in
enable_instrumentation()silently resetsenable_console_exporterstoFalsewhenever the env var is absent, clobbering any value previously set byconfigure_otel_providers(enable_console_exporters=True). This is a destructive side-effect on a function documented as only enabling the instrumentation flag. -
enable_console_exportersis only consumed inside_configure(), whichenable_instrumentation()never calls. The refresh has no effect on theenable_instrumentation()-only code path and is redundant for theconfigure_otel_providers()path (which already re-reads the env var), so it does not solve a real problem but introduces regression risk.
Suggestions
- If the goal is to let
enable_instrumentation()influence console exporters, add an explicitenable_console_exporters: bool | None = Noneparameter (mirroringenable_sensitive_data) with a conditional pattern: use the explicit value when provided, fall back to_read_bool_envotherwise. This preserves caller intent and maintains API symmetry. If the goal is solely to fix theload_dotenvtiming issue, no change toenable_instrumentation()is needed—configure_otel_providers()already re-reads the env var at call time. - Consider adding a reverse-direction test (set ENABLE_CONSOLE_EXPORTERS=true before reload, then delete it, call
enable_instrumentation(), and assert the setting reverts to False) to verify the re-read works in both directions. - Consider adding a test that verifies
enable_console_exportersis refreshed even whenenable_sensitive_datais explicitly passed, since the new line runs unconditionally regardless of that branch.
Automated review by eavanvalkenburg's agents
…nstrumentation() (microsoft#4119) enable_instrumentation() is documented as not configuring exporters, so managing enable_console_exporters there was a leaky abstraction. The unconditional _read_bool_env call silently reset the value to False when ENABLE_CONSOLE_EXPORTERS was absent from env, clobbering any value previously set by configure_otel_providers(enable_console_exporters=True). - Remove the unconditional overwrite line from enable_instrumentation() - Replace test_enable_instrumentation_reads_env_console_exporters with test_enable_instrumentation_does_not_touch_console_exporters - Add regression test: enable_instrumentation() does not clobber a previously configured enable_console_exporters value - Add test: explicit enable_sensitive_data param still leaves enable_console_exporters untouched Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eavanvalkenburg
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 94%
✓ Correctness
The diff correctly removes the side-effect where enable_instrumentation() was unconditionally overwriting enable_console_exporters from the environment, which could clobber a value previously set by configure_otel_providers(). The responsibility for managing enable_console_exporters is properly scoped to configure_otel_providers(), which still reads the env var (lines 1100-1104). The four new tests thoroughly verify the fix: env var set after import is not picked up, prior configure_otel_providers value is preserved, explicit enable_sensitive_data param doesn't clobber, and env var removal after reload doesn't reset. No correctness issues found.
✓ Security Reliability
This diff removes a line from
enable_instrumentation()that was unconditionally overwritingOBSERVABILITY_SETTINGS.enable_console_exportersby re-reading from the environment. This was a bug: it clobbered values previously set byconfigure_otel_providers(). The fix is correct—enable_console_exportersis an exporter concern managed exclusively byconfigure_otel_providers, which already handles both explicit parameters and env-var fallback (lines 1079-1104). The new tests thoroughly verify thatenable_instrumentationno longer touches the console-exporters setting. No security or reliability issues found.
✓ Test Coverage
The diff removes a two-line side-effect from
enable_instrumentationthat was clobberingenable_console_exporters, and replaces one existing test with four well-targeted tests covering the new behavior. The original test was updated to assert the opposite outcome (value staysFalseinstead of being set toTrue), and three new tests cover: clobbering afterconfigure_otel_providers, clobbering whenenable_sensitive_datais passed, and preservation when the env var is removed post-reload. Assertions are meaningful and specific. The test coverage for the behavioral change is thorough.
✓ Design Approach
The diff removes an unconditional re-read of
ENABLE_CONSOLE_EXPORTERSfromenable_instrumentation(), which previously clobbered any value set byconfigure_otel_providers. This is a well-motivated fix:enable_instrumentation()is documented as not configuring exporters or providers, yet the removed code was silently overwriting the exporter-level setting on every call, making it impossible to preserve a value set byconfigure_otel_providers(enable_console_exporters=True). The fix correctly narrowsenable_instrumentationto its stated contract. The four new tests are clear and cover the key scenarios (env var post-reload, clobbering a prior configure call, combined with explicit sensitive_data param). No blocking design issues found.
Suggestions
- The first test (
test_enable_instrumentation_does_not_touch_console_exporters) cleans up fewer env vars than the other three new tests (it doesn't clearENABLE_SENSITIVE_DATA,VS_CODE_EXTENSION_PORT, or theOTEL_EXPORTER_OTLP_*vars). While this doesn't cause a correctness issue today, aligning the env cleanup across all four tests would make them more robust against future refactors. - The same clobbering risk now exists for
enable_sensitive_data: callingconfigure_otel_providers(enable_sensitive_data=True)followed byenable_instrumentation()(without the explicit param) resets it to the env-var value. This is pre-existing and outside this PR's scope, but worth a follow-up to make the two settings consistent in how they handle values already set byconfigure_otel_providers.
Automated review by eavanvalkenburg's agents
Motivation and Context
When users call
load_dotenv()after importing theagent_framework.observabilitymodule, environment variables likeENABLE_SENSITIVE_DATAandVS_CODE_EXTENSION_PORTwere ignored becauseObservabilitySettingswas only read once at import time. This meant the module-level singleton retained stale values even after the environment changed.Fixes #4119
Description
The root cause was that
enable_instrumentation()andconfigure_otel_providers()only updated settings when explicit parameters were passed, falling back to the already-stale module-levelOBSERVABILITY_SETTINGSsingleton otherwise. The fix re-reads environment variables by constructing a freshObservabilitySettings()instance at call time, using those refreshed values as defaults when no explicit parameter is provided. Explicit parameters still take precedence over environment variables. Tests verify the re-read behavior forENABLE_SENSITIVE_DATA,VS_CODE_EXTENSION_PORT, and that explicit arguments override env vars.Contribution Checklist