Python Basics: Build Focused Error Reports with traceback
A command-line tool may need a short validation error, while a diagnostic report needs the failing call path. traceback lets you choose which details to retain and format. These examples intentionally raise and catch local exceptions, producing stable summaries instead of machine-specific absolute paths.
Prepare a scratch directory
These standalone examples were tested on Linux with Python 3.12.14. Use Python 3.10 or newer because format_exception_only accepts an exception directly in the first example. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the assertions remain enabled.
Create a fresh directory and keep using the same terminal:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Save each named source below as UTF-8. Avoid filenames that shadow standard-library modules.
1. Format an exception without its stack frames
Save as format_validation_error.py:
import traceback
def validate_count(count):
if count < 0:
raise ValueError("count must be nonnegative")
return count
assert validate_count(3) == 3
try:
validate_count(-2)
except ValueError as error:
message = "".join(traceback.format_exception_only(error)).strip()
assert message == "ValueError: count must be nonnegative"
print(message)
else:
raise AssertionError("Expected a validation failure")Run it:
python3 -B format_validation_error.pyOutput:
ValueError: count must be nonnegativeformat_exception_only returns text pieces containing the exception type and message without the call stack. Join them rather than assuming one item. This works well for an expected validation failure; unexpected failures may need a fuller diagnostic record. See format_exception_only.
2. Extract the innermost part of a call path
Save as summarize_failure_frames.py:
from pathlib import Path
import traceback
def read_count(text):
return int(text)
def build_summary(text):
return {"count": read_count(text)}
try:
build_summary("three")
except ValueError as error:
frames = traceback.extract_tb(error.__traceback__, limit=-2)
names = [frame.name for frame in frames]
assert names == ["build_summary", "read_count"]
deepest = frames[-1]
assert deepest.line == "return int(text)"
print("Call path:", " -> ".join(names))
print("File:", Path(deepest.filename).name)
print("Failing source:", deepest.line)
else:
raise AssertionError("Expected a conversion failure")Run it:
python3 -B summarize_failure_frames.pyOutput:
Call path: build_summary -> read_count
File: summarize_failure_frames.py
Failing source: return int(text)A negative limit retains the last frames, nearest the failure. FrameSummary exposes structured information, so this report prints a basename rather than an absolute path. Source lines are useful for local diagnosis but can contain private data. See extract_tb.
3. Keep a formatted snapshot without collecting locals
Save as capture_error_snapshot.py:
import traceback
def load_setting():
internal_note = "example-private-value"
raise LookupError("setting is missing")
try:
load_setting()
except LookupError as error:
snapshot = traceback.TracebackException.from_exception(
error, capture_locals=False
)
else:
raise AssertionError("Expected a missing setting")
# Formatting happens after the exception handler has finished.
report = "".join(snapshot.format())
assert report.startswith("Traceback (most recent call last):")
assert report.rstrip().endswith("LookupError: setting is missing")
assert "example-private-value" not in report
assert all(frame.locals is None for frame in snapshot.stack)
print("Captured frames:", len(snapshot.stack))
print("Locals recorded:", any(frame.locals is not None for frame in snapshot.stack))
print("Final line:", report.splitlines()[-1])Run it:
python3 -B capture_error_snapshot.pyOutput:
Captured frames: 2
Locals recorded: False
Final line: LookupError: setting is missingTracebackException captures information for later formatting without retaining the original exception and traceback objects. capture_locals=False avoids collecting representations of local variables. The assertion checks this specific fixture, not a general redaction guarantee. See TracebackException.
Error messages, source lines, filenames, chained exceptions, and exception notes may still contain secrets even when local variables are omitted. Decide what a report may include before sending it outside the process. Keep raw diagnostics access-controlled and apply deliberate redaction when needed. Exact traceback presentation varies with Python versions; these tests verify chosen fields and summaries, not the entire interpreter display.
Clean up
In the same terminal, remove only the named sources, return to the original directory, and remove the empty scratch directory:
rm -- format_validation_error.py summarize_failure_frames.py capture_error_snapshot.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf rmdir reports remaining files, inspect them instead of deleting unknown contents.
References
Acknowledgements
This post was created with assistance from AI.