Back to blog
By Rasul

Django Sentry: Production Error Monitoring

Configure Django Sentry for releases, privacy, sampling, workers, issue ownership, and a tested production incident workflow.

A production Django Sentry setup should do more than send an exception to a dashboard. It should identify the deployed release, keep sensitive fields out of events, capture web and worker failures once, sample performance traffic by value, route each issue to an owner, and prove in staging that the alert can be closed. Installing sentry-sdk is the smallest part of that contract.

The default Django integration already captures unhandled errors, request context, transactions, and logging breadcrumbs. Production work begins where the quickstart ends: deciding what may leave the process, which signals deserve volume, how one event maps back to a release, and who acts when an issue opens.

This guide was verified against Django 6.0, Sentry's current Python documentation, sentry-sdk 2.66.1, Django Q2 1.6 documentation, and the current Djass-generated Sentry implementation on August 5, 2026.

On this page: operating contract · configuration · signal boundaries · releases · privacy · sampling · workers · ownership · tests · Djass audit · checklist

Start with the operating contract

Error monitoring is a workflow, not a storage destination. Define the action each signal should cause before selecting SDK options:

Signal What it should answer Normal action Common source of noise
Error event What failed, for whom, and in which release? Triage, assign, fix, verify Expected validation and duplicate captures
Breadcrumb What happened before the failure? Reconstruct the path High-volume routine logs and sensitive values
Log What operational event occurred? Search or correlate Sending every INFO record as retained log volume
Transaction Where did request or job time go? Find slow spans and regressions Health, static, media, and polling traffic
Profile Which Python code consumed CPU during a sampled trace? Inspect a known slow path Profiling before a trace identifies a useful target

This separation prevents two expensive mistakes. The first is treating every log record as a Sentry issue. The second is sending every request as a performance transaction because a quickstart used traces_sample_rate=1.0.

The Sentry Python logging integration uses separate thresholds: records can become breadcrumbs, retained logs, or error events. Its documented default event_level is ERROR. Keep those paths explicit so one logger.exception() does not become an automatic exception event, a second manually captured event, and a third event produced by a worker reporter.

Use the Django logging production guide to define event names, correlation IDs, and the boundary that owns a failure. Sentry should consume that contract rather than force application code to invent a second logging API.

Configure Django Sentry as a deployment contract

Install the maintained SDK and initialize it once from settings:

uv add sentry-sdk
import logging
import os

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import LoggingIntegration

from your_project.sentry_utils import before_send, traces_sampler


SENTRY_ENABLED = os.getenv("SENTRY_ENABLED", "False").lower() == "true"
SENTRY_DSN = os.getenv("SENTRY_DSN", "")

if SENTRY_ENABLED and SENTRY_DSN:
    sentry_sdk.init(
        dsn=SENTRY_DSN,
        environment=os.getenv("SENTRY_ENVIRONMENT", "dev"),
        release=os.getenv("SENTRY_RELEASE") or None,
        send_default_pii=False,
        integrations=[
            DjangoIntegration(
                middleware_spans=True,
                cache_spans=True,
            ),
            LoggingIntegration(
                level=logging.INFO,
                event_level=logging.ERROR,
            ),
        ],
        before_send=before_send,
        traces_sampler=traces_sampler,
    )

The explicit enable flag matters. Sentry's Python configuration reference states that a missing DSN prevents the SDK from sending data, but an enable flag makes intent visible and prevents an accidentally injected DSN from activating monitoring in a local or test process. The Djass environment-variable reference uses this same two-part boundary.

Do not copy sample rates from the snippet yet. The correct values depend on request volume, worker volume, Sentry quota, and which performance questions you are investigating. Configure errors first, send a deliberate staging event, then add traces and profiles with a budget.

The official Django integration reference documents middleware, signal, and cache spans. Enable only the spans you will inspect. More spans increase context and event size; they do not replace an owner or an investigation question.

Separate errors, logs, traces, and profiles

Four related products travel through one SDK, but they have different cost and retention behavior.

Keep error events complete until volume proves otherwise

Sentry's Python sampling documentation states that error events default to a sample_rate of 1.0. That is a sound starting point for a new application: losing a rare payment, authorization, or data-integrity failure to random sampling is usually worse than receiving all errors.

Reduce error volume by removing known non-errors first:

  • return ordinary 4xx responses for expected validation and authorization;
  • filter a known exception only when another system already owns it;
  • group events that share the same root cause;
  • stop capturing the same exception at multiple layers;
  • archive or ignore a known issue with a documented reason and expiry.

Use error_sampler only when the event type itself supports a defensible policy. A global 20% error rate answers a billing problem while creating a reliability problem: any individual production error has an 80% chance of disappearing.

Use breadcrumbs for the path to failure

Standard Python logs become breadcrumbs by default through Sentry's logging integration. Breadcrumbs are a short event-local history. They should contain stable event names, bounded outcomes, request or correlation IDs, and safe object IDs.

Do not put passwords, authorization headers, session values, raw request bodies, prompt text, or arbitrary provider responses in a log and expect a later scrubber to make the call safe. The application logging allow-list is the first privacy boundary.

Use retained logs for deliberate search

Sentry Logs can retain selected logging records independently of breadcrumbs and error events. Keep a separate log threshold, normally WARNING or higher until you have measured the value and volume. Your deployment's stdout log collector should remain the durable general-purpose stream; Sentry logs are most useful when they add searchable context beside issues and traces.

Trace a representative set of work

Transactions and spans answer latency questions. A request trace may include Django middleware, database queries, cache operations, Redis calls, and outbound HTTP. A background trace may represent a queued task or scheduled job. Their traffic shapes differ, so they should not automatically share one rate.

Profiles are narrower still. The current Python SDK supports trace-linked profiling with profile_lifecycle="trace". Profile only sampled work and use it after a trace shows that Python execution, rather than a database or network span, is the likely bottleneck.

Make release and environment non-optional

Every production event should answer two questions without reading a message:

  1. Which environment produced it?
  2. Which exact code revision was running?

Set SENTRY_ENVIRONMENT to a small stable vocabulary such as staging and production. Do not generate one environment per ephemeral container or hostname. Set SENTRY_RELEASE to the deployed commit SHA or an immutable version derived from it, and use the same value in web and worker processes.

Sentry's Python release documentation recommends setting the release explicitly and supports a Git SHA as the value. Release data lets Sentry identify new regressions, connect issues to commits, and compare the issue before and after a deploy. Create the release and associate commits in CI before the new process sends events when your deployment pipeline supports it.

The release value is evidence, not decoration. This is a broken contract:

web:    SENTRY_RELEASE=main
worker: SENTRY_RELEASE=

The web event cannot identify the immutable code, and the worker event cannot join the same deploy. Prefer this:

web:    SENTRY_RELEASE=8fb3c1d7...
worker: SENTRY_RELEASE=8fb3c1d7...

Also add an application-specific tag or context field for a bounded domain identity such as project_id or invoice_id. A release tells you which code ran; a safe domain identifier tells you which workflow failed. Avoid email addresses and other direct identifiers unless your policy explicitly permits them.

Keep sensitive data out before the event leaves

send_default_pii=False is a baseline, not proof that an event is clean. Sentry's sensitive-data documentation explains that the default event scrubber targets common security and PII field names, but does not recursively inspect every value by default. Stack locals, breadcrumbs, database queries, HTTP spans, query strings, and unparameterized transaction names can still carry sensitive data.

Use three privacy layers:

  1. Application allow-list: do not log or attach a value unless its purpose is clear and its shape is bounded.
  2. SDK filter: remove fields in before_send, before_send_log, and before_send_transaction before they leave the process.
  3. Server-side scrubber: configure Sentry-side rules as defense in depth for new events.

A small error-event filter can remove application-owned fields:

SENSITIVE_KEYS = {
    "authorization",
    "cookie",
    "password",
    "secret",
    "token",
}


def scrub_mapping(value):
    if isinstance(value, dict):
        return {
            key: "[Filtered]" if str(key).lower() in SENSITIVE_KEYS else scrub_mapping(item)
            for key, item in value.items()
        }
    if isinstance(value, list):
        return [scrub_mapping(item) for item in value]
    return value


def before_send(event, hint):
    return scrub_mapping(event)

Treat this as an application example, not a universal scrubber. Exact key matching misses names such as access_token; broad substring matching can erase harmless fields such as input_tokens. Build the deny-list from real payloads, add representative privacy tests, and keep SDK and server-side scrubbing enabled.

Leave local-variable capture off until a reviewed use case justifies it. The same rule applies to AI prompt and response capture. Prompt text can contain customer input, credentials, source code, or personal data; tracing an AI call does not require recording its content.

Sample by workload, not with one global rate

A useful sampler distinguishes user requests, background jobs, and traffic that should never become a stored transaction.

IGNORED_PATHS = {"/api/healthcheck", "/favicon.ico", "/robots.txt"}
IGNORED_PREFIXES = ("/static/", "/media/")


def traces_sampler(context):
    transaction = context.get("transaction_context") or {}
    environ = context.get("wsgi_environ") or {}
    scope = context.get("asgi_scope") or {}
    path = environ.get("PATH_INFO") or scope.get("path") or ""

    if path in IGNORED_PATHS or path.startswith(IGNORED_PREFIXES):
        return 0.0

    parent_sampled = context.get("parent_sampled")
    if parent_sampled is not None:
        return float(parent_sampled)

    if transaction.get("op", "").startswith("http") or path:
        return float(os.getenv("SENTRY_HTTP_SAMPLE_RATE", "0.2"))

    return float(os.getenv("SENTRY_BACKGROUND_SAMPLE_RATE", "0.05"))

The Sentry sampling reference recommends respecting an upstream parent decision so distributed traces remain connected. The example makes one deliberate exception: routine local paths are dropped before inheritance because they are not part of a useful product trace. Document exceptions like this; otherwise a frontend's sampled request can unexpectedly override the backend budget.

Set initial rates from a volume budget:

expected stored transactions
  = requests per month × HTTP rate
  + background executions per month × background rate

Then inspect accepted, filtered, and rate-limited volume in Sentry. Raise a rate temporarily for a specific investigation. Lower it after you have a baseline. Avoid dropping failed transactions just because routine successes are sampled lightly; use a dynamic policy when the SDK context exposes the distinction you need.

Health-check transactions are a clear first filter. The Django health-check guide separates liveness, readiness, diagnostics, and worker freshness so the monitoring path stays meaningful.

Capture background worker failures

A web integration cannot prove that a background worker reports failures. Your queue process needs the same DSN, environment, and release as the web process, plus a worker-specific capture path.

Django Q2 exposes a pluggable error_reporter configuration. Its error reporter documentation states that a reporter receives cluster errors through a configured plugin. For Sentry, configure the reporter only when Sentry is active:

if SENTRY_ENABLED and SENTRY_DSN:
    Q_CLUSTER.setdefault("error_reporter", {})["sentry"] = {"dsn": SENTRY_DSN}

Also initialize the Python SDK in the worker process so logs, breadcrumbs, custom context, and performance spans use the same release contract. Then send one controlled failing task in staging and count the events that arrive.

Capture the failure once at the boundary that owns it. If the task wrapper logs an exception, the logging integration creates an event, and the Q2 reporter sends the cluster exception, you may receive duplicates. Decide which path owns unhandled worker crashes and which path owns caught domain failures.

Persist user-visible job state in the database. Sentry may explain why an export failed, but it should not be the only record that the export is failed. The Django Q2 architecture guide shows the durable worker boundary used by generated projects.

Turn an issue into owned work

An unassigned issue inbox is a second log archive. Define a small triage policy:

Condition Priority Owner action
New or regressed error on a paid or security-sensitive path Page or urgent queue Assign, reproduce, mitigate, link the fix
Repeated error affecting a core workflow Current engineering queue Assign with release and domain evidence
One-off third-party or client failure with safe degradation Review queue Track frequency; archive with a condition if appropriate
Expected validation or probe event Not an issue Fix instrumentation or filter at source

Sentry ownership rules can assign by path or URL and can import a repository's CODEOWNERS data when code mappings are configured. Start with a few stable product boundaries such as payments, authentication, and project generation. A rule that sends everything to "backend" has technically assigned the issue but has not created ownership.

Use Sentry's default grouping first. Add a custom fingerprint only when events with different messages share one operational root cause, or when the default group hides distinct causes. Fingerprinting is part of the event contract, so cover it with tests. A fingerprint that includes a raw user or object ID can create one issue per customer and exhaust the inbox.

Close the loop on every actionable issue:

  1. Confirm environment, first release, last release, and affected workflow.
  2. Assign one owner and link the engineering issue or incident.
  3. Record the mitigation and expected fixed release.
  4. Deploy the fix with an immutable SENTRY_RELEASE.
  5. Exercise the failing path in staging or production-safe verification.
  6. Resolve the issue in the release and watch for regression.

Sentry's issue-status documentation distinguishes new, ongoing, escalating, regressed, archived, and resolved issues. Use those states as workflow evidence, not as an inbox-cleanup target.

Test the monitoring contract in staging

An SDK import test proves very little. Run a staging canary that exercises the same network path, process type, and release metadata as production.

Web error canary

Expose a staff-only or temporary route that raises a distinctive test exception. Verify:

  • exactly one event arrives in the intended issue;
  • environment and immutable release are correct;
  • the route and safe correlation ID are present;
  • authorization, cookies, request bodies, and representative secrets are not;
  • the issue reaches the expected owner and notification channel.

Remove or disable the route after the check. Never expose a public endpoint that lets arbitrary callers create billable errors.

Worker error canary

Queue a task that raises after binding a safe correlation ID. Verify that the worker event carries the same release as the web process, identifies the task boundary, avoids duplicate events, and updates durable task state separately.

Sampling tests

Unit-test deterministic branches rather than random outcomes:

def test_sampler_drops_healthcheck():
    context = {
        "transaction_context": {"op": "http.server"},
        "wsgi_environ": {"PATH_INFO": "/api/healthcheck"},
    }

    assert traces_sampler(context) == 0.0


def test_sampler_respects_parent():
    assert traces_sampler({"parent_sampled": True}) == 1.0
    assert traces_sampler({"parent_sampled": False}) == 0.0

Also test web and background rates, static/media filters, malformed context, and every custom fingerprint. A configuration that controls spend, privacy, or grouping is application behavior and deserves regression coverage.

Privacy tests

Build a representative event containing authorization, cookie, email, password, token, query-string, breadcrumb, and local-variable examples. Pass it through your SDK hooks and assert what remains. Add a harmless field such as input_tokens so an over-broad filter cannot silently destroy useful AI usage metadata.

Finally, inspect the received staging event. Unit tests cover your hook; they do not prove what another integration adds after your application log call or what the server-side scrubber stores.

What Djass generates for Sentry today

We audited the current django-saas-starter Sentry branch at commit e240ab67 on August 5, 2026. When use_sentry = y, the generated repository includes more than the quickstart:

  • separate enable and DSN checks;
  • one release value shared with the generated service version;
  • explicit breadcrumb, error-event, and retained-log levels;
  • different HTTP and background trace rates;
  • health, static, media, favicon, and robots transaction filters;
  • upstream parent-sampling inheritance for distributed traces;
  • Django middleware and cache spans plus Redis integration;
  • Q2 worker error reporting;
  • default PII and local-variable capture disabled;
  • a before_send_log filter for sensitive attributes;
  • tests for log levels, redaction, ignored routes, parent decisions, and web-versus-background sampling;
  • deployment documentation for performance baselines and release comparison.

That audit is the information-gain layer in this guide: the production policy is backed by an operating implementation, not inferred from an installation snippet. It also exposes the remaining responsibility. Generated safeguards do not choose your real sample rates, data policy, ownership rules, alert thresholds, or incident process. You must set those against your traffic, customers, team, and Sentry plan.

Djass uses one option catalog across its web UI, API, CLI, and MCP workflows. Review the available generator modules when you want this Sentry baseline, Django Q2, deployment configuration, and the surrounding SaaS structure in the same generated repository.

Django Sentry production checklist

  • [ ] Sentry activates only when both the enable flag and DSN are present.
  • [ ] Web and worker processes use the same stable environment and immutable release.
  • [ ] Error events remain unsampled until measured volume supports a narrower policy.
  • [ ] Logs, breadcrumbs, error events, traces, and profiles have separate thresholds.
  • [ ] Routine health, static, media, and bot paths do not consume trace volume.
  • [ ] Distributed traces respect parent sampling except for documented local filters.
  • [ ] send_default_pii and local-variable capture are disabled by default.
  • [ ] SDK hooks and server-side rules scrub representative sensitive fields.
  • [ ] AI prompt or response content is excluded unless an approved policy permits it.
  • [ ] Q2 or another worker reports a controlled failure once with the correct release.
  • [ ] Ownership rules route core product boundaries to a named person or team.
  • [ ] Staging canaries prove delivery, redaction, assignment, and release closure.
  • [ ] Unit tests cover sampling, redaction, logging thresholds, and fingerprints.
  • [ ] Durable application state records the failed workflow independently of Sentry.

Djass can generate the maintained Sentry, logging, Q2, and deployment baseline before product-specific work begins. Compare the current Djass pricing when you want to start from that repository instead of assembling the monitoring contract by hand.