Skip to main content

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.

Prefer to look before you leap? slomo init scaffolds .slomo/ with a commented config so you can tune redaction, auto-trace, and retention before the first run — and prints the two lines to paste into your app.


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:

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 queriessqlite3 out of the box, everything else via SQLAlchemy's event API (Postgres, MySQL, ...)
  • HTTP callsrequests and httpx (sync + async), request/response pairs correlated
  • Log recordslogging WARNING 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 init optional project setup — scaffold .slomo/ with a commented config before the first run
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 -9 loses 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


Download files

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

Source Distribution

slomo-0.1.2.tar.gz (168.3 kB view details)

Uploaded Source

Built Distribution

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

slomo-0.1.2-py3-none-any.whl (74.1 kB view details)

Uploaded Python 3

File details

Details for the file slomo-0.1.2.tar.gz.

File metadata

  • Download URL: slomo-0.1.2.tar.gz
  • Upload date:
  • Size: 168.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for slomo-0.1.2.tar.gz
Algorithm Hash digest
SHA256 00d5611e73b3d616f77bf112cb62748d4d8bfdb2222f8b24d2049808fceda0bf
MD5 89c387eff5951812c271c9573b1a053f
BLAKE2b-256 d8275bcb2ba00d16d54236957882527beac341e65ee1fdb25c4e7ba924620a9f

See more details on using hashes here.

File details

Details for the file slomo-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: slomo-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 74.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for slomo-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 05aa844c8e00d85d2849d284d5acef1f19009af769946de8ca083cf3577b74ac
MD5 6c350a8b4d6f485f4d7cafe3461bf341
BLAKE2b-256 e7664f00080707f67d8d3d2c23c8232f0fd624d3f2b28626684ffde9f0f480d3

See more details on using hashes here.

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