How to Print an Exception Chain in Python for Debugging

A helper that walks an exception's __cause__ and __context__ chain, printing each level with indentation to make debugging nested errors clearer.

Medium Python 3.9+ Aug 9, 2026 Errors & debugging 12 views 0 copies

Python code

36 lines
Python 3.9+
import sys
import traceback

def pretty_exception_chain(exc):
    """Print the full exception chain with cause/context details."""
    chain = []
    current = exc
    seen = set()
    
    while current is not None and id(current) not in seen:
        seen.add(id(current))
        chain.append(current)
        current = current.__cause__ or current.__context__
    
    for i, exception in enumerate(reversed(chain)):
        indent = "  " * i
        print(f"{indent}Level {i}: {exception.__class__.__name__}: {exception}")
        if exception.__cause__:
            print(f"{indent}  caused by:")
        elif exception.__context__ and not exception.__suppress_context__:
            print(f"{indent}  context:")

def demonstrate_chain():
    try:
        try:
            raise ValueError("invalid data")
        except ValueError as e:
            raise KeyError("missing key") from e
    except KeyError as final_error:
        print("--- Exception Chain ---")
        pretty_exception_chain(final_error)
        print("\n--- Standard traceback ---")
        traceback.print_exc()

if __name__ == "__main__":
    demonstrate_chain()

Output

stdout
--- Exception Chain ---
Level 0: KeyError: missing key
  caused by:
Level 1: ValueError: invalid data

--- Standard traceback ---
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
    raise ValueError("invalid data")
ValueError: invalid data

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
    raise KeyError("missing key") from e
KeyError: missing key

How it works

Exceptions maintain a chain through __cause__ (set explicitly with raise ... from) and __context__ (implicit when an exception is raised while handling another). This code walks that linked list using a set to prevent infinite loops in circular chains. Reversing the list shows the most recent exception first, with indentation reflecting depth. The __suppress_context__ flag tells whether the implicit context was overridden by an explicit cause, so the output labels the relationship correctly.

Common mistakes

  • Using `__context__` alone, which misses explicitly set `__cause__` relationships.
  • Forgetting to guard against circular exception chains, causing an infinite loop.
  • Not checking `__suppress_context__` to distinguish explicit cause from implicit context.
  • Confusing the order—printing the chain from oldest to newest without reversing.

Variations

  1. Use `traceback.format_exception(exc)` to get a formatted string instead of printing.
  2. Add a maximum depth parameter to avoid deep recursion in pathological chains.

Real-world use cases

  • Diagnosing multi-layer service call failures where one error masks another.
  • Building custom logging that captures the full root-cause story in incident reports.
  • Teaching junior engineers to read implicit Python exception context in tests and CI logs.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Errors & debugging

Related tutorials and quizzes for this topic.