Python
Exception Handling
Logging
Uncaught Exceptions
Programming Tips

Logging uncaught exceptions in Python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python already prints a traceback for uncaught exceptions, but production systems usually need more than output to standard error. You often want uncaught exceptions recorded through the same logging pipeline as the rest of the application so they reach files, monitoring systems, or centralized log collection.

The main technique is to install top-level exception hooks. In modern Python, that often means sys.excepthook for the main thread, threading.excepthook for background threads, and an event-loop exception handler for asyncio code.

Log Main-Thread Uncaught Exceptions with sys.excepthook

Python calls sys.excepthook when an exception reaches the interpreter without being handled. You can replace it with a function that logs the full traceback:

python
1import logging
2import sys
3
4logging.basicConfig(
5    level=logging.ERROR,
6    format="%(asctime)s %(levelname)s %(message)s",
7    filename="app.log"
8)
9
10logger = logging.getLogger(__name__)
11
12def log_uncaught_exceptions(exc_type, exc_value, exc_traceback):
13    if issubclass(exc_type, KeyboardInterrupt):
14        sys.__excepthook__(exc_type, exc_value, exc_traceback)
15        return
16
17    logger.critical(
18        "Uncaught exception",
19        exc_info=(exc_type, exc_value, exc_traceback)
20    )
21
22sys.excepthook = log_uncaught_exceptions
23
24raise RuntimeError("boom")

That captures exceptions that would otherwise only print to the terminal.

Cover Background Threads Too

sys.excepthook does not automatically cover exceptions escaping Python threads. For that, use threading.excepthook on modern Python:

python
1import logging
2import threading
3
4logger = logging.getLogger(__name__)
5
6def thread_exception_handler(args):
7    logger.critical(
8        "Uncaught thread exception in %s",
9        args.thread.name,
10        exc_info=(args.exc_type, args.exc_value, args.exc_traceback)
11    )
12
13threading.excepthook = thread_exception_handler

Without this, thread failures can be easy to miss, especially in services with worker threads.

Handle asyncio Exceptions Explicitly

Asynchronous programs have another path for uncaught failures. The event loop can report exceptions from tasks or callbacks that no code awaited properly. Install an asyncio exception handler so those also reach your logger:

python
1import asyncio
2import logging
3
4logger = logging.getLogger(__name__)
5
6def loop_exception_handler(loop, context):
7    message = context.get("message", "Unhandled asyncio exception")
8    exception = context.get("exception")
9
10    if exception is not None:
11        logger.critical(message, exc_info=exception)
12    else:
13        logger.critical(message)
14
15async def broken_task():
16    raise ValueError("async failure")
17
18async def main():
19    loop = asyncio.get_running_loop()
20    loop.set_exception_handler(loop_exception_handler)
21    asyncio.create_task(broken_task())
22    await asyncio.sleep(0.1)
23
24asyncio.run(main())

This is important for async services where background tasks may fail after the original request handler has moved on.

Centralize Logging Setup Early

The hooks need to be installed near process startup. If you configure them only after part of the application has already started threads or event loops, you may miss early failures.

It is also worth deciding what should happen after logging. In many cases, the process should still terminate for an uncaught exception because the program reached an unexpected state. Logging is not the same as recovery.

Distinguish Uncaught from Caught Exceptions

Do not use uncaught-exception hooks as an excuse to stop writing local error handling. The hook is the last line of defense for failures that escaped normal control flow. Regular application errors should still be caught where the program can respond meaningfully.

A useful rule is:

  • catch expected failures where you can handle them
  • let truly unexpected failures reach the top-level hook so they are logged clearly

Common Pitfalls

  • Installing only sys.excepthook and assuming thread or async failures are covered too.
  • Swallowing exceptions after logging and leaving the process in an invalid state.
  • Forgetting to exempt KeyboardInterrupt, which makes normal shutdown awkward during development.
  • Treating uncaught-exception logging as a substitute for proper local exception handling.

Summary

  • Use sys.excepthook to log uncaught exceptions from the main thread.
  • Use threading.excepthook for uncaught exceptions in worker threads.
  • Use an event-loop exception handler for asyncio task and callback failures.
  • Install the hooks early during process startup.
  • Log uncaught exceptions for visibility, but still handle expected failures closer to where they occur.

Course illustration
Course illustration

All Rights Reserved.