|
4 | 4 | from typing import TYPE_CHECKING |
5 | 5 |
|
6 | 6 | import sentry_sdk |
| 7 | +from sentry_sdk.consts import SPANDATA |
7 | 8 | from sentry_sdk.integrations import DidNotEnable |
8 | 9 | from sentry_sdk.scope import should_send_default_pii |
9 | | -from sentry_sdk.traces import StreamedSpan |
| 10 | +from sentry_sdk.traces import StreamedSpan, get_current_span |
10 | 11 | from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource |
11 | 12 | from sentry_sdk.tracing_utils import has_span_streaming_enabled |
12 | 13 | from sentry_sdk.utils import transaction_from_function |
13 | 14 |
|
14 | 15 | if TYPE_CHECKING: |
15 | | - from typing import Any, Callable, Dict |
| 16 | + from typing import Any, Awaitable, Callable, Dict |
16 | 17 |
|
17 | 18 | from sentry_sdk._types import Event |
18 | 19 |
|
19 | 20 | try: |
20 | 21 | from sentry_sdk.integrations.starlette import ( |
21 | 22 | StarletteIntegration, |
22 | 23 | StarletteRequestExtractor, |
23 | | - _set_request_body_data_on_streaming_segment, |
| 24 | + _get_cached_request_body_attribute, |
24 | 25 | ) |
25 | 26 | except DidNotEnable: |
26 | 27 | raise DidNotEnable("Starlette is not installed") |
@@ -75,6 +76,66 @@ def _set_transaction_name_and_source( |
75 | 76 | scope.set_transaction_name(name, source=source) |
76 | 77 |
|
77 | 78 |
|
| 79 | +async def _wrap_async_handler( |
| 80 | + handler: "Callable[..., Awaitable[Any]]", *args: "Any", **kwargs: "Any" |
| 81 | +) -> "Any": |
| 82 | + """ |
| 83 | + Wraps an asynchronous handler function to attach request info to errors and the server segment span. |
| 84 | + The request body cached on the Starlette Request object is attached to streamed spans, but consuming the request body in the event |
| 85 | + processor can still cause application hangs. |
| 86 | + """ |
| 87 | + client = sentry_sdk.get_client() |
| 88 | + integration = client.get_integration(FastApiIntegration) |
| 89 | + if integration is None: |
| 90 | + return await handler(*args, **kwargs) |
| 91 | + |
| 92 | + request = args[0] |
| 93 | + |
| 94 | + _set_transaction_name_and_source( |
| 95 | + sentry_sdk.get_current_scope(), integration.transaction_style, request |
| 96 | + ) |
| 97 | + sentry_scope = sentry_sdk.get_isolation_scope() |
| 98 | + extractor = StarletteRequestExtractor(request) |
| 99 | + info = await extractor.extract_request_info() |
| 100 | + |
| 101 | + def _make_request_event_processor( |
| 102 | + req: "Any", integration: "Any" |
| 103 | + ) -> "Callable[[Event, Dict[str, Any]], Event]": |
| 104 | + def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": |
| 105 | + # Extract information from request |
| 106 | + request_info = event.get("request", {}) |
| 107 | + if info: |
| 108 | + if "cookies" in info and should_send_default_pii(): |
| 109 | + request_info["cookies"] = info["cookies"] |
| 110 | + if "data" in info: |
| 111 | + request_info["data"] = info["data"] |
| 112 | + event["request"] = deepcopy(request_info) |
| 113 | + |
| 114 | + return event |
| 115 | + |
| 116 | + return event_processor |
| 117 | + |
| 118 | + sentry_scope._name = FastApiIntegration.identifier |
| 119 | + sentry_scope.add_event_processor( |
| 120 | + _make_request_event_processor(request, integration) |
| 121 | + ) |
| 122 | + |
| 123 | + try: |
| 124 | + return await handler(*args, **kwargs) |
| 125 | + finally: |
| 126 | + current_span = get_current_span() |
| 127 | + |
| 128 | + if type(current_span) is StreamedSpan: |
| 129 | + request_body = _get_cached_request_body_attribute( |
| 130 | + client=client, request=request |
| 131 | + ) |
| 132 | + if request_body: |
| 133 | + current_span._segment.set_attribute( |
| 134 | + SPANDATA.HTTP_REQUEST_BODY_DATA, |
| 135 | + request_body, |
| 136 | + ) |
| 137 | + |
| 138 | + |
78 | 139 | def patch_get_request_handler() -> None: |
79 | 140 | old_get_request_handler = fastapi.routing.get_request_handler |
80 | 141 |
|
@@ -113,46 +174,7 @@ def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any": |
113 | 174 | old_app = old_get_request_handler(*args, **kwargs) |
114 | 175 |
|
115 | 176 | async def _sentry_app(*args: "Any", **kwargs: "Any") -> "Any": |
116 | | - client = sentry_sdk.get_client() |
117 | | - integration = client.get_integration(FastApiIntegration) |
118 | | - if integration is None: |
119 | | - return await old_app(*args, **kwargs) |
120 | | - |
121 | | - request = args[0] |
122 | | - |
123 | | - _set_transaction_name_and_source( |
124 | | - sentry_sdk.get_current_scope(), integration.transaction_style, request |
125 | | - ) |
126 | | - sentry_scope = sentry_sdk.get_isolation_scope() |
127 | | - extractor = StarletteRequestExtractor(request) |
128 | | - info = await extractor.extract_request_info() |
129 | | - |
130 | | - def _make_request_event_processor( |
131 | | - req: "Any", integration: "Any" |
132 | | - ) -> "Callable[[Event, Dict[str, Any]], Event]": |
133 | | - def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event": |
134 | | - # Extract information from request |
135 | | - request_info = event.get("request", {}) |
136 | | - if info: |
137 | | - if "cookies" in info and should_send_default_pii(): |
138 | | - request_info["cookies"] = info["cookies"] |
139 | | - if "data" in info: |
140 | | - request_info["data"] = info["data"] |
141 | | - event["request"] = deepcopy(request_info) |
142 | | - |
143 | | - return event |
144 | | - |
145 | | - return event_processor |
146 | | - |
147 | | - sentry_scope._name = FastApiIntegration.identifier |
148 | | - sentry_scope.add_event_processor( |
149 | | - _make_request_event_processor(request, integration) |
150 | | - ) |
151 | | - |
152 | | - if has_span_streaming_enabled(client.options): |
153 | | - _set_request_body_data_on_streaming_segment(info) |
154 | | - |
155 | | - return await old_app(*args, **kwargs) |
| 177 | + return await _wrap_async_handler(old_app, *args, **kwargs) |
156 | 178 |
|
157 | 179 | return _sentry_app |
158 | 180 |
|
|
0 commit comments