Skip to content

Commit ccf8f09

Browse files
authored
test(flask): Add span streaming test coverage (#6264)
Add test coverage to ensure that span streaming works for Flask. No additional changes are needed because we are no longer adding the request body to the streamed spans, and span streaming support has been added earlier to the uWSGI middleware which the Flask integration leverages. Fixes PY-2323 Fixes #6021
1 parent 9594a8c commit ccf8f09

2 files changed

Lines changed: 166 additions & 51 deletions

File tree

sentry_sdk/integrations/flask.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,9 @@ def setup_once() -> None:
9595
def sentry_patched_wsgi_app(
9696
self: "Any", environ: "Dict[str, str]", start_response: "Callable[..., Any]"
9797
) -> "_ScopedResponse":
98-
if sentry_sdk.get_client().get_integration(FlaskIntegration) is None:
99-
return old_app(self, environ, start_response)
100-
10198
integration = sentry_sdk.get_client().get_integration(FlaskIntegration)
99+
if integration is None:
100+
return old_app(self, environ, start_response)
102101

103102
middleware = SentryWsgiMiddleware(
104103
lambda *a, **kw: old_app(self, *a, **kw),

tests/integrations/flask/test_flask.py

Lines changed: 164 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from flask.views import View
1616
from flask_login import LoginManager, login_user
1717

18+
from sentry_sdk.traces import SpanStatus
19+
1820
try:
1921
from werkzeug.wrappers.request import UnsupportedMediaType
2022
except ImportError:
@@ -82,6 +84,7 @@ def test_has_context(sentry_init, app, capture_events):
8284
assert event["request"]["url"] == "http://localhost/message"
8385

8486

87+
@pytest.mark.parametrize("span_streaming", [True, False])
8588
@pytest.mark.parametrize(
8689
"url,transaction_style,expected_transaction,expected_source",
8790
[
@@ -91,29 +94,45 @@ def test_has_context(sentry_init, app, capture_events):
9194
("/message/123456", "url", "/message/<int:message_id>", "route"),
9295
],
9396
)
94-
def test_transaction_style(
97+
def test_transaction_or_segment_style(
9598
sentry_init,
9699
app,
97100
capture_events,
101+
capture_items,
98102
url,
99103
transaction_style,
100104
expected_transaction,
101105
expected_source,
106+
span_streaming,
102107
):
103108
sentry_init(
104109
integrations=[
105110
flask_sentry.FlaskIntegration(transaction_style=transaction_style)
106-
]
111+
],
112+
traces_sample_rate=1.0,
113+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
107114
)
108-
events = capture_events()
115+
116+
if span_streaming:
117+
items = capture_items("span")
118+
else:
119+
events = capture_events()
109120

110121
client = app.test_client()
111122
response = client.get(url)
112123
assert response.status_code == 200
113124

114-
(event,) = events
115-
assert event["transaction"] == expected_transaction
116-
assert event["transaction_info"] == {"source": expected_source}
125+
if span_streaming:
126+
sentry_sdk.flush()
127+
spans = [i.payload for i in items if i.type == "span"]
128+
assert len(spans) == 1
129+
(segment,) = spans
130+
assert segment["name"] == expected_transaction
131+
assert segment["attributes"]["sentry.span.source"] == expected_source
132+
else:
133+
(_, event) = events
134+
assert event["transaction"] == expected_transaction
135+
assert event["transaction_info"] == {"source": expected_source}
117136

118137

119138
@pytest.mark.parametrize("debug", (True, False))
@@ -763,8 +782,15 @@ def zerodivision(e):
763782
assert not events
764783

765784

766-
def test_tracing_success(sentry_init, capture_events, app):
767-
sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()])
785+
@pytest.mark.parametrize("span_streaming", [True, False])
786+
def test_tracing_success(
787+
sentry_init, capture_events, capture_items, app, span_streaming
788+
):
789+
sentry_init(
790+
traces_sample_rate=1.0,
791+
integrations=[flask_sentry.FlaskIntegration()],
792+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
793+
)
768794

769795
@app.before_request
770796
def _():
@@ -776,30 +802,61 @@ def hi_tx():
776802
capture_message("hi")
777803
return "ok"
778804

779-
events = capture_events()
805+
if span_streaming:
806+
items = capture_items("event", "span")
807+
else:
808+
events = capture_events()
780809

781810
with app.test_client() as client:
782811
response = client.get("/message_tx")
783812
assert response.status_code == 200
784813

785-
message_event, transaction_event = events
814+
if span_streaming:
815+
sentry_sdk.flush()
816+
spans = [i.payload for i in items if i.type == "span"]
817+
message_events = [i.payload for i in items if i.type == "event"]
786818

787-
assert transaction_event["type"] == "transaction"
788-
assert transaction_event["transaction"] == "hi_tx"
789-
assert transaction_event["contexts"]["trace"]["status"] == "ok"
790-
assert transaction_event["tags"]["view"] == "yes"
791-
assert transaction_event["tags"]["before_request"] == "yes"
819+
assert len(spans) == 1
820+
assert len(message_events) == 1
792821

793-
assert message_event["message"] == "hi"
794-
assert message_event["transaction"] == "hi_tx"
795-
assert message_event["tags"]["view"] == "yes"
796-
assert message_event["tags"]["before_request"] == "yes"
822+
(segment,) = spans
823+
(message_event,) = message_events
797824

825+
assert segment["name"] == "hi_tx"
826+
assert segment["status"] == SpanStatus.OK
827+
assert segment["attributes"]["sentry.origin"] == "auto.http.flask"
798828

799-
def test_tracing_error(sentry_init, capture_events, app):
800-
sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()])
829+
assert message_event["message"] == "hi"
830+
assert message_event["transaction"] == "hi_tx"
831+
assert message_event["tags"]["view"] == "yes"
832+
assert message_event["tags"]["before_request"] == "yes"
833+
else:
834+
message_event, transaction_event = events
801835

802-
events = capture_events()
836+
assert transaction_event["type"] == "transaction"
837+
assert transaction_event["transaction"] == "hi_tx"
838+
assert transaction_event["contexts"]["trace"]["status"] == "ok"
839+
assert transaction_event["tags"]["view"] == "yes"
840+
assert transaction_event["tags"]["before_request"] == "yes"
841+
842+
assert message_event["message"] == "hi"
843+
assert message_event["transaction"] == "hi_tx"
844+
assert message_event["tags"]["view"] == "yes"
845+
assert message_event["tags"]["before_request"] == "yes"
846+
847+
848+
@pytest.mark.parametrize("span_streaming", [True, False])
849+
def test_tracing_error(sentry_init, capture_events, capture_items, app, span_streaming):
850+
sentry_init(
851+
traces_sample_rate=1.0,
852+
integrations=[flask_sentry.FlaskIntegration()],
853+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
854+
)
855+
856+
if span_streaming:
857+
items = capture_items("event", "span")
858+
else:
859+
events = capture_events()
803860

804861
@app.route("/error")
805862
def error():
@@ -810,15 +867,33 @@ def error():
810867
response = client.get("/error")
811868
assert response.status_code == 500
812869

813-
error_event, transaction_event = events
870+
if span_streaming:
871+
sentry_sdk.flush()
872+
spans = [i.payload for i in items if i.type == "span"]
873+
error_events = [i.payload for i in items if i.type == "event"]
814874

815-
assert transaction_event["type"] == "transaction"
816-
assert transaction_event["transaction"] == "error"
817-
assert transaction_event["contexts"]["trace"]["status"] == "internal_error"
875+
assert len(spans) == 1
876+
assert len(error_events) == 1
818877

819-
assert error_event["transaction"] == "error"
820-
(exception,) = error_event["exception"]["values"]
821-
assert exception["type"] == "ZeroDivisionError"
878+
(segment,) = spans
879+
(error_event,) = error_events
880+
881+
assert segment["name"] == "error"
882+
assert segment["status"] == SpanStatus.ERROR
883+
884+
assert error_event["transaction"] == "error"
885+
(exception,) = error_event["exception"]["values"]
886+
assert exception["type"] == "ZeroDivisionError"
887+
else:
888+
error_event, transaction_event = events
889+
890+
assert transaction_event["type"] == "transaction"
891+
assert transaction_event["transaction"] == "error"
892+
assert transaction_event["contexts"]["trace"]["status"] == "internal_error"
893+
894+
assert error_event["transaction"] == "error"
895+
(exception,) = error_event["exception"]["values"]
896+
assert exception["type"] == "ZeroDivisionError"
822897

823898

824899
def test_error_has_trace_context_if_tracing_disabled(sentry_init, capture_events, app):
@@ -995,34 +1070,54 @@ def test_response_status_code_not_found_in_transaction_context(
9951070
assert transaction["contexts"]["response"]["status_code"] == 404
9961071

9971072

998-
def test_span_origin(sentry_init, app, capture_events):
1073+
@pytest.mark.parametrize("span_streaming", [True, False])
1074+
def test_span_origin(sentry_init, app, capture_events, capture_items, span_streaming):
9991075
sentry_init(
10001076
integrations=[flask_sentry.FlaskIntegration()],
10011077
traces_sample_rate=1.0,
1078+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
10021079
)
1003-
events = capture_events()
1080+
1081+
if span_streaming:
1082+
items = capture_items("span")
1083+
else:
1084+
events = capture_events()
10041085

10051086
client = app.test_client()
10061087
client.get("/message")
10071088

1008-
(_, event) = events
1009-
1010-
assert event["contexts"]["trace"]["origin"] == "auto.http.flask"
1089+
if span_streaming:
1090+
sentry_sdk.flush()
1091+
spans = [i.payload for i in items if i.type == "span"]
1092+
assert len(spans) == 1
1093+
(segment,) = spans
1094+
assert segment["attributes"]["sentry.origin"] == "auto.http.flask"
1095+
else:
1096+
(_, event) = events
1097+
assert event["contexts"]["trace"]["origin"] == "auto.http.flask"
10111098

10121099

1013-
def test_transaction_http_method_default(
1100+
@pytest.mark.parametrize("span_streaming", [True, False])
1101+
def test_transaction_or_segment_http_method_default(
10141102
sentry_init,
10151103
app,
10161104
capture_events,
1105+
capture_items,
1106+
span_streaming,
10171107
):
10181108
"""
1019-
By default OPTIONS and HEAD requests do not create a transaction.
1109+
By default OPTIONS and HEAD requests do not create a transaction or segment.
10201110
"""
10211111
sentry_init(
10221112
traces_sample_rate=1.0,
10231113
integrations=[flask_sentry.FlaskIntegration()],
1114+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
10241115
)
1025-
events = capture_events()
1116+
1117+
if span_streaming:
1118+
items = capture_items("span")
1119+
else:
1120+
events = capture_events()
10261121

10271122
client = app.test_client()
10281123
response = client.get("/nomessage")
@@ -1034,16 +1129,25 @@ def test_transaction_http_method_default(
10341129
response = client.head("/nomessage")
10351130
assert response.status_code == 200
10361131

1037-
(event,) = events
1038-
1039-
assert len(events) == 1
1040-
assert event["request"]["method"] == "GET"
1132+
if span_streaming:
1133+
sentry_sdk.flush()
1134+
spans = [i.payload for i in items if i.type == "span"]
1135+
assert len(spans) == 1
1136+
(segment,) = spans
1137+
assert segment["attributes"]["http.request.method"] == "GET"
1138+
else:
1139+
(event,) = events
1140+
assert len(events) == 1
1141+
assert event["request"]["method"] == "GET"
10411142

10421143

1043-
def test_transaction_http_method_custom(
1144+
@pytest.mark.parametrize("span_streaming", [True, False])
1145+
def test_transaction_or_segment_http_method_custom(
10441146
sentry_init,
10451147
app,
10461148
capture_events,
1149+
capture_items,
1150+
span_streaming,
10471151
):
10481152
"""
10491153
Configure FlaskIntegration to ONLY capture OPTIONS and HEAD requests.
@@ -1058,8 +1162,13 @@ def test_transaction_http_method_custom(
10581162
) # capitalization does not matter
10591163
) # case does not matter
10601164
],
1165+
_experiments={"trace_lifecycle": "stream" if span_streaming else "static"},
10611166
)
1062-
events = capture_events()
1167+
1168+
if span_streaming:
1169+
items = capture_items("span")
1170+
else:
1171+
events = capture_events()
10631172

10641173
client = app.test_client()
10651174
response = client.get("/nomessage")
@@ -1071,8 +1180,15 @@ def test_transaction_http_method_custom(
10711180
response = client.head("/nomessage")
10721181
assert response.status_code == 200
10731182

1074-
assert len(events) == 2
1075-
1076-
(event1, event2) = events
1077-
assert event1["request"]["method"] == "OPTIONS"
1078-
assert event2["request"]["method"] == "HEAD"
1183+
if span_streaming:
1184+
sentry_sdk.flush()
1185+
spans = [i.payload for i in items if i.type == "span"]
1186+
assert len(spans) == 2
1187+
(options_segment, head_segment) = spans
1188+
assert options_segment["attributes"]["http.request.method"] == "OPTIONS"
1189+
assert head_segment["attributes"]["http.request.method"] == "HEAD"
1190+
else:
1191+
assert len(events) == 2
1192+
(event1, event2) = events
1193+
assert event1["request"]["method"] == "OPTIONS"
1194+
assert event2["request"]["method"] == "HEAD"

0 commit comments

Comments
 (0)