Skip to content

Commit f579122

Browse files
authored
feat(trace): This adds culprit, short-id and type to the trace response (#95167)
- We need these for ANR issues to help deprecate the events-trace endpoint faster
1 parent e325e7a commit f579122

File tree

1 file changed

+48
-8
lines changed

1 file changed

+48
-8
lines changed

src/sentry/api/endpoints/organization_trace.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from sentry.api.paginator import GenericOffsetPaginator
1717
from sentry.api.utils import handle_query_errors, update_snuba_params_with_timestamp
1818
from sentry.issues.issue_occurrence import IssueOccurrence
19+
from sentry.models.group import Group
1920
from sentry.models.organization import Organization
2021
from sentry.models.project import Project
2122
from sentry.organizations.services.organization import RpcOrganization
@@ -25,6 +26,7 @@
2526
from sentry.snuba.dataset import Dataset
2627
from sentry.snuba.referrer import Referrer
2728
from sentry.snuba.spans_rpc import run_trace_query
29+
from sentry.utils.numbers import base32_encode
2830

2931
# 1 worker each for spans, errors, performance issues
3032
_query_thread_pool = ThreadPoolExecutor(max_workers=3)
@@ -44,6 +46,9 @@ class SerializedIssue(SerializedEvent):
4446
level: str
4547
start_timestamp: float
4648
end_timestamp: NotRequired[datetime]
49+
culprit: str | None
50+
short_id: str | None
51+
issue_type: str
4752

4853

4954
class SerializedSpan(SerializedEvent):
@@ -94,10 +99,26 @@ def get_projects(
9499
include_all_accessible=True,
95100
)
96101

97-
def serialize_rpc_issue(self, event: dict[str, Any]) -> SerializedIssue:
102+
def serialize_rpc_issue(
103+
self, event: dict[str, Any], group_cache: dict[int, Group]
104+
) -> SerializedIssue:
105+
def _qualify_short_id(project: str, short_id: int | None) -> str | None:
106+
"""Logic for qualified_short_id is copied from property on the Group model
107+
to prevent an N+1 query from accessing project.slug everytime"""
108+
if short_id is not None:
109+
return f"{project.upper()}-{base32_encode(short_id)}"
110+
else:
111+
return None
112+
98113
if event.get("event_type") == "occurrence":
99114
occurrence = event["issue_data"]["occurrence"]
100115
span = event["span"]
116+
issue_id = event["issue_data"]["issue_id"]
117+
if issue_id in group_cache:
118+
issue = group_cache[issue_id]
119+
else:
120+
issue = Group.objects.get(id=issue_id, project__id=occurrence.project_id)
121+
group_cache[issue_id] = issue
101122
return SerializedIssue(
102123
event_id=occurrence.id,
103124
project_id=occurrence.project_id,
@@ -107,15 +128,24 @@ def serialize_rpc_issue(self, event: dict[str, Any]) -> SerializedIssue:
107128
transaction=span["transaction"],
108129
description=occurrence.issue_title,
109130
level=occurrence.level,
110-
issue_id=event["issue_data"]["issue_id"],
131+
issue_id=issue_id,
111132
event_type="occurrence",
133+
culprit=issue.culprit,
134+
short_id=_qualify_short_id(span["project.slug"], issue.short_id),
135+
issue_type=issue.type,
112136
)
113137
elif event.get("event_type") == "error":
114138
timestamp = (
115139
datetime.fromisoformat(event["timestamp_ms"]).timestamp()
116140
if "timestamp_ms" in event and event["timestamp_ms"] is not None
117141
else datetime.fromisoformat(event["timestamp"]).timestamp()
118142
)
143+
issue_id = event["issue.id"]
144+
if issue_id in group_cache:
145+
issue = group_cache[issue_id]
146+
else:
147+
issue = Group.objects.get(id=issue_id, project__id=event["project.id"])
148+
group_cache[issue_id] = issue
119149

120150
return SerializedIssue(
121151
event_id=event["id"],
@@ -127,16 +157,25 @@ def serialize_rpc_issue(self, event: dict[str, Any]) -> SerializedIssue:
127157
level=event["tags[level]"],
128158
issue_id=event["issue.id"],
129159
event_type="error",
160+
culprit=issue.culprit,
161+
short_id=_qualify_short_id(event["project.name"], issue.short_id),
162+
issue_type=issue.type,
130163
)
131164
else:
132165
raise Exception(f"Unknown event encountered in trace: {event.get('event_type')}")
133166

134-
def serialize_rpc_event(self, event: dict[str, Any]) -> SerializedEvent | SerializedIssue:
167+
def serialize_rpc_event(
168+
self, event: dict[str, Any], group_cache: dict[int, Group]
169+
) -> SerializedEvent | SerializedIssue:
135170
if event.get("event_type") == "span":
136171
return SerializedSpan(
137-
children=[self.serialize_rpc_event(child) for child in event["children"]],
138-
errors=[self.serialize_rpc_issue(error) for error in event["errors"]],
139-
occurrences=[self.serialize_rpc_issue(error) for error in event["occurrences"]],
172+
children=[
173+
self.serialize_rpc_event(child, group_cache) for child in event["children"]
174+
],
175+
errors=[self.serialize_rpc_issue(error, group_cache) for error in event["errors"]],
176+
occurrences=[
177+
self.serialize_rpc_issue(error, group_cache) for error in event["occurrences"]
178+
],
140179
event_id=event["id"],
141180
transaction_id=event["transaction.event_id"],
142181
project_id=event["project.id"],
@@ -163,7 +202,7 @@ def serialize_rpc_event(self, event: dict[str, Any]) -> SerializedEvent | Serial
163202
event_type="span",
164203
)
165204
else:
166-
return self.serialize_rpc_issue(event)
205+
return self.serialize_rpc_issue(event, group_cache)
167206

168207
def errors_query(self, snuba_params: SnubaParams, trace_id: str) -> DiscoverQueryBuilder:
169208
"""Run an error query, getting all the errors for a given trace id"""
@@ -321,7 +360,8 @@ def query_trace_data(self, snuba_params: SnubaParams, trace_id: str) -> list[Ser
321360
)
322361
for errors in id_to_error.values():
323362
result.extend(errors)
324-
return [self.serialize_rpc_event(root) for root in result]
363+
group_cache: dict[int, Group] = {}
364+
return [self.serialize_rpc_event(root, group_cache) for root in result]
325365

326366
def has_feature(self, organization: Organization, request: Request) -> bool:
327367
return bool(

0 commit comments

Comments
 (0)