Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
)
```

- The FastAPI and Starlette integrations no longer eagerly consume the request body. As a result, the body is only reported in events if your handler parsed it with `Request.json()` or `Request.form()` before the event is captured.
- The UnraisableHookIntegration is now enabled by default.
- We now don't suppress chained exceptions in the ASGI and asyncio integrations by default. The related `suppress_asgi_chained_exceptions` experimental option was removed.
- In the AWS Lambda and GCP integrations, the message of the warning the SDK optionally emits if a function is about to time out has changed.
Expand Down
3 changes: 2 additions & 1 deletion sentry_sdk/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,15 @@ async def _wrap_async_handler(
)
sentry_scope = sentry_sdk.get_isolation_scope()
extractor = StarletteRequestExtractor(request)
info = await extractor.extract_request_info()

def _make_request_event_processor(
req: "Any", integration: "Any"
) -> "Callable[[Event, Dict[str, Any]], Event]":
def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
# Extract information from request
request_info = event.get("request", {})

info = extractor.extract_request_info()
if info:
if "cookies" in info:
request_info["cookies"] = info["cookies"]
Expand Down
44 changes: 21 additions & 23 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,14 +531,14 @@ async def _wrap_async_handler(
sentry_scope = sentry_sdk.get_isolation_scope()
extractor = StarletteRequestExtractor(request)

info = await extractor.extract_request_info()

def _make_request_event_processor(
req: "Any", integration: "Any"
) -> "Callable[[Event, dict[str, Any]], Event]":
def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
# Add info from request to event
request_info = event.get("request", {})

info = extractor.extract_request_info()
if info:
if "cookies" in info:
request_info["cookies"] = info["cookies"]
Expand Down Expand Up @@ -743,7 +743,7 @@ def extract_cookies_from_request(

return cookies

async def extract_request_info(
def extract_request_info(
Comment thread
alexander-alderman-webb marked this conversation as resolved.
Resolved
self: "StarletteRequestExtractor",
) -> "Optional[Dict[str, Any]]":
client = sentry_sdk.get_client()
Expand All @@ -763,7 +763,7 @@ async def extract_request_info(
request_info["cookies"] = self.cookies()

# If there is no body, just return the cookies
content_length = await self.content_length()
content_length = self.content_length()
if not content_length:
return request_info

Expand All @@ -774,32 +774,30 @@ async def extract_request_info(
request_info["data"] = AnnotatedValue.removed_because_over_size_limit()
return request_info

# Add JSON body, if it is a JSON request
json = await self.json()
if json:
request_info["data"] = json
if hasattr(self.request, "_json"):
request_info["data"] = self.request._json
return request_info

# Add form as key/value pairs, if request has form data
form = await self.form()
if form:
form_data = {}
for key, val in form.items():
is_file = isinstance(val, UploadFile)
form_data[key] = (
val
if not is_file
else AnnotatedValue.removed_because_raw_data()
)
formdata_body = getattr(self.request, "_form", None)
if formdata_body is None and hasattr(self.request, "_body"):
# Raw data, do not add body just an annotation
request_info["data"] = AnnotatedValue.removed_because_raw_data()
return request_info

request_info["data"] = form_data
if formdata_body is None:
return request_info

# Raw data, do not add body just an annotation
request_info["data"] = AnnotatedValue.removed_because_raw_data()
form_data = {}
for key, val in formdata_body.items():
is_file = isinstance(val, UploadFile)
form_data[key] = (
val if not is_file else AnnotatedValue.removed_because_raw_data()
)

request_info["data"] = form_data
return request_info

async def content_length(self: "StarletteRequestExtractor") -> "Optional[int]":
def content_length(self: "StarletteRequestExtractor") -> "Optional[int]":
if "content-length" in self.request.headers:
return int(self.request.headers["content-length"])

Expand Down
1 change: 1 addition & 0 deletions tests/integrations/fastapi/test_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ async def test_original_request_not_scrubbed(sentry_init, capture_events):

@app.post("/error")
async def _error(request: Request):
await request.json()
logging.critical("Oh no!")
assert request.headers["Authorization"] == "Bearer ohno"
assert request.headers["Proxy-Authorization"] == "Basic ohno"
Expand Down
1 change: 1 addition & 0 deletions tests/integrations/starlette/test_starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,7 @@ def test_original_request_not_scrubbed(sentry_init, capture_events):
events = capture_events()

async def _error(request):
await request.json()
logging.critical("Oh no!")
assert request.headers["Authorization"] == "Bearer ohno"
assert request.headers["Proxy-Authorization"] == "Basic ohno"
Expand Down
12 changes: 7 additions & 5 deletions tests/integrations/strawberry/test_strawberry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
SentrySyncExtension,
StrawberryIntegration,
)
from tests.conftest import ApproxDict

try:
from strawberry.extensions.tracing import (
Expand Down Expand Up @@ -339,11 +340,12 @@ def test_event_processor_data_collection(

# request.data comes from the framework integration and must not be
# overwritten by the strawberry integration
assert error_event["request"]["data"] == {
"query": query,
"operationName": "ErrorQuery",
"variables": {"value": "boom"},
}
assert error_event["request"]["data"] == ApproxDict(
{
"operationName": "ErrorQuery",
"variables": {"value": "boom"},
}
)

if expect_api_target:
assert error_event["request"]["api_target"] == "graphql"
Expand Down
Loading