Skip to content

Commit c15fd9b

Browse files
committed
Merge remote-tracking branch 'origin/master' into pablo/merge-master-into-3.0
# Conflicts: # .github/workflows/test-integrations-agents.yml # .github/workflows/test-integrations-cloud.yml # scripts/populate_tox/config.py # scripts/populate_tox/package_dependencies.jsonl # scripts/populate_tox/releases.jsonl # scripts/populate_tox/tox.jinja # sentry_sdk/integrations/falcon.py # tests/integrations/falcon/test_falcon.py # tox.ini
2 parents ca07d0d + 63707d7 commit c15fd9b

11 files changed

Lines changed: 3787 additions & 4420 deletions

File tree

‎scripts/populate_tox/config.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"celery": {
7777
"package": "celery",
7878
"deps": {
79-
"*": ["newrelic<10.17.0", "redis", "pytest-forked"],
79+
"*": ["newrelic<10.17.0", "redis", "pytest-forked", "pytest<9.2.0"],
8080
"py3.7": ["importlib-metadata<5.0"],
8181
},
8282
},
@@ -103,6 +103,7 @@
103103
"djangorestframework",
104104
"pytest-django",
105105
"pytest-forked",
106+
"pytest<9.2.0",
106107
"Werkzeug",
107108
"channels[daphne]",
108109
"executing",
@@ -193,15 +194,20 @@
193194
"types-protobuf",
194195
"pytest-asyncio",
195196
"pytest-forked",
197+
"pytest<9.2.0",
196198
],
197199
},
198200
},
199201
"httpx0": {
200202
"package": "httpx",
201203
"integration_name": "httpx",
202204
"deps": {
203-
"*": ["anyio>=3,<5", "pytest-asyncio"],
205+
# Old pytest-asyncio versions are not compatible with pytest>=9.2.0
206+
"*": ["anyio>=3,<5", "pytest-asyncio", "pytest<9.2.0"],
204207
"<0.24": ["anyio<4"],
208+
# pytest-cov>=7 requires pytest>=7, so installing the latest pytest-cov
209+
# would override the pytest<7 pin these versions resolve to
210+
"<0.21": ["pytest-cov<7"],
205211
">=0.16,<0.17": ["pytest-httpx==0.10.0"],
206212
">=0.17,<0.19": ["pytest-httpx==0.12.0"],
207213
">=0.19,<0.21": ["pytest-httpx==0.14.0"],
@@ -401,7 +407,13 @@
401407
"quart": {
402408
"package": "quart",
403409
"deps": {
404-
"*": ["quart-auth", "pytest-asyncio", "pytest-forked", "Werkzeug"],
410+
"*": [
411+
"quart-auth",
412+
"pytest-asyncio",
413+
"pytest-forked",
414+
"pytest<9.2.0",
415+
"Werkzeug",
416+
],
405417
">=0.19,<0.23": ["quart-flask-patch"],
406418
"<0.19": [
407419
"blinker<1.6",

‎scripts/populate_tox/package_dependencies.jsonl‎

Lines changed: 258 additions & 785 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/populate_tox/releases.jsonl‎

Lines changed: 0 additions & 219 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/populate_tox/tox.jinja‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ deps =
8383
py3.7-common: pytest<7.0.0
8484
# Handled by importlib.metadata on Python 3.8+
8585
py3.7-common: setuptools<82
86-
{py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t,py3.15,py3.15t}-common: pytest
86+
# pytest-forked is not compatible with pytest>=9.2.0
87+
{py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t,py3.15,py3.15t}-common: pytest<9.2.0
8788
# coverage 7.11.1-7.11.3 makes some of our tests flake
8889
{py3.14,py3.14t,py3.15,py3.15t}-common: coverage==7.11.0
8990

@@ -94,7 +95,8 @@ deps =
9495
# and https://github.com/pytest-dev/pytest-forked/issues/67
9596
# for justification of the upper bound on pytest
9697
py3.7-gevent: pytest<7.0.0
97-
{py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest
98+
# pytest-forked is not compatible with pytest>=9.2.0
99+
{py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest<9.2.0
98100
gevent: brotli
99101
gevent: executing
100102
gevent: httpcore[http2]

‎sentry_sdk/integrations/falcon.py‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
)
1515

1616
if TYPE_CHECKING:
17-
from typing import Any, Dict, Optional
17+
from typing import Any, Dict, Optional, Union
1818

1919
from sentry_sdk._types import Event, EventProcessor
2020

@@ -194,18 +194,23 @@ def sentry_patched_prepare_middleware(
194194

195195

196196
def _exception_leads_to_http_5xx(ex: Exception, response: "falcon.Response") -> bool:
197-
is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith(
198-
"5"
197+
is_server_error = isinstance(ex, falcon.HTTPError) and _is_http_5xx_status(
198+
ex.status or ""
199199
)
200200
is_unhandled_error = not isinstance(
201201
ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)
202202
)
203203

204-
return (is_server_error or is_unhandled_error) and _has_http_5xx_status(response)
204+
return (is_server_error or is_unhandled_error) and _is_http_5xx_status(
205+
response.status
206+
)
205207

206208

207-
def _has_http_5xx_status(response: "falcon.Response") -> bool:
208-
return response.status.startswith("5")
209+
def _is_http_5xx_status(status: "Union[int, str]") -> bool:
210+
if isinstance(status, int):
211+
return 500 <= status < 600
212+
if isinstance(status, str):
213+
return status.startswith("5")
209214

210215

211216
def _make_request_event_processor(

‎sentry_sdk/tracing_utils.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ def serialize(self, include_third_party: bool = False) -> str:
842842
item = Baggage.SENTRY_PREFIX + quote(key) + "=" + quote(str(val))
843843
items.append(item)
844844

845-
if include_third_party:
845+
if include_third_party and self.third_party_items:
846846
items.append(self.third_party_items)
847847

848848
return ",".join(items)

‎tests/integrations/falcon/test_falcon.py‎

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from http import HTTPStatus
23

34
import falcon
45
import falcon.testing
@@ -8,6 +9,7 @@
89
from sentry_sdk.consts import SPANDATA
910
from sentry_sdk.integrations.falcon import FalconIntegration
1011
from sentry_sdk.integrations.logging import LoggingIntegration
12+
from sentry_sdk.utils import parse_version
1113

1214
try:
1315
import falcon.asgi
@@ -17,6 +19,9 @@
1719
import falcon.inspect # We only need this module for the ASGI test
1820

1921

22+
FALCON_VERSION = parse_version(falcon.__version__)
23+
24+
2025
@pytest.fixture
2126
def make_app(sentry_init):
2227
def inner():
@@ -178,18 +183,41 @@ def on_get(self, req, resp):
178183
assert " by zero" in event["exception"]["values"][0]["value"]
179184

180185

186+
@pytest.mark.parametrize(
187+
"status",
188+
[
189+
falcon.HTTP_502,
190+
pytest.param(
191+
502,
192+
marks=pytest.mark.skipif(
193+
(FALCON_VERSION or ()) < (4,),
194+
reason="Integer HTTPError statuses require Falcon 4+",
195+
),
196+
),
197+
pytest.param(
198+
HTTPStatus.BAD_GATEWAY,
199+
id="HTTPStatus.BAD_GATEWAY",
200+
marks=pytest.mark.skipif(
201+
(FALCON_VERSION or ()) < (4,),
202+
reason="Integer HTTPError statuses require Falcon 4+",
203+
),
204+
),
205+
],
206+
)
181207
def test_raised_5xx_errors(
182208
sentry_init,
183209
capture_exceptions,
184210
capture_items,
211+
status,
185212
):
213+
"""String, integer, and http.HTTPStatus 5xx HTTPError statuses should be captured."""
186214
sentry_init(
187215
integrations=[FalconIntegration()],
188216
)
189217

190218
class Resource:
191219
def on_get(self, req, resp):
192-
raise falcon.HTTPError(falcon.HTTP_502)
220+
raise falcon.HTTPError(status)
193221

194222
app = falcon.API()
195223
app.add_route("/", Resource())
@@ -210,18 +238,41 @@ def on_get(self, req, resp):
210238
assert event["exception"]["values"][0]["type"] == "HTTPError"
211239

212240

241+
@pytest.mark.parametrize(
242+
"status",
243+
[
244+
falcon.HTTP_400,
245+
pytest.param(
246+
400,
247+
marks=pytest.mark.skipif(
248+
(FALCON_VERSION or ()) < (4,),
249+
reason="Integer HTTPError statuses require Falcon 4+",
250+
),
251+
),
252+
pytest.param(
253+
HTTPStatus.BAD_REQUEST,
254+
id="HTTPStatus.BAD_REQUEST",
255+
marks=pytest.mark.skipif(
256+
(FALCON_VERSION or ()) < (4,),
257+
reason="Integer HTTPError statuses require Falcon 4+",
258+
),
259+
),
260+
],
261+
)
213262
def test_raised_4xx_errors(
214263
sentry_init,
215264
capture_exceptions,
216265
capture_items,
266+
status,
217267
):
268+
"""String, integer, and http.HTTPStatus 4xx HTTPError statuses should not be captured."""
218269
sentry_init(
219270
integrations=[FalconIntegration()],
220271
)
221272

222273
class Resource:
223274
def on_get(self, req, resp):
224-
raise falcon.HTTPError(falcon.HTTP_400)
275+
raise falcon.HTTPError(status)
225276

226277
app = falcon.API()
227278
app.add_route("/", Resource())
@@ -236,22 +287,41 @@ def on_get(self, req, resp):
236287
assert len(events) == 0
237288

238289

290+
@pytest.mark.parametrize(
291+
"status",
292+
[
293+
falcon.HTTP_508,
294+
pytest.param(
295+
508,
296+
marks=pytest.mark.skipif(
297+
(FALCON_VERSION or ()) < (4,),
298+
reason="Integer HTTPStatus statuses require Falcon 4+",
299+
),
300+
),
301+
pytest.param(
302+
HTTPStatus.LOOP_DETECTED,
303+
id="HTTPStatus.LOOP_DETECTED",
304+
marks=pytest.mark.skipif(
305+
(FALCON_VERSION or ()) < (4,),
306+
reason="Integer HTTPStatus statuses require Falcon 4+",
307+
),
308+
),
309+
],
310+
)
239311
def test_http_status(
240312
sentry_init,
241313
capture_exceptions,
242314
capture_items,
315+
status,
243316
):
244-
"""
245-
This just demonstrates, that if Falcon raises a HTTPStatus with code 500
246-
(instead of a HTTPError with code 500) Sentry will not capture it.
247-
"""
317+
"""String, integer, and http.HTTPStatus 5xx falcon.HTTPStatus exceptions are not captured."""
248318
sentry_init(
249319
integrations=[FalconIntegration()],
250320
)
251321

252322
class Resource:
253323
def on_get(self, req, resp):
254-
raise falcon.http_status.HTTPStatus(falcon.HTTP_508)
324+
raise falcon.http_status.HTTPStatus(status)
255325

256326
app = falcon.API()
257327
app.add_route("/", Resource())
@@ -267,6 +337,53 @@ def on_get(self, req, resp):
267337
assert len(events) == 0
268338

269339

340+
@pytest.mark.parametrize(
341+
"status",
342+
[
343+
400,
344+
pytest.param(HTTPStatus.BAD_REQUEST, id="HTTPStatus.BAD_REQUEST"),
345+
499,
346+
500,
347+
pytest.param(
348+
HTTPStatus.INTERNAL_SERVER_ERROR, id="HTTPStatus.INTERNAL_SERVER_ERROR"
349+
),
350+
599,
351+
600,
352+
],
353+
)
354+
def test_integer_response_status(sentry_init, capture_items, status):
355+
"""A handler's integer or HTTPStatus response status should capture only 5xx errors."""
356+
sentry_init(
357+
integrations=[FalconIntegration()],
358+
)
359+
360+
class Resource:
361+
def on_get(self, req, resp):
362+
raise ValueError("handled error")
363+
364+
def error_handler(req, resp, ex, params):
365+
resp.status = status
366+
367+
app = falcon.API()
368+
app.add_route("/", Resource())
369+
app.add_error_handler(ValueError, error_handler)
370+
items = capture_items("event")
371+
372+
response = falcon.testing.TestClient(app).simulate_get("/")
373+
374+
# this exists in falcon >= 3
375+
assert response.status_code == status
376+
if 500 <= status < 600:
377+
(event,) = (item.payload for item in items)
378+
exception = event["exception"]["values"][-1]
379+
assert exception["type"] == "ValueError"
380+
assert exception["value"] == "handled error"
381+
assert exception["mechanism"]["type"] == "falcon"
382+
assert exception["mechanism"]["handled"] is False
383+
else:
384+
assert not items
385+
386+
270387
@pytest.mark.parametrize("max_value_length", [1024, None])
271388
def test_falcon_large_json_request(
272389
sentry_init,

‎tests/test_tracing_utils.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,11 @@ def test_strip_sentry_baggage(header, expected):
133133
(Baggage(sentry_items={}, mutable=False), '<Baggage "", mutable=False>'),
134134
(
135135
Baggage(sentry_items={"foo": "bar"}),
136-
'<Baggage "sentry-foo=bar,", mutable=True>',
136+
'<Baggage "sentry-foo=bar", mutable=True>',
137137
),
138138
(
139139
Baggage(sentry_items={"foo": "bar"}, mutable=False),
140-
'<Baggage "sentry-foo=bar,", mutable=False>',
140+
'<Baggage "sentry-foo=bar", mutable=False>',
141141
),
142142
(
143143
Baggage(sentry_items={"foo": "bar"}, third_party_items="asdf=1234,"),

‎tests/tracing/test_baggage.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ def test_mixed_baggage():
6767
)
6868

6969

70+
def test_sentry_only_baggage():
71+
header = (
72+
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
73+
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
74+
"sentry-sample_rate=0.01337"
75+
)
76+
baggage = Baggage.from_incoming_header(header)
77+
78+
assert baggage.third_party_items == ""
79+
80+
serialized = baggage.serialize(include_third_party=True)
81+
82+
# with no third-party items, include_third_party should change nothing
83+
assert serialized == baggage.serialize()
84+
assert serialized == (
85+
"sentry-trace_id=771a43a4192642f0b136d5159a501700,"
86+
"sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
87+
"sentry-sample_rate=0.01337"
88+
)
89+
90+
7091
def test_malformed_baggage():
7192
header = ","
7293

0 commit comments

Comments
 (0)