Skip to content

Commit fdd83b4

Browse files
authored
Merge branch 'master' into mjq/http-route-server-spans
2 parents 1d15a6d + 8fae8a6 commit fdd83b4

22 files changed

Lines changed: 1016 additions & 71 deletions

.github/workflows/flaky-test-detector.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ jobs:
108108
# and the repo, and writes the issue body to flaky-issue-body.md.
109109
- name: Analyze logs and summarize flaky tests
110110
if: steps.collect.outputs.collected != '0'
111-
uses: anthropics/claude-code-action@1f291e1cfe0f5fc21db2aef19af844591600ade7 # v1.0.206
111+
uses: anthropics/claude-code-action@70fec183852c4f82f3f1969faed7dd60c5149ca7 # v1.0.207
112112
with:
113113
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
114114
github_token: ${{ github.token }}

sentry_sdk/_types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ class DataCollectionUserOptions(TypedDict, total=False):
188188
gen_ai: "GenAICollectionUserOptions"
189189
database_query_data: bool
190190
queues: bool
191-
stack_frame_variables: bool
191+
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"
192192
frame_context_lines: int
193193

194194
class DataCollection(TypedDict):
@@ -202,7 +202,7 @@ class DataCollection(TypedDict):
202202
gen_ai: "GenAICollectionBehaviour"
203203
database_query_data: bool
204204
queues: bool
205-
stack_frame_variables: bool
205+
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"
206206
frame_context_lines: int
207207

208208
# "critical" is an alias of "fatal" recognized by Relay

sentry_sdk/data_collection.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@
2323
"""
2424

2525
import warnings
26-
from typing import TYPE_CHECKING, List, Mapping, Optional, Union, cast
26+
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union, cast
2727
from urllib.parse import parse_qs, urlencode
2828

2929
from sentry_sdk._types import SENSITIVE_DATA_SUBSTITUTE
3030

3131
if TYPE_CHECKING:
32-
from typing import Any, Dict
33-
3432
from sentry_sdk._types import (
3533
DataCollection,
3634
GenAICollectionBehaviour,
@@ -190,29 +188,33 @@ def _map_from_send_default_pii(
190188

191189
def _resolve_explicit(
192190
d: "dict[str, Any]",
193-
include_local_variables: bool,
194-
include_source_context: bool,
195191
) -> "DataCollection":
196192
"""
197193
Build a fully-resolved ``DataCollection`` from a user-supplied
198194
``data_collection`` dict, filling in spec defaults for any omitted or
199-
partially-specified field. Frame fields fall back to the legacy
200-
``include_local_variables`` / ``include_source_context`` options when unset.
195+
partially-specified field.
201196
"""
202197
# frame_context_lines accepts an integer or a boolean fallback (spec: True
203198
# -> platform default of 5, False -> 0). bool is a subclass of int, so
204199
# coerce explicitly before treating it as a line count.
205200
frame_context_lines = d.get("frame_context_lines")
206201
if frame_context_lines is None:
207-
frame_context_lines = (
208-
_DEFAULT_FRAME_CONTEXT_LINES if include_source_context else 0
209-
)
202+
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES
210203
elif isinstance(frame_context_lines, bool):
211204
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES if frame_context_lines else 0
205+
else:
206+
if not isinstance(frame_context_lines, int) or frame_context_lines < 0:
207+
raise ValueError(
208+
"Invalid `frame_context_lines` value: Must be 0 or greater."
209+
)
210+
211+
raw_stack_frame_variables = d.get("stack_frame_variables", True)
212+
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"
212213

213-
stack_frame_variables = d.get("stack_frame_variables")
214-
if stack_frame_variables is None:
215-
stack_frame_variables = include_local_variables
214+
if isinstance(raw_stack_frame_variables, dict):
215+
stack_frame_variables = _kvcb_from_value(raw_stack_frame_variables)
216+
else:
217+
stack_frame_variables = bool(raw_stack_frame_variables)
216218

217219
# http_bodies: omitted means "all valid types"; [] is the explicit opt-out.
218220
http_bodies = d.get("http_bodies")
@@ -323,8 +325,6 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
323325
)
324326
return _resolve_explicit(
325327
user_dc,
326-
include_local_variables,
327-
include_source_context,
328328
)
329329

330330
return _map_from_send_default_pii(

sentry_sdk/integrations/aiohttp.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,7 @@ def aiohttp_processor(
561561
if request is None:
562562
return event
563563

564+
client_options = sentry_sdk.get_client().options
564565
with capture_internal_exceptions():
565566
request_info = event.setdefault("request", {})
566567

@@ -578,7 +579,16 @@ def aiohttp_processor(
578579
# Just attach raw data here if it is within bounds, if available.
579580
# Unfortunately there's no way to get structured data from aiohttp
580581
# without awaiting on some coroutine.
581-
request_info["data"] = get_aiohttp_request_data(request)
582+
if has_data_collection_enabled(client_options):
583+
if (
584+
"incoming_request"
585+
in client_options["data_collection"]["http_bodies"]
586+
):
587+
request_info["data"] = get_aiohttp_request_data(request)
588+
else:
589+
# We never gated this prior to data collection, so it should be attached
590+
# when data collection is not enabled.
591+
request_info["data"] = get_aiohttp_request_data(request)
582592

583593
return event
584594

sentry_sdk/integrations/aws_lambda.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,11 @@ def event_processor(
453453
ip = identity.get("sourceIp")
454454
if ip is not None:
455455
user_info.setdefault("ip_address", ip)
456+
457+
if "incoming_request" in client_options["data_collection"]["http_bodies"]:
458+
if "body" in aws_event:
459+
request["data"] = aws_event.get("body", "")
460+
456461
elif should_send_default_pii():
457462
user_info = sentry_event.setdefault("user", {})
458463

sentry_sdk/integrations/dramatiq.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
AnnotatedValue,
1818
capture_internal_exceptions,
1919
event_from_exception,
20+
has_data_collection_enabled,
2021
)
2122

2223
R = TypeVar("R")
@@ -241,10 +242,17 @@ def extract_into_event(self, event: "Event") -> None:
241242
request_info = contexts.setdefault("dramatiq", {})
242243
request_info["type"] = "dramatiq"
243244

244-
data: "Optional[Union[AnnotatedValue, Dict[str, Any]]]" = None
245-
if not request_body_within_bounds(client, self.content_length()):
246-
data = AnnotatedValue.removed_because_over_size_limit()
247-
else:
248-
data = self.message_data
245+
attach_request_body = True
246+
if has_data_collection_enabled(client.options):
247+
attach_request_body = (
248+
"incoming_request" in client.options["data_collection"]["http_bodies"]
249+
)
250+
251+
if attach_request_body:
252+
data: "Optional[Union[AnnotatedValue, Dict[str, Any]]]" = None
253+
if not request_body_within_bounds(client, self.content_length()):
254+
data = AnnotatedValue.removed_because_over_size_limit()
255+
else:
256+
data = self.message_data
249257

250-
request_info["data"] = data
258+
request_info["data"] = data

sentry_sdk/integrations/fastapi.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from sentry_sdk.traces import StreamedSpan, get_current_span
1010
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
1111
from sentry_sdk.tracing_utils import has_span_streaming_enabled
12-
from sentry_sdk.utils import transaction_from_function
12+
from sentry_sdk.utils import has_data_collection_enabled, transaction_from_function
1313

1414
if TYPE_CHECKING:
1515
from typing import Any, Awaitable, Callable, Dict
@@ -122,7 +122,15 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
122122
if "cookies" in info:
123123
request_info["cookies"] = info["cookies"]
124124
if "data" in info:
125-
request_info["data"] = info["data"]
125+
attach_request_data = True
126+
if has_data_collection_enabled(client.options):
127+
attach_request_data = (
128+
"incoming_request"
129+
in client.options["data_collection"]["http_bodies"]
130+
)
131+
132+
if attach_request_data:
133+
request_info["data"] = info["data"]
126134
event["request"] = deepcopy(request_info)
127135

128136
return event
@@ -140,14 +148,22 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
140148
current_span = get_current_span()
141149

142150
if type(current_span) is StreamedSpan:
143-
request_body = _get_cached_request_body_attribute(
144-
client=client, request=request
145-
)
146-
if request_body:
147-
current_span._segment.set_attribute(
148-
SPANDATA.HTTP_REQUEST_BODY_DATA,
149-
request_body,
151+
attach_request_data = True
152+
if has_data_collection_enabled(client.options):
153+
attach_request_data = (
154+
"incoming_request"
155+
in client.options["data_collection"]["http_bodies"]
156+
)
157+
158+
if attach_request_data:
159+
request_body = _get_cached_request_body_attribute(
160+
client=client, request=request
150161
)
162+
if request_body:
163+
current_span._segment.set_attribute(
164+
SPANDATA.HTTP_REQUEST_BODY_DATA,
165+
request_body,
166+
)
151167

152168

153169
def patch_get_request_handler() -> None:

sentry_sdk/integrations/gcp.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,10 @@ def event_processor(event: "Event", hint: "Hint") -> "Optional[Event]":
246246
if hasattr(gcp_event, "method"):
247247
request["method"] = gcp_event.method
248248

249+
client_options = sentry_sdk.get_client().options
250+
249251
if hasattr(gcp_event, "query_string"):
250252
query_string = gcp_event.query_string.decode("utf-8", errors="replace")
251-
client_options = sentry_sdk.get_client().options
252253
if has_data_collection_enabled(client_options):
253254
if query_string:
254255
filtered_qs = _apply_data_collection_filtering_to_query_string(
@@ -263,11 +264,16 @@ def event_processor(event: "Event", hint: "Hint") -> "Optional[Event]":
263264
if hasattr(gcp_event, "headers"):
264265
request["headers"] = _filter_headers(gcp_event.headers)
265266

266-
if should_send_default_pii():
267-
if hasattr(gcp_event, "data"):
267+
if hasattr(gcp_event, "data"):
268+
if has_data_collection_enabled(client_options):
269+
if (
270+
"incoming_request"
271+
in client_options["data_collection"]["http_bodies"]
272+
):
273+
request["data"] = gcp_event.data
274+
elif should_send_default_pii():
268275
request["data"] = gcp_event.data
269-
else:
270-
if hasattr(gcp_event, "data"):
276+
else:
271277
# Unfortunately couldn't find a way to get structured body from GCP
272278
# event. Meaning every body is unstructured to us.
273279
request["data"] = AnnotatedValue.removed_because_raw_data()

sentry_sdk/integrations/litestar.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,16 +321,24 @@ async def handle_wrapper(
321321
def event_processor(event: "Event", _: "Hint") -> "Event":
322322
request_info = event.get("request", {})
323323
request_info["content_length"] = len(scope.get("_body", b""))
324+
should_attach_request_body = True
325+
324326
if has_data_collection_enabled(client.options):
325327
cookies = _apply_key_value_collection_filtering(
326328
items=extracted_request_data["cookies"],
327329
behaviour=client.options["data_collection"]["cookies"],
328330
)
329331
if cookies:
330332
request_info["cookies"] = cookies
333+
334+
should_attach_request_body = (
335+
"incoming_request"
336+
in client.options["data_collection"]["http_bodies"]
337+
)
331338
elif should_send_default_pii():
332339
request_info["cookies"] = extracted_request_data["cookies"]
333-
if request_data is not None:
340+
341+
if request_data is not None and should_attach_request_body:
334342
request_info["data"] = request_data
335343

336344
event["request"] = deepcopy(request_info)

sentry_sdk/integrations/starlette.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,15 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
562562
if "cookies" in info:
563563
request_info["cookies"] = info["cookies"]
564564
if "data" in info:
565-
request_info["data"] = info["data"]
565+
attach_request_data = True
566+
if has_data_collection_enabled(client.options):
567+
attach_request_data = (
568+
"incoming_request"
569+
in client.options["data_collection"]["http_bodies"]
570+
)
571+
572+
if attach_request_data:
573+
request_info["data"] = info["data"]
566574
event["request"] = deepcopy(request_info)
567575

568576
return event
@@ -580,15 +588,23 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
580588
current_span = get_current_span()
581589

582590
if type(current_span) is StreamedSpan:
583-
request_body = _get_cached_request_body_attribute(
584-
client=client, request=request
585-
)
586-
if request_body:
587-
current_span._segment.set_attribute(
588-
SPANDATA.HTTP_REQUEST_BODY_DATA,
589-
request_body,
591+
attach_request_data = True
592+
if has_data_collection_enabled(client.options):
593+
attach_request_data = (
594+
"incoming_request"
595+
in client.options["data_collection"]["http_bodies"]
590596
)
591597

598+
if attach_request_data:
599+
request_body = _get_cached_request_body_attribute(
600+
client=client, request=request
601+
)
602+
if request_body:
603+
current_span._segment.set_attribute(
604+
SPANDATA.HTTP_REQUEST_BODY_DATA,
605+
request_body,
606+
)
607+
592608

593609
def patch_request_response() -> None:
594610
old_request_response = starlette.routing.request_response
@@ -827,21 +843,24 @@ async def json(self: "StarletteRequestExtractor") -> "Optional[Dict[str, Any]]":
827843
return None
828844

829845

830-
def _transaction_name_from_router(scope: "StarletteScope") -> "Optional[str]":
846+
def _transaction_name_and_source_from_router(
847+
scope: "StarletteScope",
848+
) -> "Tuple[Optional[str], TransactionSource]":
831849
router = scope.get("router")
832850
if not router:
833-
return None
851+
return None, TransactionSource.ROUTE
834852

835853
for route in router.routes:
836854
match = route.matches(scope)
837855
if match[0] == Match.FULL:
838856
try:
839-
return route.path
857+
return route.path, TransactionSource.ROUTE
840858
except AttributeError:
841-
# routes added via app.host() won't have a path attribute
842-
return scope.get("path")
859+
# Host routes have no path template, so fall back to the
860+
# concrete request path and classify it as a URL.
861+
return scope.get("path"), TransactionSource.URL
843862

844-
return None
863+
return None, TransactionSource.ROUTE
845864

846865

847866
def _set_transaction_name_and_source(
@@ -856,7 +875,7 @@ def _set_transaction_name_and_source(
856875
name = transaction_from_function(endpoint) or None
857876

858877
elif transaction_style == "url":
859-
name = _transaction_name_from_router(request.scope)
878+
name, source = _transaction_name_and_source_from_router(request.scope)
860879

861880
if name is None:
862881
name = _DEFAULT_TRANSACTION_NAME
@@ -877,7 +896,6 @@ def _get_transaction_from_middleware(
877896
name = transaction_from_function(app.__class__)
878897
source = TransactionSource.COMPONENT
879898
elif integration.transaction_style == "url":
880-
name = _transaction_name_from_router(asgi_scope)
881-
source = TransactionSource.ROUTE
899+
name, source = _transaction_name_and_source_from_router(asgi_scope)
882900

883901
return name, source

0 commit comments

Comments
 (0)