Skip to main content

Beautiful, Readable Python Stack Traces

Release Notes Downloads Python Versions GitHub CI Status License: MIT

Human readable stacktraces for Python.

Comparison of standard Python traceback vs Beautiful Traceback

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, and install() all read these. Call it at app startup, including production.
  • install() replaces sys.excepthook and threading.excepthook so 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.py This 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.py This 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 optional thread parameter 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 to false/0/no to make install() a no-op. Useful when a library calls install() and an application wants it off.
  • BEAUTIFUL_TRACEBACK_LOCAL_STACK_ONLY - Set to true/1/yes to filter out library/framework frames.
  • BEAUTIFUL_TRACEBACK_SHOW_ALIASES - Set to false/0/no to 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 .pth file 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

Alternatives

Other traceback formatters (sorted by github stars):

License

MIT 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

beautiful_traceback-1.0.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

beautiful_traceback-1.0.0-py3-none-any.whl (25.7 kB view details)

Uploaded Python 3

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

Hashes for beautiful_traceback-1.0.0.tar.gz
Algorithm Hash digest
SHA256 c653d9cc32491c00835663860a6ea469219d2a6c889d4b8ed7e141a386d91bee
MD5 483c3de79725aa205045defe09623a6e
BLAKE2b-256 22b8d34fdb7261502754af698d002be33ce4dcd8fbbc1fa6a719d179609db66f

See more details on using hashes here.

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

Hashes for beautiful_traceback-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d94fc414499cafb1ab098793b7486fbe3b956a3fee041544dc4004c39469e25
MD5 fb66ab877c72c9b1a30afef1e9b1a5ea
BLAKE2b-256 5b7da5a1bf599dcb23beeea63b0c146773924ac4ddfafe369f059c8a986497e5

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.2

2 files

1.0.1

2 files

This release

1.0.0 This release

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page