Skip to main content

excdump

Capture everything about an exception the moment it happens, then debug it later on your own machine — with the frames, the locals, related globals, and the source as they were.

A traceback tells you where a program failed. It does not tell you what order held, what rate had been computed to, or what the file looked like before you edited it. excdump writes all of that to a file at the moment of failure, and gives you an offline debugger to walk it afterwards.

from excdump import dump_exception

try:
    handle_checkout()
except Exception:
    trace_id = dump_exception()   # returns the id -- log it
    raise
$ python -m excdump inspect <trace-id>

Install

Requires Python 3.14+.

$ uv sync          # or: pip install -e .

Try it without writing any code:

$ python -m excdump demo
$ python -m excdump inspect

Capturing

Two ways in. dump_exception() is called from an except block and returns the trace id, which is what you log:

except Exception:
    logger.error("checkout failed", extra={"trace_id": dump_exception()})
    raise

@dump_on_exception wraps a function and captures anything that escapes it, then re-raises unchanged. It takes options, or none at all:

from excdump import dump_on_exception

@dump_on_exception
def handle_checkout(cart):
    ...

@dump_on_exception(on_dump=report_to_sentry, n_depth_up=2)
def calculate_tax(order):
    ...

The decorated function is the pivot the session opens on, with the decorator's own frames left out, so up reaches its real caller.

Both capture the frames of the traceback plus a configurable number of caller frames above it, every exception in a raise ... from ... chain, and the source of each file involved.

Inspecting

$ python -m excdump list                 # what the store holds, grouped by failure
$ python -m excdump inspect              # the most recent dump
$ python -m excdump inspect <trace-id>   # a specific one; a unique prefix works
$ python -m excdump inspect <path-id>    # newest dump of one failure
$ python -m excdump gc                   # reclaim paths nothing hits any more

inspect opens a pdb-like session over the dump. Frames, locals and globals are all there; --plain forces the readline prompt instead of the TUI.

Nothing about the session touches the process that crashed — it no longer exists. That is the point: you get to look around at your leisure, on a different machine, days later.

How dumps are filed

A dump is filed under its exception path — the (filename, lineno) list of its traceback, hashed. One bug hit a million times shares one path, so retention applies per failure and a hot loop cannot push a rarer failure out of the store.

.exception_dumps/
  .gc                              when the last sweep ran
  <path_id>/
    path.json                      the (filename, lineno) list, for humans
    sources/<hash>.json.gz         full text of a captured file
    <trace_id>.dump                one capture

Nothing is ever read to decide where a dump goes, so many processes can write into one store with no coordination between them.

Three things bound the size:

bound by when
dumps within a path count, max_dumps_per_path every capture
whole paths age, max_path_age_days hourly during capture, and gc
abandoned temporaries age, one hour with the sweep above

Paths need collecting because their ids churn: the id hashes line numbers, so a deploy that shifts a line puts every traceback through that file under a new id and leaves the old one unreachable. Age tells those apart from failures that simply have not recurred yet — and needs no deploy hook to do it.

Removing a path removes everything it held. No rule here reads anything but file names, which is why the sweep can run during capture without loading a thing.

Source is stored, not re-read

Each captured file's full text is written into the path's sources/ directory, named by the hash of its contents. The inspector reads that, not the file on disk.

This matters more than it sounds. Read the live file back and every dump written before your last edit points its arrow at the wrong line — silently, and most confusingly for the old dumps you most want to trust. Content addressing also means a file edited between two dumps of one path simply lands in a second blob, and two processes writing the same text write the same bytes to the same name.

What gets stored for a value

Each captured value is stored the cheapest way that keeps it, decided per value:

  • plain pickle if pickle can take it and the result will resolve wherever the dump is read;
  • otherwise dill, which handles functions, classes and closures pickle refuses. Everything dill takes goes into one shared stream per dump, so ten functions from one module carry that module's namespace once between them. Both of dill's encodings are tried and the smaller kept: neither wins in general, and the one that lost by 8× on a batch of functions from one module won by 3× on a single decorated one.
  • modules are stored by name and re-imported on load, rather than pickled whole;
  • anything left becomes a capped repr, visible as a placeholder rather than a failed load.

A dump that cannot be fully reconstructed still opens. A class this machine cannot import shows as <Unavailable pkg.Thing> and everything around it still reads.

Set serializer to "dill" to send everything through dill (much larger), or "pickle" to drop whatever pickle will not take.

Configuration

configure() validates as it sets, so a typo fails at startup rather than mid-exception:

from excdump import configure

configure(store_dir="/var/log/exception_dumps", max_dumps_per_path=500)

Every field also reads EXCDUMP_<NAME> from the environment, so a deployment can tune capture without touching code. A malformed value is ignored rather than raised — a bad environment variable must not stop an app from starting.

option default meaning
store_dir ./.exception_dumps root of the dump store
max_dumps_per_path 200 dumps kept per failure; 0 disables pruning
max_path_age_days 14 age at which a whole path is dropped; 0 keeps forever
gc_interval_seconds 3600 how often capture sweeps; 0 leaves it to gc
n_depth_up 5 caller frames captured above the handling frame
n_depth_down 5 traceback frames captured below it
serializer "auto" "auto", "dill" or "pickle"
source_radius 5 lines kept either side of each captured line
max_repr_chars 2000 cap on a stored repr
max_dill_bytes 65536 backstop on one dill-serialized value
max_source_bytes 1000000 cap on a captured file's stored text
enabled True master switch
on_dump None callback given each trace id
verbose False print the dump path to stderr

Capture never breaks the caller

Everything on the capture path is written to fail quietly. A store that cannot be written, a value that cannot be serialized, a sweep that cannot run — none of them raise, because all of it happens with an exception already in flight and losing the original failure is always worse.

Layout

module holds
config.py Config, configure(), environment overrides
paths.py trace and path ids, file-name conventions
model.py ExceptionDump, ExceptionRecord, FrameSnapshot
sources.py captured source and the per-path sidecar
values.py which serializer each value gets
capture.py dump_exception, dump_on_exception
store.py on-disk layout, retention, collection, lookup
loading.py reading a dump back, tolerantly
session.py the offline debugging session
cli.py commands and the readline inspector
tui.py the full-screen inspector

Metadata

Release files for excdump 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for excdump 0.1.0
File Size Uploaded Provenance
excdump-0.1.0.tar.gz 37.1 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for excdump 0.1.0
File Interpreter ABI Platform
excdump-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 81.2 kB

Release files / excdump-0.1.0.tar.gz

Download URL excdump-0.1.0.tar.gz
Size 37.1 kB
Tags Source
SHA-256 checksum
How to use checksums
02873dde0a69c10d2af94e1fbf7293415b874de1de3f5d13fb959dd5749967ae
BLAKE2b-256 checksum
How to use checksums
7edfc90747bfd911a0b7d20a55f9c362be7f1cf1cdb61c4c330fbc1b0b143a1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / excdump-0.1.0-py3-none-any.whl

Download URL excdump-0.1.0-py3-none-any.whl
Size 44.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
84892b5cd0314717af545981175e07beccd46e668c4efbb75a28417dbeece888
BLAKE2b-256 checksum
How to use checksums
5fbb65f941f5ec5829a0f0dd7ec371586c578a98323d3dc7de387f3d27c44dc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for excdump 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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