forge-ops-tracker
Python error reporting client for a private, self-hosted ForgeOps tracker instance. Requires Python 3.9+. It captures unhandled and explicitly reported exceptions, builds a backtrace, scrubs likely PII, and delivers events to ForgeOps over HTTP without blocking the request or process that raised them.
Installation
Not yet published to PyPI -- install directly from this path (or a local checkout, once split into its own repo):
pip install -e path/to/forge_ops/sdks/python
For Django or Flask integration, install the matching extra:
pip install -e "path/to/forge_ops/sdks/python[django]"
pip install -e "path/to/forge_ops/sdks/python[flask]"
Configuration
Set a DSN (from a project's settings page in ForgeOps), either via the FORGE_OPS_DSN environment
variable or explicitly:
import forge_ops_tracker
forge_ops_tracker.init(
dsn="https://<api_key>@your-forgeops-host/api/v1/events", # or leave unset to read FORGE_OPS_DSN
release="...",
environment="production",
)
Call init() once at startup -- Django's settings.py, or right after creating a Flask app. Any
Configuration attribute can be overridden by keyword.
Django
# settings.py
import forge_ops_tracker
forge_ops_tracker.init(dsn="https://<api_key>@your-forgeops-host/api/v1/events")
MIDDLEWARE = [
...,
"forge_ops_tracker.integrations.django.ForgeOpsTrackerMiddleware",
]
Flask
from flask import Flask
import forge_ops_tracker
from forge_ops_tracker.integrations.flask import init_flask
forge_ops_tracker.init(dsn="https://<api_key>@your-forgeops-host/api/v1/events")
app = Flask(__name__)
init_flask(app)
What gets reported automatically, and what doesn't
An exception that crashes a request needs no further wiring at all. The Django middleware's
process_exception hook and Flask's got_request_exception signal both fire for anything that
propagates uncaught out of a view, then let the framework handle it exactly as if this client
weren't installed.
An exception your own code catches and handles is different -- neither integration ever sees it, since it never propagates far enough to reach either hook:
try:
charge_card(order)
except CardError as e:
logger.warning("card declined: %s", e)
# ForgeOps never sees this -- caught locally, never reaches the
# middleware/signal at all.
There's no application-wide hook that reports an exception while still letting your own except
block handle it -- report it explicitly instead, right at the catch site:
except CardError as e:
forge_ops_tracker.capture_exception(e, context={"order_id": order.id})
logger.warning("card declined: %s", e)
Called with no arguments, capture_exception() picks up whichever exception is currently being
handled (same as a bare raise inside an except: block), so it usually reads as just
forge_ops_tracker.capture_exception() from inside the block that already caught it.
Outside a web request (scripts, management commands, workers)
init() also installs a sys.excepthook wrapper by default (Configuration.install_excepthook,
True unless set otherwise), which reports anything that crashes the whole interpreter -- a plain
script, a Django management command, a worker's own top-level loop -- with no wiring needed, the
same "unhandled needs no wiring" case the Django/Flask integrations cover for web requests. It
still calls whatever sys.excepthook was already installed afterward, so it never changes program
behavior. This does not catch a web request's unhandled exception under a real WSGI server
(Gunicorn/uWSGI catch that themselves per-request, long before it would ever reach the interpreter
level) -- that's what the Django/Flask integrations are for.
Delivery happens on a background thread with a bounded queue and a short per-request HTTP timeout
(Configuration.timeout, 2s default). Every failure mode -- network errors, timeouts, a full queue,
a malformed DSN -- is caught and dropped rather than raised, so a broken or unreachable tracker can
never take down the host app. The worker thread starts lazily, on first push, not at import time --
Gunicorn (prefork) and uWSGI commonly fork worker processes after the application has already
loaded, which would leave an eagerly-started thread dead in every forked child; starting fresh on
first push means each forked worker gets its own live thread regardless of when it was forked
relative to import.
in_app backtrace frames
Python runs interpreted directly from real .py files on disk, so file-path matching against
Configuration.app_root is a straightforward prefix comparison against those on-disk paths.
Defaults to the current working directory; set it explicitly if that doesn't match your app's
actual layout (a WSGI server started from a different directory than your app's root, for
instance). Standard-library and installed-package (site-packages/dist-packages) frames are
never marked in_app, regardless of app_root.
PII scrubbing
By default, the message, backtrace, and any context/tags you attach are scanned
for likely personal data -- email addresses, formatted SSNs/credit cards, known API key/token
formats, and anything under a suspiciously-named key (password, api_key, ssn, and similar) --
and redacted before the payload ever leaves this process. ForgeOps itself scrubs again on arrival
regardless, so this is a second, earlier layer, not the only one.
To disable it:
forge_ops_tracker.init(dsn="...", scrub_pii=False)
Running the tests
cd sdks/python
python3 -m venv .venv
./.venv/bin/pip install -e ".[test]"
./.venv/bin/python -m pytest
./.venv/bin/ruff check src tests
Release files for forge-ops-tracker 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| forge_ops_tracker-0.1.1.tar.gz | 19.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| forge_ops_tracker-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.8 kB
Release files / forge_ops_tracker-0.1.1.tar.gz
| Download URL | forge_ops_tracker-0.1.1.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b5c25c0b1bb315cb4f0a951c9825b43e944b81de0556a7e486290ffcbe5392f2
|
|
BLAKE2b-256 checksum How to use checksums |
022265eba34b7f2cc6dca52305b426feb257bbeb9915dafe8cec9b81a7c07bd7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / forge_ops_tracker-0.1.1-py3-none-any.whl
| Download URL | forge_ops_tracker-0.1.1-py3-none-any.whl |
|---|---|
| Size | 15.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf5f03bde63638ac5b63b05cc07325fefd17981b8f50d78b910a44db7ca93209
|
|
BLAKE2b-256 checksum How to use checksums |
f8daf990cb37922321fc4214c9a1222965258468110cbe9678543d4031f83ea0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|