forge-ops-tracker
Python error reporting client for a private, self-hosted ForgeOps tracker instance.
Requires Python 3.9+. A from-scratch port of gems/forge_ops_tracker
(the Rails client) -- see that gem's README for the shared design rationale; this document only
covers what's Python-specific.
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 Django/Flask-wide equivalent to Rails' Rails.error.handle here -- 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
Unlike the .NET SDK (where a compiled assembly's file path never matches its original source
location), Python runs interpreted directly from real .py files on disk, so file-path matching
against Configuration.app_root works the same way it does in the Ruby gem's Rails.root
comparison. 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
Same behavior as the Ruby gem: 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.0
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.0.tar.gz | 19.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| forge_ops_tracker-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.0 kB
Release files / forge_ops_tracker-0.1.0.tar.gz
| Download URL | forge_ops_tracker-0.1.0.tar.gz |
|---|---|
| Size | 19.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2fff17ef110a15e3f72a9ebc64d2cb56fe6d10364f979712b1d576e6650dfa43
|
|
BLAKE2b-256 checksum How to use checksums |
2e04c8635ac10b6c79258fedc56b81bd93fac69ae00c159e8e1c3cd7546cefbe
|
| 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.0-py3-none-any.whl
| Download URL | forge_ops_tracker-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b85261a938ff4bcbc05bf2cec8f5f894c378342eb70b8ab58c21ce344404dcd
|
|
BLAKE2b-256 checksum How to use checksums |
2afe6649a90717539812c634b83e819727e1848cb7ddb72c8c1c11007bf1c384
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|