Skip to content

About

Python post-mortem debugger | 针对Python的异常调试器

Topics

Resources

Stars

59 stars

Watchers

1 watching

Forks

Repository files navigation

Pydumpling

English | 简体中文

Pydumpling saves Python exception or live stack snapshots to compressed .dump files. Print a traceback, inspect saved variables with pdb, or open a remote debugger. Derived from elifiner/pydump.

Requires Python 3.11+. Capturing another process requires CPython 3.14+.

Install

python -m pip install pydumpling
pydumpling --version

These examples describe 0.2.0.

Save an exception

Save this complete example as example.py and run python example.py:

from pydumpling import save_dumping


def calculate():
    quantity = 3
    price = "12.50"
    return quantity + price


try:
    calculate()
except TypeError:
    save_dumping("error.dump", strict=True)
pydumpling --print error.dump
pydumpling --debug error.dump
# The module entry point also works:
python -m pydumpling --print error.dump

--print writes the traceback and exception text to standard output, so it survives a pipe or a redirect; the omission notice stays on standard error.

Useful pdb commands include bt, u, d, list, ll, and pp price. A snapshot lets you inspect saved state; it cannot resume the original program.

Print machine-readable and metadata views

pydumpling --json error.dump                # indented JSON report
pydumpling --json --globals error.dump       # include each frame's globals
pydumpling --info error.dump                 # versions, budgets, truncation

The JSON object carries capture metadata (created_at, pid, thread_name, duration_ms, versions), the exception (type, message, formatted text, or null for stack snapshots), a frames list from outermost to innermost, and the truncation mapping; variable values are guarded repr strings, so the output pipes cleanly into jq and CI tooling.

Embed source and share an HTML report

from pydumpling import DumpOptions, save_dumping

try:
    risky_step()
except ValueError:
    save_dumping("error.dump", options=DumpOptions(embed_source_lines=5), strict=True)

embed_source_lines stores that many lines of context around each frame's current line inside the dump (None embeds whole files). --print, --debug, --rdebug, and --html then show the source that ran wherever the local file is missing or no longer matches it; an unchanged local file keeps serving its full text. If the dump exceeds its byte budget, embedded source is dropped before captured variables are reduced.

pydumpling --html report.html error.dump   # standalone HTML, no scripts

The HTML report shows the exception, a source window per frame (chosen the same way, with the current line highlighted), and the locals table.

Save the current stack

from pydumpling import dump_current_traceback


def calculate():
    quantity = 3
    price = 12.5
    dump_current_traceback("current.dump", strict=True)
    return quantity * price


calculate()

Call catch_any_exception() once at application startup to capture unhandled main-thread exceptions. It installs sys.excepthook and retains the previous hook for normal exception reporting. Pass include_threads=True to also capture exceptions that end a threading.Thread (a thread's SystemExit is a normal exit and saves nothing), and restore_exception_hooks() to stop every installed hook again; a hook another package installed on top of ours stays, and ours below it only forwards. For asyncio, call install_asyncio_handler() inside async code (or pass loop=) to capture exceptions the event loop reports. Without a manager, every hook writes a new dump into the working directory and never deletes one, so long-running services should pass a DumpManager.

Manage a dump directory

DumpManager saves into one directory with unique names and bounded retention:

from pydumpling import DumpManager

manager = DumpManager("dumps", retention=20)
try:
    risky_step()
except ValueError:
    manager.save_exception()  # or save_current() for the current stack

Every default file name now carries the source line, a UTC timestamp, the pid, and a per-process sequence, so repeated failures never overwrite an earlier snapshot. The manager prunes by file modification time; saves close enough for the filesystem to give them equal times keep their write order through the timestamp and sequence in the name. target() creates the managed directory and returns a fresh path. Hooks accept the manager directly: catch_any_exception(manager=manager, include_threads=True) and install_asyncio_handler(manager=manager). Each saved snapshot records its creation time, duration, pid, host name, working directory, command line, and capturing thread in snapshot_meta.

Limit capture overhead

DumpOptions() applies these defaults:

Setting Default
max_frames 64 frames
max_depth 5 nested levels
max_items 100 items per container
max_string_bytes 4,096 bytes per captured string value
max_nodes 10,000 visited values
max_dump_bytes 16 MiB of uncompressed serialized data

Values may be summarized when a budget is reached. Customize the budget or explicitly request unrestricted capture:

from pydumpling import DumpOptions, dump_current_traceback

dump_current_traceback(
    "small.dump",
    options=DumpOptions(max_frames=16, max_depth=3, max_items=25),
    strict=True,
)
dump_current_traceback("unlimited.dump", options=DumpOptions.unlimited(), strict=True)

Unrestricted capture can consume substantial resources and permits custom object serialization hooks to execute. Omission reasons and applied budgets are recorded in the loaded snapshot's snapshot_meta dictionary. Unrestricted original objects can depend on their original environment or custom loading hooks; successful round-trips are not guaranteed for every object. The default readable snapshot mode is unaffected.

save_dumping(filename=None, exc_info=None, *, options=None, strict=False) and dump_current_traceback(filename=None, *, options=None, strict=False) report capture/write failures as warnings by default. Use strict=True to raise them. catch_any_exception(*, options=None, include_threads=False, manager=None) accepts the same budgets, and install_asyncio_handler(loop=None, *, options=None, manager=None) covers an event loop; both keep the previous hooks reporting.

Loading and debugging default to at most 64 MiB of decompressed dump data. load_dumpling(path, max_bytes=None) or debug_dumpling(path, max_bytes=None) disables this limit; the CLI equivalent is --max-load-mib 0.

Capture a running process by PID

Run this target program with CPython 3.14+ and updated Pydumpling installed:

import os
import time

import pydumpling

print(f"PID: {os.getpid()}", flush=True)
counter = 0
while True:
    counter += 1
    time.sleep(0.1)

In another terminal, replace 12345 with the printed PID:

pydumpling --capture-pid 12345 --output live.dump --timeout 30
pydumpling --print live.dump

The API is capture_pid(pid, filename, *, options=None, timeout=30) -> Path. Controller and target must use the same CPython version on the same machine, under the same user, with shared process and filesystem namespaces. The target must have updated Pydumpling installed and permit CPython remote execution. Operating system permissions may also prevent attachment.

A timeout stops waiting; an already dispatched request can still execute later. The CLI prints the retained request directory on timeout. Avoid submitting repeated requests while one is pending. Use --unlimited to remove capture budgets explicitly. Remove retained request files only after status.json confirms completion or the target process has exited.

Remote inspection

The debugger defaults to 127.0.0.1:4444:

# Server:
pydumpling --rdebug error.dump --host 127.0.0.1 --port 4444

# Workstation:
ssh -L 4444:127.0.0.1:4444 user@server
# A second local terminal:
nc 127.0.0.1 4444

Remote pdb allows Python expression execution and has no built-in authentication or encryption. Keep it on loopback and use an SSH tunnel.

Trust and privacy

Only load trusted dumps: dill and pickle deserialization can execute code. Size limits do not make an untrusted file safe. Snapshots can contain local variables, credentials, personal data, and other application state; control file access, retention, and sharing.

Inspect dumps with matching Python and source versions. Cross-version loading and third-party debugger compatibility are not guaranteed. Old 0.1 dumps remain readable when their objects are compatible with the interpreter. See 0.2 migration notes.

Development

uv sync --locked --dev --extra doc
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked pytest --cov=pydumpling --cov-report=term-missing
uv run --locked --extra doc sphinx-build -W --keep-going -b html docs/source docs/build/html
uv build --sdist
uv build --wheel dist/pydumpling-0.2.0.tar.gz
uv run --locked twine check --strict dist/*

uv.lock is committed. Run uv lock after deliberately changing dependencies and include the updated lockfile. CI checks Linux on Python 3.11–3.15 and Python 3.14 on Windows/macOS, with an 85% coverage floor, strict documentation builds, and installed-wheel checks outside the source checkout. Hatchling builds distributions; uv manages development environments.

About

Python post-mortem debugger | 针对Python的异常调试器

Topics

Resources

Stars

59 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages