Skip to content

Prevent logging rollover errors from corrupting the TUI#116

Merged
SSobol77 merged 1 commit into
mainfrom
issue/logging-rotation-no-terminal-stderr
Jun 25, 2026
Merged

Prevent logging rollover errors from corrupting the TUI#116
SSobol77 merged 1 commit into
mainfrom
issue/logging-rotation-no-terminal-stderr

Conversation

@SSobol77

@SSobol77 SSobol77 commented Jun 25, 2026

Copy link
Copy Markdown
Owner

This PR fixes a runtime logging failure where RotatingFileHandler rollover errors could print Python logging tracebacks directly into the curses TUI.

Changes:

  • adds SafeRotatingFileHandler for editor.log, error.log, and keytrace.log
  • catches rollover FileNotFoundError/OSError and reopens the base log stream
  • prevents logging-internal errors from writing to stderr/stdout during runtime
  • sets logging.raiseExceptions = False during ECLI logging setup
  • closes old root/key-event handlers during reconfiguration
  • adds regression tests for rollover failure, idempotent setup, closed handlers, no stderr stream handlers, and keytrace safety

Validation:

  • make clean-logs
  • uv run ruff check src tests
  • uv run ruff format --check src tests
  • uv run python scripts/check_runtime_imports.py
  • uv run pytest -q tests/utils tests/core
  • uv run pytest -q tests/extensions/test_textmate_multiline_protection.py
  • uv run pytest -q tests/ui/test_file_browser_panel.py tests/ui/test_pysh_console_panel.py tests/ui/test_terminal_panel_reservation.py
  • uv run pytest -q tests/utils/test_logging_config.py

Manual smoke:

  • make clean-logs
  • uv run ecli /tmp/ecli_textmate_multiline_smoke.ts
  • arrows, F10, Esc, Ctrl+Q
  • no "--- Logging error ---"
  • no traceback
  • no FileNotFoundError logs/editor.log.*
  • no curses corruption

Non-goals:

  • no F4 Diagnostics work
  • no F7 AI work
  • no F10 File Browser changes
  • no F11 PySH changes
  • no TextMate changes
  • no extension asset changes

Summary by CodeRabbit

  • Bug Fixes
    • Improved log file rotation reliability and reduced the risk of logging failures during rollover.
    • Prevented duplicate log entries and stale file handles when logging is reconfigured.
    • Kept logging quiet in normal use while avoiding unwanted terminal error output.
  • Tests
    • Added coverage for repeated logging setup, safe rotation behavior, file cleanup, and optional key event logging.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds SafeRotatingFileHandler and updates setup_logging() to remove and close existing handlers before attaching new file handlers. The tests cover cleanup, rollover recovery, rotation, console logging behavior, and keytrace logging.

Changes

Safe logging reconfiguration

Layer / File(s) Summary
Handler safety primitives
src/ecli/utils/logging_config.py
threading is imported, and SafeRotatingFileHandler adds rollover locking, failure recovery, handleError() stderr suppression, and _remove_and_close_handlers() for logger cleanup.
Logging setup wiring
src/ecli/utils/logging_config.py
Docstrings and setup_logging() remove and close old root and ecli.keyevents handlers, set logging.raiseExceptions = False, and use SafeRotatingFileHandler for editor.log, error.log, and keytrace.log.
Reconfiguration tests
tests/utils/test_logging_config.py
The test module adds logging-state isolation plus tests for handler duplication, cleanup of pre-existing handlers, and closure of replaced handlers across repeated setup_logging() calls.
Rollover and keytrace tests
tests/utils/test_logging_config.py
The test module checks rollover failure recovery, successful rotation, console-handler behavior, and keytrace handler setup.

Sequence Diagram(s)

sequenceDiagram
  participant SafeRotatingFileHandler
  participant RotatingFileHandler
  participant logging
  SafeRotatingFileHandler->>RotatingFileHandler: doRollover()
  RotatingFileHandler-->>SafeRotatingFileHandler: FileNotFoundError
  SafeRotatingFileHandler->>SafeRotatingFileHandler: recreate base stream
  SafeRotatingFileHandler->>logging: handleError() with raiseExceptions disabled
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preventing logging rollover errors from corrupting the TUI.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/logging-rotation-no-terminal-stderr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ecli/utils/logging_config.py`:
- Around line 80-83: The handleError method in Logging-related code has a
redundant trailing return after already declaring a None return type and
deleting the unused record parameter. Remove the explicit return from
handleError in the logging_config module so the method ends after del record,
keeping the suppression behavior unchanged.
- Around line 70-78: The doRollover method in the logging handler has duplicate
exception handling because FileNotFoundError is already covered by OSError, and
both branches call _recover_base_stream(). Simplify the rollover logic by
keeping a single except OSError branch in doRollover and routing all recovery
through _recover_base_stream(), using the existing _rollover_lock and superclass
call unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6bf42151-9eed-46c0-a837-758ae98b1f9d

📥 Commits

Reviewing files that changed from the base of the PR and between 08c6a3b and 65a6447.

📒 Files selected for processing (2)
  • src/ecli/utils/logging_config.py
  • tests/utils/test_logging_config.py

Comment on lines +70 to +78
def doRollover(self) -> None:
"""Perform rollover with in-process serialization and recovery."""
with self._rollover_lock:
try:
super().doRollover()
except FileNotFoundError:
self._recover_base_stream()
except OSError:
self._recover_base_stream()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Collapse the duplicate except branches.

FileNotFoundError is a subclass of OSError, and both handlers invoke the same _recover_base_stream(). The separate branch is dead and can be merged for clarity.

♻️ Proposed simplification
         with self._rollover_lock:
             try:
                 super().doRollover()
-            except FileNotFoundError:
-                self._recover_base_stream()
             except OSError:
                 self._recover_base_stream()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def doRollover(self) -> None:
"""Perform rollover with in-process serialization and recovery."""
with self._rollover_lock:
try:
super().doRollover()
except FileNotFoundError:
self._recover_base_stream()
except OSError:
self._recover_base_stream()
def doRollover(self) -> None:
"""Perform rollover with in-process serialization and recovery."""
with self._rollover_lock:
try:
super().doRollover()
except OSError:
self._recover_base_stream()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ecli/utils/logging_config.py` around lines 70 - 78, The doRollover method
in the logging handler has duplicate exception handling because
FileNotFoundError is already covered by OSError, and both branches call
_recover_base_stream(). Simplify the rollover logic by keeping a single except
OSError branch in doRollover and routing all recovery through
_recover_base_stream(), using the existing _rollover_lock and superclass call
unchanged.

Comment on lines +80 to +83
def handleError(self, record: logging.LogRecord) -> None:
"""Suppress logging-internal stderr writes during curses runtime."""
del record
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the redundant trailing return.

The explicit return at the end of a None-returning method is superfluous (SonarCloud). The del record already documents the intentionally-unused parameter.

♻️ Proposed cleanup
     def handleError(self, record: logging.LogRecord) -> None:
         """Suppress logging-internal stderr writes during curses runtime."""
         del record
-        return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def handleError(self, record: logging.LogRecord) -> None:
"""Suppress logging-internal stderr writes during curses runtime."""
del record
return
def handleError(self, record: logging.LogRecord) -> None:
"""Suppress logging-internal stderr writes during curses runtime."""
del record
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 83-83: Remove this redundant return.

See more on https://sonarcloud.io/project/issues?id=SSobol77_ecli&issues=AZ7_N57Km0zzAkiNzMEd&open=AZ7_N57Km0zzAkiNzMEd&pullRequest=116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ecli/utils/logging_config.py` around lines 80 - 83, The handleError
method in Logging-related code has a redundant trailing return after already
declaring a None return type and deleting the unused record parameter. Remove
the explicit return from handleError in the logging_config module so the method
ends after del record, keeping the suppression behavior unchanged.

Source: Linters/SAST tools

@SSobol77 SSobol77 merged commit d4f1a18 into main Jun 25, 2026
7 checks passed
@SSobol77 SSobol77 deleted the issue/logging-rotation-no-terminal-stderr branch June 25, 2026 14:52
@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant