Skip to content

Commit a674788

Browse files
committed
Merge remote-tracking branch 'origin/master' into potel-base
2 parents 383c0b5 + 295dd8d commit a674788

File tree

7 files changed

+135
-28
lines changed

7 files changed

+135
-28
lines changed

sentry_sdk/integrations/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def iter_default_integrations(with_auto_enabling_integrations):
9696
"sentry_sdk.integrations.huey.HueyIntegration",
9797
"sentry_sdk.integrations.huggingface_hub.HuggingfaceHubIntegration",
9898
"sentry_sdk.integrations.langchain.LangchainIntegration",
99+
"sentry_sdk.integrations.litestar.LitestarIntegration",
99100
"sentry_sdk.integrations.loguru.LoguruIntegration",
100101
"sentry_sdk.integrations.openai.OpenAIIntegration",
101102
"sentry_sdk.integrations.pymongo.PyMongoIntegration",

sentry_sdk/integrations/falcon.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
FALCON3 = False
4444

4545

46+
_FALCON_UNSET = None # type: Optional[object]
47+
if FALCON3: # falcon.request._UNSET is only available in Falcon 3.0+
48+
with capture_internal_exceptions():
49+
from falcon.request import _UNSET as _FALCON_UNSET # type: ignore[import-not-found, no-redef]
50+
51+
4652
class FalconRequestExtractor(RequestExtractor):
4753
def env(self):
4854
# type: () -> Dict[str, Any]
@@ -73,27 +79,23 @@ def raw_data(self):
7379
else:
7480
return None
7581

76-
if FALCON3:
77-
78-
def json(self):
79-
# type: () -> Optional[Dict[str, Any]]
80-
try:
81-
return self.request.media
82-
except falcon.errors.HTTPBadRequest:
83-
return None
84-
85-
else:
86-
87-
def json(self):
88-
# type: () -> Optional[Dict[str, Any]]
89-
try:
90-
return self.request.media
91-
except falcon.errors.HTTPBadRequest:
92-
# NOTE(jmagnusson): We return `falcon.Request._media` here because
93-
# falcon 1.4 doesn't do proper type checking in
94-
# `falcon.Request.media`. This has been fixed in 2.0.
95-
# Relevant code: https://github.com/falconry/falcon/blob/1.4.1/falcon/request.py#L953
96-
return self.request._media
82+
def json(self):
83+
# type: () -> Optional[Dict[str, Any]]
84+
# fallback to cached_media = None if self.request._media is not available
85+
cached_media = None
86+
with capture_internal_exceptions():
87+
# self.request._media is the cached self.request.media
88+
# value. It is only available if self.request.media
89+
# has already been accessed. Therefore, reading
90+
# self.request._media will not exhaust the raw request
91+
# stream (self.request.bounded_stream) because it has
92+
# already been read if self.request._media is set.
93+
cached_media = self.request._media
94+
95+
if cached_media is not _FALCON_UNSET:
96+
return cached_media
97+
98+
return None
9799

98100

99101
class SentryFalconMiddleware:

sentry_sdk/integrations/httpx.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from sentry_sdk.consts import OP, SPANDATA
33
from sentry_sdk.integrations import Integration, DidNotEnable
44
from sentry_sdk.tracing import BAGGAGE_HEADER_NAME
5-
from sentry_sdk.tracing_utils import should_propagate_trace
5+
from sentry_sdk.tracing_utils import Baggage, should_propagate_trace
66
from sentry_sdk.utils import (
77
SENSITIVE_DATA_SUBSTITUTE,
88
capture_internal_exceptions,
@@ -14,6 +14,7 @@
1414
from typing import TYPE_CHECKING
1515

1616
if TYPE_CHECKING:
17+
from collections.abc import MutableMapping
1718
from typing import Any
1819

1920

@@ -82,11 +83,9 @@ def send(self, request, **kwargs):
8283
key=key, value=value, url=request.url
8384
)
8485
)
85-
if key == BAGGAGE_HEADER_NAME and request.headers.get(
86-
BAGGAGE_HEADER_NAME
87-
):
88-
# do not overwrite any existing baggage, just append to it
89-
request.headers[key] += "," + value
86+
87+
if key == BAGGAGE_HEADER_NAME:
88+
_add_sentry_baggage_to_headers(request.headers, value)
9089
else:
9190
request.headers[key] = value
9291

@@ -178,3 +177,21 @@ async def send(self, request, **kwargs):
178177
return rv
179178

180179
AsyncClient.send = send
180+
181+
182+
def _add_sentry_baggage_to_headers(headers, sentry_baggage):
183+
# type: (MutableMapping[str, str], str) -> None
184+
"""Add the Sentry baggage to the headers.
185+
186+
This function directly mutates the provided headers. The provided sentry_baggage
187+
is appended to the existing baggage. If the baggage already contains Sentry items,
188+
they are stripped out first.
189+
"""
190+
existing_baggage = headers.get(BAGGAGE_HEADER_NAME, "")
191+
stripped_existing_baggage = Baggage.strip_sentry_baggage(existing_baggage)
192+
193+
separator = "," if len(stripped_existing_baggage) > 0 else ""
194+
195+
headers[BAGGAGE_HEADER_NAME] = (
196+
stripped_existing_baggage + separator + sentry_baggage
197+
)

sentry_sdk/tracing_utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,21 @@ def serialize(self, include_third_party=False):
604604

605605
return ",".join(items)
606606

607+
@staticmethod
608+
def strip_sentry_baggage(header):
609+
# type: (str) -> str
610+
"""Remove Sentry baggage from the given header.
611+
612+
Given a Baggage header, return a new Baggage header with all Sentry baggage items removed.
613+
"""
614+
return ",".join(
615+
(
616+
item
617+
for item in header.split(",")
618+
if not Baggage.SENTRY_PREFIX_REGEX.match(item.strip())
619+
)
620+
)
621+
607622

608623
def should_propagate_trace(client, url):
609624
# type: (sentry_sdk.client.BaseClient, str) -> bool

tests/integrations/falcon/test_falcon.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,48 @@ def test_span_origin(sentry_init, capture_events, make_client):
460460
(_, event) = events
461461

462462
assert event["contexts"]["trace"]["origin"] == "auto.http.falcon"
463+
464+
465+
def test_falcon_request_media(sentry_init):
466+
# test_passed stores whether the test has passed.
467+
test_passed = False
468+
469+
# test_failure_reason stores the reason why the test failed
470+
# if test_passed is False. The value is meaningless when
471+
# test_passed is True.
472+
test_failure_reason = "test endpoint did not get called"
473+
474+
class SentryCaptureMiddleware:
475+
def process_request(self, _req, _resp):
476+
# This capture message forces Falcon event processors to run
477+
# before the request handler runs
478+
sentry_sdk.capture_message("Processing request")
479+
480+
class RequestMediaResource:
481+
def on_post(self, req, _):
482+
nonlocal test_passed, test_failure_reason
483+
raw_data = req.bounded_stream.read()
484+
485+
# If the raw_data is empty, the request body stream
486+
# has been exhausted by the SDK. Test should fail in
487+
# this case.
488+
test_passed = raw_data != b""
489+
test_failure_reason = "request body has been read"
490+
491+
sentry_init(integrations=[FalconIntegration()])
492+
493+
try:
494+
app_class = falcon.App # Falcon ≥3.0
495+
except AttributeError:
496+
app_class = falcon.API # Falcon <3.0
497+
498+
app = app_class(middleware=[SentryCaptureMiddleware()])
499+
app.add_route("/read_body", RequestMediaResource())
500+
501+
client = falcon.testing.TestClient(app)
502+
503+
client.simulate_post("/read_body", json={"foo": "bar"})
504+
505+
# Check that simulate_post actually calls the resource, and
506+
# that the SDK does not exhaust the request body stream.
507+
assert test_passed, test_failure_reason

tests/integrations/gcp/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import pytest
2+
import os
3+
4+
5+
if "gcp" not in os.environ.get("TOX_ENV_NAME", ""):
6+
pytest.skip("GCP tests only run in GCP environment", allow_module_level=True)

tests/test_tracing_utils.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import asdict, dataclass
22
from typing import Optional, List
33

4-
from sentry_sdk.tracing_utils import _should_be_included
4+
from sentry_sdk.tracing_utils import _should_be_included, Baggage
55
import pytest
66

77

@@ -94,3 +94,24 @@ def test_should_be_included(test_case, expected):
9494
kwargs = asdict(test_case)
9595
kwargs.pop("id")
9696
assert _should_be_included(**kwargs) == expected
97+
98+
99+
@pytest.mark.parametrize(
100+
("header", "expected"),
101+
(
102+
("", ""),
103+
("foo=bar", "foo=bar"),
104+
(" foo=bar, baz = qux ", " foo=bar, baz = qux "),
105+
("sentry-trace_id=123", ""),
106+
(" sentry-trace_id = 123 ", ""),
107+
("sentry-trace_id=123,sentry-public_key=456", ""),
108+
("foo=bar,sentry-trace_id=123", "foo=bar"),
109+
("foo=bar,sentry-trace_id=123,baz=qux", "foo=bar,baz=qux"),
110+
(
111+
"foo=bar,sentry-trace_id=123,baz=qux,sentry-public_key=456",
112+
"foo=bar,baz=qux",
113+
),
114+
),
115+
)
116+
def test_strip_sentry_baggage(header, expected):
117+
assert Baggage.strip_sentry_baggage(header) == expected

0 commit comments

Comments
 (0)