Sentry is an application monitoring platform that tells you, in real time, when your Django app breaks in production — and gives you the stack trace, request data, and the exact release that introduced the regression. Beyond errors, it also handles performance tracing (slow views, N+1 queries), profiling (which lines of Python burn CPU), release health, and alerting. You wire it up by installing the sentry-sdk package and calling sentry_sdk.init() once at startup; the SDK then captures unhandled exceptions automatically, with no per-view code.
This guide covers a production-ready setup for Django 5.x with the current sentry-sdk (the old raven client has been deprecated for years — if you still have raven in your project, replace it). You will configure error capture, tracing, profiling, environments and releases, noise filtering, PII scrubbing, and Celery/logging integration.
What Sentry gives a Django application
- Real-time error tracking — unhandled exceptions in views, middleware, templates, signals, and management commands are captured automatically, grouped into issues, and deduplicated.
- Rich context — request URL, headers, query params, the user (if you opt in), local variables in the stack trace, and breadcrumbs (the trail of events leading up to the error).
- Performance tracing — transactions and spans show where request time goes, including database query spans and automatic N+1 query detection.
- Profiling — function-level CPU profiles sampled from real traffic.
- Release health & suspect commits — tie issues to a deploy and let Sentry suggest the commit that likely caused a regression.
- Alerting & workflow — alert rules (Slack, email, PagerDuty), issue assignment, and ignore/resolve states.
Sentry is available as a hosted SaaS (sentry.io) and as a self-hostable open-source server. As of 2026 the hosted service offers a free developer tier with usage/quota-based limits — verify the current quotas on sentry.io, as they change. If you would rather run your own instance, see our walkthrough on setting up a self-hosted Sentry server.
Install the Sentry SDK
Install the SDK into your project's virtual environment. The Django integration ships inside sentry-sdk, so there is nothing extra to add for basic Django support.
pip install --upgrade "sentry-sdk[django]"The [django] extra is optional — pip install sentry-sdk is enough — but it pins compatible helper dependencies. Pin the version in your requirements.txt or pyproject.toml so deploys are reproducible.
Initialise Sentry in settings.py
Call sentry_sdk.init() once, as early as possible. settings.py is the natural place for Django. Never hardcode the DSN — read it from an environment variable so it differs per environment and never lands in version control.
# settings.py
import os
import sentry_sdk
SENTRY_DSN = os.environ.get("SENTRY_DSN")
if SENTRY_DSN: # stay silent in local dev when the var is unset
sentry_sdk.init(
dsn=SENTRY_DSN,
# The Django integration is AUTO-ENABLED by the modern SDK whenever
# Django is installed, so you do NOT need integrations=[DjangoIntegration()].
# Pass it explicitly only when you want to change its options.
# Performance: fraction of requests traced (0.0-1.0). Start low in prod.
traces_sample_rate=0.2,
# Profiling: fraction of *traced* transactions that are profiled.
profiles_sample_rate=1.0,
# Tag events with the deploy environment and the running version.
environment=os.environ.get("DJANGO_ENV", "production"),
release=os.environ.get("GIT_SHA"), # e.g. set by CI to the commit SHA
# Privacy: do NOT attach user IP/cookies/PII by default. See the
# "Scrubbing PII" section before turning this on.
send_default_pii=False,
)That is the entire baseline. With the DSN set, restart the app and trigger an exception — for example hit a view that raises ZeroDivisionError — and the issue appears in your Sentry project within seconds, complete with the stack trace and request data.
Because the Django integration is auto-enabled, Sentry already instruments your views, middleware, the ORM, template rendering, signals, and manage.py commands. You only reach for the manual API when you want to record something Sentry would not catch on its own.
Capturing errors and messages manually
Unhandled exceptions are reported automatically. For handled exceptions (those you catch in a try/except) or for non-exception events, use the explicit API.
import sentry_sdk
def sync_invoice(request, invoice_id):
try:
push_to_accounting(invoice_id)
except AccountingAPIError as exc:
# You handled the error gracefully, but you still want visibility.
sentry_sdk.capture_exception(exc)
return render(request, "invoices/sync_failed.html", status=502)
# Capture a standalone message (no exception object) at a chosen level.
sentry_sdk.capture_message(f"Invoice {invoice_id} synced", level="info")Adding context: users, tags, breadcrumbs
Context is what turns a bare stack trace into something you can actually debug. Attach the current user, custom tags you can filter on, structured context blobs, and breadcrumbs.
import sentry_sdk
# Identify the user on the current scope (respect send_default_pii / privacy rules).
sentry_sdk.set_user({"id": request.user.id, "username": request.user.username})
# Tags are indexed and filterable/searchable in the Sentry UI.
sentry_sdk.set_tag("tenant", request.tenant.slug)
sentry_sdk.set_tag("payment_provider", "stripe")
# Context is structured data shown on the issue (not indexed for search).
sentry_sdk.set_context("subscription", {"plan": "pro", "seats": 25})
# Breadcrumbs record the trail of events leading up to an error.
sentry_sdk.add_breadcrumb(category="checkout", message="Coupon applied", level="info")
# Scope everything to a single block so it does not leak to other events:
with sentry_sdk.new_scope() as scope:
scope.set_tag("job", "nightly-export")
run_export()Performance monitoring and tracing
Set traces_sample_rate (or a traces_sampler function) above 0 and Sentry records transactions — one per request — broken into spans for database queries, cache calls, template rendering, and outgoing HTTP. This is where you spot slow endpoints and N+1 query patterns (Sentry flags repeated, near-identical DB spans). For the underlying query fixes, pair this with Django ORM query optimization.
Wrap your own hot paths in custom spans to see them on the trace.
import sentry_sdk
def generate_report(user):
# Create a transaction for code outside the request cycle (e.g. a cron job).
with sentry_sdk.start_transaction(op="task", name="generate_report"):
with sentry_sdk.start_span(op="db", description="aggregate orders"):
data = aggregate_orders(user)
with sentry_sdk.start_span(op="pdf", description="render pdf"):
return render_pdf(data)Sample rates are a cost/volume control: 1.0 traces everything (fine in staging), while 0.1-0.2 is typical in high-traffic production. Tracing volume counts against your Sentry quota, so tune it deliberately.
Profiling slow Python code
Profiling adds function-level CPU samples on top of tracing, so you can see which lines make a transaction slow. With the transaction-based model, profiles_sample_rate is the fraction of sampled transactions that are also profiled (it requires traces_sample_rate > 0):
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
traces_sample_rate=0.2,
profiles_sample_rate=1.0, # profile 100% of the 20% that are traced
)As of 2026 the SDK also supports continuous (UI) profiling, configured with profile_session_sample_rate and profile_lifecycle="trace" instead of profiles_sample_rate. Use one model or the other, not both — check the current Sentry Python docs for which fits your plan.
Environments and releases
Setting environment ("production", "staging", etc.) lets you filter issues and gives you separate alert rules per environment. Setting release to your deployed version unlocks release health (crash-free rates) and suspect commits — Sentry highlights which commit in a release most likely caused a new issue.
Python is server-side, so there are no source maps to upload (those are a JavaScript concern). You just tell Sentry about each deploy. The clean way is to create the release and associate commits from CI, then pass the same version into release.
# In CI, after the build, using sentry-cli:
export SENTRY_RELEASE="$(git rev-parse HEAD)"
sentry-cli releases new "$SENTRY_RELEASE"
sentry-cli releases set-commits "$SENTRY_RELEASE" --auto # enables suspect commits
sentry-cli releases finalize "$SENTRY_RELEASE"
# Then deploy with that value exported, and read it in settings.py:
# release=os.environ.get("SENTRY_RELEASE")Filtering noise: before_send, ignored errors, sampling
Not every exception deserves a page at 3am. Reduce noise three ways: ignore specific exception types, drop or mutate events in a before_send hook, and sample low-value transactions with a traces_sampler.
from django.http import Http404
def before_send(event, hint):
"""Drop or modify an error event right before it is sent."""
exc = hint.get("exc_info")
if exc and isinstance(exc[1], Http404):
return None # returning None drops the event entirely
return event
def traces_sampler(ctx):
"""Per-transaction sampling: skip health checks, trace everything else."""
path = ctx.get("wsgi_environ", {}).get("PATH_INFO", "")
if path.startswith("/healthz"):
return 0.0
return 0.2
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
before_send=before_send,
traces_sampler=traces_sampler, # use instead of traces_sample_rate
ignore_errors=[KeyboardInterrupt], # never report these
)Scrubbing PII and sensitive data
This matters for GDPR, HIPAA, PCI, and general data hygiene. Keep send_default_pii=False (the default) so Sentry does not attach IP addresses, cookies, or request bodies. The SDK also runs a default data scrubber that redacts fields whose names look sensitive (password, token, secret, authorization, credit-card patterns). For anything custom, strip it in before_send.
def before_send(event, hint):
# Remove an Authorization header that may carry a bearer token.
headers = event.get("request", {}).get("headers", {})
headers.pop("Authorization", None)
# Redact a custom field that the default scrubber would not catch.
extra = event.get("extra", {})
if "ssn" in extra:
extra["ssn"] = "[redacted]"
return event
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
send_default_pii=False, # do not attach IP / cookies / request body
before_send=before_send,
)If you operate under strict data-residency rules, you can also run a self-hosted Sentry so no event data leaves your infrastructure.
Monitoring Celery workers and Python logging
The modern SDK auto-enables the Celery integration when Celery is installed, so unhandled task failures are reported with no extra code. Configure it explicitly only when you want extras such as Celery Beat cron monitoring. The logging integration is also auto-enabled: by default it records INFO-level log records as breadcrumbs and sends ERROR-level records as Sentry events. You can tune those thresholds, so worker errors and scheduled-job failures surface in the same Sentry project as your web errors.
import logging
import sentry_sdk
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
sentry_sdk.init(
dsn=os.environ["SENTRY_DSN"],
integrations=[
# Optional explicit config: monitor Celery Beat scheduled tasks.
CeleryIntegration(monitor_beat_tasks=True),
# Capture logging: INFO+ as breadcrumbs, ERROR+ as events.
LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
],
traces_sample_rate=0.2,
)
# Anywhere in your code, normal logging now flows to Sentry:
logger = logging.getLogger(__name__)
logger.error("Payment webhook failed", extra={"order_id": 4821})Alerts and the issue workflow
Configure these in the Sentry UI, not in code:
- Alert rules — notify a Slack channel, email, or PagerDuty when a new issue appears, when an issue's event volume spikes, or when a metric (error rate, p95 latency) crosses a threshold. Scope rules per environment so staging noise never pages on-call.
- Ownership & assignment — set ownership rules by file path or URL so issues auto-assign to the right team, and resolve/ignore/snooze issues to keep the inbox actionable.
- Regression detection — when a resolved issue reappears in a later release, Sentry reopens it and flags it as a regression.
Sentry vs logging vs APM vs Django error emails
These tools overlap but solve different problems. Most production Django teams run Sentry alongside centralized logs.
| Tool | Best at | Weak at |
|---|---|---|
| Sentry | Error tracking with grouping, stack traces + local vars, release health, suspect commits; lightweight tracing & profiling | Long-term log retention/search; deep infra metrics |
| Plain logging (file / stdout / ELK) | Full event history, custom queries, audit trails, cheap retention | No grouping, no alerting out of the box, manual correlation, no stack-trace context |
| Full APM (Datadog, New Relic, Elastic APM) | End-to-end traces across many services, host/infra metrics, dashboards, log+metric+trace correlation | Heavier and pricier; error grouping/triage often weaker than Sentry |
Django error emails (ADMINS + mail_admins) |
Zero extra dependency; fine for a tiny low-traffic site | No grouping (one email per error → inbox flood), no search, no context, no trends |
Rule of thumb: use Sentry for errors and triage, structured logs for history and audit, and reach for a full APM only when you need cross-service distributed tracing and infrastructure metrics in one pane.
Getting it right in production
A correct Sentry setup is mostly about the details: DSNs out of source control, sample rates tuned to your traffic and quota, PII scrubbed for compliance, releases wired into CI for suspect commits, and alert rules that page on signal rather than noise. Getting those wrong means either a flood of useless alerts or — worse — silence when something is actually broken.
At MicroPyramid we have built and maintained Django applications in production for 12+ years across 50+ projects, and we instrument them with Sentry as standard. If you want a second pair of hands, our Django development services cover building and instrumenting apps, and our server maintenance and monitoring services cover keeping them healthy — alerting, on-call triage, and performance tuning — after launch.
Frequently Asked Questions
Do I still need the raven package for Sentry in Django?
No. raven is the legacy client and has been deprecated for years. The current SDK is the sentry-sdk package, configured with a single sentry_sdk.init() call. If your project still imports raven or has raven.contrib.django in INSTALLED_APPS, remove it and migrate to sentry-sdk.
Do I need to add DjangoIntegration explicitly?
Usually not. The modern sentry-sdk auto-detects Django and enables the integration for you, so a plain sentry_sdk.init(dsn=...) already instruments views, the ORM, middleware, and templates. You only pass integrations=[DjangoIntegration(...)] when you want to change its default options.
Where should I put the Sentry DSN?
In an environment variable (for example SENTRY_DSN), read at startup in settings.py. Never hardcode it or commit it. The DSN is not a deeply secret credential, but keeping it in the environment lets you use different projects per environment and avoids leaking it through your repo history.
What is the difference between traces_sample_rate and profiles_sample_rate?
traces_sample_rate controls what fraction of requests get performance traces (transactions and spans). profiles_sample_rate controls what fraction of those already-traced transactions also get CPU profiles. Profiling requires tracing to be on, so set traces_sample_rate above 0 first.
How do I stop Sentry from reporting expected errors like 404s?
Filter them. Add the exception type to ignore_errors, or return None from a before_send hook for that exception (for example Http404). For noisy endpoints like health checks, return 0.0 from a traces_sampler so they are never traced.
Is Sentry free for Django projects?
Sentry is open source and self-hostable at no license cost, and the hosted service at sentry.io offers a free developer tier with usage/quota-based limits. Exact quotas and paid tiers change over time, so verify the current details on sentry.io as of 2026. This article focuses on the implementation, which is the same regardless of which tier you run.