Skip to content

Conversation

@sawarn24
Copy link
Contributor

@sawarn24 sawarn24 commented Jan 4, 2026

Fixes #918

What this PR does

: Fixes infinite recursion in InterceptHandler.emit
: Prevents re-entering the same logging handler
: Safely forwards intercepted Uvicorn log records to root handlers
: Ensures each log record is processed exactly once

Context
When running PictoPy (especially on Python 3.12 and Windows), backend startup could crash with a RecursionError.

This happened because InterceptHandler.emit() was re-logging intercepted records using logger.log(...), where the logger was configured with the same InterceptHandler.
As a result, the handler kept calling itself indefinitely until Python hit the maximum recursion depth.

Solution

This change avoids re-logging entirely.

Instead of emitting a new log via logger.log(), the handler:

   : Updates the existing LogRecord in-place (adds [uvicorn] prefix)
   : Forwards the record directly to the root logger’s handlers
   : Explicitly skips the current handler to prevent re-entry

This follows Python logging best practices and guarantees there is no recursion.

Tested

: Backend starts successfully using python main.py
: Backend runs correctly with fastapi dev
: Sync microservice starts without errors
: Verified on Windows + Python 3.12
: Confirmed working in Tauri dev flow

Summary by CodeRabbit

  • Refactor
    • Standardized log messages to include a clear module prefix for more consistent, identifiable output.
    • Consolidated log dispatch to broadcast through root handlers, reducing per-module routing differences and preventing duplicate stack traces.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions github-actions bot added backend bug Something isn't working labels Jan 4, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

The InterceptHandler.emit() was reworked to stop re-logging through module-specific loggers; it now prefixes the incoming LogRecord message with the module name, clears record.args, record.exc_info, and record.stack_info, and forwards the modified record to all root logger handlers except itself.

Changes

Cohort / File(s) Summary
Logging handler recursion fix — backend
backend/app/logging/setup_logging.py
Rewrote InterceptHandler.emit() to avoid calling other loggers. The handler now mutates the LogRecord (prefixes message, clears args, exc_info, stack_info) and dispatches it to all root logger handlers except the current handler to prevent recursive emission.
Logging handler recursion fix — sync microservice
sync-microservice/app/logging/setup_logging.py
Same change mirrored: InterceptHandler.emit() modified to prefix message, clear record traceback/args, and broadcast the record to root handlers excluding itself, removing module-scoped re-logging to eliminate recursion.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I snipped a looping whisper from the lair,
Branded each note with its burrowed name,
Cleared the echoing crumbs and sent it fair,
To root paths straight — no circle, no flame,
Hop, hum, and startup's gentle claim.

Pre-merge checks and finishing touches

✅ 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 accurately describes the main change: fixing infinite recursion in InterceptHandler logging, which directly addresses the issue's primary problem.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #918: modifying InterceptHandler.emit() to avoid re-logging, updating LogRecord in-place, forwarding to root handlers, and skipping the current handler to prevent recursion.
Out of Scope Changes check ✅ Passed All changes are directly focused on fixing the infinite recursion issue by modifying the emit() method's implementation; no unrelated or out-of-scope changes are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4910a4a and f0f9107.

📒 Files selected for processing (1)
  • sync-microservice/app/logging/setup_logging.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.
📚 Learning: 2025-10-13T16:40:22.622Z
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.

Applied to files:

  • sync-microservice/app/logging/setup_logging.py
📚 Learning: 2025-10-01T20:44:19.146Z
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/utils/microservice.py:53-62
Timestamp: 2025-10-01T20:44:19.146Z
Learning: In backend/app/utils/microservice.py, the stream_logs function uses print() instead of logger to forward sync-microservice output because the sync-microservice already formats its logs using its own logging setup (get_sync_logger and LoggerWriter). Using logger would result in double-prefixing.

Applied to files:

  • sync-microservice/app/logging/setup_logging.py
🔇 Additional comments (1)
sync-microservice/app/logging/setup_logging.py (1)

254-263: Excellent fix for the infinite recursion issue.

The refactored emit() method correctly prevents infinite recursion by:

  • Modifying the LogRecord in-place instead of calling logger.log(), which would re-enter the same handler
  • Using record.getMessage() to get the formatted message before updating record.msg with the module prefix
  • Clearing record.args after formatting to prevent double formatting
  • Clearing record.exc_info and record.stack_info to avoid duplicating traces already captured in the formatted message
  • Forwarding the record directly to root logger handlers while explicitly skipping itself using identity comparison (is not self)

The uvicorn and asyncio logger configuration at lines 293–305 is already in place and properly routes these loggers through the InterceptHandler, ensuring the fix will work as intended across the logging stack.


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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/logging/setup_logging.py (1)

246-246: Consider using component_name instead of hardcoded "[uvicorn]".

The handler stores component_name in __init__ but uses a hardcoded "[uvicorn]" prefix. For consistency and flexibility, consider using self.component_name instead. This would make the handler more generic and reusable across different components.

🔎 Proposed refactor
-        record.msg = f"[uvicorn] {msg}"
+        record.msg = f"[{self.component_name}] {msg}"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe45e2f and 073d78c.

📒 Files selected for processing (1)
  • backend/app/logging/setup_logging.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.
📚 Learning: 2025-10-13T16:40:22.622Z
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.

Applied to files:

  • backend/app/logging/setup_logging.py
🔇 Additional comments (1)
backend/app/logging/setup_logging.py (1)

249-252: Good fix for the recursion issue!

The approach of directly calling handler.handle(record) on root handlers while skipping self correctly prevents the infinite recursion that was causing the RecursionError. This follows Python logging best practices by avoiding re-entry into the same handler.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/logging/setup_logging.py (1)

221-229: Consider removing or utilizing the unused component_name parameter.

The component_name parameter is stored in self.component_name but never referenced in the emit() method. You could either:

  1. Remove the parameter if it's not needed, or
  2. Use it to create dynamic prefixes (as suggested in the previous comment)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 073d78c and e993f70.

📒 Files selected for processing (1)
  • backend/app/logging/setup_logging.py
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.
📚 Learning: 2025-10-13T16:40:22.622Z
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/logging/setup_logging.py:224-243
Timestamp: 2025-10-13T16:40:22.622Z
Learning: In the PictoPy project's centralized logging system (backend/app/logging/setup_logging.py and sync-microservice/app/logging/setup_logging.py), stack traces should not be shown in logs. When rerouting logs through InterceptHandler, exc_info and stack_info should not be preserved.

Applied to files:

  • backend/app/logging/setup_logging.py
📚 Learning: 2025-10-15T07:13:34.502Z
Learnt from: Hemil36
Repo: AOSSIE-Org/PictoPy PR: 548
File: backend/app/database/images.py:115-115
Timestamp: 2025-10-15T07:13:34.502Z
Learning: In the PictoPy backend and sync-microservice, exception logging using `logger.error(f"...")` without stack traces (i.e., without `exc_info=True` or `logger.exception()`) is acceptable for console logging, as this is a deliberate design decision made with the maintainer for console-only logs.

Applied to files:

  • backend/app/logging/setup_logging.py

Copy link
Contributor

@rahulharpal1603 rahulharpal1603 left a comment

Choose a reason for hiding this comment

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

Apply the same change in sync-microservice/app/logging/setup_logging.py

@sawarn24
Copy link
Contributor Author

sawarn24 commented Jan 6, 2026

@rahulharpal1603 I’ve applied the same changes to sync-microservice/app/logging/setup_logging.py as requested.
Please review when you get a chance.

@rahulharpal1603 rahulharpal1603 merged commit 4647bac into AOSSIE-Org:main Jan 6, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RecursionError during backend startup on Python 3.12 due to recursive logging handler

2 participants