Shipping Python APIs/
Lesson

Structured logs tell you what happened. Sentry tells you what went wrong. These are complementary tools, not alternatives, logs record the flow of normal operations, while error tracking focuses specifically on failures, grouping them, counting them, and alerting you when something new breaks.

The biggest difference: logs are pull-based (you search them when you suspect a problem), while Sentry is push-based (it alerts you when something breaks, even if no user reports it).

What Sentry captures that logs do not

When an exception occurs, Sentry collects far more than a stack traceWhat is stack trace?A list of function calls recorded at the moment an error occurs, showing exactly which functions were active and in what order.. Each error report is a rich document that makes reproduction possible without asking the user "what were you doing?"

DataWhat it showsWhy it matters
Stack traceExact line and call chainWhere the error occurred
BreadcrumbsHTTP requests, DB queries, user actions before the crashWhat led to the error
User contextUser ID, email, IP addressWho was affected
Request dataURL, headers, body, methodWhat triggered it
EnvironmentPython version, OS, server nameWhere it happened
ReleaseGit SHA or version tagWhich deployment introduced it
FrequencyHow many times, how many usersHow urgent it is

Logs give you a single line: ERROR: KeyError 'email'. Sentry gives you the full picture: this error started after deploy abc123, it happens 40 times per hour, it affects 12% of users, and it only occurs when the user signs up via Google OAuthWhat is oauth?An authorization protocol that lets users grant a third-party app limited access to their account on another service without sharing their password. (because the Google profile response does not always include an email field).

02

Integrating Sentry with FastAPI

Installation

pip install sentry-sdk[fastapi]

The [fastapi] extra installs the FastAPI integration automatically. Sentry will hookWhat is hook?A special function in React (starting with "use") that lets you add state, side effects, or other React features to a component without writing a class. into FastAPI's exception handling without any additional configuration.

Initialization

Initialize Sentry as early as possible, before you import your routes, models, or anything else. This ensures Sentry's monkey-patching is in place before any other code runs.

import sentry_sdk
import os

sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    environment=os.environ.get("ENVIRONMENT", "development"),
    release=os.environ.get("GIT_SHA", "unknown"),
    traces_sample_rate=0.1,   # Trace 10% of requests for performance
    profiles_sample_rate=0.1, # Profile 10% of traced requests
)
AI pitfall
AI often hardcodes the DSN string directly in the source code. This is a security issue (DSNs can be used to send fake errors to your project) and a maintenance problem (you cannot use different Sentry projects for staging vs production). Always read the DSN from an environment variable.

What happens automatically

Once initialized, the FastAPI integration captures:

  • All unhandled exceptions in route handlers (returns 500 to the user, sends the full error to Sentry)
  • All HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. requests as breadcrumbs (so you see what APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. calls happened before the crash)
  • SQLAlchemy queries as breadcrumbs (if you use SQLAlchemy)
  • Logging calls as breadcrumbs (your logger.warning() calls appear in the Sentry timeline)

You do not need to wrap every route in a try/except. Sentry hooks into FastAPI's exception pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production. automatically.

03

Enriching errors with context

Identifying users

Anonymous error reports are hard to act on. Attach user identity so you can see which users are affected and reach out to them:

from fastapi import Request
import sentry_sdk

@app.middleware("http")
async def set_sentry_user(request: Request, call_next):
    if request.state.user:
        sentry_sdk.set_user({
            "id": request.state.user.id,
            "email": request.state.user.email,
            "username": request.state.user.username,
        })
    else:
        sentry_sdk.set_user(None)

    return await call_next(request)

With user identity attached, Sentry can tell you "this error affects 3 users, all on the free plan, all using the mobile app." That context changes how you prioritize the fix.

Adding custom context and tags

Tags are indexed and searchable. Context is not indexed but provides rich detail on individual error reports.

# Tags - filterable in the Sentry UI
sentry_sdk.set_tag("plan", user.subscription_plan)
sentry_sdk.set_tag("api_version", "v2")

# Context - rich detail, not searchable
sentry_sdk.set_context("order", {
    "order_id": order.id,
    "items_count": len(order.items),
    "total": float(order.total),
    "payment_method": order.payment_method,
})

Use tags for things you want to filter by ("show me all errors from v2 APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. users on the enterprise plan"). Use context for details that help reproduce a specific error.

Capturing handled exceptions

Sentry only auto-captures unhandled exceptions. When you catch an error and recover gracefully, Sentry does not see it, unless you tell it:

try:
    result = await external_api.fetch_data(resource_id)
except ExternalAPIError as e:
    sentry_sdk.capture_exception(e)
    # Fall back to cached data
    result = await cache.get(resource_id)
    logger.warning("External API failed, using cache", extra={
        "resource_id": resource_id,
        "error": str(e)
    })

This is important for errors you handle gracefully but still want to track. If the external API fails 500 times a day, you want to know even if your users never see an error.

04

Breadcrumbs, the timeline before the crash

Breadcrumbs are the most underrated feature of Sentry. They record a timeline of events leading up to the error, HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. requests made, database queries executed, log messages emitted, user interactions.

# Automatic breadcrumbs (enabled by default):
# - HTTP requests via httpx/requests
# - Database queries via SQLAlchemy
# - Log messages at WARNING and above
# - Redis commands

# Custom breadcrumbs for business events
sentry_sdk.add_breadcrumb(
    category="checkout",
    message="User applied discount code",
    level="info",
    data={"code": "SAVE20", "discount_percent": 20}
)

When you open an error in the Sentry UI, the breadcrumb timeline shows you exactly what the user did in the 30 seconds before the crash. This is often more valuable than the stack traceWhat is stack trace?A list of function calls recorded at the moment an error occurs, showing exactly which functions were active and in what order. itself, the stack trace tells you where the code broke, but the breadcrumbs tell you why.

05

Alerting rules, signal vs noise

Not every error deserves a notification at 3am. Sentry lets you configure alert rules based on conditions that matter.

What to alert on

ConditionAlert?Rationale
New issue (first occurrence)YesLikely introduced by recent deploy
Regression (resolved issue reappears)YesFix did not hold
Error spike (200%+ increase in 1 hour)YesSomething changed
High user impact (>50 affected users)YesWidespread problem
Known low-severity errorNoSuppress with issue rules
Client-side network timeoutNoOut of your control

Filtering noise with before_send

def before_send(event, hint):
    exception = hint.get("exc_info")
    if exception:
        exc_type, exc_value, _ = exception

        # Ignore client disconnects - not our problem
        if exc_type.__name__ == "ClientDisconnect":
            return None

        # Ignore 404s - they are not errors
        if hasattr(exc_value, "status_code") and exc_value.status_code == 404:
            return None

    return event

sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    before_send=before_send,
)
AI pitfall
When you ask AI to "add error tracking," it often wraps every route in a try/except that catches all exceptions and calls sentry_sdk.capture_exception(). This is redundant, the FastAPI integration already captures unhandled exceptions. The try/except also swallows the error, preventing FastAPI from returning a proper 500 response. Only use capture_exception() for errors you handle and recover from.
06

Quick reference

ConceptWhat to doWhy
DSNEnvironment variable, never hardcodedDifferent projects per environment
traces_sample_rate0.1 in production100% tracing is expensive
User identitySet in middleware after authKnow who is affected
TagsPlan, API version, regionFilterable dimensions
before_sendDrop noise (404s, client disconnects)Keep signal-to-noise ratio high
Handled errorscapture_exception() in catch blocksTrack graceful failures
AlertsNew issues + regressions + spikesWake up for real problems only