← Python Code Setup & Execution
Browse Python Concepts

Reading a Traceback Correctly

Mental Model

Imagine a traceback as a stack of dominoes falling. While the last domino to fall (the final exception) might be the one you see, the root cause is the very first domino that initiated the chain reaction. Python's chained tracebacks effectively show you all the dominoes in order.

Rule: When reading a Python traceback, always scan up to the top of the error chain to locate the original root cause exception.

The Setup

You are reviewing logs for a crashed microservice. An exception chain was raised, and you need to figure out which line actually triggered the original failure versus which line handled it.

What Does This Print?

Broken code
Python
def fetch_data_from_cache(key):
    raise KeyError(f"Key '{key}' not found in cache")

def get_user_profile(user_id):
    try:
        fetch_data_from_cache(user_id)
    except KeyError as e:
        # Chaining a new exception
        raise RuntimeError("Failed to resolve user profile") from e

# Executing the entry point
get_user_profile("user_12345")
Read the code and think about how Python formats the traceback output. Which exception will print first, and where should you look to find the root source of the crash?

The Output

What actually happens
Traceback (most recent call last): File "script.py", line 7, in get_user_profile fetch_data_from_cache(user_id) File "script.py", line 2, in fetch_data_from_cache raise KeyError(f"Key '{key}' not found in cache") KeyError: "Key 'user_12345' not found in cache" The above exception was the direct cause of the following exception: Traceback (most recent call last): File "script.py", line 12, in <module> get_user_profile("user_12345") File "script.py", line 9, in get_user_profile raise RuntimeError("Failed to resolve user profile") from e RuntimeError: Failed to resolve user profile

Python's traceback outputs show the original exception at the top of the terminal output and the final raised exception at the very bottom. When an exception is caught and re-raised using raise ... from e, the entire historical chain is preserved in the terminal. If you only look at the bottom line, you will miss the root cause of the error, leading to misdirected debugging and wasted hours patching secondary symptoms. The stack trace lists active execution frames in chronological order, with the oldest events printed first. Understanding this structured hierarchy allows developers to locate exactly where the crash originated, rather than focus on where the exception propagation halted.

Why Python Does This

CPython attaches traceback objects to exceptions via the __traceback__ attribute during frame unwinding. When you raise an exception inside an except block, CPython automatically links the existing exception to the new exception's __context__ attribute. If you use the explicit raise ... from e syntax, it populates the __cause__ attribute. When printing the exception to stderr, the interpreter's default formatting logic recursively climbs the __cause__ or __context__ chain, outputting each traceback starting from the earliest exception. This ensures that the chain of causality is never lost during execution error handling.

The Fix

Corrected pattern
Python
def fetch_data_from_cache(key):
    raise KeyError(f"Key '{key}' not found in cache")

def get_user_profile(user_id):
    try:
        fetch_data_from_cache(user_id)
    except KeyError as e:
        raise RuntimeError("Failed to resolve user profile") from e

try:
    get_user_profile("user_12345")
except RuntimeError as exc:
    # The bottom of the chain is what you caught — not the root cause
    print(f"Caught: {exc}")

    # Walk up the chain to find the original failure
    root = exc
    while root.__cause__ is not None:
        root = root.__cause__
    print(f"Root cause: {type(root).__name__}: {root}")
    # Root cause: KeyError: "Key 'user_12345' not found in cache"
    # Fix the root, not the symptom

Accessing __cause__ programmatically (or reading from the top of the printed traceback) exposes the original exception before it was caught and re-raised. Fixing the root cause — the KeyError from the cache miss — eliminates the chain entirely, rather than patching the RuntimeError wrapper that is merely a symptom.

How This Fails in Real Systems

An API crashed inside a third-party payment callback. The developer looked only at the bottom line of the logs, which said 'KeyError: status', and spent 8 hours rewriting local parsing logic. In reality, the top of the chain contained an SSLError indicating the outbound connection failed, which had been caught and re-raised as a generic KeyError.

Key Takeaway

When reading a Python traceback, always scan up to the top of the error chain to locate the original root cause exception.
Common mistake: Developers often focus solely on the last exception in a chained traceback, missing the crucial The above exception was the direct cause of the following exception: section, which points to the original, underlying problem.