Add logging capabilities to AIProjectClient and related samples - #48394
Add logging capabilities to AIProjectClient and related samples#48394howieleung wants to merge 9 commits into
Conversation
- Introduced a new logging transport class (_OpenAILoggingTransport) for handling OpenAI requests and responses. - Enhanced AIProjectClient to support console logging and custom user agents. - Created utility functions for generating timestamped log files. - Added multiple sample scripts demonstrating logging configurations, including: - Capturing both Azure-core and OpenAI transport logs. - Writing logs to console and files with different logging levels. - Implemented unit tests for logging behavior in both synchronous and asynchronous contexts. - Updated test helpers to accommodate new logging features and configurations.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds configurable logging for synchronous and asynchronous OpenAI clients created through AIProjectClient.
Changes:
- Adds dedicated HTTPX logging transports with configurable redaction.
- Adds console/file logging samples and utilities.
- Adds synchronous and asynchronous logging tests.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/responses/test_openai_client_overrides.py |
Updates sync transport tests. |
tests/responses/test_openai_client_overrides_async.py |
Updates async transport tests. |
tests/responses/test_client_logging.py |
Tests sync logging behavior. |
tests/responses/test_client_logging_async.py |
Tests async logging behavior. |
tests/responses/openai_test_helpers.py |
Adds logging configuration to test clients. |
samples/logs/util.py |
Exposes shared sample helpers. |
samples/logs/log_utils.py |
Creates timestamped log paths. |
samples/logs/sample_log_with_logging_disabled.py |
Demonstrates reduced logging. |
samples/logs/sample_log_to_console.py |
Demonstrates console logging. |
samples/logs/sample_log_from_sdk.py |
Demonstrates Azure SDK logging. |
samples/logs/sample_log_from_openai_client.py |
Demonstrates OpenAI transport logging. |
samples/logs/sample_log_all.py |
Demonstrates combined logging. |
azure/ai/projects/_patch.py |
Implements synchronous logging transport and wiring. |
azure/ai/projects/_patch.pyi |
Adds synchronous logging type declarations. |
azure/ai/projects/aio/_patch.py |
Implements asynchronous logging transport. |
azure/ai/projects/aio/_patch.pyi |
Adds asynchronous logging type declarations. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:46
- This explicit
logging_enable=FalsepreventsNetworkTraceLoggingPolicyfrom emitting Azure-core HTTP traces, while the console-logging constructor setsazure.core.pipeline.policies.http_logging_policytoERROR. Consequently, this sample advertised as capturing both Azure-core and OpenAI HTTP logs only emits the OpenAI transport logs. Either enable network tracing here or preserve the redacted HTTP policy when full logging is disabled.
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=False) as project_client,
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- The sample test suite discovers files only within folders explicitly passed to
get_sample_paths, andtests/samples/test_samples.pyhas no registration forlogs. As a result, none of the five new logging samples run in CI. Add a recorded sample test forget_sample_paths("logs", ...), explicitly skipping by filename only where recording is not possible.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/.tmp_probe_openai_stream.py:1
- This is a one-off diagnostic probe that executes immediately on import, prints request details, and deliberately raises an exception; it is neither a package module nor a test/sample covered by the PR. Remove this temporary file before merging.
import httpx
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364
- When reduced logging is selected, this still writes the complete URL (including caller-supplied
default_queryvalues), and_sanitize_auth_headerleaves anapi-keyheader unchanged before this loop emits it. This contradicts the documented default redaction and can expose credentials or sensitive query parameters in ordinary debug logs. Redact query values and every credential-bearing header unless explicit body logging is enabled; authentication credentials should remain redacted in either mode.
_OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270
- The async path also logs the full URL and emits
api-keyunchanged whenlogging_enabled=False. Because callers may supply arbitrarydefault_queryvalues and headers, reduced logging can leak sensitive data. Redact URL query values and all credential-bearing headers in the async transport as well.
_OPENAI_TRANSPORT_LOGGER.debug("\n==> Request:\n%s %s", request.method, request.url)
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description says the console sample writes to a file, but the implementation only enables
AZURE_AI_PROJECTS_CONSOLE_LOGGINGand writes to the console. Update the description so users are not directed to expect a log file.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:26
- This adds user-visible OpenAI transport logging behavior, but
CHANGELOG.mdstill ends at 2.4.0 and contains no entry for the feature. Add a release-history entry describing the new logging behavior and samples so the significant SDK change is discoverable to users.
_OPENAI_TRANSPORT_LOGGER_NAME = "azure.ai.projects.openai_transport"
_OPENAI_TRANSPORT_LOGGER = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/samples/logs/log_utils.py:11
- This annotation is evaluated when the module is imported, but
str | Pathrequires Python 3.10 while this package supports Python 3.9 (pyproject.toml:31). Running any of these samples on Python 3.9 will therefore fail while importinglog_utils; usetyping.Unionfor compatibility.
def create_timestamped_temp_log_file(script_path: str | Path) -> Path:
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215
- Passing a plain
httpx.Clientchanges OpenAI's defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that customhttpx.Clientinstances use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the logging client withopenai.DefaultHttpxClient(or explicitly preserve all OpenAI defaults).
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143
- Passing a plain
httpx.AsyncClientchanges OpenAI's async defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that custom clients use HTTPX defaults instead of its 600-second timeout, larger connection limits, and redirect handling; this can make existing long-running or redirected requests fail. Construct the transport withopenai.DefaultAsyncHttpxClient(or explicitly preserve all OpenAI defaults).
return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description says this console sample writes logs into a file, contradicting both its name and the later statement on line 25. Describe the console destination here so users do not expect a log file to be created.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
…e handlers for openai_transport
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Suppressed comments (9)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:378
- This only treats SSE as streaming. The returned OpenAI client also exposes
with_streaming_responsefor non-SSE responses (for example file downloads withapplication/octet-stream), and those responses still take theresponse.read()branch and are fully buffered before the caller sees them. Preserve the response stream for every streaming request rather than inferring streaming solely fromContent-Type.
if self._is_streaming_response(response):
_OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
else:
content = response.read()
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:364
- The sanitizer's contract includes
api-key, but it only rewritesauthorization. Becauseget_openai_clientaccepts caller-provideddefault_headers, anapi-keyheader is logged verbatim here even withlogging_enable=False. Redactapi-key(case-insensitively) in reduced mode before iterating the headers.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:270
- The async sanitizer says it handles
api-key, but it only redactsauthorization. A caller-supplieddefault_headers={"api-key": ...}value is therefore emitted unchanged here under reduced logging. Redactapi-keycase-insensitively wheneverlogging_enable=False.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:284
- This only protects SSE streams. Async OpenAI's
with_streaming_responsecan stream non-SSE payloads such as file downloads, which still reachresponse.aread()here and are fully buffered before being returned. Preserve the async response stream for all streaming requests instead of using only the response content type as the signal.
if self._is_streaming_response(response):
_OPENAI_TRANSPORT_LOGGER.debug("Body: [Streaming response not logged]")
else:
content = await response.aread()
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:215
- Supplying a plain
httpx.Clientreplaces OpenAI's own default HTTP client even when logging is disabled. In particular, httpx defaultsfollow_redirectsto false while OpenAI's default client enables it, so redirects that previously succeeded can now be returned as errors; other OpenAI connection defaults are also bypassed. Build this around OpenAI's default client configuration (or reproduce all of its defaults) when installing the transport.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:143
- This replaces OpenAI's default async HTTP client for every caller, including
logging_enable=False. A plainhttpx.AsyncClientdoes not preserve OpenAI's defaults (notablyfollow_redirects=True), so redirected requests can regress and other connection settings may change. Use OpenAI's default async client configuration while injecting this transport.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.AsyncClient(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- None of the five new
samples/logs/sample_*.pyfiles is registered intests/samples/test_samples.py. That suite discovers samples only within folders explicitly passed toget_sample_paths, and there is currently nologsentry, so these samples—including their local utility imports and logger setup—will never execute in CI. Add alogssample parametrization, or explicitly skip individual filenames with a reason if they cannot be recorded.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:323
- The capture handler is attached both to the root logger and directly to this child logger, but the transport logger normally has
propagate=True. Every OpenAI transport record is therefore appended toprint_callstwice in ordinary samples, inflating and duplicating the text sent to LLM validation. Temporarily disable propagation while the direct handler is installed, then restore the prior value.
directly_attached_loggers = []
for logger_name in ("azure.ai.projects.openai_transport",):
logger_instance = logging.getLogger(logger_name)
logger_instance.addHandler(capture_handler)
directly_attached_loggers.append(logger_instance)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description says this console sample writes logs into a file, contradicting both its name and the usage note below. Describe the console destination so users do not look for a log file that is never created.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (8)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:270
- Using a plain
httpx.Clientchanges OpenAI's transport defaults for every caller, including when logging is disabled: httpx uses a 5-second timeout and does not follow redirects, while OpenAI 2.8 configures a 600-second timeout, larger connection limits, and redirects. Existing long-running Responses calls can therefore start timing out after this change. Build this with OpenAI's exportedDefaultHttpxClient(or explicitly preserve the same defaults) while supplying the logging transport.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:419
- In the advertised reduced-logging mode, only the request
authorizationheader is sanitized. This loop still writes other sensitive request headers such asapi-keyorcookieverbatim, and the response loop writes every response header (includingset-cookie) verbatim. Apply an allowlist/redaction policy to both request and response headers before emitting them.
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:272
- The async reduced-logging path also sanitizes only request
authorization; all other request headers and every response header are emitted unchanged. A user following the new file-logging guidance can therefore persistapi-key, cookie, or similar secret values even withlogging_enable=False. Redact non-allowlisted headers on both sides before logging.
headers = dict(request.headers)
self._sanitize_auth_header(headers)
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:310
- The sync creation message is now sent to the dedicated transport logger, but the async implementation still sends the same message to
azure.ai.projects.aio._patch(aio/_patch.py:182). Consequently, a handler attached only as documented receives this event for sync clients but not async clients. Route both implementations through the same logger.
_OPENAI_TRANSPORT_LOGGER.debug( # pylint: disable=specify-parameter-names-in-call
"[get_openai_client] Creating OpenAI client using Entra ID authentication, base_url = `%s`", # pylint: disable=line-too-long
base_url,
)
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:323
- This handler is attached both here and to the root logger. Since
azure.ai.projects.openai_transportpropagates by default, each transport record is appended toprint_callstwice for normal samples. Disable propagation while the direct handler is installed and restore its prior value afterward.
directly_attached_loggers = []
for logger_name in ("azure.ai.projects.openai_transport",):
logger_instance = logging.getLogger(logger_name)
logger_instance.addHandler(capture_handler)
directly_attached_loggers.append(logger_instance)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:232
- Each client constructed with console logging enabled appends another handler to this process-global logger and never removes it. Creating two clients makes every OpenAI transport record print twice, and closing either client does not restore the logger. Reuse a single marked handler or track ownership and remove it during client shutdown.
This issue also appears on line 307 of the same file.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:270
- This introduces release-visible logging behavior and changes the default OpenAI HTTP client path, but the PR does not add a CHANGELOG entry. Add the feature and behavior change under the target package version so users can discover it and assess the compatibility impact.
logging_kwargs = getattr(self, "_kwargs", {})
logging_enabled = bool(logging_kwargs.get("logging_enable", False))
return httpx.Client(transport=_OpenAILoggingTransport(logging_enabled=logging_enabled))
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This console sample says it writes logs to a single file, but it does not create a file handler and later correctly states that logs go to the console. Update the description so users are not told to look for a nonexistent log file.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Suppressed comments (5)
sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py:205
- This second branch has the same guaranteed failure:
DefaultHttpxClientis imported independently, so replacing the module'shttpx.Clientdoes not intercept its construction. Patch and assert againstDefaultHttpxClient.
patch("azure.ai.projects._patch.httpx") as mock_httpx,
sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py:216
- This branch also patches
httpx.AsyncClient, which cannot intercept construction of the already importedDefaultAsyncHttpxClient;assert_called_once()therefore fails. Patch the constructor actually used by_get_openai_http_client.
patch("azure.ai.projects.aio._patch.httpx") as mock_httpx,
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:323
- The same handler is attached here and to the root logger above. While this logger's default
propagate=Trueremains in effect, every OpenAI transport record is appended toprint_callstwice, duplicating the input used for sample validation. Disable propagation for the direct attachment and restore its prior value afterward.
logger_instance.addHandler(capture_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:9
- This description says the sample writes logs to a file, but the sample only enables console logging and line 25 correctly says the destination is the console.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
sdk/ai/azure-ai-projects/README.md:284
- This adds a user-visible logging feature and a new sample suite, but the package's
CHANGELOG.mdstill ends at 2.4.0 without mentioning either change. Add the corresponding feature and sample-update entries for the release that will introduce this behavior.
See the logging samples in the `samples/logs/` folder for complete end-to-end examples, including console logging, file logging, and OpenAI transport logging.
|
Please target this PR to the vnext branch |
[Pilot] PR Pipeline Failure AnalysisA CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green. What failedThe
All failures are in Recommended next steps
Raw pipeline analysis (azsdk ci analyze)
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Suppressed comments (8)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:434
- Reduced logging still emits every request header here.
_sanitize_auth_headeronly changesauthorization, so credentials in headers such asCookieorProxy-Authorizationare written verbatim even withlogging_enable=False. Redact every non-allowlisted header in reduced mode, matching Azure-core's behavior insdk/core/azure-core/azure/core/pipeline/policies/_universal.py:412-465.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(headers.items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:290
- The async response path also logs every response header verbatim in reduced mode. For example,
Set-Cookiewould be written to configured handlers even whenlogging_enable=False. Redact non-allowlisted response headers before logging them.
_OPENAI_TRANSPORT_LOGGER.debug("Headers:")
for key, value in sorted(dict(response.headers).items()):
_OPENAI_TRANSPORT_LOGGER.debug(" %s: %s", key, value)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:394
- Wrapping this transport in
DefaultHttpxClientdoes not preserve OpenAI's pool limits: HTTPX returns an explicitly supplied transport unchanged, so the client'slimits=DEFAULT_CONNECTION_LIMITSnever reaches thisHTTPTransport. This transport therefore uses HTTPX's 100/20 defaults instead of OpenAI's 1000/100 limits, reducing concurrency for all callers. Initialize the transport with OpenAI'sDEFAULT_CONNECTION_LIMITS.
def __init__(self, *, logging_enabled: bool) -> None:
super().__init__()
self._logging_enabled = logging_enabled
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:239
- The async transport similarly falls back to HTTPX's 100/20 pool limits.
DefaultAsyncHttpxClientcannot apply OpenAI's 1000/100 defaults after an explicit transport is supplied because HTTPX returns that transport unchanged. Pass OpenAI'sDEFAULT_CONNECTION_LIMITSintoAsyncHTTPTransportto avoid throttling high-concurrency callers.
def __init__(self, *, logging_enabled: bool) -> None:
super().__init__()
self._logging_enabled = logging_enabled
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- The description says this sample writes to a file with
logging_enable=False, but the implementation enablesAZURE_AI_PROJECTS_CONSOLE_LOGGINGand writes to the console; that setting also defaultslogging_enableto true. Update the description so users are not given the opposite security and destination behavior.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/tests/responses/test_client_logging_async.py:78
- This handler is attached to
azure.ai.projects.aio._patch, but the fixture does not restore that logger. The open file handler therefore survives the test, leaks a descriptor, and may write later logs into a removedtmp_path. Include this logger inlogger_names.
"azure.ai.projects.openai_transport",
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- The new
samples/logsfolder is not registered bytests/samples/test_samples.py, and sample discovery only scans explicitly named folders. Consequently none of these five executable samples is exercised by the package's sample test infrastructure. Add a parameterized logs-folder test (or explicitly document/skip samples that cannot be recorded).
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:17
- These samples require the new 2.5.0 behavior, but the package CHANGELOG still starts at 2.4.0 and contains no entry for the logging transport or samples. Add the required 2.5.0 feature/sample notes so users can discover this significant behavior change.
pip install "azure-ai-projects>=2.5.0" python-dotenv
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (8)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:394
DefaultHttpxClientcannot apply its OpenAI connection limits to an already-created transport: HTTPX returns the supplied transport directly, while this constructor initializes it with HTTPX's smaller defaults. Every generated OpenAI client therefore drops from OpenAI 2.8's 1000/100 connection/keep-alive limits to HTTPX's 100/20 limits, which can throttle concurrent workloads. Pass OpenAI-equivalent limits into this transport (and keep the async path aligned).
def __init__(self, *, logging_enabled: bool) -> None:
super().__init__()
self._logging_enabled = logging_enabled
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:239
- Wrapping this transport in
DefaultAsyncHttpxClientdoes not preserve OpenAI's pool limits because HTTPX ignores the client'slimitsargument when a transport is supplied. This async transport therefore uses HTTPX's 100/20 defaults instead of OpenAI 2.8's 1000/100 limits, reducing supported concurrency. Initialize the transport with the OpenAI-equivalent limits, consistently with the sync path.
def __init__(self, *, logging_enabled: bool) -> None:
super().__init__()
self._logging_enabled = logging_enabled
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- Every client created with console logging adds another handler to this process-global logger, and client shutdown never removes it. Creating clients repeatedly or having multiple clients alive makes each OpenAI transport message print once per client and retains stale handlers indefinitely. Configure the global handler idempotently or track and remove client-owned handlers during close.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- The async constructor also appends a new handler to the same process-global transport logger for every client without removing it on close. Repeated sync/async client construction therefore accumulates handlers and duplicates each transport log line. Make registration idempotent or remove client-owned handlers as part of client shutdown.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description says the console sample writes to a file with
logging_enable=False, but the implementation setsAZURE_AI_PROJECTS_CONSOLE_LOGGING=trueand writes to the console with detailed logging enabled. Update the description so users are not given the opposite logging behavior.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- The sample test suite registers each samples subfolder explicitly before auto-discovering its
sample_*.pyfiles, but there is no registration for the newsamples/logsfolder. Consequently none of these five end-to-end samples are executed by sample CI. Add aget_sample_paths("logs", ...)test (with exact skips only for scenarios that cannot be recorded).
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/README.md:279
- This user-facing logging feature and its new samples are not recorded in
CHANGELOG.md; the package metadata and latest changelog entry remain at 2.4.0 even though every new sample instructs users to installazure-ai-projects>=2.5.0. Add the corresponding release-history entry/version metadata so the documented requirement identifies an actual package release containing this behavior.
See the logging samples in the `samples/logs/` folder for complete end-to-end examples, including console logging, file logging, and OpenAI transport logging.
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:235
- This still says the transport is used only for the console environment flag, but it is now installed for every generated OpenAI client and emits through the dedicated logger. Align the docstring with the synchronous implementation so maintainers do not rely on the obsolete activation behavior.
AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable.
"""
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:393
- The OpenAI client’s
limits=DEFAULT_CONNECTION_LIMITSsetting does not configure a caller-supplied transport—HTTPX returns that transport unchanged. Because this transport initializesHTTPTransportwith its own defaults, all default OpenAI clients now use HTTPX’s smaller 100/20 connection pool instead of OpenAI’s 1000/100 pool, which can introduce connection-pool contention under concurrency. Initialize both custom transports with OpenAI’s default connection limits.
super().__init__()
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:238
DefaultAsyncHttpxClientcannot apply itsDEFAULT_CONNECTION_LIMITSto this supplied transport because HTTPX uses a custom transport unchanged. Thissuper().__init__()therefore reduces the async OpenAI pool from OpenAI’s 1000/100 defaults to HTTPX’s 100/20 defaults, potentially throttling concurrent workloads. Initialize both custom transports with OpenAI’s default connection limits.
super().__init__()
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:340
- Removing the environment-variable check makes the shipped
SAMPLE_TEST_ERROR_LOG,SAMPLE_TEST_FAILED_LOG, andSAMPLE_TEST_PASSED_LOGsettings in `.env.template:101-107 dead configuration, although that file still says uncommenting them enables logging. Update/remove those settings and comments, or retain the gating, so users are not given ineffective configuration.
def _build_live_log_file_path(self, suffix: str) -> Optional[str]:
"""Build a live-mode sample log path in the system temp directory."""
# Only create logs in live mode
if not _is_live_mode():
return None
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: no file handler is created, and line 39 enables console logging, which causes the client to default
logging_enabletoTrue, notFalse. Describe console output and the console redaction behavior instead.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Suppressed comments (7)
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456
- The new “print output” log is not faithful to
print(): capture discardssepandend, and this writer appends a newline to every call. For the added streaming samples,print(event.delta, end="")is therefore rewritten as one line per delta. Preserve each call's rendered separator/terminator (or capture into a text buffer) and write it verbatim; update the CLI executor overrides as well.
if self.print_output_calls:
for print_call in self.print_output_calls:
file_handle.write(f"{print_call}\n")
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:233
- Each client constructed with
AZURE_AI_PROJECTS_CONSOLE_LOGGING=truecreates and permanently attaches another handler to this process-global logger. Creating two clients therefore emits every OpenAI transport record twice, and closing either client does not restore the logger. Make this setup idempotent or manage the handler's lifetime explicitly; the async constructor must use the same shared strategy.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py:113
- This async constructor also adds a fresh handler to the same global transport logger on every client creation. Mixing sync/async clients or constructing multiple async clients duplicates each log record and leaves handlers behind after clients close. Use the same idempotent, lifecycle-managed logger configuration as the sync path.
openai_transport_logger = logging.getLogger(_OPENAI_TRANSPORT_LOGGER_NAME)
openai_transport_logger.setLevel(logging.DEBUG)
openai_transport_logger.propagate = False
openai_transport_logger.addHandler(console_handler)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: it configures console output, not a file, and the environment flag causes
logging_enableto default toTrue, notFalse. Describe the actual console-logging behavior so users do not expect reduced file logging.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- The transport now wraps SSE streams and logs every raw response chunk when
logging_enable=True, so the statement that streamed events are not written to SDK logs is incorrect. Clarify that raw stream chunks go to the log while parsed events are printed to the console.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59
- None of the new
samples/logs/sample_*.pyscripts are collected:tests/samples/test_samples.pyregisters folders through explicitget_sample_paths(...)calls, and there is nologsentry. Add sync/async sample coverage for this folder, using exact-filename skips only for scripts that cannot run under recorded tests.
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, logging_enable=True) as project_client,
sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py:384
- This adds a user-visible logging transport and changes the default
get_openai_client()HTTP-client behavior, but the PR has no CHANGELOG entry. The package records user-visible features underCHANGELOG.md“Features Added” (for example lines 5–16); add an entry for this feature before release.
class _OpenAILoggingTransport(httpx.HTTPTransport):
"""Custom HTTP transport that logs OpenAI API requests and responses.
This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and emit
detailed request/response information through a dedicated logger. It automatically
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Suppressed comments (4)
sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py:11
- This description contradicts the sample: it writes to the console, not a file, and setting
AZURE_AI_PROJECTS_CONSOLE_LOGGING=truemakes the client defaultlogging_enabletoTrue, so request and response bodies are included rather than excluded. Update the description to match the demonstrated console/full-logging behavior.
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
With logging_enable=False, the transport still logs request and response metadata,
but excludes request bodies and response bodies while keeping sensitive headers redacted.
sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:9
- None of the new
samples/logs/sample_*.pyscripts are exercised by the package's sample suite.get_sample_pathsdiscovers samples only within folders explicitly parameterized intests/samples/test_samples.py, and that file has nologsentry. Add sync/async sample coverage (or explicit skips with reasons) so these executable examples are validated like the other sample folders.
"""
DESCRIPTION:
This sample demonstrates how to capture both Azure-core HTTP logs and
OpenAI transport logs into a single file while running a Prompt Agent operation.
sdk/ai/azure-ai-projects/tests/samples/sample_executor.py:456
- The new print-only log does not preserve actual
print()semantics:_capture_printdiscardssep/end, and this loop then appends a newline after every call. For example,sample_log_stream_events*.pyusesprint(event.delta, end=""), but its output log will put every streamed delta on a separate line. Capture each call with its effectivesepandendand write those fragments verbatim; the sync and async CLI overrides need the same treatment.
for print_call in self.print_output_calls:
file_handle.write(f"{print_call}\n")
sdk/ai/azure-ai-projects/samples/logs/sample_log_stream_events.py:12
- With full logging enabled, the new transport wrapper logs each SSE body chunk lazily as it is consumed, so streamed response data is automatically written to this log file. The sample description currently says the opposite.
With logging_enable=True, request bodies, response metadata, and token are
included in the log file. Streamed response events are printed to the
console and are not automatically written to SDK logs.
Description
Please add an informative description that covers that changes made by the pull request and link all relevant issues.
If an SDK is being regenerated based on a new API spec, a link to the pull request containing these API spec changes should be included above.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines