Skip to main content

Record once, replay anywhere: a time-travel debugger for Python with a visual browser-based player.

Project description

tracesnap

Record once, replay anywhere. A time-travel debugger for Python with a visual browser-based player. Run your code under instrumentation, get a self-contained JSON trace, then scrub through it line-by-line in three views (text, simulator, call graph) — no re-execution.

Status: alpha. Stdlib-only core, plug-and-play integrations for Flask, Django, and FastAPI.


Why

pdb lets you pause once. print() litters your code. Profilers count nanoseconds but don't show you state. tracesnap captures every line, every assignment, every branch, and every outbound HTTP call into one JSON file you can replay, share in a PR, or attach to a bug report.

Install

pip install tracesnap                # core + CLI + bundled players
pip install tracesnap[flask]         # + Flask integration
pip install tracesnap[django]        # + Django middleware
pip install tracesnap[fastapi]       # + FastAPI integration
pip install tracesnap[all]           # everything above

Requires Python 3.9+. The core has zero runtime dependencies.

Quickstart

Record a script:

tracesnap record examples/sample_complex.py

Open it in your browser:

tracesnap view

That's it. tracesnap view (no args) opens the library — a list of every recording you've made — and you can pick one to replay. To jump straight into a specific trace:

tracesnap view <id>          # use the id printed by `record`
tracesnap view trace.json    # or a saved file

You'll get a three-view player:

  • Text view — current step, call stack, full per-variable history.
  • Simulator — animated flowchart with loops as cycle boxes, branches with the taken arm tagged ✓, variable chips that flash when changed.
  • Call graph — every function as a node, edges labelled with args going in and return values coming back; click a node for per-call details.

All three views consume the same trace and switch via the header buttons.

Library use

Three equally-valid entry points, same underlying engine:

import tracesnap

# 1) Context manager (preferred for explicit scope)
with tracesnap.record(trace_id="demo") as out:
    do_stuff()
# out.path        -> "trace.json"  (when path= is passed)
# out.event_count
# out.trace       -> the trace dict in memory

# 2) Decorator (records every call to the wrapped function)
@tracesnap.record(trace_id="checkout")
def checkout(items, coupon):
    ...

# 3) Imperative (lowest level; what the integrations use under the hood)
tracesnap.start_recording(trace_id="x", source_files=[__file__])
try:
    do_stuff()
finally:
    trace = tracesnap.stop_recording()
    tracesnap.write_trace(trace, "trace.json")

Framework integrations

All three frameworks expose the same @traced decorator. Decorate only the endpoints you actually want to record — middleware-style blanket capture is intentionally not offered, because sys.settrace is too expensive to pay on every request and most endpoints aren't worth recording. Recording is gated on the TRACESNAP_ENABLED=1 environment variable, so the decorator is a no-op in production.

Flask

from flask import Flask
from tracesnap.integrations.flask import traced

app = Flask(__name__)
app.config["TRACESNAP"] = {
    "output_dir": "traces",
    "source_files": [__file__],
}

@app.route("/checkout")
@traced
def checkout():
    ...

Stack @traced below @app.route(...) (closer to the function).

Django

# settings.py
TRACESNAP = {
    "output_dir": "traces",
    "source_files": [str(BASE_DIR / "myapp" / "views.py")],
}

# views.py
from tracesnap.integrations.django import traced

@traced
def checkout(request):
    ...

# DRF ViewSet action
class ProductViewSet(viewsets.ModelViewSet):
    @traced
    @action(detail=False, methods=["get"], url_path="low-stock")
    def low_stock(self, request):
        ...

For inherited DRF actions (list, retrieve, create, update, partial_update, destroy), override and call super():

class ProductViewSet(viewsets.ModelViewSet):
    @traced
    def create(self, request, *args, **kwargs):
        return super().create(request, *args, **kwargs)

FastAPI

from fastapi import FastAPI, Request
from tracesnap.integrations.fastapi import configure, traced

app = FastAPI()
configure(output_dir="traces", source_files=[__file__])

@app.get("/checkout")
@traced
async def checkout(request: Request):
    ...

Stack @traced below @app.get(...). Declare a request: Request parameter on the view so the trace can capture method/path (FastAPI only injects it if asked). Works on both def and async def handlers.

Async note: sys.settrace is per-thread; contextvars are per-task. For one handler per request (the common case) this works correctly. Concurrent asyncio.gather(...) of multiple traced sub-tasks within a single request boundary share the same recording session — not recommended for production. Document/test your specific use.

CLI reference

tracesnap record  PATH [--out FILE] [--id NAME] [--name NAME]
                       [--redact NAMES] [--no-library] [--kind KIND]
                       [--structure-out FILE]

tracesnap view    [PATH] [--view text|simulator|graph|events|home|record]
                         [--port PORT] [--no-browser] [--scan-root DIR]

tracesnap list                    # show the library
tracesnap rename  ID NEW_NAME
tracesnap delete  ID [-f]

tracesnap --version

record

Runs the given .py under instrumentation, auto-discovers sibling modules it imports, and saves the trace to the on-disk library (under ~/.tracesnap/ by default). Useful flags:

  • --out FILE — also write a standalone copy to disk.
  • --id NAME — short identifier stored inside the trace and used as the library id (default: "trace").
  • --name NAME — human-readable display name for the library entry.
  • --redact NAMES — comma-separated extra variable names to redact, on top of the built-in set (password, token, secret, authorization, api_key).
  • --no-library — skip saving to the library.

view

Starts a tiny stdlib http.server (no extra deps), copies the bundled player HTMLs into a tmpdir, and opens your default browser.

Port behavior:

  • Default port is 8765 — stable across runs, so bookmarks and open tabs survive a restart.
  • If 8765 is taken, tracesnap falls back to a random free port and prints a notice.
  • --port N pins to your own choice (same fallback applies).
  • --port 0 always picks a random free port.

Other useful flags:

  • --view NAME — start on a specific page (text, simulator, graph, events, home, or record). Default: home when browsing the library, call_graph when a specific trace is given.
  • --no-browser — print the URL but don't auto-open the browser (useful over SSH or in containers).
  • --scan-root DIR — directory the in-browser "New record" page walks when offering scripts to run (default: CWD).

What gets recorded

Every event carries seq, ts, depth, line, parent_seq plus type-specific fields:

type fields
call func, args (each value with repr, type, redacted)
line func
assign var, scope, value, prev, change_index
branch node_id, taken ("if" / "else")
loop node_id, iteration (0-based)
return func, value
extcall kind, verb, target, status, duration_ms, started_ts, ended_ts

Values are {repr, type, id, truncated, redacted}. repr is capped at 120 chars (truncated: true if hit). Variables named password, token, secret, authorization, api_key get <redacted> — both as function args and as locals. Extend the set via redact_names= on any entry point or --redact on the CLI.

parent_seq links events into a tree:

  • Inside a for/while body, every event has parent_seq pointing at the current iteration's loop event.
  • Inside an if/else arm, every event points at the branch event.
  • Top-level events in a function have parent_seq: null.
  • A call event's parent_seq is the caller's context at the call site (so a call made inside an if arm points at the branch event in the caller, not at the new frame).

Full spec: docs/trace-format-v0.1.md.

Examples

The examples/ directory has runnable samples:

tracesnap record examples/sample_program.py    # straight-line script
tracesnap record examples/sample_complex.py    # branches, loops, recursion
tracesnap record examples/sample_pipeline.py   # multi-stage data flow
tracesnap record examples/sample_backtrace.py  # exception path
python  examples/flask_app.py                  # Flask demo (needs [flask])
python  examples/fastapi_app.py                # FastAPI demo (needs [fastapi])
python  examples/django_app.py                 # Django demo (needs [django])

For framework demos, install the extra first:

pip install -e .[flask]
python examples/flask_app.py
# then in another terminal:
curl http://127.0.0.1:5050/checkout
tracesnap view              # browse the request traces

Known issues / edges

  • Recursion — the player keys frames by stack position, so recursive calls collide. Roadmap item: per-call frame_id.
  • Assign attribution is one line latesys.settrace fires before a line runs; we attribute the diff to the previous line. Documented in the trace-format spec.
  • Value-change vs assignment — we log value changes, so in-place mutation like xs.append(y) doesn't emit. Use rebinding (xs = xs + [y]) to see growth.
  • extcall scope — only requests.Session.send and urllib.request.urlopen are wrapped. httpx, DB drivers, and stdlib socket are not (yet).
  • Async — per-task contextvars work; per-task threading.settrace does not (yet). Concurrent traced sub-tasks share the same session.

Development

git clone https://github.com/bangi98/mindplayer
cd mindplayer
pip install -e .[dev]

python -m pytest tests/        # run the test suite
python -m build                # build a wheel

The repo directory is still called mindplayer (the original project name) — the published package is tracesnap.

Roadmap

  • SQLite backend for traces > ~10k events (single-trace decision; JSON stays default).
  • depends_on field on assign events → true data-flow graphs in the call-graph view.
  • exception events on unwind.
  • httpx + DB driver extcall capture.
  • Per-task threading.settrace for parallel-async support.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tracesnap-0.2.0.tar.gz (120.2 kB view details)

Uploaded Source

Built Distribution

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

tracesnap-0.2.0-py3-none-any.whl (111.9 kB view details)

Uploaded Python 3

File details

Details for the file tracesnap-0.2.0.tar.gz.

File metadata

  • Download URL: tracesnap-0.2.0.tar.gz
  • Upload date:
  • Size: 120.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tracesnap-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e4d56c328204d820cae213556e253dc19cc7600215ce0a7199643d9f7a866e7d
MD5 9241e28866a876f068aab5c682d40486
BLAKE2b-256 55c36ac607429c57cc6d23b9d9ea9e44ce9241a0bbd442a11ec1bfef82dd4d0c

See more details on using hashes here.

Provenance

The following attestation bundles were made for tracesnap-0.2.0.tar.gz:

Publisher: publish.yml on bangi98/mindplayer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tracesnap-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: tracesnap-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 111.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tracesnap-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ec1d8ad96f0299c7b66a3e4e91799ca4534b4c2c36fcf1aac5c621d1907816c4
MD5 3dfe968e5151f2002af8a7ba76d19cfe
BLAKE2b-256 5dd635e1c662e7764ef9238b0aa1700e61b3eef3a00bd1565669eac6b81003f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for tracesnap-0.2.0-py3-none-any.whl:

Publisher: publish.yml on bangi98/mindplayer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page