Skip to content

fix(ci_visibility): filter ddtrace logs at closed stream handlers during pytest teardown - #20101

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
mainfrom
gnufede/issue-16712
Sep 10, 2026
Merged

gh-worker-dd-mergequeue-cf854d[bot] merged 10 commits into
mainfrom
gnufede/issue-16712

Conversation

@gnufede

@gnufede gnufede commented Sep 7, 2026 •

Copy link
Copy Markdown
Member

Description

Fixes #16712.

When the new pytest plugin is enabled (default since v4.5.0), ddtrace log records
emitted at interpreter shutdown via Tracer._atexit propagate to the root logger.
If a user has configured a custom root logger with logging.config.dictConfig
(and disable_existing_loggers: False) that installs a StreamHandler on
sys.stdout, pytest closes that stream during teardown before the atexit
handler fires. The handler then raises ValueError: I/O operation on closed file,
producing a --- Logging error --- traceback at the end of the test session.

The fix installs a per-handler filter on plain logging.StreamHandler instances
during pytest teardown:

  • Only ddtrace records destined for an already-closed stream are skipped; healthy
    handlers keep receiving records, including file output and log forwarding.
    Propagation settings, handler ownership, formatting, and the global tracer's
    lifetime are all preserved.
  • Scanning runs at the cleanup hook (registered before capture starts so it runs
    after capture cleanup), pytest_sessionfinish, and pytest_unconfigure, so
    handlers installed by later teardown hooks are covered.
  • FileHandler and custom StreamHandler subclasses are excluded (file handlers
    can reopen their streams; custom handlers own their error handling). Existing
    filters are preserved, duplicate filters are avoided across repeated pytest
    sessions, and delivery resumes if a handler's stream is replaced with an open
    one.

Testing

Added subprocess regression tests in tests/testing/internal/pytest/test_pytest_log_propagation.py
(the bug only manifests at interpreter shutdown, which does not fire in
inline_run) and unit tests for the filter and scanning helper in
tests/testing/internal/test_logging.py.

Risks

The protection is limited to existing standard stream handlers at pytest teardown;
custom/file handler behavior is unchanged. Handlers installed after pytest returns
remain the caller's responsibility. The filter checks whether a stream is already
closed at dispatch, not arbitrary concurrent closure between filtering and writing.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

No remaining files require a CODEOWNERS review.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 1 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 230 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 230 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=134)
ddtrace.internal.ci_visibility.filters -×-> ddtrace.trace  (product:ci_visibility -> product:tracing, score=132)
ddtrace.llmobs._integrations.openai -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)
ddtrace.llmobs._integrations.vertexai -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)
ddtrace.llmobs._utils -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=132)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

@datadog-official

datadog-official Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: ccb707a | Docs | View more details | Give us feedback!

@gnufede gnufede added the CI App label Sep 7, 2026
@gnufede gnufede changed the title fix(ci_visibility): logger handler error fix fix(ci_visibility): prevent ddtrace logger propagation to closed root handlers Sep 7, 2026
@gnufede
gnufede marked this pull request as ready for review September 7, 2026 15:58
@gnufede
gnufede requested review from a team as code owners September 7, 2026 15:58
@gnufede
gnufede requested a review from ZStriker19 September 7, 2026 15:58

@datadog-official datadog-official Bot 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.

Datadog Autotest: PASS

More details

The setting runs before the early enable guard. Thus, shutdown logs cannot reach a closed root stream in either plug-in path.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit e662d76 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e662d76679

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/testing/internal/pytest/test_pytest_log_propagation.py Outdated
Comment thread ddtrace/testing/internal/pytest/plugin.py Outdated
Comment thread releasenotes/notes/fix-pytest-log-propagation-closed-file-3dcb4cc3c51c967f.yaml Outdated
<!-- dd-meta
{"pullId":"cea9b0fd-5851-4c4f-b4b0-18a30ddd7584","source":"chat","resourceId":"c0096dbc-5d7d-4dc0-9dac-95643f43b151","workflowId":"f964c044-b498-4df8-a367-1861336af4f1","codeChangeId":"f964c044-b498-4df8-a367-1861336af4f1","sourceType":"slack"}
-->
## Description

Fix the closed-stream shutdown error in #16712 without the whole-process
loss of tracer log delivery exposed by the compatibility probes for
#20101.

- Replace the unconditional ddtrace propagation cutoff with per-handler
filters installed during pytest teardown. Only ddtrace records destined
for already-closed ordinary StreamHandlers are skipped; healthy root and
direct handlers continue receiving records.
- Rescan at session finish, unconfigure, and final cleanup, including
when instrumentation is disabled. Preserve propagation settings, handler
ownership, formatting, application records, and the global tracer's
lifetime.
- Exclude FileHandler and custom handler subclasses, preserve existing
filters, avoid duplicate filters across repeated pytest sessions, and
allow delivery again if a handler's stream is replaced.
- Retain the 29 subprocess compatibility/control cases added earlier,
replace the implementation-specific propagation assertion with
preservation tests, and add shutdown/root-file delivery, late
configuration, collection-error, repeated pytest.main(), and filter unit
tests.
- Update the existing customer-facing release note to describe preserved
diagnostic delivery.

## Testing

Python 3.13.13:

- scripts/run-tests --venv 3dc4202 -- -n 0 -k
'test_pytest_log_propagation or test_logging or
test_pytest_log_correlation or test_plugin': 181 passed on pytest 7.4.4.
- scripts/run-tests -s --venv 1b6f43f -- -n 0 -k
'test_pytest_log_propagation or test_logging or
test_pytest_log_correlation or test_plugin': 181 passed on pytest 8.4.2
in the final restored state.
- All 18 previously failing delivery probes now pass, alongside the
original shutdown regressions and
application/correlation/submission-handler controls.
- Negative control: temporarily disabled the closed-stream filter; all
three targeted fd-capture shutdown tests failed with ValueError: I/O
operation on closed file. Restored the filter before the final passing
run.
- Python formatting and Ruff checks passed through scripts/lint for all
four edited Python files; git diff --check passed.
- Scoped type checking passed for the logging helper and subprocess test
module. Wider typing reports existing diagnostics in the plugin,
imported dependencies, and an unchanged unreachable statement in
test_logging.py. Full lint remains limited by unavailable auxiliary
tools, including cython-lint.

## Risks

The protection is deliberately limited to existing standard stream
handlers at pytest teardown; custom/file handler behavior is unchanged.
Handlers installed after pytest returns remain the caller's
responsibility. The filter checks whether a stream is already closed at
dispatch, not arbitrary concurrent closure between filtering and
writing. The submission probe uses the real handler with a mock writer;
it does not validate remote intake delivery.

## Additional Notes

No global logging monkeypatch, no global exception suppression, and no
early shutdown of the global tracer. No commit, push, or remote PR
update performed.

---

PR by Bits - [View session in
Datadog](https://app.datadoghq.com/code/c0096dbc-5d7d-4dc0-9dac-95643f43b151)

Comment @DataDog to request changes

---------

Co-authored-by: datadog-bits <263423550+datadog-bits@users.noreply.github.com>
Co-authored-by: gnufede <412857+gnufede@users.noreply.github.com>
@gnufede
gnufede marked this pull request as draft September 8, 2026 15:05
@gnufede gnufede changed the title fix(ci_visibility): prevent ddtrace logger propagation to closed root handlers fix(ci_visibility): filter ddtrace logs at closed stream handlers during pytest teardown Sep 8, 2026
@gnufede
gnufede marked this pull request as ready for review September 8, 2026 16:14

@datadog-official datadog-official Bot 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.

Datadog Autotest: PASS

More details

The filter blocks only ddtrace records when a standard stream is closed. Open streams and non-ddtrace records keep their current behavior.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit 9cb2b3f · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@gnufede

gnufede commented Sep 10, 2026

Copy link
Copy Markdown
Member Author

/merge -f --reason "unrelated system tests failures"

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-09-10 07:46:47 UTC ℹ️ Start processing command /merge -f --reason "unrelated system tests failures"


2026-09-10 07:46:52 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in main is approximately 0s (p90).


2026-09-10 07:47:02 UTC ℹ️ MergeQueue: This merge request was merged

Warning

This change was merged without running any pre merge CI checks

Reason: unrelated system tests failures

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 52d8ef1 into main Sep 10, 2026
511 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the gnufede/issue-16712 branch September 10, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: ddtrace throws logging exception during unit test runs with pytest with the new plugin

3 participants