Sentry Error Tracking in Python — Deep Dive

Sentry in a demo captures every error beautifully. Sentry in production at scale requires deliberate choices about what to capture, how to group it, how to control costs, and how to integrate with your incident response workflow. The Python SDK exposes hooks and configuration options that make these decisions programmable.

SDK internals

The Sentry Python SDK operates through a hub-and-scope model (v1) or the newer unified API (v2+). The key components:

  • Client: Handles transport (HTTP to Sentry’s ingest endpoint) and event processing.
  • Scope: Holds context (tags, user, breadcrumbs) for the current execution context.
  • Integration: Automatically instruments frameworks and libraries.
  • Transport: Manages event delivery with buffering and retry.

The SDK uses contextvars for async-safe scope management. Each async task or thread gets its own scope fork.

The before_send hook

The most powerful customization point. This function receives every event before it is sent to Sentry and can modify or discard it:

def before_send(event, hint):
    # Drop health check noise
    if event.get("request", {}).get("url", "").endswith("/health"):
        return None
    
    # Scrub sensitive data
    if "extra" in event:
        for key in list(event["extra"].keys()):
            if "password" in key.lower() or "token" in key.lower():
                event["extra"][key] = "[REDACTED]"
    
    # Add deployment metadata
    event.setdefault("tags", {})["k8s_pod"] = os.environ.get("POD_NAME", "unknown")
    
    return event

sentry_sdk.init(
    dsn="...",
    before_send=before_send,
)

The hint parameter provides the original exception object, which is useful for type-based filtering:

def before_send(event, hint):
    exc_info = hint.get("exc_info")
    if exc_info:
        exc_type = exc_info[0]
        # Ignore expected operational errors
        if exc_type in (ConnectionResetError, TimeoutError):
            return None
    return event

Custom fingerprinting

Default grouping uses the stack trace, which sometimes groups too broadly or too narrowly.

Override for specific exceptions

def before_send(event, hint):
    exc_info = hint.get("exc_info")
    if exc_info and exc_info[0].__name__ == "PaymentError":
        # Group by payment provider instead of stack trace
        provider = getattr(exc_info[1], "provider", "unknown")
        event["fingerprint"] = ["payment-error", provider]
    return event

Grouping rules in Sentry UI

For larger teams, configure grouping rules in the Sentry project settings. Server-side rules apply to all SDKs and are easier to adjust without redeploying.

Transaction and span instrumentation

Beyond automatic framework instrumentation, add custom spans for business-critical operations:

import sentry_sdk

@sentry_sdk.trace
def charge_payment(order):
    with sentry_sdk.start_span(op="http.client", description="Stripe API"):
        result = stripe.charges.create(amount=order.total)
    
    with sentry_sdk.start_span(op="db", description="Update order status"):
        order.update(status="paid", charge_id=result.id)
    
    return result

The @sentry_sdk.trace decorator creates a transaction. Inner start_span calls create child spans. The Sentry performance dashboard shows a waterfall view of these operations.

Sampling strategies

Error sampling

By default, all errors are captured. For high-volume services, sample:

sentry_sdk.init(
    dsn="...",
    sample_rate=0.5,  # Capture 50% of errors
)

This is a blunt instrument. Better: use before_send for selective filtering based on error type, endpoint, or user segment.

Transaction sampling

Performance monitoring generates far more data than error tracking. Use traces_sampler for intelligent decisions:

def traces_sampler(sampling_context):
    # Always trace errors
    if sampling_context.get("parent_sampled") is True:
        return 1.0
    
    # Never trace health checks
    transaction_name = sampling_context.get("transaction_context", {}).get("name", "")
    if "/health" in transaction_name:
        return 0.0
    
    # Sample payment flows at higher rate
    if "/api/payment" in transaction_name:
        return 0.5
    
    # Default rate
    return 0.05

sentry_sdk.init(
    dsn="...",
    traces_sampler=traces_sampler,
)

Integration with Celery

Celery tasks run in worker processes separate from the web server. The Sentry Celery integration creates transactions per task and captures task failures:

from sentry_sdk.integrations.celery import CeleryIntegration

sentry_sdk.init(
    dsn="...",
    integrations=[CeleryIntegration()],
)

Task retries appear as separate events linked to the same issue. Monitor retry storms by alerting on issue frequency spikes for task-related errors.

Integration with SQLAlchemy

The SQLAlchemy integration captures slow queries as breadcrumbs and optionally as spans:

from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration

sentry_sdk.init(
    dsn="...",
    integrations=[SqlalchemyIntegration()],
)

When an error occurs, the breadcrumb trail shows recent database queries, making it easy to spot N+1 queries or unexpected query patterns leading to failures.

Self-hosted Sentry

For data sovereignty or cost control, run Sentry on your own infrastructure. The official Docker Compose setup provides the full Sentry stack:

git clone https://github.com/getsentry/self-hosted.git
cd self-hosted
./install.sh
docker compose up -d

Self-hosted Sentry requires PostgreSQL, Redis, Kafka, and ClickHouse. Budget at least 8 GB RAM for a small deployment. The tradeoff is full data control and unlimited events at the cost of operational overhead.

Cost control tactics

Sentry pricing is based on event volume. Unchecked, a single noisy error can generate thousands of events per hour.

  1. Rate limiting: Set per-project rate limits in Sentry’s settings.
  2. Inbound filters: Drop known browser extensions, legacy browsers, or specific error messages server-side.
  3. before_send filtering: Drop noisy operational errors (connection resets, client disconnects) in the SDK.
  4. Spike protection: Sentry’s built-in spike protection drops events during volume spikes to prevent budget overruns.
  5. Quota alerts: Set alerts when you approach your monthly event limit.
# Track how many events you're sending
event_count = 0

def before_send(event, hint):
    global event_count
    event_count += 1
    if event_count > 10000:  # Safety valve
        return None
    return event

Alerts and workflow integration

Configure Sentry alerts to match your incident workflow:

  • Issue alerts: “When a new issue is created in the payment service, page on-call via PagerDuty.”
  • Metric alerts: “When error count exceeds 100/minute, send to Slack #incidents.”
  • Regression alerts: “When a resolved issue reappears, assign to the developer who resolved it.”

Integrate with Jira or GitHub Issues to create tickets directly from Sentry issues. The Sentry issue includes all context, so the developer starts with a full picture.

Testing Sentry integration

Verify your configuration catches errors correctly:

def test_sentry_captures_errors(sentry_init, capture_events):
    sentry_init()
    events = capture_events()
    
    try:
        1 / 0
    except ZeroDivisionError:
        sentry_sdk.capture_exception()
    
    assert len(events) == 1
    assert events[0]["exception"]["values"][0]["type"] == "ZeroDivisionError"

The sentry_sdk.testing module provides fixtures for pytest that capture events without sending them to Sentry.

One thing to remember: Production Sentry requires before_send hooks for noise filtering and data scrubbing, intelligent transaction sampling to control costs, and custom fingerprinting so issues group meaningfully — these three configurations determine whether Sentry becomes a useful tool or a noisy expense.

pythonsentrysentry-sdk

See Also

  • Python Adaptive Learning Systems How Python builds learning apps that adjust to each student like a personal tutor who knows exactly what you need next.
  • Python Airflow Learn Airflow as a timetable manager that makes sure data tasks run in the right order every day.
  • Python Altair Learn Altair through the idea of drawing charts by describing rules, not by hand-placing every visual element.
  • Python Automated Grading How Python grades homework and exams automatically, from simple answer keys to understanding written essays.
  • Python Batch Vs Stream Processing Batch processing is like doing laundry once a week; stream processing is like a self-cleaning shirt that cleans itself constantly.