How would you use sys.excepthook so unhandled tracebacks stop reaching users?
answer
- The interpreter asks something to print it
- One attribute decides every crash message
- Threads and finalizers take different routes
- sys.__excepthook__ keeps the original
- tracebacklimit truncates frames, not the message
basics
~20 sAssign a function to sys.excepthook: the interpreter calls it instead of printing when an exception reaches the top of the main thread. Format the traceback into your private log with a reference id, and print only a one-line message. Threads need threading.excepthook separately.
solid answer
~50 s`sys.excepthook` is the last-chance handler the interpreter invokes with `(exc_type, exc, traceback)` when an exception escapes the top of the **main** thread; the original is kept as `sys.__excepthook__`. Replacing it lets a process decide once, centrally, that a crash writes a full `traceback.format_exception` result to the log sink and shows the operator or user only a generic line plus a reference id. Its blind spots matter as much as its use: it does not fire for exceptions in other threads — that is `threading.excepthook`, added in 3.8 — nor for exceptions swallowed during finalization, which go to `sys.unraisablehook`, nor for anything an inner `except` already caught. `sys.tracebacklimit` is a different and much blunter knob: it truncates how many frames the default display prints, keeping the ones nearest the failure. It is a noise control, not a security control.
code
python · 18 linesimport subprocess, sys
SRC = '''
import sys, traceback, uuid
def hook(exc_type, exc, tb):
ref = uuid.uuid4().hex[:8]
print("[private log] ref=" + ref, file=sys.stderr)
print("".join(traceback.format_exception(exc_type, exc, tb)), end="", file=sys.stderr)
print("the import failed; quote reference " + ref, file=sys.stderr)
sys.excepthook = hook
raise RuntimeError("catalogue row 41 rejected")
'''
done = subprocess.run([sys.executable, "-c", SRC], capture_output=True, text=True)
print(done.stderr, end="")
print("exit status:", done.returncode)go deeper
Know that sys.excepthook is the function the interpreter calls to print an unhandled exception, and that assigning your own replaces the default traceback output for the main thread.
Explain the signature it receives, that sys.excepthook holds the original, and that sys.tracebacklimit only trims frames from the default display while the exception message always survives.
Show that you install the hook in the entry point rather than at import, cover threading.excepthook and sys.unraisablehook too, and pair the hook with a reference-id scheme shared with the request-level error boundary.
Own where crash detail is allowed to land across a fleet: which sinks retain full tracebacks, who can read them, how a reference id is correlated across services, and how a crash stays diagnosable without becoming public.
## What sys.excepthook is When an exception propagates out of the top-level code of the **main** thread, CPython does not print the traceback itself. It calls `sys.excepthook(exc_type, exc_value, traceback_object)`. The default implementation formats and writes to `sys.stderr`, and the pristine original is preserved as `sys.__excepthook__` so you can delegate to it or restore it. Because it is just an attribute, a process can install its own at startup: ```python import sys, traceback, uuid def hook(exc_type, exc, tb): ref = uuid.uuid4().hex[:8] audit_log.error("crash ref=%s\n%s", ref, "".join(traceback.format_exception(exc_type, exc, tb))) print(f"the import failed; quote reference {ref}", file=sys.stderr) sys.excepthook = hook ``` This is the process-wide equivalent of the error boundary you put around a request handler, and it is what catches the cases the boundary missed: a failure during startup before any handler exists, a crash in a batch entry point, an error raised while shutting down. For a museum-catalogue importer run as a scheduled job, the difference is whether the shared operations console shows a full traceback with source lines, install paths and a driver error containing the connection string, or one line and an id. ## The blind spots — this is the senior half of the answer An interviewer is really asking whether you know where the hook *does not* run. * **Other threads.** An exception escaping a thread's `run` never reaches `sys.excepthook`; it goes to `threading.excepthook`, introduced in 3.8, and the default prints the traceback to stderr with the thread name. A process that installs only `sys.excepthook` and then does its work in a worker thread has changed nothing about what gets printed. * **Finalization.** An exception raised in `__del__`, in a weakref callback, or during interpreter shutdown cannot be propagated. It is routed to `sys.unraisablehook` instead, also 3.8. These leak quietly, at the worst possible time, when your logging may already be torn down. * **Anything already caught.** The hook is the top of the propagation path. Code that catches an exception and prints it, or returns it in a response, never gets there. Installing the hook does not retroactively make sloppy inner handlers safe. * **A child process.** Every interpreter has its own `sys.excepthook`; a worker started under a process-based executor does not inherit the parent's installed hook through every start method, so hook installation belongs in the worker's entry point, not only in the parent's. * **Faults below Python.** A segmentation fault or a hard abort in native code bypasses the hook entirely; that output comes from the fault handler, not from `sys`. ## sys.tracebacklimit, and why it is not a control `sys.tracebacklimit` is an integer attribute that is **not defined by default**; setting it changes how many frames the interpreter's default display prints. Setting it to `0` (or a negative value) reduces the output to just the exception type and message, with no "Traceback (most recent call last):" header at all. Three things make it the wrong tool for disclosure: 1. **It keeps the wrong end.** With `sys.tracebacklimit = 1` on 3.14, the frame you keep is the innermost one — nearest the failure — which is typically the most revealing. (This is the opposite of `traceback.print_exception(exc, limit=1)`, whose positive limit keeps the outermost frame; the interpreter's default display and the `traceback` module's limit count from different ends.) 2. **It leaves the message.** The exception type and its message survive at any limit, and the message is frequently where the path, the DSN or the offending value lives. 3. **Nothing else obeys it.** Any code path that calls `traceback.format_exc()` itself — your own boundary, a library's error reporter, a debug page — produces a full traceback regardless. So use it, if at all, as a tidiness knob for a CLI where a deep stack is noise for the user. Do not present it as a hardening measure; the hardening is the hook plus the handler policy. ## Putting it together A hardened entry point does four things before it does any work: installs `sys.excepthook`, installs `threading.excepthook`, installs `sys.unraisablehook`, and configures the log sink they all write to. Each of them formats the traceback into that sink and emits only a reference outward. Then the error boundary inside the request path does the same thing with the same id scheme, so support has one lookup regardless of where the failure surfaced. Restoring `sys.__excepthook__` in tests keeps the real traceback visible where you want it.
- Your worker does its work in a thread. Does sys.excepthook cover it?No. An exception escaping a thread's run method is handled by threading.excepthook, added in 3.8, whose default prints the full traceback with the thread name. You have to install both, plus sys.unraisablehook for exceptions raised in __del__ and during finalization, or the hardening only covers the main thread's own stack.
- Is setting sys.tracebacklimit to 0 an adequate way to hide a traceback?No. It only shortens the interpreter's default display; the exception type and message still print, and those often carry the path or connection string. Anything that formats its own traceback with traceback.format_exc ignores the setting entirely. Treat it as noise control for a CLI, not as a disclosure control.
- How do you keep real tracebacks while testing code that installs a hook?Restore sys.__excepthook__, which the interpreter preserves as the untouched original, or install the hook only behind the production entry point rather than at import time. Installing it at import time is the usual mistake: it silences the test runner's own reporting and makes failures far harder to diagnose.
sys.excepthook is the press office standing between an incident and the front door: everything still gets written down inside, but only one sentence and a case number leave the building.
saying these in an interview costs you the question
- Believes sys.excepthook also catches exceptions in threads
- Treats sys.tracebacklimit as a security control
- Installs the hook at import time, silencing test output
- Thinks the hook can resume execution after the exception
- Forgets sys.__excepthook__ exists for delegating or restoring
- Assumes a child process inherits the parent's installed hook