Beautiful, Readable Python Stack Traces
Human readable stacktraces for Python.
Quick Start
The fastest way to see it in action:
# Clone and run an example
git clone https://github.com/iloveitaly/beautiful-traceback
cd beautiful-traceback
uv run examples/simple.py
Overview
Beautiful Traceback groups together what belongs together, adds coloring and alignment. All of this makes it easier for you to see patterns and filter out the signal from the noise. This tabular format is best viewed in a wide terminal.
Installation
uv add beautiful-traceback
Usage
Two calls, with different jobs:
configure(...)sets process-wide defaults for frame filtering (exclude_patterns,local_stack_only,show_aliases).exc_to_json(), the pytest plugin, andinstall()all read these. Call it at app startup, including production.install()replacessys.excepthookandthreading.excepthookso uncaught exceptions print a pretty traceback to stderr. Call it in development. Skip it when another library already owns the exception hook.
configure() exists so you do not pass the same exclude_patterns on every log call. For example, structlog-config renders exceptions via exc_to_json() with no extra kwargs — one configure() is how those logs drop sentry/pytest/playwright frames.
install() is only for pretty terminal crash output. Pytest and IPython do not need it: the pytest plugin activates automatically, and IPython uses %load_ext beautiful_traceback.
Application (FastAPI, Celery, CLI)
Call this once when the process starts. python-starter-template does this in app/configuration/debugging.py, which runs from app/__init__.py:
import beautiful_traceback
# Always: shared defaults for JSON logs, pytest, and the exception hook.
beautiful_traceback.configure(
show_aliases=False,
exclude_patterns=[
r"^sentry_sdk/",
r"^_pytest/",
r"^pluggy/",
r"^playwright/",
],
)
# Dev only: pretty traces when a process crashes to the terminal.
# In production, skip this and let your logging library (such as
# structlog-config) log uncaught exceptions as JSON
# (`configure_logger(install_exception_hook=True)`).
if not is_production:
beautiful_traceback.install(
# rich/typer may have already hooked; take over anyway
only_hook_if_default_excepthook=False,
)
That pairs with:
from structlog_config import configure_logger
log = configure_logger(
json_logger=is_production,
install_exception_hook=is_production,
)
In production, structlog owns the exception hook and renders via exc_to_json(), which inherits the configure() defaults. In development, install() owns the hook for a readable stderr traceback.
Scripts
For a one-off script, install() at the entrypoint is enough:
try:
import beautiful_traceback
beautiful_traceback.install()
except ImportError:
pass # no need to fail because of missing dev dependency
By default install() only replaces Python's built-in hook. Pass only_hook_if_default_excepthook=False to override an existing hook (rich, typer, etc).
Libraries
If you are publishing a package, do not call install() from __init__.py or any other module that your users may import. They may not want you to change how their tracebacks are printed.
If you must call install() from shared library code, users can set BEAUTIFUL_TRACEBACK_ENABLED=false to make it a no-op.
LoggingFormatter
A logging.Formatter subclass is also available (e.g. for integration with Flask, FastAPI, etc).
import os
from flask.logging import default_handler
try:
if os.getenv("FLASK_DEBUG") == "1":
import beautiful_traceback
default_handler.setFormatter(beautiful_traceback.LoggingFormatter())
except ImportError:
pass # no need to fail because of missing dev dependency
IPython and Jupyter Integration
Beautiful Traceback works seamlessly in IPython and Jupyter notebooks:
# Load the extension
%load_ext beautiful_traceback
# Unload if needed
%unload_ext beautiful_traceback
The extension automatically installs beautiful tracebacks for your interactive session.
Pytest Integration
Beautiful Traceback includes a pytest plugin that automatically enhances test failure output.
Automatic Activation
The plugin activates automatically when beautiful-traceback is installed. No configuration needed!
Configuration Options
Customize the plugin in your pytest.ini or pyproject.toml:
[tool.pytest.ini_options]
enable_beautiful_traceback = true # Enable/disable the plugin
enable_beautiful_traceback_local_stack_only = true # Show only local code (filter libraries)
beautiful_traceback_show_aliases = false # Hide sys.path aliases section (default: true)
beautiful_traceback_exclude_patterns = [ # Regex patterns to drop frames
"click/core\\.py",
]
Or in pytest.ini:
[pytest]
enable_beautiful_traceback = true
enable_beautiful_traceback_local_stack_only = true
beautiful_traceback_show_aliases = true
beautiful_traceback_exclude_patterns =
click/core\.py
Example: filter out pytest, pluggy, and playwright frames from CI tracebacks:
[tool.pytest.ini_options]
beautiful_traceback_exclude_patterns = [
"^_pytest/",
"^pluggy/",
"^playwright/",
]
Pattern Matching: Patterns are tested against multiple representations of each frame:
_pytest/runner.py(short module path)/path/to/site-packages/_pytest/runner.py(full module path)<site> _pytest/runner.py:353 from_call result: ...(formatted line with short path)<site> /path/to/.../runner.py:353 from_call result: ...(formatted line with full path)
This allows you to write simpler patterns like ^_pytest/ instead of needing to match the full site-packages path.
JSON / Structured Logging
exc_to_json() converts an exception to a JSON-serializable dict, suitable for production log pipelines (structlog, python-json-logger, etc.).
import sys
from beautiful_traceback import exc_to_json
# pass sys.exc_info() directly
try:
...
except Exception:
log.error("unhandled exception", **exc_to_json(sys.exc_info()))
# or pass the exception and traceback separately
try:
...
except Exception as e:
log.error("unhandled exception", **exc_to_json(e, e.__traceback__))
Output shape:
{
"exception": "ValueError",
"message": "something went wrong",
"frames": [
{"module": "app/service.py", "alias": "<pwd>", "function": "process", "lineno": 42}
],
"notes": ["added via exc.add_note(...)"],
"syntax_error": {
"filename": "script.py", "lineno": 10, "offset": 5, "text": "bad code",
"end_lineno": 10, "end_offset": 9, "msg": "invalid syntax"
},
"chain": [
{
"exception": "KeyError",
"message": "'missing_key'",
"relationship": "caused_by",
"frames": [...]
}
]
}
notes is only present when exc.add_note() was called (Python 3.11+). syntax_error is only present for SyntaxError exceptions. chain is only present when the exception has __cause__ or __context__.
Exclude frames by module or file path
If your production logs include frames like these:
{
"alias": "<site>",
"function": "__call__",
"lineno": 60,
"module": "uvicorn/middleware/proxy_headers.py"
}
{
"alias": "<site>",
"function": "__call__",
"lineno": 1160,
"module": "fastapi/applications.py"
}
you can exclude them with exclude_patterns by matching either the short module value, the full file path, or the alias-prefixed rendered line:
import sys
from beautiful_traceback import exc_to_json
try:
...
except Exception:
payload = exc_to_json(
sys.exc_info(),
exclude_patterns=[
# Match the short module value from JSON output.
r"^uvicorn/middleware/proxy_headers\.py$",
r"^fastapi/applications\.py$",
# Match the absolute file path on disk.
r"/site-packages/fastapi/routing\.py$",
r"/site-packages/sentry_sdk/integrations/starlette\.py$",
# Or match a specific deployment path if you want to be exact.
r"^/app/\.venv/lib/python3\.13/site-packages/uvicorn/middleware/proxy_headers\.py$",
],
)
The matcher checks all of these representations for each frame:
uvicorn/middleware/proxy_headers.pyThis is the short module path, relative to the alias root such as<site>or<pwd>./app/.venv/lib/python3.13/site-packages/uvicorn/middleware/proxy_headers.pyThis is the full absolute path on disk.<site> uvicorn/middleware/proxy_headers.py:60 __call__ ...This is the rendered traceback line with the alias and short path.<site> /app/.venv/lib/python3.13/site-packages/uvicorn/middleware/proxy_headers.py:60 __call__ ...This is the rendered traceback line with the alias and full path.
That means you can write broad patterns like ^fastapi/ for alias-relative matching, /site-packages/fastapi/ for absolute path matching, or ^<site> .*fastapi/ if you want to require a specific alias.
The same patterns work with install() and configure(). If you want to drop a whole integration layer, match the module prefix instead:
exclude_patterns = [
r"^uvicorn/",
r"^fastapi/",
r"^starlette/",
r"^sentry_sdk/integrations/",
]
Global defaults with configure()
Without configure(), every exc_to_json() call would need the same exclude_patterns. Call it once at startup (see Usage) and those defaults apply to JSON logs, pytest, and install().
from beautiful_traceback import configure, exc_to_json
configure(
local_stack_only=True,
exclude_patterns=[r"site-packages/"],
show_aliases=False,
)
# these options are now applied automatically
try:
...
except Exception:
log.error("unhandled exception", **exc_to_json(sys.exc_info()))
Per-call arguments always override configure() defaults. Formatting options passed to install() are also written into the same global config.
Threading Support
beautiful_traceback.install() hooks both sys.excepthook and threading.excepthook, so unhandled exceptions in background threads are automatically formatted.
- Thread name and daemon status are shown in the exception header (e.g.,
Exception in thread Worker-1 (daemon):) exc_to_json()accepts an optionalthreadparameter to include thread metadata in structured JSON output
See examples/threading_example.py for a complete demonstration.
Examples
Check out the examples/ directory for basic usage, exception chaining, logging integration, and more.
Configuration
See Usage for when to call configure() vs install(). Options below apply to both.
beautiful_traceback.configure(
local_stack_only=False,
show_aliases=False,
exclude_patterns=["click/core\\.py"],
)
beautiful_traceback.install(
color=True, # Enable colored output
only_tty=True, # Only activate for TTY output
only_hook_if_default_excepthook=True, # Only install if default hook
local_stack_only=None, # Defaults to configure() / BEAUTIFUL_TRACEBACK_LOCAL_STACK_ONLY
show_aliases=None, # Defaults to configure() / BEAUTIFUL_TRACEBACK_SHOW_ALIASES (default: false)
exclude_patterns=["click/core\\.py"], # Regex patterns to drop frames
)
Environment Variables
NO_COLOR- Disables colored output when set (respects no-color.org standard)BEAUTIFUL_TRACEBACK_ENABLED- Set tofalse/0/noto makeinstall()a no-op. Useful when a library callsinstall()and an application wants it off.BEAUTIFUL_TRACEBACK_LOCAL_STACK_ONLY- Set totrue/1/yesto filter out library/framework frames.BEAUTIFUL_TRACEBACK_SHOW_ALIASES- Set tofalse/0/noto hide the sys.path aliases section.
These env vars serve as fallback defaults for both install() and the pytest plugin (CLI args and pytest.ini settings take precedence over env vars for pytest).
LoggingFormatterMixin
For more advanced logging integration, you can use LoggingFormatterMixin as a base class:
import logging
import beautiful_traceback
class MyFormatter(beautiful_traceback.LoggingFormatterMixin, logging.Formatter):
def __init__(self):
super().__init__(fmt="%(levelname)s: %(message)s")
This gives you full control over the log format while adding beautiful traceback support.
Global Installation via PTH File
You can enable beautiful-traceback across all Python projects without modifying any source code by using a .pth file. Python automatically executes import statements in .pth files during interpreter startup, making this perfect for development environments.
Using the CLI Command
The easiest way to inject beautiful-traceback into your current virtual environment:
beautiful-traceback
This command:
- Only works within virtual environments (for safety)
- Installs the
.pthfile into your current environment's site-packages - Displays the installation path every time it runs
Output:
Beautiful traceback injection installed: /path/to/.venv/lib/python3.11/site-packages/beautiful_traceback_injection.pth
Using a Shell Function (Alternative)
Alternatively, add this function to your .zshrc or .bashrc:
# Create a file to automatically import beautiful-traceback on startup
python-inject-beautiful-traceback() {
local site_packages=$(python -c "import site; print(site.getsitepackages()[0])")
local pth_file=$site_packages/beautiful_traceback_injection.pth
local py_file=$site_packages/_beautiful_traceback_injection.py
cat <<'EOF' >"$py_file"
def run_startup_script():
try:
import beautiful_traceback
beautiful_traceback.install(only_tty=False)
except ImportError:
pass
run_startup_script()
EOF
echo "import _beautiful_traceback_injection" >"$pth_file"
echo "Beautiful traceback injection created: $pth_file"
}
After sourcing your shell config, run python-inject-beautiful-traceback to enable beautiful tracebacks globally for that Python environment.
Related Projects
- python-starter-template — full-stack app that uses this package as shown in Usage
- structlog-config — opinionated structlog setup; uses beautiful-traceback automatically for console and JSON exceptions
Alternatives
Other traceback formatters (sorted by github stars):
- https://github.com/qix-/better-exceptions
- https://github.com/cknd/stackprinter
- https://github.com/onelivesleft/PrettyErrors
- https://github.com/skorokithakis/tbvaccine
- https://github.com/aroberge/friendly-traceback
- https://github.com/HallerPatrick/frosch
- https://github.com/nir0s/backtrace
- https://github.com/mbarkhau/pretty-traceback
- https://github.com/staticshock/colored-traceback.py
- https://github.com/chillaranand/ptb
- https://github.com/laurb9/rich-traceback
- https://github.com/willmcgugan/rich#tracebacks
License
This project was created from iloveitaly/python-package-template
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file beautiful_traceback-1.0.0.tar.gz.
File metadata
- Download URL: beautiful_traceback-1.0.0.tar.gz
- Upload date:
- Size: 20.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c653d9cc32491c00835663860a6ea469219d2a6c889d4b8ed7e141a386d91bee
|
|
| MD5 |
483c3de79725aa205045defe09623a6e
|
|
| BLAKE2b-256 |
22b8d34fdb7261502754af698d002be33ce4dcd8fbbc1fa6a719d179609db66f
|
File details
Details for the file beautiful_traceback-1.0.0-py3-none-any.whl.
File metadata
- Download URL: beautiful_traceback-1.0.0-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d94fc414499cafb1ab098793b7486fbe3b956a3fee041544dc4004c39469e25
|
|
| MD5 |
fb66ab877c72c9b1a30afef1e9b1a5ea
|
|
| BLAKE2b-256 |
5b7da5a1bf599dcb23beeea63b0c146773924ac4ddfafe369f059c8a986497e5
|