Skip to main content

TraceFox — trace, monitor, debug

PyPI Python versions CI License: MIT

An elegant debug assistant for Python web apps, modelled on Laravel Telescope. It records the requests coming into your app, plus the exceptions, logs, database queries, ORM model changes, queued jobs, mail, notifications, cache operations, Redis commands, scheduled tasks, outgoing HTTP calls, template renders and variable dumps behind them. All of it appears in a Telescope-style dashboard.

pip install tracefox

It works with FastAPI / Starlette, Flask and Django, plus any other ASGI or WSGI framework.

┌─────────────┐  hooks   ┌──────────┐  batch   ┌────────┐  thread  ┌──────────────┐
│ your app    │ ───────► │ watchers │ ───────► │ core   │ ───────► │ SQL storage  │ ◄── dashboard (SPA)
│ (any fw)    │          │ (18)     │          │ filter │          │ (SQLAlchemy) │
└─────────────┘          └──────────┘          └────────┘          └──────────────┘

Screenshots

TraceFox requests screen: every incoming request with method, path, status, duration and time

Request detail with payload, masked password and the SQL queries it ran Exception detail with code preview of the failing line and stack trace
Request detail: payload (password masked), headers, response, and every query, log, job and model change from that request Exceptions: grouped occurrences, the failing line in context, and the stack trace
Queries screen in dark mode with a slow query highlighted Mail detail in dark mode with recipients and an HTML preview
Queries: SQL with bindings filled in, slow queries flagged (dark mode) Mail: recipients, subject, HTML preview and .eml download (dark mode)

Features

Everything that Laravel Telescope does, mapped to the Python ecosystem:

Telescope screen What tracefox records Hooks
Requests method, path, controller action, middleware, headers, payload, session, response, status, duration, memory, authenticated user ASGI middleware, WSGI middleware, Django middleware
Commands command, arguments, options, exit code (plus every entry recorded while it ran) Click / Typer / Flask CLI, Django management commands, tracefox.command()
Schedule command, expression, description, timezone, output APScheduler 3.x, Celery beat
Jobs name, queue, connection, tries, timeout, data, status pending → processed / failed, exception Celery (dispatch + worker), FastAPI/Starlette BackgroundTasks, @tracefox.job
Batches total / pending / processed / failed jobs, progress Celery group / chord
Cache hit / missed / set / forget, key, value, expiration Django cache framework, Flask-Caching, tracefox.record_cache()
Dumps dumped values with file:line tracefox.dump(value)
Events name, payload, listeners Django signals, blinker (Flask) signals, tracefox.record_event()
Exceptions class, message, location with code preview, stack trace, context, occurrences grouped by family unhandled request errors, logger.exception(), sys.excepthook, thread excepthook
Gates ability, allowed / denied, arguments, location Django has_perm, @tracefox.gate(), tracefox.record_gate()
HTTP Client method, URI, headers, payload, status, response, duration httpx (sync + async), requests, aiohttp
Logs level, message, channel (logger), context (extra=), location stdlib logging
Mail mailable, from, to, cc, bcc, reply-to, subject, HTML preview, .eml download Django mail backends, smtplib (Flask-Mail), aiosmtplib (fastapi-mail), tracefox.record_mail()
Models created / updated (with changed attributes) / deleted, plus hydration counts per request SQLAlchemy ORM, Django ORM
Notifications notification, notifiable, channel, response tracefox.record_notification()
Queries SQL with bindings interpolated, bindings, duration, slow tag, file:line of your code SQLAlchemy (every engine), Django ORM
Redis command, connection, duration (pipelines too) redis-py (sync + asyncio)
Views template name, path, data keys Jinja2 (Flask, Starlette templates), Django templates

Plus the rest of Telescope's machinery:

  • Batches of related entries. Everything recorded during one request, job or command shares a batch_id. The detail page shows it all in tabs: a request's queries, logs, exceptions, jobs, mail and so on.
  • Tags. Tags are added automatically: Auth:<id>, model keys like app.models.User:1, slow, failed and mail recipients. You can add your own with tracefox.tag(...) and search by tag on every screen.
  • Monitoring. Entries with a monitored tag are always recorded, even when filters would drop them.
  • Exception families. Repeats of the same exception collapse into one row with an occurrence count and an "other occurrences" view.
  • Filtering. Local, dev, test, UAT and staging environments record everything. In production (and any environment not on that list), a default filter keeps only failed requests, exceptions, failed jobs, scheduled tasks, slow queries and monitored tags. You can add tracefox.filter(...) / tracefox.filter_batch(...).
  • Privacy. Sensitive request headers and parameters are masked as ********. password, authorization and cookie are masked by default, and the lists are configurable.
  • Pause / resume recording, clear all entries, light / dark / system theme, live "Load New Entries", pruning.
  • Safe by default. The dashboard is open only in local; everywhere else it needs a login (see Dashboard access). Every write needs a custom header, which blocks CSRF. Watchers never raise into your app.
  • Low overhead. Entries are buffered per batch and written on a background thread. Libraries are hooked only if your app imports them, through import hooks.

Install

pip install tracefox            # core (SQLAlchemy is the only dependency)
pip install "tracefox[fastapi]" # or [flask] / [django]

Like Laravel Telescope, TraceFox keeps its entries in your application's own database. Its migrations add four tables: tracefox_entries, tracefox_entries_tags, tracefox_monitoring and tracefox_settings. See Database & migrations.

Quick start

FastAPI / Starlette

from fastapi import FastAPI
from sqlalchemy import create_engine
from tracefox.integrations.fastapi import init_tracefox

engine = create_engine(DATABASE_URL)   # your app's engine (an AsyncEngine works too)

app = FastAPI(debug=True)
init_tracefox(app, engine=engine)      # middleware + dashboard at /tracefox, entries in your database

Flask

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from tracefox.integrations.flask import FlaskTracefox

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "postgresql://..."
db = SQLAlchemy(app)
FlaskTracefox(app)            # uses Flask-SQLAlchemy's database; dashboard + `flask tracefox prune|clear|pause|resume`

Django

# settings.py
INSTALLED_APPS += ["tracefox.integrations.django"]
MIDDLEWARE.insert(0, "tracefox.integrations.django.middleware.TracefoxMiddleware")
TRACEFOX = {
    "authorization": lambda request: request.user.is_staff,   # who may open the dashboard outside DEBUG
}

# urls.py
urlpatterns += [path("tracefox/", include("tracefox.integrations.django.urls"))]
python manage.py migrate      # creates the tracefox tables in your default database

Management commands: tracefox_prune [--hours 48] [--keep-exceptions], tracefox_clear, tracefox_pause, tracefox_resume.

Any other ASGI / WSGI framework

from tracefox import tracefox
from tracefox.integrations import TracefoxMiddleware, TracefoxWSGIMiddleware
from tracefox.dashboard import TracefoxASGI, TracefoxWSGI

tracefox.start(environment="local")
asgi_app = TracefoxMiddleware(asgi_app)                 # record requests
# mount TracefoxASGI() (or TracefoxWSGI()) at /tracefox with your router

Open http://localhost:8000/tracefox.

Database & migrations

TraceFox works the way Laravel Telescope does: it stores entries next to your data, in your application's database, and creates its tables with migrations.

Stack Where entries go How the tables are created
Django the default database (or TRACEFOX = {"connection": "analytics"}) shipped migrations: python manage.py migrate
Flask + Flask-SQLAlchemy Flask-SQLAlchemy's engine, detected automatically (SQLALCHEMY_DATABASE_URI also works) Flask-Migrate: tracefox install → flask db upgrade, or created on first use
FastAPI / Starlette / SQLAlchemy the engine you pass: init_tracefox(app, engine=engine) Alembic: tracefox install → alembic upgrade head, or created on first use
No app database database_url, defaulting to a local tracefox.sqlite3 created on first use

Alembic / Flask-Migrate. This is the counterpart of php artisan telescope:install:

tracefox install            # writes migrations/versions/<rev>_create_tracefox_tables.py on top of your current head
alembic upgrade head        # or: flask db upgrade

Then keep --autogenerate from proposing to drop the tables. In env.py:

from tracefox.integrations.alembic import include_object

context.configure(connection=connection, target_metadata=target_metadata, include_object=include_object)

If migrations own your schema, pass auto_migrate=False so TraceFox never creates tables by itself. The migration is safe to run either way: it skips tables that already exist.

Async apps. If you pass an AsyncEngine (asyncpg, aiomysql, aiosqlite…), TraceFox writes to the same database through the matching blocking driver (psycopg / psycopg2, PyMySQL, sqlite3), because its writer runs on a background thread. Install that driver alongside your async one.

Own queries are invisible. TraceFox suspends recording while it reads or writes its tables, so they never show up on the Queries or Models screens.

Dashboard access

The environment name decides who can open the dashboard. TraceFox reads it from TRACEFOX_ENV, then APP_ENV, ENVIRONMENT and ENV; if none is set, it uses local when your framework runs in debug mode and production otherwise.

Environment Login needed? What is recorded
local no everything
dev, development, test, qa, uat, stage, staging yes everything
production and anything else yes exceptions, failed requests and jobs, slow queries, scheduled tasks, monitored tags

Outside local, the dashboard stays locked (403) until you choose how people log in.

1. Username and password (works with every framework). Set two environment variables on the server:

TRACEFOX_ENV=staging
TRACEFOX_USERNAME=admin
TRACEFOX_PASSWORD='a-long-random-password'

The browser shows a login prompt (HTTP Basic auth) when you open /tracefox. You can also set it in code, for one or several people:

init_tracefox(app, basic_auth=("admin", os.environ["TRACEFOX_PASSWORD"]))
init_tracefox(app, basic_auth={"ada": "…", "grace": "…"})

Serve the dashboard over HTTPS so the password isn't sent in clear text. Behind Apache mod_wsgi, enable WSGIPassAuthorization On so the login header reaches the app.

2. Your app's own login (like Laravel's viewTelescope gate). Pass a callback; it receives your framework's request and returns True for people allowed in:

# Django: staff users who are logged in to your site
TRACEFOX = {"authorization": lambda request: request.user.is_authenticated and request.user.is_staff}

# Flask (Flask-Login)
FlaskTracefox(app, authorization=lambda request: current_user.is_authenticated and current_user.is_admin)

# FastAPI / Starlette: anything you can check on the request, e.g. a session cookie or an internal network
init_tracefox(app, authorization=lambda request: request.session.get("role") == "admin")

If you set both, a viewer needs the username and password and must pass the callback.

To change which environments are open or record everything, use open_environments and record_all_environments. For example, open_environments=["local", "dev"] drops the login on a private dev box.

Configuration

Options are passed as keyword arguments to init_tracefox(app, ...) / FlaskTracefox(app, ...) / tracefox.start(...), through Flask's app.config["TRACEFOX"], or through Django's TRACEFOX = {...}.

Option Default
enabled $TRACEFOX_ENABLED or True master switch
environment $TRACEFOX_ENV / $APP_ENV / $ENVIRONMENT / $ENV; otherwise local when the framework is in debug mode, else production decides dashboard access and what gets recorded
open_environments ["local"] environments where the dashboard needs no login
record_all_environments local, dev, development, test, testing, qa, uat, stage, staging environments that record every entry; others use the default filter
basic_auth $TRACEFOX_USERNAME + $TRACEFOX_PASSWORD built-in login: ("user", "password"), {"user": "password", ...} or "user:password"
path /tracefox dashboard URL
engine Flask-SQLAlchemy's engine when present your app's SQLAlchemy Engine / AsyncEngine, or a callable returning one
connection (Django) "default" which DATABASES alias holds the tracefox tables
database_url $TRACEFOX_DATABASE_URL, else sqlite:///tracefox.sqlite3 when no app database is given a separate SQLAlchemy URL, if you'd rather not share the app database
auto_migrate True create missing tracefox tables on first use (SQLAlchemy only; Django uses migrate)
authorization None callable(request) -> bool; receives the framework's request object (combined with basic_auth when both are set)
watchers all enabled e.g. {"queries": {"slow": 50}, "redis": False, "logs": {"level": "WARNING"}}
ignore_paths / only_paths static assets ignored glob patterns for request recording
hidden_request_headers / hidden_request_parameters / hidden_response_parameters auth headers, cookies, passwords, tokens masked as ********
user_resolver framework default callable(request) -> {"id", "name", "email"}
default_filter True apply the production filter outside record_all_environments
async_writes True write on a background thread (set False in tests)
auto_prune_hours None prune automatically every hour, keeping N hours

Watcher options:

Watcher Options
requests size_limit (KB, default 64), ignore_http_methods, ignore_status_codes
queries slow (ms, default 100), ignore_patterns (regex)
logs level (default DEBUG), ignore_loggers
exceptions ignore (class names)
commands ignore (runserver, shell, migrate, … by default)
events ignore, include_framework (framework signals are skipped by default)
models events (created, updated, deleted), hydrations
cache hidden (key globs whose values are masked)
gates ignore_abilities
http_client size_limit

Customising

from tracefox import tracefox, dump

# Tag entries
@tracefox.tag
def tags(entry):
    return ["vip"] if entry.is_request() and entry.content.get("user", {}).get("id") == 1 else []

# Only keep what matters (monitored tags always pass)
tracefox.filter(lambda entry: entry.is_exception() or entry.is_failed_request() or entry.is_slow_query())

# Keep or drop a whole request/job/command batch
tracefox.filter_batch(lambda entries: any(e.is_exception() for e in entries))

tracefox.hide_request_parameters("ssn", "card_number")
tracefox.hide_request_headers("x-internal-token")

Manual instrumentation

Some Laravel concepts have no single standard in Python, so each one also has a small API:

dump(user, order)                                         # Dumps screen; returns its argument
tracefox.record_event("order.shipped", {"id": 7}, listeners=["NotifyCustomer"])
tracefox.record_notification("InvoicePaid", user, "slack", response={"ok": True})
tracefox.record_mail(mailable="WelcomeMail", to=["a@b.c"], subject="Hi", html="<h1>Hi</h1>")
tracefox.record_cache("hit", "user:1", value)
tracefox.record_gate("edit-post", allowed, [post])

@tracefox.gate("publish-post")           # records the boolean result
def can_publish(user, post): ...

@tracefox.job(queue="emails")            # records a processed / failed job and batches its entries
def send_digest(user_id): ...

with tracefox.command("import:users", {"file": path}):   # records a custom command
    ...

with tracefox.batch():                   # group entries recorded outside a request
    ...

with tracefox.without_recording():       # skip recording for a block
    ...

Maintenance

tracefox prune --hours 48 --keep-exceptions
tracefox clear
tracefox pause | resume
tracefox --database-url postgresql://... migrate
tracefox install                                   # add the Alembic / Flask-Migrate migration

Django has the same commands under manage.py: tracefox_prune, tracefox_clear, tracefox_pause, tracefox_resume. Run prune on a schedule, as Laravel's docs recommend, or set auto_prune_hours.

How it works

  • Watchers hook libraries only when your app imports them. A sys.meta_path hook runs the callback after import, so tracefox never pulls in Celery or Django by itself. Every patch is reversible with tracefox.stop().
  • Batches live in a ContextVar, so they follow async code, threadpools, asgiref and Celery tasks correctly. Entries are buffered and written when the request, job or command finishes.
  • Storage mirrors Laravel Telescope's schema: tracefox_entries, tracefox_entries_tags, tracefox_monitoring, plus tracefox_settings. Repeated exceptions are folded into families when they are written.
  • Out-of-order updates. A job can finish (in a worker, or a background task) before the request that dispatched it is written. That update is applied in memory if the entry is still buffered; otherwise the writer retries it for two minutes.
  • The dashboard is a dependency-free single-page app served by a framework-agnostic handler, with thin ASGI, WSGI and Django adapters.

Development

uv venv && uv pip install -e ".[dev]"
pytest
TRACEFOX_ENV=local uvicorn examples.fastapi_app:app --reload   # demo that exercises every watcher

Releasing

Releases go to PyPI from GitHub Actions through Trusted Publishing, so no API token is stored.

main is protected: changes land through pull requests, and all six CI jobs (Python 3.9–3.14) must pass before merging. Direct pushes, force-pushes and deleting main are blocked for everyone. Release tags (v*) are locked once pushed, so they can't be moved or deleted.

  1. Open a pull request that bumps version in pyproject.toml and __version__ in src/tracefox/__init__.py, and merge it once CI passes.
  2. Tag the merged commit and push the tag: git checkout main && git pull && git tag v0.1.3 && git push origin v0.1.3.
  3. The Publish to PyPI workflow checks the tag matches the version, then tests, builds and uploads the release.

A tag can't be moved after it's pushed. If a release goes wrong, fix it on main and release the next version.

License

MIT — see LICENSE.

Metadata

Release files for tracefox 0.1.3

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

Source distribution (sdist)

Source distribution for tracefox 0.1.3
File Size Uploaded Provenance Actions
tracefox-0.1.3.tar.gz 125.8 kB With provenance Details for tracefox-0.1.3.tar.gz

Built distribution (wheel)

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

Total release size: 263.4 kB

Release files / tracefox-0.1.3.tar.gz

Download URL tracefox-0.1.3.tar.gz
Size 125.8 kB
Tags Source
SHA-256 checksum
How to use checksums
12e2fd9937659962a044d897bcfa2835d262819dde96cf6a113c78a5dae348cc
BLAKE2b-256 checksum
How to use checksums
23bd8a582ea3fda74df9697bf7b5175a0d3b955a7c33a063e3af8d134eb76ce6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / tracefox-0.1.3-py3-none-any.whl

Download URL tracefox-0.1.3-py3-none-any.whl
Size 137.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
31d9e8400a36229982ab016c934575a48cb2c12985ecab81765da34c5288d5b7
BLAKE2b-256 checksum
How to use checksums
e8ccd9cfddbc2d44ccc6f33b594cead9dc13ff436f504a3c1dc3e4570778d0c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for tracefox 0.1.3

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for tracefox-0.1.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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