@@ -31,10 +31,16 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
3131 # HTTP server request spans should be treated as transaction roots even when they have
3232 # an external parent span ID (from distributed tracing)
3333 is_transaction_root =
34- span_record . parent_span_id == nil or is_http_server_request_span? ( span_record )
34+ span_record . parent_span_id == nil or
35+ is_http_server_request_span? ( span_record ) or
36+ is_live_view_server_span? ( span_record )
3537
3638 if is_transaction_root do
37- child_span_records = SpanStorage . get_child_spans ( span_record . span_id )
39+ child_span_records =
40+ span_record . span_id
41+ |> SpanStorage . get_child_spans ( )
42+ |> maybe_add_remote_children ( span_record )
43+
3844 transaction = build_transaction ( span_record , child_span_records )
3945
4046 result =
@@ -83,6 +89,68 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
8389 Map . has_key? ( attributes , to_string ( HTTPAttributes . http_request_method ( ) ) )
8490 end
8591
92+ defp is_live_view_server_span? ( % { kind: :server , origin: origin , name: name } )
93+ when origin in [ "opentelemetry_phoenix" , :opentelemetry_phoenix ] do
94+ String . ends_with? ( name , ".mount" ) or
95+ String . contains? ( name , ".handle_params" ) or
96+ String . contains? ( name , ".handle_event" )
97+ end
98+
99+ defp is_live_view_server_span? ( _span_record ) , do: false
100+
101+ defp maybe_add_remote_children ( child_span_records , % { parent_span_id: nil } ) do
102+ child_span_records
103+ end
104+
105+ defp maybe_add_remote_children ( child_span_records , span_record ) do
106+ if is_live_view_server_span? ( span_record ) do
107+ existing_ids = MapSet . new ( child_span_records , & & 1 . span_id )
108+
109+ adopted_children =
110+ span_record . parent_span_id
111+ |> SpanStorage . get_child_spans ( )
112+ |> Enum . filter ( & eligible_for_adoption? ( & 1 , span_record , existing_ids ) )
113+ |> Enum . map ( & % { & 1 | parent_span_id: span_record . span_id } )
114+
115+ Enum . each ( adopted_children , fn child ->
116+ :ok = SpanStorage . remove_child_span ( span_record . parent_span_id , child . span_id )
117+ end )
118+
119+ child_span_records ++ adopted_children
120+ else
121+ child_span_records
122+ end
123+ end
124+
125+ defp eligible_for_adoption? ( child , span_record , existing_ids ) do
126+ not MapSet . member? ( existing_ids , child . span_id ) and
127+ child . parent_span_id == span_record . parent_span_id and
128+ child . trace_id == span_record . trace_id and
129+ child . kind != :server and
130+ occurs_within_span? ( child , span_record )
131+ end
132+
133+ defp occurs_within_span? ( child , parent ) do
134+ with { :ok , parent_start } <- parse_datetime ( parent . start_time ) ,
135+ { :ok , parent_end } <- parse_datetime ( parent . end_time ) ,
136+ { :ok , child_start } <- parse_datetime ( child . start_time ) ,
137+ { :ok , child_end } <- parse_datetime ( child . end_time ) do
138+ DateTime . compare ( child_start , parent_start ) != :lt and
139+ DateTime . compare ( child_end , parent_end ) != :gt
140+ else
141+ _ -> true
142+ end
143+ end
144+
145+ defp parse_datetime ( nil ) , do: :error
146+
147+ defp parse_datetime ( timestamp ) do
148+ case DateTime . from_iso8601 ( timestamp ) do
149+ { :ok , datetime , _offset } -> { :ok , datetime }
150+ { :error , _ } -> :error
151+ end
152+ end
153+
86154 defp build_transaction ( root_span_record , child_span_records ) do
87155 root_span = build_span ( root_span_record )
88156 child_spans = Enum . map ( child_span_records , & build_span ( & 1 ) )
0 commit comments