How to print a stack trace in Python

Learn how to print a stack trace in Python. Explore various methods, tips, real-world applications, and common error debugging.

How to print a stack trace in Python
Published on: 
Tue
Apr 21, 2026
Updated on: 
Wed
Apr 22, 2026
The Replit Team

A Python stack trace is a report of active stack frames when an error occurs. It's an essential tool for debugging that lets you understand the program's flow before the exception.

You'll explore techniques using the traceback module to print and analyze stack traces. You'll also find practical tips, real-world applications, and debugging advice to resolve issues efficiently.

Using traceback.print_exc() for basic error handling

import traceback

try:
1/0
except:
traceback.print_exc()--OUTPUT--Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

The try...except block catches the ZeroDivisionError, preventing the program from crashing. Within the handler, traceback.print_exc() prints the full exception report to the console. This gives you the same detailed stack trace you'd see from an unhandled exception, but it allows your program to continue or exit gracefully.

Using traceback.print_exc() is more powerful than a simple print() statement for debugging. It provides the complete execution path leading to the error, offering crucial context to find and fix the root cause efficiently.

Basic approaches to stack trace handling

While printing the trace is a great start, you'll often need more control, like capturing it as a string or inspecting the current stack.

Using traceback.format_exc() to get trace as string

import traceback

try:
1/0
except:
error_message = traceback.format_exc()
print(error_message)--OUTPUT--Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

While traceback.print_exc() prints directly to your console, traceback.format_exc() gives you more control by returning the stack trace as a string. This allows you to capture the full error report in a variable for further processing.

  • You can log the error string to a file for later review.
  • You could send it to an error-monitoring service.
  • It can be included in a custom error message.

This flexibility is key for building robust logging systems where you decide how and where error information goes.

Using sys.exc_info() to access exception details

import sys
import traceback

try:
1/0
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
print(f"Exception type: {exc_type}")
print(f"Exception value: {exc_value}")
traceback.print_tb(exc_traceback)--OUTPUT--Exception type: <class 'ZeroDivisionError'>
Exception value: division by zero
File "<stdin>", line 2, in <module>

For more granular control, sys.exc_info() provides direct access to the current exception's details. It returns a tuple containing three key pieces of information that you can unpack and use individually.

  • Exception type: The class of the error, like ZeroDivisionError.
  • Exception value: The specific error message.
  • Traceback object: A raw object you can pass to other traceback functions, such as traceback.print_tb(), for custom processing.

Using traceback.print_stack() for current stack

import traceback

def func_a():
func_b()

def func_b():
traceback.print_stack()

func_a()--OUTPUT--File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in func_a
File "<stdin>", line 5, in func_b

Unlike functions that handle exceptions, traceback.print_stack() lets you inspect the call stack at any point, even when there isn't an error. It’s a proactive debugging tool for understanding your program’s flow. You can see exactly how you arrived at a certain point in your code.

  • The output shows the execution path, starting from the top-level script.
  • It then traces the call from func_a() into func_b().
  • Finally, it shows that traceback.print_stack() was called from within func_b().

Advanced stack trace techniques

To take your error handling further, you can programmatically extract, format, and log stack trace information for more precise control.

Using traceback.extract_tb() for custom formatting

import traceback
import sys

try:
1/0
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
tb_list = traceback.extract_tb(exc_traceback)
for filename, line, func, text in tb_list:
print(f"File: {filename}, Line: {line}, Function: {func}")--OUTPUT--File: <stdin>, Line: 2, Function: <module>

The traceback.extract_tb() function parses a traceback object into a structured list of frames, giving you precise control over formatting. Instead of a single string, you get a list that you can process programmatically.

  • Each frame in the list contains the filename, line number, and function name.
  • You can loop through this list to create custom log messages or reports.

This approach is ideal for building sophisticated error handlers that present information in a specific, structured way, rather than just printing the default output.

Integrating with the logging module

import logging

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

try:
1/0
except Exception as e:
logger.error("An error occurred", exc_info=True)--OUTPUT--ERROR:__main__:An error occurred
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

For production-ready applications, integrating stack traces with the logging module is best practice. It channels error reports into a centralized and configurable system—like a log file or an external service—rather than just printing to the console.

  • The key is setting the exc_info parameter to True in your logging call, such as logger.error().
  • This automatically captures the exception information and appends the full stack trace to your log message, giving you complete context for debugging.

Using traceback.format_stack() for programmatic access

import traceback

def get_current_stack():
stack = traceback.format_stack()[:-1] # Exclude this function call
return ''.join(stack)

def function_a():
result = get_current_stack()
print(result)

function_a()--OUTPUT--File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in function_a

The traceback.format_stack() function captures the current call stack as a list of strings, giving you programmatic access. Unlike print_stack(), which only prints to the console, you can store this list in a variable for custom processing.

  • The example joins the list into a single string that can be logged or displayed.
  • It uses slicing with [:-1] to neatly exclude the final frame—the call to get_current_stack() itself—from the output.

This approach is ideal for building custom debugging tools where you need to handle the stack trace as data.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. You don't need to worry about configuring environments or installing packages; just open your browser and begin.

While mastering functions like traceback.format_exc() is crucial for debugging, Agent 4 helps you move from piecing together individual techniques to building complete applications. Instead of just practicing error handling, you can describe the final tool you want to build, and Agent will create it:

  • A custom error logging service that captures exceptions with traceback.format_exc() and sends formatted reports to a dashboard.
  • A debugging utility that uses traceback.extract_tb() to parse stack traces and display them in a simplified, human-readable format.
  • An application health monitor that periodically uses traceback.format_stack() to log the execution path for performance analysis.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Working with stack traces involves navigating common challenges like silent errors, lost context when re-raising exceptions, and potential memory leaks.

Avoiding silent exception handling with traceback

A common mistake is using a bare except block with a pass statement. This practice, known as silent failure, swallows errors without a trace, making debugging nearly impossible because your program fails without telling you why. It’s a recipe for hidden bugs.

The code below shows how this can happen. The function attempts an operation that could raise a KeyError or ZeroDivisionError, but the empty except block ensures you'll never know what went wrong.

def process_data(data):
try:
result = data['key'] / 0
return result
except:
# Silent failure, no indication of what went wrong
pass

process_data({})

Here, the code tries to access a missing key and divide by zero. The empty except block swallows both errors, making the failure invisible. The following example shows how to expose the issue without crashing the program.

import traceback

def process_data(data):
try:
result = data['key'] / 0
return result
except Exception as e:
# Log the error with traceback information
print(f"Error: {e}")
traceback.print_exc()
return None

process_data({})

The corrected code catches the Exception and uses traceback.print_exc() to log the full error details, preventing a silent failure. Instead of crashing, it prints the error and returns None, allowing the program to continue gracefully. This is crucial in functions where operations might fail, like with dictionary lookups or calculations. This approach ensures you always know why an error occurred instead of having your program mysteriously stop working.

Preserving the original traceback when re-raising exceptions with from

When you catch an exception and raise a new one, you risk losing the original error's context. This common pitfall makes debugging much harder because the root cause gets hidden. The following code demonstrates how re-raising an exception obscures the original traceback.

def validate(value):
try:
if value <= 0:
raise ValueError("Value must be positive")
except ValueError:
# This loses the original traceback
raise RuntimeError("Validation failed")

validate(-5)

Raising a new RuntimeError within the except block discards the original ValueError. This hides the root cause, making it unclear why validation failed. The corrected code below demonstrates how to link the exceptions for a complete traceback.

def validate(value):
try:
if value <= 0:
raise ValueError("Value must be positive")
except ValueError as e:
# This preserves the original traceback
raise RuntimeError("Validation failed") from e

validate(-5)

Using raise RuntimeError(...) from e chains the exceptions together. This is the key to preserving the original ValueError, so you don't lose critical debugging information. The resulting traceback shows both the new error and the one that caused it, giving you the full story. You'll find this technique essential whenever you catch a low-level error and need to raise a more specific, application-level one without hiding the root cause.

Avoiding memory leaks when storing traceback objects

Avoiding memory leaks when storing traceback objects

Storing entire traceback objects in memory, especially in long-running applications, can create subtle memory leaks. These objects hold references to stack frames, which in turn keep other objects alive and prevent garbage collection. The following code demonstrates this potential issue.

import sys

stored_tracebacks = []

def log_operation(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
# Storing entire traceback objects can lead to memory issues
_, _, tb = sys.exc_info()
stored_tracebacks.append(tb)
raise
return wrapper

The wrapper function appends each raw traceback object to the stored_tracebacks list, which grows indefinitely and holds onto memory. The following example shows a better way to handle this without retaining the entire object.

import traceback

stored_traceback_texts = []

def log_operation(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
# Store the formatted text instead of the traceback object
tb_text = traceback.format_exc()
stored_traceback_texts.append(tb_text)
raise
return wrapper

The corrected code avoids memory leaks by converting the traceback into a string using traceback.format_exc(). Instead of appending the memory-heavy object to a list, it stores the lightweight text representation. This simple change prevents your application from holding onto unnecessary objects and their associated memory. You should watch for this issue in long-running applications like web servers or background workers, where errors might accumulate over time.

Real-world applications

Putting it all together, you can apply these traceback techniques to build robust features like automated error logging and debugging decorators.

Logging file processing errors with traceback

In batch operations like processing files, functions like traceback.format_exc() help you build resilient scripts that can log errors for individual files and continue running instead of crashing.

import traceback
import os

def process_files(directory):
try:
for filename in os.listdir(directory):
try:
with open(os.path.join(directory, filename), 'r') as f:
content = f.read()
print(f"Processed {filename}")
except Exception:
error_log = traceback.format_exc()
print(f"Error with file {filename}:")
print(error_log)
except FileNotFoundError:
print(f"Directory not found: {directory}")
traceback.print_exc()

# Example with a non-existent directory
process_files("non_existent_dir")

This function uses a two-tiered error-handling strategy to manage different failure types. An outer try...except block catches fatal errors, like a missing directory. It uses traceback.print_exc() to report the issue, which stops the script since it can't proceed.

  • Inside, a nested try...except loop handles each file individually. If one file is unreadable, traceback.format_exc() captures the error details as a string.
  • This isolates the problem, so the script can log the specific error and continue processing other files.

Building a decorator for error logging with traceback

You can build a decorator to create a clean, reusable error logger that automatically wraps any function and captures detailed stack traces when exceptions occur.

import traceback
from datetime import datetime

def log_error(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
error_type = type(e).__name__

# Get formatted traceback
tb_text = traceback.format_exc()

print(f"{timestamp} - {error_type} in {func.__name__}()")
print(f"Details: {tb_text}")

# Re-raise for higher-level handling
raise
return wrapper

@log_error
def divide(a, b):
return a / b

# Demo with error
try:
result = divide(10, 0)
except:
print("Error was caught and logged")

This code defines a log_error decorator that adds error reporting to any function it wraps. When divide(10, 0) fails, the decorator’s wrapper function intercepts the exception.

  • It captures the full stack trace as a string using traceback.format_exc().
  • It prints a custom log message with a timestamp and error details.
  • Finally, the raise keyword re-throws the original exception, allowing other parts of the program to handle it.

This pattern ensures errors are logged without being silently swallowed, maintaining a clear debugging trail.

Get started with Replit

Put your knowledge into practice. Ask Replit Agent to build "a file processor that logs errors with full tracebacks" or "a web dashboard that displays formatted exception reports from a live application."

Replit Agent will write the code, test for bugs, and help you deploy your application directly from your browser. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.