Remove process-wide sys.excepthook; harden log formatter (#1516)#1517
Merged
Conversation
Importing datajoint installed a global sys.excepthook that routed every uncaught exception in the host process through datajoint's logger. Combined with LevelAwareFormatter overriding format() without rendering exc_info, this reduced every uncaught exception to a contentless '[ERROR]: Uncaught exception' line, discarding the type, message, and traceback -- strictly worse than Python's default handler. - Remove the sys.excepthook installation. A library must not change how unrelated exceptions surface in the importing process; uncaught exceptions now fall through to Python's default handler (full traceback to stderr). - Harden LevelAwareFormatter to append exc_info/stack_info like the base logging.Formatter, so internal logger.exception()/exc_info=... calls never silently drop tracebacks. - Add regression tests.
MilagrosMarin
approved these changes
Jul 21, 2026
MilagrosMarin
left a comment
Contributor
There was a problem hiding this comment.
LGTM.
Two small non-blocking suggestions:
-
Formatter tests only exercise WARNING/ERROR levels. exc_info/stack_info handling is appended after the level branches, so it applies uniformly at runtime — but a
pytest.parametrizeon level acrossINFO,JOBS,WARNING,ERRORwould catch a future regression that adds an earlyreturn messageinside theJOBSorINFObranch. -
Out of scope, follow-up thought:
logging.py:17still doeslogging.Logger.jobs = jobs— same architectural pattern as the excepthook you're removing (library patching global state at import time). ADataJointLogger(logging.Logger)subclass would be the polite version, but separate PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1516.
Problem
Importing
datajointinstalled a process-widesys.excepthookas an import side effect, routing every uncaught exception in the host process — DataJoint-related or not — through DataJoint's logger. Combined withLevelAwareFormatteroverridingformat()without renderingexc_info, every uncaught exception collapsed to a contentless line:with no type, message, or traceback — strictly worse than Python's default handler, which would have printed the full traceback for free.
History (this is a regression, not the original design)
The hook was originally non-lossy: it inlined the exception message (
Uncaught exception: {exc_value}) and rendered a full traceback underDJ_LOG_LEVEL=DEBUGvia the stdlib formatter. Two later changes compounded into the silent-discard behavior:a9dee8d9— simplified the excepthook to rely solely onexc_info, dropping the inline message.41dad5d2— introduced the customLevelAwareFormatter, which overridesformat()and never rendersexc_info.Change
sys.excepthookinstallation. A library must not change how unrelated exceptions surface in the importing process. Uncaught exceptions now fall through to Python's default handler (full traceback to stderr). Nothing in the codebase reads the hook, and no internal code path attachesexc_infoto a log record, so removal is self-contained.LevelAwareFormatterto appendexc_info/stack_infolike the baselogging.Formatter, so any futurelogger.exception(...)/exc_info=…call never silently drops a traceback.tests/unit/test_logging.py): import no longer replacessys.excepthook; the formatter renders exception and stack info.The centralized
datajointlogger and its handler/level config are unchanged — only the process-global excepthook is removed.Compatibility
Behavior change worth a release note: fatal uncaught tracebacks now print to stderr (Python's default handler) rather than as a single line on stdout. This is the correct destination for a crash, and restores the full type/message/traceback that the previous hook discarded.