Skip to content

Commit c74acb1

Browse files
committed
feat(flask): Add span streaming support and request body capture
Add request body data to the streaming segment span when span streaming is enabled. Update existing tests to cover both streaming and non-streaming code paths, and add new tests for request body capture, size limits, and header scrubbing in streaming mode. Fixes PY-2323 Fixes #6021
1 parent d1fc138 commit c74acb1

2 files changed

Lines changed: 418 additions & 81 deletions

File tree

sentry_sdk/integrations/flask.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
1+
import json
2+
from typing import TYPE_CHECKING
3+
14
import sentry_sdk
2-
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
5+
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
36
from sentry_sdk.integrations._wsgi_common import (
7+
_RAW_DATA_EXCEPTIONS,
48
DEFAULT_HTTP_METHODS_TO_CAPTURE,
59
RequestExtractor,
10+
request_body_within_bounds,
611
)
712
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
813
from sentry_sdk.scope import should_send_default_pii
14+
from sentry_sdk.traces import StreamedSpan, _get_current_streamed_span
915
from sentry_sdk.tracing import SOURCE_FOR_STYLE
16+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
1017
from sentry_sdk.utils import (
18+
AnnotatedValue,
1119
capture_internal_exceptions,
1220
ensure_integration_enabled,
1321
event_from_exception,
1422
package_version,
1523
)
1624

17-
from typing import TYPE_CHECKING
18-
1925
if TYPE_CHECKING:
2026
from typing import Any, Callable, Dict, Union
2127

28+
from werkzeug.datastructures import FileStorage, ImmutableMultiDict
29+
2230
from sentry_sdk._types import Event, EventProcessor
2331
from sentry_sdk.integrations.wsgi import _ScopedResponse
24-
from werkzeug.datastructures import FileStorage, ImmutableMultiDict
2532

2633

2734
try:
@@ -94,10 +101,9 @@ def setup_once() -> None:
94101
def sentry_patched_wsgi_app(
95102
self: "Any", environ: "Dict[str, str]", start_response: "Callable[..., Any]"
96103
) -> "_ScopedResponse":
97-
if sentry_sdk.get_client().get_integration(FlaskIntegration) is None:
98-
return old_app(self, environ, start_response)
99-
100104
integration = sentry_sdk.get_client().get_integration(FlaskIntegration)
105+
if integration is None:
106+
return old_app(self, environ, start_response)
101107

102108
middleware = SentryWsgiMiddleware(
103109
lambda *a, **kw: old_app(self, *a, **kw),
@@ -158,6 +164,49 @@ def _request_started(app: "Flask", **kwargs: "Any") -> None:
158164
evt_processor = _make_request_event_processor(app, request, integration)
159165
scope.add_event_processor(evt_processor)
160166

167+
client = sentry_sdk.get_client()
168+
if has_span_streaming_enabled(client.options):
169+
_set_request_body_data_on_streaming_segment(request, client)
170+
171+
172+
def _set_request_body_data_on_streaming_segment(
173+
request: "Request", client: "sentry_sdk.client.BaseClient"
174+
) -> None:
175+
current_span = _get_current_streamed_span()
176+
if type(current_span) is not StreamedSpan:
177+
return
178+
179+
with capture_internal_exceptions():
180+
content_length = int(request.content_length or 0)
181+
extractor = FlaskRequestExtractor(request)
182+
183+
if not request_body_within_bounds(client, content_length):
184+
data = AnnotatedValue.substituted_because_over_size_limit()
185+
else:
186+
raw_data = None
187+
try:
188+
raw_data = extractor.raw_data()
189+
except _RAW_DATA_EXCEPTIONS:
190+
pass
191+
192+
parsed_body = extractor.parsed_body()
193+
if parsed_body is not None:
194+
data = parsed_body
195+
elif raw_data:
196+
data = AnnotatedValue.substituted_because_raw_data()
197+
else:
198+
return
199+
200+
def _default(value: "Any") -> "Any":
201+
if isinstance(value, AnnotatedValue):
202+
return value.value
203+
return str(value)
204+
205+
current_span._segment.set_attribute(
206+
"http.request.body.data",
207+
json.dumps(data, default=_default),
208+
)
209+
161210

162211
class FlaskRequestExtractor(RequestExtractor):
163212
def env(self) -> "Dict[str, str]":

0 commit comments

Comments
 (0)