Skip to content

Commit 83fa16d

Browse files
use cached attributes directly
1 parent 5004aae commit 83fa16d

2 files changed

Lines changed: 13 additions & 111 deletions

File tree

sentry_sdk/integrations/starlette.py

Lines changed: 13 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
from starlette import __version__ as STARLETTE_VERSION
4949
from starlette.applications import Starlette # type: ignore
5050
from starlette.datastructures import ( # type: ignore
51-
FormData,
5251
UploadFile,
5352
)
5453
from starlette.middleware import Middleware # type: ignore
@@ -92,9 +91,6 @@
9291

9392
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
9493

95-
_SCOPE_STATE_JSON_REQUEST_BODY_KEY = "sentry_sdk.json_request_body"
96-
_SCOPE_STATE_FORMDATA_REQUEST_BODY_KEY = "sentry_sdk.formdata_request_body"
97-
9894

9995
class StarletteIntegration(Integration):
10096
identifier = "starlette"
@@ -153,16 +149,6 @@ def setup_once() -> None:
153149
if version >= (0, 24):
154150
patch_templates()
155151

156-
def setup_once_with_options(
157-
self: "StarletteIntegration", options: "Optional[dict[str, Any]]" = None
158-
) -> None:
159-
is_span_streaming_enabled = has_span_streaming_enabled(options)
160-
if not is_span_streaming_enabled:
161-
return
162-
163-
_patch_json_request_body_accessor()
164-
_patch_formdata_request_body_accessor()
165-
166152

167153
def _enable_span_for_middleware(middleware_class: "Any") -> type:
168154
old_call = middleware_class.__call__
@@ -495,61 +481,13 @@ def _is_async_callable(obj: "Any") -> bool:
495481
)
496482

497483

498-
def _patch_json_request_body_accessor() -> None:
499-
"""
500-
Caches request body data on the ASGI scope, so that the body can be attached to telemetry after the request handler runs.
501-
Without the cache, consuming the stream causes applications to hang when middleware or handlers consume the raw
502-
`receive()` callable exposed by Starlette.
503-
"""
504-
_original_json = Request.json
505-
506-
@functools.wraps(_original_json)
507-
async def wrapped_json(self: "Request", *args: "Any", **kwargs: "Any") -> "Any":
508-
request_json = await _original_json(self, *args, **kwargs)
509-
self.scope.setdefault("state", {})[_SCOPE_STATE_JSON_REQUEST_BODY_KEY] = (
510-
request_json
511-
)
512-
return request_json
513-
514-
Request.json = wrapped_json
515-
516-
517-
def _patch_formdata_request_body_accessor() -> None:
518-
"""
519-
Caches request body data on the ASGI scope, so that the body can be attached to telemetry after the request handler runs.
520-
Without the cache, consuming the stream causes applications to hang when middleware or handlers consume the raw
521-
`receive()` callable exposed by Starlette.
522-
"""
523-
if not hasattr(Request, "_get_form"):
524-
return
525-
526-
_original_form = Request._get_form
527-
528-
@functools.wraps(_original_form)
529-
async def wrapped_form(
530-
self: "Request", *args: "Any", **kwargs: "Any"
531-
) -> "FormData":
532-
request_formdata = await _original_form(self, *args, **kwargs)
533-
self.scope.setdefault("state", {})[_SCOPE_STATE_FORMDATA_REQUEST_BODY_KEY] = (
534-
request_formdata
535-
)
536-
return request_formdata
537-
538-
Request._get_form = wrapped_form
539-
540-
541-
def _serialize_cached_request_body_attribute(
484+
def _get_cached_request_body_attribute(
542485
client: "sentry_sdk.client.BaseClient", request: "Request"
543486
) -> "Optional[str]":
544487
"""
545-
Returns a stringified JSON representation of the request body if the request body is cached on the ASGI scope and within size bounds.
488+
Returns a stringified JSON representation of the request body if the request body is cached and within size bounds.
546489
"""
547-
scope_state = request.scope.get("state", {})
548-
if (
549-
"content-length" not in request.headers
550-
or _SCOPE_STATE_JSON_REQUEST_BODY_KEY not in scope_state
551-
and _SCOPE_STATE_FORMDATA_REQUEST_BODY_KEY not in scope_state
552-
):
490+
if "content-length" not in request.headers:
553491
return None
554492

555493
try:
@@ -560,13 +498,16 @@ def _serialize_cached_request_body_attribute(
560498
if content_length and not request_body_within_bounds(client, content_length):
561499
return OVER_SIZE_LIMIT_SUBSTITUTE
562500

563-
if _SCOPE_STATE_JSON_REQUEST_BODY_KEY in request.scope["state"]:
564-
return json.dumps(request.scope["state"][_SCOPE_STATE_JSON_REQUEST_BODY_KEY])
501+
json_body = getattr(request, "_json", None)
502+
if json_body is not None:
503+
return json.dumps(json_body)
565504

566-
form = request.scope["state"][_SCOPE_STATE_FORMDATA_REQUEST_BODY_KEY]
505+
formdata_body = getattr(request, "_form", None)
506+
if formdata_body is None:
507+
return
567508

568509
form_data = {}
569-
for key, val in form.items():
510+
for key, val in formdata_body.items():
570511
is_file = isinstance(val, UploadFile)
571512
form_data[key] = val if not is_file else "[Unparsable]"
572513

@@ -627,13 +568,13 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
627568
current_span = _get_current_streamed_span()
628569

629570
if type(current_span) is StreamedSpan:
630-
serialized_request_body = _serialize_cached_request_body_attribute(
571+
request_body = _get_cached_request_body_attribute(
631572
client=client, request=request
632573
)
633-
if serialized_request_body:
574+
if request_body:
634575
current_span._segment.set_attribute(
635576
SPANDATA.HTTP_REQUEST_BODY_DATA,
636-
serialized_request_body,
577+
request_body,
637578
)
638579

639580

tests/integrations/starlette/test_starlette.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,8 @@
2121
from starlette.middleware import Middleware
2222
from starlette.middleware.authentication import AuthenticationMiddleware
2323
from starlette.middleware.trustedhost import TrustedHostMiddleware
24-
from starlette.requests import Request
2524
from starlette.testclient import TestClient
2625

27-
try:
28-
from starlette.middleware.exceptions import ExceptionMiddleware
29-
except ImportError:
30-
from starlette.exceptions import ExceptionMiddleware
31-
3226
import sentry_sdk
3327
from sentry_sdk import capture_message, get_baggage, get_traceparent
3428
from sentry_sdk.consts import SPANDATA
@@ -279,31 +273,6 @@ async def my_send(*args, **kwargs):
279273
await self.app(scope, partial_receive, partial_send)
280274

281275

282-
@pytest.fixture(autouse=True)
283-
def reset_starlette_integration(uninstall_integration):
284-
original_request_json = Request.json
285-
original_request_form = Request.form
286-
original_starlette_call = starlette.applications.Starlette.__call__
287-
original_request_response = starlette.routing.request_response
288-
original_middleware_init = Middleware.__init__
289-
original_authentication_call = AuthenticationMiddleware.__call__
290-
original_exception_middleware_init = ExceptionMiddleware.__init__
291-
original_exception_middleware_call = ExceptionMiddleware.__call__
292-
293-
yield
294-
295-
Request.json = original_request_json
296-
Request.form = original_request_form
297-
starlette.applications.Starlette.__call__ = original_starlette_call
298-
starlette.routing.request_response = original_request_response
299-
Middleware.__init__ = original_middleware_init
300-
AuthenticationMiddleware.__call__ = original_authentication_call
301-
ExceptionMiddleware.__init__ = original_exception_middleware_init
302-
ExceptionMiddleware.__call__ = original_exception_middleware_call
303-
304-
uninstall_integration("starlette")
305-
306-
307276
@pytest.mark.asyncio
308277
@pytest.mark.parametrize("span_streaming", [True, False])
309278
async def test_request_info_json_body(
@@ -375,10 +344,6 @@ async def test_request_info_json_body(
375344

376345

377346
@pytest.mark.asyncio
378-
@pytest.mark.skipif(
379-
STARLETTE_VERSION < (0, 24),
380-
reason="Patched `_get_form()` was added in version 0.24 with https://github.com/Kludex/starlette/commit/c568b55dff8be94b9c917e186e512ab53d7310e1",
381-
)
382347
@pytest.mark.parametrize("span_streaming", [True, False])
383348
async def test_formdata_request_body(
384349
sentry_init, capture_events, capture_items, span_streaming
@@ -460,10 +425,6 @@ async def test_formdata_request_body(
460425

461426

462427
@pytest.mark.asyncio
463-
@pytest.mark.skipif(
464-
STARLETTE_VERSION < (0, 24),
465-
reason="Patched `_get_form()` was added in version 0.24 with https://github.com/Kludex/starlette/commit/c568b55dff8be94b9c917e186e512ab53d7310e1",
466-
)
467428
@pytest.mark.parametrize("span_streaming", [True, False])
468429
async def test_request_body_too_big(
469430
sentry_init, capture_events, capture_items, span_streaming

0 commit comments

Comments
 (0)