Skip to content

Commit 87b9f29

Browse files
fix(replay): safer access to event payload fields in summary endpoint (#95055)
fixes SENTRY-467N
1 parent 63fdc80 commit 87b9f29

File tree

1 file changed

+81
-68
lines changed

1 file changed

+81
-68
lines changed

src/sentry/replays/endpoints/project_replay_summarize_breadcrumbs.py

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def gen_request_data(
324324
# Yield the current event's log message
325325
event_type = which(event)
326326
if event_type == EventType.FEEDBACK:
327-
feedback_id = event["data"]["payload"].get("data", {}).get("feedbackId", None)
327+
feedback_id = event["data"]["payload"].get("data", {}).get("feedbackId")
328328
feedback = fetch_feedback_details(feedback_id, project_id)
329329
if feedback:
330330
yield generate_feedback_log_message(feedback)
@@ -374,74 +374,87 @@ def as_log_message(event: dict[str, Any]) -> str | None:
374374
event_type = which(event)
375375
timestamp = event.get("timestamp", 0.0)
376376

377-
match event_type:
378-
case EventType.CLICK:
379-
return f"User clicked on {event["data"]["payload"]["message"]} at {timestamp}"
380-
case EventType.DEAD_CLICK:
381-
return f"User clicked on {event["data"]["payload"]["message"]} but the triggered action was slow to complete at {timestamp}"
382-
case EventType.RAGE_CLICK:
383-
return f"User rage clicked on {event["data"]["payload"]["message"]} but the triggered action was slow to complete at {timestamp}"
384-
case EventType.NAVIGATION:
385-
return f"User navigated to: {event["data"]["payload"]["data"]["to"]} at {timestamp}"
386-
case EventType.CONSOLE:
387-
return f"Logged: {event["data"]["payload"]["message"]} at {timestamp}"
388-
case EventType.UI_BLUR:
389-
timestamp_ms = timestamp * 1000
390-
return f"User looked away from the tab at {timestamp_ms}"
391-
case EventType.UI_FOCUS:
392-
timestamp_ms = timestamp * 1000
393-
return f"User returned to tab at {timestamp_ms}"
394-
case EventType.RESOURCE_FETCH:
395-
timestamp_ms = timestamp * 1000
396-
payload = event["data"]["payload"]
397-
parsed_url = urlparse(payload["description"])
398-
399-
path = f"{parsed_url.path}?{parsed_url.query}"
400-
401-
# Safely get (request_size, response_size)
402-
sizes_tuple = parse_network_content_lengths(event)
403-
response_size = None
404-
405-
# Check if the tuple is valid and response size exists
406-
if sizes_tuple and sizes_tuple[1] is not None:
407-
response_size = str(sizes_tuple[1])
408-
409-
status_code = payload["data"]["statusCode"]
410-
duration = payload["endTimestamp"] - payload["startTimestamp"]
411-
method = payload["data"]["method"]
412-
413-
# if status code is successful, ignore it
414-
if str(status_code).startswith("2"):
377+
try:
378+
match event_type:
379+
case EventType.CLICK:
380+
message = event["data"]["payload"]["message"]
381+
return f"User clicked on {message} at {timestamp}"
382+
case EventType.DEAD_CLICK:
383+
message = event["data"]["payload"]["message"]
384+
return f"User clicked on {message} but the triggered action was slow to complete at {timestamp}"
385+
case EventType.RAGE_CLICK:
386+
message = event["data"]["payload"]["message"]
387+
return f"User rage clicked on {message} but the triggered action was slow to complete at {timestamp}"
388+
case EventType.NAVIGATION:
389+
to = event["data"]["payload"]["data"]["to"]
390+
return f"User navigated to: {to} at {timestamp}"
391+
case EventType.CONSOLE:
392+
message = event["data"]["payload"]["message"]
393+
return f"Logged: {message} at {timestamp}"
394+
case EventType.UI_BLUR:
395+
timestamp_ms = timestamp * 1000
396+
return f"User looked away from the tab at {timestamp_ms}"
397+
case EventType.UI_FOCUS:
398+
timestamp_ms = timestamp * 1000
399+
return f"User returned to tab at {timestamp_ms}"
400+
case EventType.RESOURCE_FETCH:
401+
timestamp_ms = timestamp * 1000
402+
payload = event["data"]["payload"]
403+
method = payload["data"]["method"]
404+
status_code = payload["data"]["statusCode"]
405+
description = payload["description"]
406+
duration = payload["endTimestamp"] - payload["startTimestamp"]
407+
408+
# Parse URL path
409+
parsed_url = urlparse(description)
410+
path = f"{parsed_url.path}?{parsed_url.query}"
411+
412+
# Check if the tuple is valid and response size exists
413+
sizes_tuple = parse_network_content_lengths(event)
414+
response_size = None
415+
if sizes_tuple and sizes_tuple[1] is not None:
416+
response_size = str(sizes_tuple[1])
417+
418+
# Skip successful requests
419+
if status_code and str(status_code).startswith("2"):
420+
return None
421+
422+
if response_size is None:
423+
return f'Application initiated request: "{method} {path} HTTP/2.0" with status code {status_code}; took {duration} milliseconds at {timestamp_ms}'
424+
else:
425+
return f'Application initiated request: "{method} {path} HTTP/2.0" with status code {status_code} and response size {response_size}; took {duration} milliseconds at {timestamp_ms}'
426+
case EventType.LCP:
427+
timestamp_ms = timestamp * 1000
428+
duration = event["data"]["payload"]["data"]["size"]
429+
rating = event["data"]["payload"]["data"]["rating"]
430+
return f"Application largest contentful paint: {duration} ms and has a {rating} rating at {timestamp_ms}"
431+
case EventType.FCP:
432+
timestamp_ms = timestamp * 1000
433+
duration = event["data"]["payload"]["data"]["size"]
434+
rating = event["data"]["payload"]["data"]["rating"]
435+
return f"Application first contentful paint: {duration} ms and has a {rating} rating at {timestamp_ms}"
436+
case EventType.HYDRATION_ERROR:
437+
return f"There was a hydration error on the page at {timestamp}"
438+
case EventType.RESOURCE_XHR:
415439
return None
416-
417-
if response_size is None:
418-
return f'Application initiated request: "{method} {path} HTTP/2.0" with status code {status_code}; took {duration} milliseconds at {timestamp_ms}'
419-
else:
420-
return f'Application initiated request: "{method} {path} HTTP/2.0" with status code {status_code} and response size {response_size}; took {duration} milliseconds at {timestamp_ms}'
421-
case EventType.RESOURCE_XHR:
422-
return None
423-
case EventType.LCP:
424-
timestamp_ms = timestamp * 1000
425-
duration = event["data"]["payload"]["data"]["size"]
426-
rating = event["data"]["payload"]["data"]["rating"]
427-
return f"Application largest contentful paint: {duration} ms and has a {rating} rating at {timestamp_ms}"
428-
case EventType.FCP:
429-
timestamp_ms = timestamp * 1000
430-
duration = event["data"]["payload"]["data"]["size"]
431-
rating = event["data"]["payload"]["data"]["rating"]
432-
return f"Application first contentful paint: {duration} ms and has a {rating} rating at {timestamp_ms}"
433-
case EventType.HYDRATION_ERROR:
434-
return f"There was a hydration error on the page at {timestamp}"
435-
case EventType.MUTATIONS:
436-
return None
437-
case EventType.UNKNOWN:
438-
return None
439-
case EventType.CANVAS:
440-
return None
441-
case EventType.OPTIONS:
442-
return None
443-
case EventType.FEEDBACK:
444-
return None # the log message is processed before this method is called
440+
case EventType.MUTATIONS:
441+
return None
442+
case EventType.UNKNOWN:
443+
return None
444+
case EventType.CANVAS:
445+
return None
446+
case EventType.OPTIONS:
447+
return None
448+
case EventType.FEEDBACK:
449+
return None # the log message is processed before this method is called
450+
except (KeyError, ValueError):
451+
logger.exception(
452+
"Error parsing event in replay AI summary",
453+
extra={
454+
"event": json.dumps(event),
455+
},
456+
)
457+
return None
445458

446459

447460
def make_seer_request(request_data: str) -> bytes:

0 commit comments

Comments
 (0)