Adding Sentry to a Django backend is usually presented as a two-line change: install sentry-sdk, call sentry_sdk.init(dsn=...). That's true once you actually have a DSN. On Tabeer.ai's backend, getting there — and then keeping the project usable afterward — took a bit more than that.
The Token You're Handed Often Isn't the One You Need
I was given a Sentry token starting with sntryu_. That's a Sentry User Auth Token — used for Sentry's own REST API (creating projects, managing teams, reading issues programmatically). It is not a DSN, and it will not work if you drop it into sentry_sdk.init(dsn="sntryu_...") — the SDK expects a project-specific ingest URL that looks nothing like it, something like:
https://<public_key>@o<org_id>.ingest.<region>.sentry.io/<project_id>
Rather than ask for the "right" credential and wait, the auth token was enough to get there directly, using Sentry's own API:
# Find the org and confirm which region it's on
curl -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://sentry.io/api/0/organizations/"
This particular org came back registered on Sentry's EU region, which matters — Sentry's API is region-partitioned, and sentry.io's general endpoint won't manage projects that live on de.sentry.io (or us.sentry.io). Once that was clear, project creation and DSN retrieval both went through the region-specific host:
curl -X POST -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://de.sentry.io/api/0/teams/mediod-consulting/mediod-consulting/projects/" \
-d '{"name": "tabeer-backend", "platform": "python-django"}'
curl -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
"https://de.sentry.io/api/0/projects/mediod-consulting/tabeer-backend/keys/"
That last call is what actually returns the DSN — the thing sentry_sdk.init() wants, and the thing the auth token itself is not.
The Django Side
Standard, once the real DSN is in hand:
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
if SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[DjangoIntegration()],
send_default_pii=False,
traces_sample_rate=0.1,
)
send_default_pii=False is worth calling out as a deliberate default, not an oversight — Sentry can capture request headers, user IDs, and other identifying data by default, and for a platform handling student accounts, that's not something to enable without a specific reason to.
The Bug: My Own Test Suite Was Reporting to Production
Sentry initializing unconditionally on Django startup means it also initializes every time manage.py test runs — including tests written specifically to deliberately raise an exception, to verify that a failure path behaves correctly. One such test intentionally raised RuntimeError: SMTP down to check the app's handling of a failed email send. With SENTRY_DSN set in the local .env (needed for local debugging), every local test run was reporting that deliberate, fake error straight to the real, production Sentry project — indistinguishable from an actual incident.
This surfaced two genuine noise events in the Sentry dashboard whose stack trace absPath pointed at a local development machine's file path — the giveaway that these weren't real production errors at all, just test runs polluting the signal.
The fix is a one-line guard:
import sys
if SENTRY_DSN and "test" not in sys.argv:
sentry_sdk.init(...)
sys.argv during a manage.py test invocation contains "test" as the subcommand; checking for it is a cheap, reliable way to skip SDK initialization specifically during test runs, without needing a separate settings file or environment variable just for this. Any CI test run, any local manage.py test, stops reporting to Sentry entirely — while the app still initializes normally for runserver, gunicorn, Celery workers, and everything else that isn't the test runner.
Why This Matters Beyond the One Bug
A monitoring tool that reports noise erodes trust in it fast — the first time someone dismisses a real alert because "it's probably another test run," the tool has stopped doing its job. The fix here is small, but the failure mode it prevents (a team gradually tuning out their own error tracker) is not.
The Frontend Side, Briefly
The Nuxt frontend uses the same DSN pattern but loads Sentry via its CDN script rather than the @sentry/nuxt npm package — matching how the rest of the app's third-party scripts (analytics, session recording) are loaded, and avoiding the SSR-side complexity and bundle weight an npm SDK package adds for something that only actually needs to run in the browser.
If you're setting up Sentry — or auditing an existing setup for exactly this kind of test-pollution — get in touch.
By Shahid Malik