11import logging
22import types
33import uuid
4- from copy import deepcopy
5- from typing import Any , cast
4+ from collections . abc import Sequence
5+ from typing import cast
66
77from django .core .exceptions import ValidationError
88from sentry_kafka_schemas .schema_types .buffered_segments_v1 import SegmentSpan
2626from sentry .receivers .features import record_generic_event_processed
2727from sentry .receivers .onboarding import record_release_received
2828from sentry .signals import first_insight_span_received , first_transaction_received
29- from sentry .spans .consumers .process_segments .enrichment import Enricher , Span , compute_breakdowns
29+ from sentry .spans .consumers .process_segments .enrichment import TreeEnricher , compute_breakdowns
30+ from sentry .spans .consumers .process_segments .shim import build_shim_event_data , make_compatible
31+ from sentry .spans .consumers .process_segments .types import CompatibleSpan
3032from sentry .spans .grouping .api import load_span_grouping_config
3133from sentry .utils import metrics
3234from sentry .utils .dates import to_datetime
3941
4042
4143@metrics .wraps ("spans.consumers.process_segments.process_segment" )
42- def process_segment (unprocessed_spans : list [SegmentSpan ], skip_produce : bool = False ) -> list [Span ]:
44+ def process_segment (
45+ unprocessed_spans : list [SegmentSpan ], skip_produce : bool = False
46+ ) -> list [CompatibleSpan ]:
4347 segment_span , spans = _enrich_spans (unprocessed_spans )
4448 if segment_span is None :
4549 return spans
@@ -69,7 +73,9 @@ def process_segment(unprocessed_spans: list[SegmentSpan], skip_produce: bool = F
6973
7074
7175@metrics .wraps ("spans.consumers.process_segments.enrich_spans" )
72- def _enrich_spans (unprocessed_spans : list [SegmentSpan ]) -> tuple [Span | None , list [Span ]]:
76+ def _enrich_spans (
77+ unprocessed_spans : list [SegmentSpan ],
78+ ) -> tuple [CompatibleSpan | None , list [CompatibleSpan ]]:
7379 """
7480 Enriches all spans with data derived from the span tree and the segment.
7581
@@ -80,7 +86,11 @@ def _enrich_spans(unprocessed_spans: list[SegmentSpan]) -> tuple[Span | None, li
8086 Returns the segment span, if any, and the list of enriched spans.
8187 """
8288
83- segment , spans = Enricher .enrich_spans (unprocessed_spans )
89+ segment_idx , tree_spans = TreeEnricher .enrich_spans (unprocessed_spans )
90+
91+ # Set attributes that are needed by logic shared with the event processing pipeline.
92+ spans = [make_compatible (span ) for span in tree_spans ]
93+ segment = spans [segment_idx ] if segment_idx is not None else None
8494
8595 # Calculate grouping hashes for performance issue detection
8696 config = load_span_grouping_config ()
@@ -91,14 +101,16 @@ def _enrich_spans(unprocessed_spans: list[SegmentSpan]) -> tuple[Span | None, li
91101
92102
93103@metrics .wraps ("spans.consumers.process_segments.compute_breakdowns" )
94- def _compute_breakdowns (segment : Span , spans : list [Span ], project : Project ) -> None :
104+ def _compute_breakdowns (
105+ segment : CompatibleSpan , spans : Sequence [CompatibleSpan ], project : Project
106+ ) -> None :
95107 config = project .get_option ("sentry:breakdowns" )
96108 breakdowns = compute_breakdowns (spans , config )
97109 segment .setdefault ("data" , {}).update (breakdowns )
98110
99111
100112@metrics .wraps ("spans.consumers.process_segments.create_models" )
101- def _create_models (segment : Span , project : Project ) -> None :
113+ def _create_models (segment : CompatibleSpan , project : Project ) -> None :
102114 """
103115 Creates the Environment and Release models, along with the necessary
104116 relationships between them and the Project model.
@@ -144,11 +156,13 @@ def _create_models(segment: Span, project: Project) -> None:
144156
145157
146158@metrics .wraps ("spans.consumers.process_segments.detect_performance_problems" )
147- def _detect_performance_problems (segment_span : Span , spans : list [Span ], project : Project ) -> None :
159+ def _detect_performance_problems (
160+ segment_span : CompatibleSpan , spans : list [CompatibleSpan ], project : Project
161+ ) -> None :
148162 if not options .get ("spans.process-segments.detect-performance-problems.enable" ):
149163 return
150164
151- event_data = _build_shim_event_data (segment_span , spans )
165+ event_data = build_shim_event_data (segment_span , spans )
152166 performance_problems = detect_performance_problems (event_data , project , standalone = True )
153167
154168 if not segment_span .get ("_performance_issues_spans" ):
@@ -191,55 +205,10 @@ def _detect_performance_problems(segment_span: Span, spans: list[Span], project:
191205 )
192206
193207
194- def _build_shim_event_data (segment_span : Span , spans : list [Span ]) -> dict [str , Any ]:
195- data = segment_span .get ("data" , {})
196-
197- event : dict [str , Any ] = {
198- "type" : "transaction" ,
199- "level" : "info" ,
200- "contexts" : {
201- "trace" : {
202- "trace_id" : segment_span ["trace_id" ],
203- "type" : "trace" ,
204- "op" : data .get ("sentry.transaction.op" ),
205- "span_id" : segment_span ["span_id" ],
206- "hash" : segment_span ["hash" ],
207- },
208- },
209- "event_id" : uuid .uuid4 ().hex ,
210- "project_id" : segment_span ["project_id" ],
211- "transaction" : data .get ("sentry.transaction" ),
212- "release" : data .get ("sentry.release" ),
213- "dist" : data .get ("sentry.dist" ),
214- "environment" : data .get ("sentry.environment" ),
215- "platform" : data .get ("sentry.platform" ),
216- "tags" : [["environment" , data .get ("sentry.environment" )]],
217- "received" : segment_span ["received" ],
218- "timestamp" : segment_span ["end_timestamp_precise" ],
219- "start_timestamp" : segment_span ["start_timestamp_precise" ],
220- "datetime" : to_datetime (segment_span ["end_timestamp_precise" ]).strftime (
221- "%Y-%m-%dT%H:%M:%SZ"
222- ),
223- "spans" : [],
224- }
225-
226- if (profile_id := segment_span .get ("profile_id" )) is not None :
227- event ["contexts" ]["profile" ] = {"profile_id" : profile_id , "type" : "profile" }
228-
229- # Add legacy span attributes required only by issue detectors. As opposed to
230- # real event payloads, this also adds the segment span so detectors can run
231- # topological sorting on the span tree.
232- for span in spans :
233- event_span = cast (dict [str , Any ], deepcopy (span ))
234- event_span ["start_timestamp" ] = span ["start_timestamp_precise" ]
235- event_span ["timestamp" ] = span ["end_timestamp_precise" ]
236- event ["spans" ].append (event_span )
237-
238- return event
239-
240-
241208@metrics .wraps ("spans.consumers.process_segments.record_signals" )
242- def _record_signals (segment_span : Span , spans : list [Span ], project : Project ) -> None :
209+ def _record_signals (
210+ segment_span : CompatibleSpan , spans : list [CompatibleSpan ], project : Project
211+ ) -> None :
243212 data = segment_span .get ("data" , {})
244213
245214 record_generic_event_processed (
@@ -271,7 +240,7 @@ def _record_signals(segment_span: Span, spans: list[Span], project: Project) ->
271240
272241
273242@metrics .wraps ("spans.consumers.process_segments.record_outcomes" )
274- def _track_outcomes (segment_span : Span , spans : list [Span ]) -> None :
243+ def _track_outcomes (segment_span : CompatibleSpan , spans : list [CompatibleSpan ]) -> None :
275244 if options .get ("spans.process-segments.outcome-aggregator.enable" ):
276245 outcome_aggregator .track_outcome_aggregated (
277246 org_id = segment_span ["organization_id" ],
0 commit comments