Print stack traces in Python

David Y.
jump to solution

The Problem

When a Python script encounters an unhandled exception, it will normally print a stack trace to help with debugging. This is useful, you may need more control over when and how stack traces are printed. For example, you may want to be able to:

  1. Print stack traces manually, at different points in the script’s execution, without raising an exception.
  2. Print the stack trace of a provided exception object.

The Solution

We can do both of these things using Python’s built-in traceback module, which provides stack traces in a manner identical to the Python interpreter itself.

In addition to printing stack traces, several other methods for tracing function execution in Python are available, as discussed in this answer.

The function traceback.print_stack will print a stack trace at the current point in our script’s invocation. The optional limit parameter allows us to control how many entries are printed — by default, the entire stack trace will be printed. Consider the following example script:

import traceback

def trace():
    traceback.print_stack()

def do_something():
    a = 1 + 2
    trace()

do_something()

When executed, this script will print the following stack trace:

  File "/tmp/trace.py", line 9, in <module>
    do_something()
  File "/tmp/trace.py", line 7, in do_something
    trace()
  File "/tmp/trace.py", line 3, in trace
    traceback.print_stack()

We can use traceback.print_exception to print the traceback of an exception object we pass to it, along with the usual exception information. This function has three required arguments: the exception’s class, the exception object, and the exception’s traceback. In this scenario, we can get all of those from the same object, so it makes sense to create a wrapper function, like the one below:

import traceback

def print_exc(exception):
    traceback.print_exception(type(exception), exception, exception.__traceback__)

We can then use this wrapper function in a larger script:

import traceback

def print_exc(exception):
    traceback.print_exception(type(exception), exception, exception.__traceback__)

my_dict = {}
saved_exception = None

try:
    a = my_dict["invalid"]
except Exception as ex:
    saved_exception = ex
    print("An error occurred")

if saved_exception:
    print("Here are the details of an exception that happened earlier:")
    print_exc(saved_exception)

When executed, this script will produce the following output:

An error occurred
Here are the details of an exception that happened earlier:
Traceback (most recent call last):
  File "/tmp/manual_exc.py", line 10, in <module>
    a = my_dict["invalid"]
        ~~~~~~~^^^^^^^^^^^
KeyError: 'invalid'

Note that traceback.print_exception prints the name and details of the exception below the stack trace. If we just want the stack trace on its own, we will need to take a more manual approach, using Python’s built-in inspect module to iterate through each frame of the exception’s traceback object. For example:

import inspect

def print_stacktrace(exception):
    stacktrace = exception.__traceback__

    for frame in inspect.getinnerframes(stacktrace):
        filename = frame.filename
        lineno = frame.lineno
        function = frame.function
        code_context = frame.code_context
        code_context = code_context[0].strip() if code_context else "No code context"
        print(f"File \"{filename}\", line {lineno}, in {function}")
        print(f"  {code_context}")

The inspect.getinnerframes function returns a list of FrameInfo objects, each of which contains the details of a single frame in the stack trace, including the filename, line number, function, and code context. Inside the for loop, we retrieve each of these and then print them, using a format that mimics Python’s default stack trace printouts. Unlike traceback.print_exception, this approach gives us complete control over how the stack trace is printed.

Here’s a modified version of our example script that only prints the stack trace of the exception provided:

import inspect

def print_stacktrace(exception):
    stacktrace = exception.__traceback__

    for frame in inspect.getinnerframes(stacktrace):
        filename = frame.filename
        lineno = frame.lineno
        function = frame.function
        code_context = frame.code_context
        code_context = code_context[0].strip() if code_context else "No code context"
        print(f"File \"{filename}\", line {lineno}, in {function}")
        print(f"  {code_context}")

my_dict = {}
saved_exception = None

try:
    a = my_dict["invalid"]
except Exception as ex:
    saved_exception = ex
    print("An error occurred")

if saved_exception:
    print("Here is the stack trace for the exception that happened earlier:")
    print_stacktrace(saved_exception)

When executed, it will print the following output:

An error occured
Here is the stack trace for the exception that happened earlier:
File "/tmp/pst.py", line 19, in <module>
  a = my_dict["invalid"]

Considered "not bad" by 4 million developers and more than 150,000 organizations worldwide, Sentry provides code-level observability to many of the world's best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.

Sentry