The black box flight recorder for Python applications — record, replay, and diagnose crashes from your terminal.
Project description
slomo
The black box flight recorder for Python applications.
Stop asking developers to reproduce bugs. Start letting them replay exactly what happened.
pip install slomo
from slomo import enable
enable()
That's it. Zero configuration. No dashboard, no browser, no Docker, no external
database, no account, no telemetry. Everything is recorded locally under
.slomo/ and explored entirely from your terminal.
The 60-second demo
# app.py
import sqlite3
import slomo
from slomo import track
slomo.enable()
@track
def load_inventory(db, sku):
return db.execute("SELECT sku, qty FROM inventory WHERE sku = ?", (sku,)).fetchone()
@track
def checkout(db, sku):
inventory = load_inventory(db, sku)
return {"sku": inventory[0], "qty": inventory[1]} # BUG: inventory can be None
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE inventory (sku TEXT PRIMARY KEY, qty INTEGER)")
db.execute("INSERT INTO inventory VALUES ('widget', 5)")
checkout(db, "widget") # works
checkout(db, "gadget") # crashes
Run it a few times, then:
$ slomo issues
┃ issue ┃ title ┃ category ┃ count ┃
│ SM-8b6f710a │ TypeError: 'NoneType' object is not subsc… │ Null Reference │ 5 │
$ slomo doctor SM-8b6f710a
Category Null Reference (95% confidence)
Occurrences 5 (5 unhandled) across 5 session(s)
Likely root cause TypeError raised in checkout() at app.py:14.
Variable 'inventory' was None at the crash site.
First bad function checkout()
First bad variable inventory
Suggested fix Guard against None before the failing access at app.py:14
— trace where the value is produced and handle the missing case.
Context just before the crash:
13:04:52.133 sql.query SELECT sku, qty FROM inventory WHERE sku = ?
13:04:52.133 sql.result 0 rows, 9µs
$ slomo replay SM-8b6f710a # step through the crash, event by event
Using it with FastAPI, Flask, workers, asyncio…
Web frameworks turn exceptions into 500 responses before sys.excepthook can
see them — but auto-tracing records the exception the moment it escapes your
handler, so those 500s land in slomo issues with args, locals, and the SQL/HTTP
calls that led up to them. No decorators needed:
from fastapi import FastAPI
import slomo
slomo.enable() # that's the whole integration
app = FastAPI()
@app.get("/checkout/{sku}")
async def checkout(sku: str): ...
The examples/ directory has complete, runnable apps — each one
plants a realistic bug and shows the slomo commands that catch it:
autotrace_demo.py— the whole app on tape with one line, zero decoratorsfastapi_app.py— lifespan integration, tracked async routes, per-request middleware events, background-task failuresflask_app.py— tracked views, per-request eventsbackground_worker.py— worker-thread crashes you'd otherwise never seeasync_pipeline.py— asyncio concurrency, retries, async generatorsredaction_demo.py— proof your secrets never hit disk
What gets recorded
After enable(), automatically:
- Every function call in your project code — enter, arguments, exit, result, duration, and any exception that escapes, via
sys.monitoring(PEP 669, stdlib). Only files in your project are traced; stdlib and third-party call sites are switched off inside the interpreter after their first hit, so they cost nothing - Unhandled exceptions — main thread, worker threads, unraisable errors, with structured tracebacks and local variables from the crashing frames
- SQL queries —
sqlite3out of the box, everything else via SQLAlchemy's event API (Postgres, MySQL, ...) - HTTP calls —
requestsandhttpx(sync + async), request/response pairs correlated - Log records —
loggingWARNING and above - Session metadata: argv, cwd, python version, host, pid, exit status
Opt-in, where you want more than the automatic capture:
from slomo import track, snapshot, event
@track # force-trace code outside the project root,
def process_order(order_id): ... # or take control of arg/result capture
snapshot("before-retry", user=user, attempt=n) # explicit variable snapshot
event("cache.warmed", entries=1042) # custom event
Auto-tracing is tunable in .slomo/config.toml ([hooks.autotrace]:
enabled, capture_args, capture_results, include/exclude globs) or
switched off entirely with SLOMO_AUTOTRACE=0. Functions carrying
@track are never recorded twice.
The CLI
| Command | What it does |
|---|---|
slomo |
interactive shell (history + tab completion) |
slomo sessions / slomo session show|inspect|delete ID |
list runs; inspect draws the span tree |
slomo issues / slomo issue show|occurrences|timeline|resolve|reopen|explain ID |
crashes grouped by fingerprint |
slomo doctor ISSUE |
heuristic root-cause diagnosis |
slomo replay ISSUE|SESSION |
interactive step-through (n/p/j/t//search/inspect/vars) |
slomo timeline [REF] [--follow] [--errors] |
chronological feed; --follow live-tails a running app |
slomo search QUERY |
full-text + field filters: slomo search timeout module=checkout user=42 |
slomo vars / slomo http / slomo sql |
typed views over a session or issue |
slomo stats [--rebuild-index] |
totals, categories, storage |
slomo export json|markdown|csv|html |
shareable exports |
slomo prune |
delete oldest sessions beyond the retention limit |
How issues work
A crash is not an issue — it's an incident. Incidents are fingerprinted
(exception type + normalized stack + normalized message, line numbers and
volatile ids excluded) so the same bug tomorrow lands in the same issue with
occurrences += 1, instead of burying you in duplicates. Issues get automatic
category (Null Reference, Network, Database, Timeout, ...), severity,
stability (one-time / intermittent / recurring), and a confidence score.
slomo issue resolve marks one fixed — it auto-reopens if it ever comes back.
Near-miss crashes are surfaced as "possibly related" (never auto-merged).
Privacy
Values under keys like password, token, authorization, cookie,
api_key, ... and secret-shaped values (JWTs, bearer tokens, AWS keys,
Luhn-valid card numbers) are redacted at capture time — secrets never
touch disk. Add your own rules in .slomo/config.toml:
[redaction]
extra_keys = ["internal_id"]
extra_patterns = ["MYCO-[0-9]+"]
Design guarantees
- Fast:
enable()< 5 ms (enforced by a test); events go through one lock-free queue put; a background thread batches writes. The recorder imports zero CLI dependencies. - Crash-safe: append-only JSONL, fsync on the crash path, tolerant reader
— a
kill -9loses at most the final partial line. - Never breaks your app: every hook callback is exception-isolated; storage failures are swallowed; backpressure drops low-severity events rather than blocking.
- Multi-process-safe: one session per process; forked children start
their own session (labeled
forked_from). - Rebuildable: JSONL timelines are the source of truth; the SQLite issue
index is a cache (
slomo stats --rebuild-index).
Storage layout
.slomo/
config.toml
sessions/<timestamp>-<id>/
metadata.json
timeline.jsonl
snapshots/ # oversized variable captures
attachments/
issues/index.sqlite # derived, rebuildable
exports/
cache/
Requires Python 3.12+. MIT licensed.
Development
pip install -e ".[dev]"
pytest
ruff check src tests
Project details
Release history Release notifications | RSS feed
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 slomo-0.1.0.tar.gz.
File metadata
- Download URL: slomo-0.1.0.tar.gz
- Upload date:
- Size: 71.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8222333b95d744e3175481e9ec7e466efc88f6793f9deab3dfba919b881e8078
|
|
| MD5 |
6c8cc815fe64e1486e775f78c2ba3c03
|
|
| BLAKE2b-256 |
b2e2be3b58a3a0d3d7b29944df23c2712ffc3ba13e330641748cc05284c9e102
|
File details
Details for the file slomo-0.1.0-py3-none-any.whl.
File metadata
- Download URL: slomo-0.1.0-py3-none-any.whl
- Upload date:
- Size: 72.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c1ce56ce606bc8ff7eae216ad6ea78826b18a8718ec7363d7c0eb7d45857205
|
|
| MD5 |
6d0306d7fb0d0fedade9ad60099debff
|
|
| BLAKE2b-256 |
d60ab3cb5298472f535e0b0e318cc46ed2ac8a4681f1b9858353811f06a00546
|