Error Tracking with Sentry
The essential playbook for implementing error tracking with sentry in your SaaS.
Use Sentry to capture unhandled exceptions, trace failures by release, and keep enough context to fix production issues quickly. For a small SaaS, the baseline setup is: initialize Sentry early, set environment and release consistently, instrument backend and worker entrypoints first, then verify alerts before launch.
This page focuses on practical setup and troubleshooting for missing, duplicated, or noisy Sentry events across app, worker, and frontend services.
Quick Fix / Quick Setup
Start with backend error capture first. Add release and environment tags immediately. Keep tracing disabled until basic error reporting is stable.
Python backend
# Flask/FastAPI
pip install sentry-sdk[flask,fastapi]Flask example
import os
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
integrations=[FlaskIntegration()],
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
traces_sample_rate=0.0,
send_default_pii=False,
)FastAPI example
import os
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
integrations=[StarletteIntegration(), FastApiIntegration()],
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
traces_sample_rate=0.0,
send_default_pii=False,
)Trigger a test event
from sentry_sdk import capture_exception
try:
1 / 0
except Exception as e:
capture_exception(e)Frontend optional setup
npm install @sentry/browser @sentry/tracingimport * as Sentry from "@sentry/browser";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.VITE_APP_ENV,
release: import.meta.env.VITE_APP_RELEASE,
tracesSampleRate: 0.0,
});Required environment variables
SENTRY_DSN=https://<key>@o<org>.ingest.sentry.io/<project>
APP_ENV=production
APP_RELEASE=$(git rev-parse --short HEAD)Process Flow
What’s happening
- Sentry receives exceptions, stack traces, request metadata, and release/environment tags from your app.
- If Sentry is installed but no events appear, the common causes are bad DSN, wrong environment variables, blocked outbound HTTPS, filtered events, or initialization happening too late.
- If too many events appear, duplicate exception capture, logging integration noise, health checks, and expected application errors are usually the cause.
- For a small SaaS, instrument these three surfaces first:
- API app
- background worker
- frontend
Step-by-step implementation
1. Create projects and copy DSNs
Use separate Sentry projects when possible:
backend-apiworkerfrontend-web
This reduces noise and makes ownership clearer.
2. Store DSNs in environment variables
Do not hardcode DSNs.
# backend
SENTRY_DSN=https://<backend-key>@o<org>.ingest.sentry.io/<project>
# frontend
VITE_SENTRY_DSN=https://<frontend-key>@o<org>.ingest.sentry.io/<project>
# shared
APP_ENV=production
APP_RELEASE=git-sha-or-image-tag3. Initialize Sentry as early as possible
Initialize before routes, middleware, or worker job registration.
FastAPI app entrypoint
import os
import sentry_sdk
from fastapi import FastAPI
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
integrations=[StarletteIntegration(), FastApiIntegration()],
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
traces_sample_rate=0.0,
send_default_pii=False,
)
app = FastAPI()
@app.get("/error-test")
def error_test():
raise RuntimeError("Sentry API test")Flask app entrypoint
import os
import sentry_sdk
from flask import Flask
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
integrations=[FlaskIntegration()],
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
traces_sample_rate=0.0,
send_default_pii=False,
)
app = Flask(__name__)
@app.route("/error-test")
def error_test():
raise RuntimeError("Sentry Flask test")4. Set environment values explicitly
Use one format everywhere:
APP_ENV=development
APP_ENV=staging
APP_ENV=productionDo not mix values like prod, Prod, live.
5. Set release from CI/CD
Use deploy identifiers such as:
- Git SHA
- Docker image tag
- CI build number
Example:
APP_RELEASE=${GITHUB_SHA}Docker example:
ARG APP_RELEASE=unknown
ENV APP_RELEASE=$APP_RELEASERuntime example:
docker run -e APP_ENV=production -e APP_RELEASE=$(git rev-parse --short HEAD) ...6. Initialize workers separately
Background workers need their own initialization.
Celery example
import os
import sentry_sdk
from celery import Celery
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
traces_sample_rate=0.0,
send_default_pii=False,
)
celery_app = Celery("worker")
@celery_app.task
def failing_task():
raise RuntimeError("Sentry worker test")If jobs are short-lived, flush before exit:
import sentry_sdk
try:
raise RuntimeError("job failed")
except Exception as e:
sentry_sdk.capture_exception(e)
sentry_sdk.flush(timeout=5)7. Add user context carefully
Do not send PII by default.
sentry_sdk.set_user({"id": str(user.id)})Only add email or IP after privacy review and operational need.
8. Filter expected exceptions
Use before_send to drop noise.
import os
import sentry_sdk
from werkzeug.exceptions import NotFound, Unauthorized
def before_send(event, hint):
exc_info = hint.get("exc_info")
if exc_info:
exc_type, exc_value, tb = exc_info
if isinstance(exc_value, (NotFound, Unauthorized)):
return None
return event
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
environment=os.getenv("APP_ENV", "production"),
release=os.getenv("APP_RELEASE", "unknown"),
before_send=before_send,
traces_sample_rate=0.0,
send_default_pii=False,
)9. Add correlation IDs as tags
This makes Sentry events easier to match with logs.
import sentry_sdk
request_id = "abc-123"
sentry_sdk.set_tag("request_id", request_id)Pair this with structured logs. See Logging Setup.
10. Verify end-to-end from every surface
Test all of these:
- API exception
- worker failure
- frontend exception
Frontend test:
throw new Error("Sentry frontend test");Use this configuration matrix to validate each reporting surface before launch:
| Surface | DSN variable | APP_ENV / environment tag | APP_RELEASE source | Sampling baseline | Flush needed before exit |
|---|---|---|---|---|---|
| Backend API (Flask/FastAPI) | SENTRY_DSN | production / staging / development (consistent naming) | CI SHA or image tag injected at deploy | traces_sample_rate=0.0 initially | Usually no (long-running process) |
| Worker (Celery/RQ/cron) | SENTRY_DSN (worker project preferred) | Same value scheme as API | Same deploy identifier as API for correlation | traces_sample_rate=0.0 initially | Yes for short-lived jobs: sentry_sdk.flush(timeout=5) |
| Frontend web app | VITE_SENTRY_DSN (or framework-specific public DSN var) | Public env value mapped to same env naming | Build-time release matching backend deploy | tracesSampleRate=0.0 initially | Not typically required; SDK lifecycle handles browser flush |
Common causes
SENTRY_DSNis unset or points to the wrong project.- Sentry SDK initializes too late in the app lifecycle.
- Worker process does not initialize Sentry separately.
- Frontend DSN or build env variable is missing at compile time.
- Outbound network or firewall blocks HTTPS requests to Sentry.
before_sendor logger filters drop events unexpectedly.- Events are duplicated by both logging integration and explicit capture.
- Release/environment tags are missing or inconsistent.
- Short-lived jobs exit before the SDK flushes events.
- CSP or ad blockers prevent browser events from being sent.
Debugging tips
Check environment variables inside the running process
printenv | grep SENTRY
printenv | grep APP_If using Docker:
docker exec -it <container_name> printenv | grep SENTRY
docker logs <container_name> --tail 200If using systemd:
journalctl -u <service_name> -n 200 --no-pagerSend a manual test event from the same environment
python -c "import os, sentry_sdk; sentry_sdk.init(dsn=os.getenv('SENTRY_DSN'), environment=os.getenv('APP_ENV','production'), release=os.getenv('APP_RELEASE','debug'), debug=True); 1/0"Message test:
python - <<'PY'
import os
import sentry_sdk
from sentry_sdk import capture_message
sentry_sdk.init(
dsn=os.getenv('SENTRY_DSN'),
environment=os.getenv('APP_ENV','production'),
release=os.getenv('APP_RELEASE','debug'),
debug=True
)
capture_message('sentry test message')
sentry_sdk.flush()
print('sent')
PYVerify outbound connectivity
curl -I https://sentry.ioFor self-hosted Sentry, test your own domain instead.
Check worker health
Celery:
celery -A <app_path> inspect pingRQ:
rq infoLook for duplicate capture paths
If duplicate issues appear, check whether both of these are active:
- automatic framework/logging integration
- manual
capture_exception(...)
Check for buffering or process exit issues
For jobs, scripts, and short-lived worker processes, call:
sentry_sdk.flush(timeout=5)Compare logs against Sentry timestamps
Correlate timestamps and request IDs to determine whether the event was:
- never emitted
- blocked by filters
- delayed by buffering
- sent from the wrong project or environment
troubleshooting decision tree for no events, duplicate events, and too many events.
Checklist
- ✓
SENTRY_DSNis present in web, worker, and frontend environments where needed. - ✓ Sentry initializes before app routes and worker jobs start.
- ✓
APP_ENVuses consistent values across all services. - ✓
APP_RELEASEis set from CI/CD or git SHA. - ✓ A manual test exception appears in the correct Sentry project.
- ✓ No sensitive data is sent unintentionally.
- ✓ Expected errors are filtered out.
- ✓ Alerts are configured for production only.
- ✓ Server logs include correlation IDs or request IDs.
- ✓ Worker processes flush events before exit if short-lived.
- ✓ Frontend build exposes the DSN correctly if browser reporting is enabled.
- ✓ Browser CSP does not block Sentry ingestion endpoints.
Related guides
- Debugging Production Issues
- 502 Bad Gateway Fix Guide
- SaaS Production Checklist
- Common Auth Bugs and Fixes
FAQ
Do I need separate Sentry projects for API, frontend, and workers?
Usually yes. Separate projects reduce noise, simplify ownership, and make alerts more useful.
Why are no events showing up even though the SDK is installed?
Most often the DSN is wrong, the SDK initializes too late, or the event is filtered before send. Verify from inside the running process with a manual test exception.
Should I send user emails and IPs to Sentry?
Not by default. Start with send_default_pii=False and only add user context after privacy review and a clear operational need.
How do I connect Sentry issues to a deployment?
Set a consistent release value from CI/CD, such as a git SHA or image tag, on every service that reports to Sentry.
When should I enable performance monitoring?
After error tracking is working cleanly. Start with a low traces sample rate to control volume and cost.
Final takeaway
A working Sentry setup is not just installing the SDK. The minimum production-ready setup is:
- early initialization
- correct
DSN,environment, andrelease - low-noise reporting
- verification from every process that can fail
If you only do three things, do this:
- initialize early
- tag releases
- send a deliberate test exception from web and worker after every deployment
For broader production readiness, pair this with: