Python traceback Module — Deep Dive
Internal architecture
The traceback module operates on traceback objects — the third element of the tuple returned by sys.exc_info(). A traceback object is a linked list of frames. Each node has tb_frame (the execution frame), tb_lineno (line number where the exception occurred), and tb_next (pointer to the next traceback object in the chain). The module walks this linked list to produce human-readable output.
Working with StackSummary and FrameSummary
Since Python 3.5, extract_tb() returns a StackSummary object rather than a plain list of tuples. This matters for customization:
import traceback
import sys
try:
1 / 0
except ZeroDivisionError:
tb = sys.exc_info()[2]
summary = traceback.extract_tb(tb)
# Filter frames to only show your application code
app_frames = traceback.StackSummary.from_list(
[f for f in summary if "/my_app/" in f.filename]
)
print("".join(app_frames.format()))
StackSummary.format() returns a list of strings, each ending with a newline. The from_list class method lets you construct a new StackSummary from any iterable of FrameSummary objects or 4-tuples, which is how you build filtered or annotated tracebacks.
Custom FrameSummary with local variables
FrameSummary accepts a locals parameter that captures local variable values at each frame:
import traceback
import sys
def process_order(order_id, amount):
if amount <= 0:
raise ValueError(f"Invalid amount: {amount}")
try:
process_order("ORD-7891", -50)
except ValueError:
tb = sys.exc_info()[2]
summary = traceback.extract_tb(tb, limit=10)
# Re-extract with locals captured
detailed = traceback.StackSummary.extract(
traceback.walk_tb(tb), capture_locals=True
)
for frame in detailed:
print(f"{frame.filename}:{frame.lineno} in {frame.name}")
if frame.locals:
for var, val in frame.locals.items():
print(f" {var} = {val}")
This is expensive (it calls repr() on every local variable) so you should only enable it in development or for targeted error investigation, not in high-throughput production paths.
Handling chained exceptions
Python 3 introduced exception chaining with raise ... from ... and implicit chaining. The traceback module handles both through TracebackException:
import traceback
try:
try:
open("/nonexistent/path")
except FileNotFoundError as e:
raise RuntimeError("Config load failed") from e
except RuntimeError:
te = traceback.TracebackException(*sys.exc_info())
# Full chain including "The above exception was the direct cause..."
full_output = "".join(te.format())
print(full_output)
# Access the chain programmatically
cause = te.__cause__ # Another TracebackException
print(f"Root cause: {cause.exc_type.__name__}: {cause}")
TracebackException is the most powerful class in the module. It pre-processes all the frame data at creation time and stores lightweight string representations instead of keeping live frame references. This prevents tracebacks from keeping large objects alive through reference cycles.
The walk_tb and walk_stack generators
Two generator functions provide low-level iteration:
import traceback
import sys
# walk_tb: iterate over a traceback chain
try:
eval("1/0")
except:
for frame, lineno in traceback.walk_tb(sys.exc_info()[2]):
print(f"Frame: {frame.f_code.co_name} at line {lineno}")
print(f" File: {frame.f_code.co_filename}")
print(f" Locals: {list(frame.f_locals.keys())}")
# walk_stack: iterate over the current call stack (no exception needed)
for frame, lineno in traceback.walk_stack(None):
print(f"Stack: {frame.f_code.co_name}:{lineno}")
These are useful when you need access to live frame objects rather than the summarized data that extract_tb provides. Live frames give you access to f_locals, f_globals, and f_code for deep inspection.
Production patterns
Pattern 1: Structured error logging
import traceback
import sys
import json
from datetime import datetime, timezone
def structured_error_handler(exc_type, exc_value, exc_tb):
te = traceback.TracebackException(exc_type, exc_value, exc_tb)
frames = traceback.extract_tb(exc_tb)
error_record = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"error_type": exc_type.__name__,
"error_message": str(exc_value),
"frames": [
{
"file": f.filename,
"line": f.lineno,
"function": f.name,
"code": f.line,
}
for f in frames
],
"full_traceback": "".join(te.format()),
}
# Send to your logging pipeline
print(json.dumps(error_record, indent=2))
sys.excepthook = structured_error_handler
Pattern 2: Traceback scrubbing for security
Production tracebacks can leak sensitive information — file paths reveal server structure, local variables might contain API keys or passwords. Scrub before logging:
import traceback
import sys
import re
SENSITIVE_VARS = {"password", "token", "secret", "api_key", "authorization"}
def scrub_traceback(exc_type, exc_value, exc_tb):
summary = traceback.StackSummary.extract(
traceback.walk_tb(exc_tb), capture_locals=True
)
for frame in summary:
# Scrub file paths to relative
frame.filename = frame.filename.split("/")[-1]
# Redact sensitive local variables
if frame.locals:
for key in list(frame.locals.keys()):
if key.lower() in SENSITIVE_VARS:
frame.locals[key] = "***REDACTED***"
return "".join(summary.format())
Pattern 3: Async-aware traceback formatting
When using asyncio, tracebacks can be confusing because they mix framework internals with your code. Filter the noise:
import traceback
import sys
def format_async_traceback(exc_tb):
frames = traceback.extract_tb(exc_tb)
# Remove asyncio internals — keep only your application frames
app_frames = [
f for f in frames
if "asyncio" not in f.filename
and "concurrent/futures" not in f.filename
]
return "".join(traceback.StackSummary.from_list(app_frames).format())
Performance considerations
Creating TracebackException objects is intentionally designed to be safe for long-lived storage. Unlike raw traceback objects (which hold references to live frames and their local variables), TracebackException converts everything to strings immediately. This means:
- No risk of keeping large data structures alive through traceback references
- Safe to store in queues for deferred processing
- Thread-safe once created (immutable string data)
The tradeoff is that capture_locals=True adds overhead proportional to the total size of repr() output for all locals in all frames. For a 20-frame deep traceback with complex objects, this can take milliseconds — negligible for error paths but worth knowing about.
Traceback limits and sys.tracebacklimit
The global sys.tracebacklimit controls how many levels the default exception printer shows. The traceback module’s functions respect this unless you pass an explicit limit argument. Setting sys.tracebacklimit = 0 suppresses all tracebacks (even the exception line in Python 3.12+), which is sometimes done in production to prevent information leakage. The traceback module itself always gives you access to all frames regardless of this setting when you use extract_tb() directly.
Comparison with alternative approaches
| Approach | Best for | Limitation |
|---|---|---|
traceback module | Custom error pipelines, frame filtering | Manual integration |
logging.exception() | Standard log-based error reporting | Less frame-level control |
sentry-sdk | Production monitoring with grouping | External dependency |
faulthandler | Segfaults and hung processes | No Python-level customization |
cgitb (deprecated 3.11) | HTML error pages | Removed in 3.13 |
The one thing to remember: TracebackException is the power tool — it captures the full exception chain safely, converts frames to lightweight data, and lets you build any error reporting format your production system needs.
See Also
- Python Ast Module Code Analysis How Python's ast module reads your code like a grammar teacher diagrams sentences — turning source text into a tree you can inspect and change.
- Python Dis Module Bytecode How Python's dis module lets you peek at the secret instructions your computer actually runs when it executes your Python code.
- Python Gc Module Internals How Python's garbage collector automatically cleans up memory you are no longer using — like a tidy roommate for your program.
- Python Importlib Custom Loaders How Python's importlib lets you teach Python to load code from anywhere — databases, zip files, the internet, or even generated on the fly.
- Python Site Customization How Python's site module sets up your environment before your code even starts running — the invisible first step of every Python program.