Skip to content

Add logging capabilities to AIProjectClient and related samples - #48394

Open
howieleung wants to merge 9 commits into
mainfrom
howie/log
Open

Add logging capabilities to AIProjectClient and related samples#48394
howieleung wants to merge 9 commits into
mainfrom
howie/log

Conversation

@howieleung

Copy link
Copy Markdown
Member
  • 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.

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:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

- 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

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 03:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=False prevents NetworkTraceLoggingPolicy from emitting Azure-core HTTP traces, while the console-logging constructor sets azure.core.pipeline.policies.http_logging_policy to ERROR. 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, and tests/samples/test_samples.py has no registration for logs. As a result, none of the five new logging samples run in CI. Add a recorded sample test for get_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

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
Copilot AI review requested due to automatic review settings August 1, 2026 04:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_query values), and _sanitize_auth_header leaves an api-key header 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-key unchanged when logging_enabled=False. Because callers may supply arbitrary default_query values 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_LOGGING and 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.md still 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)

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
Comment thread sdk/ai/azure-ai-projects/samples/logs/log_utils.py Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 04:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | Path requires 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 importing log_utils; use typing.Union for 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.Client changes OpenAI's defaults for every generated client, including when logging is disabled. OpenAI 2.8 documents that custom httpx.Client instances 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 with openai.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.AsyncClient changes 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 with openai.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.

Copilot AI review requested due to automatic review settings August 1, 2026 05:00
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_response for non-SSE responses (for example file downloads with application/octet-stream), and those responses still take the response.read() branch and are fully buffered before the caller sees them. Preserve the response stream for every streaming request rather than inferring streaming solely from Content-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 rewrites authorization. Because get_openai_client accepts caller-provided default_headers, an api-key header is logged verbatim here even with logging_enable=False. Redact api-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 redacts authorization. A caller-supplied default_headers={"api-key": ...} value is therefore emitted unchanged here under reduced logging. Redact api-key case-insensitively whenever logging_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_response can stream non-SSE payloads such as file downloads, which still reach response.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.Client replaces OpenAI's own default HTTP client even when logging is disabled. In particular, httpx defaults follow_redirects to 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 plain httpx.AsyncClient does not preserve OpenAI's defaults (notably follow_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_*.py files is registered in tests/samples/test_samples.py. That suite discovers samples only within folders explicitly passed to get_sample_paths, and there is currently no logs entry, so these samples—including their local utility imports and logger setup—will never execute in CI. Add a logs sample 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 to print_calls twice 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.

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.Client changes 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 exported DefaultHttpxClient (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 authorization header is sanitized. This loop still writes other sensitive request headers such as api-key or cookie verbatim, and the response loop writes every response header (including set-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 persist api-key, cookie, or similar secret values even with logging_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_transport propagates by default, each transport record is appended to print_calls twice 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.

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated
Copilot AI review requested due to automatic review settings August 3, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: DefaultHttpxClient is imported independently, so replacing the module's httpx.Client does not intercept its construction. Patch and assert against DefaultHttpxClient.
            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 imported DefaultAsyncHttpxClient; 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=True remains in effect, every OpenAI transport record is appended to print_calls twice, 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.md still 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.

Comment thread sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py Outdated
Comment thread sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py Outdated
@dargilco

dargilco commented Aug 3, 2026

Copy link
Copy Markdown
Member

Please target this PR to the vnext branch

Comment thread sdk/ai/azure-ai-projects/samples/logs/sample_log_to_console.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.pyi Outdated
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
[Pilot] PR Pipeline Failure Analysis

A CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green.

What failed

The azure-ai-projects test suite failed on all tested platforms (macOS 3.11, Ubuntu 24.04 3.10/3.10-coverage/3.13/3.14, Windows 2022 3.12) across all install modes (sdist, whl, mindependency). The same 4 test cases fail everywhere:

  • TestHttpClientBranches.test_logging_disabled_still_creates_logging_transport
  • TestHttpClientBranches.test_logging_enable_creates_logging_transport_without_console_logging
  • TestHttpClientBranchesAsync.test_logging_disabled_still_creates_async_logging_transport
  • TestHttpClientBranchesAsync.test_logging_enable_creates_async_logging_transport_without_console_logging

All failures are in tests/responses/test_openai_client_overrides[_async].py and are categorized as test failures (the new logging transport tests introduced by this PR are not passing). The consistent cross-platform failure strongly suggests a logic issue in the implementation or the tests themselves, not an infrastructure problem.

Recommended next steps

  • Run the failing tests locally:
    pytest sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py -k "test_logging"
    pytest sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py -k "test_logging"
    
  • Check the assertions in test_logging_disabled_still_creates_logging_transport and test_logging_enable_creates_logging_transport_without_console_logging — they likely test the _OpenAILoggingTransport initialization or configuration logic introduced in this PR.
  • Verify that _OpenAILoggingTransport is created (or not) under the exact conditions the tests expect when logging is enabled/disabled and console logging is on/off.
  • See the CI troubleshooting guide: https://aka.ms/ci-fix
  • Push new commits to address the failures; this comment updates automatically on the next failing run.
Raw pipeline analysis (azsdk ci analyze)
Analyzing pipeline https://github.com/Azure/azure-sdk-for-python/pull/48394...
--------------------------------------------------------------------------------
Failed Tests
--------------------------------------------------------------------------------
Failing tests (same 4 cases across all platforms and install modes):
  - test_logging_disabled_still_creates_logging_transport (sync & async)
  - test_logging_enable_creates_logging_transport_without_console_logging (sync & async)

Platforms: macos311, ubuntu2404_310, ubuntu2404_310_coverage, Ubuntu2404_313, Ubuntu2404_314, windows2022_312
Install modes per platform: sdist, whl, mindependency

Copilot detected the failing pipeline and generated the analysis above. To have it attempt a fix automatically, reply with @copilot please fix the failing pipeline on this PR.

Generated by Pipeline Analysis - Next Steps · 21.7 AIC · ⌖ 8.77 AIC · ⊞ 6.6K ·

Copilot AI review requested due to automatic review settings August 3, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_header only changes authorization, so credentials in headers such as Cookie or Proxy-Authorization are written verbatim even with logging_enable=False. Redact every non-allowlisted header in reduced mode, matching Azure-core's behavior in sdk/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-Cookie would be written to configured handlers even when logging_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 DefaultHttpxClient does not preserve OpenAI's pool limits: HTTPX returns an explicitly supplied transport unchanged, so the client's limits=DEFAULT_CONNECTION_LIMITS never reaches this HTTPTransport. 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's DEFAULT_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. DefaultAsyncHttpxClient cannot apply OpenAI's 1000/100 defaults after an explicit transport is supplied because HTTPX returns that transport unchanged. Pass OpenAI's DEFAULT_CONNECTION_LIMITS into AsyncHTTPTransport to 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 enables AZURE_AI_PROJECTS_CONSOLE_LOGGING and writes to the console; that setting also defaults logging_enable to 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 removed tmp_path. Include this logger in logger_names.
        "azure.ai.projects.openai_transport",

sdk/ai/azure-ai-projects/samples/logs/sample_log_all.py:59

  • The new samples/logs folder is not registered by tests/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

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated
Copilot AI review requested due to automatic review settings August 3, 2026 19:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • DefaultHttpxClient cannot 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 DefaultAsyncHttpxClient does not preserve OpenAI's pool limits because HTTPX ignores the client's limits argument 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 sets AZURE_AI_PROJECTS_CONSOLE_LOGGING=true and 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_*.py files, but there is no registration for the new samples/logs folder. Consequently none of these five end-to-end samples are executed by sample CI. Add a get_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 install azure-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.
    """

@howieleung
howieleung enabled auto-merge (squash) August 3, 2026 20:22
Copilot AI review requested due to automatic review settings August 3, 2026 21:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_LIMITS setting does not configure a caller-supplied transport—HTTPX returns that transport unchanged. Because this transport initializes HTTPTransport with 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

  • DefaultAsyncHttpxClient cannot apply its DEFAULT_CONNECTION_LIMITS to this supplied transport because HTTPX uses a custom transport unchanged. This super().__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, and SAMPLE_TEST_PASSED_LOG settings 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_enable to True, not False. 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.

Copilot AI review requested due to automatic review settings August 3, 2026 21:30
Comment thread sdk/ai/azure-ai-projects/README.md Outdated
Comment thread sdk/ai/azure-ai-projects/README.md Outdated
Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 discards sep and end, 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=true creates 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_enable to default to True, not False. 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_*.py scripts are collected: tests/samples/test_samples.py registers folders through explicit get_sample_paths(...) calls, and there is no logs entry. 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 under CHANGELOG.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

Comment thread sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
Copilot AI review requested due to automatic review settings August 4, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=true makes the client default logging_enable to True, 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_*.py scripts are exercised by the package's sample suite. get_sample_paths discovers samples only within folders explicitly parameterized in tests/samples/test_samples.py, and that file has no logs entry. 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_print discards sep/end, and this loop then appends a newline after every call. For example, sample_log_stream_events*.py uses print(event.delta, end=""), but its output log will put every streamed delta on a separate line. Capture each call with its effective sep and end and 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants