16
16
from sentry .api .paginator import GenericOffsetPaginator
17
17
from sentry .api .utils import handle_query_errors , update_snuba_params_with_timestamp
18
18
from sentry .issues .issue_occurrence import IssueOccurrence
19
+ from sentry .models .group import Group
19
20
from sentry .models .organization import Organization
20
21
from sentry .models .project import Project
21
22
from sentry .organizations .services .organization import RpcOrganization
25
26
from sentry .snuba .dataset import Dataset
26
27
from sentry .snuba .referrer import Referrer
27
28
from sentry .snuba .spans_rpc import run_trace_query
29
+ from sentry .utils .numbers import base32_encode
28
30
29
31
# 1 worker each for spans, errors, performance issues
30
32
_query_thread_pool = ThreadPoolExecutor (max_workers = 3 )
@@ -44,6 +46,9 @@ class SerializedIssue(SerializedEvent):
44
46
level : str
45
47
start_timestamp : float
46
48
end_timestamp : NotRequired [datetime ]
49
+ culprit : str | None
50
+ short_id : str | None
51
+ issue_type : str
47
52
48
53
49
54
class SerializedSpan (SerializedEvent ):
@@ -94,10 +99,26 @@ def get_projects(
94
99
include_all_accessible = True ,
95
100
)
96
101
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
+
98
113
if event .get ("event_type" ) == "occurrence" :
99
114
occurrence = event ["issue_data" ]["occurrence" ]
100
115
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
101
122
return SerializedIssue (
102
123
event_id = occurrence .id ,
103
124
project_id = occurrence .project_id ,
@@ -107,15 +128,24 @@ def serialize_rpc_issue(self, event: dict[str, Any]) -> SerializedIssue:
107
128
transaction = span ["transaction" ],
108
129
description = occurrence .issue_title ,
109
130
level = occurrence .level ,
110
- issue_id = event [ "issue_data" ][ " issue_id" ] ,
131
+ issue_id = issue_id ,
111
132
event_type = "occurrence" ,
133
+ culprit = issue .culprit ,
134
+ short_id = _qualify_short_id (span ["project.slug" ], issue .short_id ),
135
+ issue_type = issue .type ,
112
136
)
113
137
elif event .get ("event_type" ) == "error" :
114
138
timestamp = (
115
139
datetime .fromisoformat (event ["timestamp_ms" ]).timestamp ()
116
140
if "timestamp_ms" in event and event ["timestamp_ms" ] is not None
117
141
else datetime .fromisoformat (event ["timestamp" ]).timestamp ()
118
142
)
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
119
149
120
150
return SerializedIssue (
121
151
event_id = event ["id" ],
@@ -127,16 +157,25 @@ def serialize_rpc_issue(self, event: dict[str, Any]) -> SerializedIssue:
127
157
level = event ["tags[level]" ],
128
158
issue_id = event ["issue.id" ],
129
159
event_type = "error" ,
160
+ culprit = issue .culprit ,
161
+ short_id = _qualify_short_id (event ["project.name" ], issue .short_id ),
162
+ issue_type = issue .type ,
130
163
)
131
164
else :
132
165
raise Exception (f"Unknown event encountered in trace: { event .get ('event_type' )} " )
133
166
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 :
135
170
if event .get ("event_type" ) == "span" :
136
171
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
+ ],
140
179
event_id = event ["id" ],
141
180
transaction_id = event ["transaction.event_id" ],
142
181
project_id = event ["project.id" ],
@@ -163,7 +202,7 @@ def serialize_rpc_event(self, event: dict[str, Any]) -> SerializedEvent | Serial
163
202
event_type = "span" ,
164
203
)
165
204
else :
166
- return self .serialize_rpc_issue (event )
205
+ return self .serialize_rpc_issue (event , group_cache )
167
206
168
207
def errors_query (self , snuba_params : SnubaParams , trace_id : str ) -> DiscoverQueryBuilder :
169
208
"""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
321
360
)
322
361
for errors in id_to_error .values ():
323
362
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 ]
325
365
326
366
def has_feature (self , organization : Organization , request : Request ) -> bool :
327
367
return bool (
0 commit comments