Skip to content

Commit 44195e9

Browse files
committed
wip - integrate LiveView detection into SpanProcessor
1 parent 4bad58a commit 44195e9

File tree

3 files changed

+58
-5
lines changed

3 files changed

+58
-5
lines changed

lib/sentry/opentelemetry/span_processor.ex

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
2222

2323
@impl :otel_span_processor
2424
def on_start(_ctx, otel_span, _config) do
25+
# Check if this is a LiveView span during static render
26+
# If so, mark it so we can filter it out in on_end
27+
if liveview_propagator_loaded?() and
28+
Sentry.OpenTelemetry.LiveViewPropagator.static_render?() do
29+
# Set an attribute on the span to mark it as from static render
30+
:otel_span.set_attribute(otel_span, :"sentry.liveview.static_render", true)
31+
end
32+
2533
# Track pending children: when a span starts with a parent, register it
2634
# as a pending child. This allows us to wait for all children when
2735
# the parent ends, solving the race condition where parent.on_end
@@ -44,7 +52,21 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
4452
@impl :otel_span_processor
4553
def on_end(otel_span, _config) do
4654
span_record = SpanRecord.new(otel_span)
47-
process_span(span_record)
55+
56+
# Skip LiveView spans from static render - they're redundant since:
57+
# 1. The HTTP span already covers the static render phase
58+
# 2. The same LiveView callbacks run again over WebSocket (the meaningful ones)
59+
if static_render_liveview_span?(span_record) do
60+
# Don't store or process this span
61+
# But still remove from pending children if it was tracked
62+
if span_record.parent_span_id != nil do
63+
SpanStorage.remove_pending_child(span_record.parent_span_id, span_record.span_id)
64+
end
65+
66+
true
67+
else
68+
process_span(span_record)
69+
end
4870
end
4971

5072
defp process_span(span_record) do
@@ -70,6 +92,10 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
7092
#
7193
# A span should NOT be a transaction root if:
7294
# - It has a LOCAL parent (parent span exists in our SpanStorage)
95+
#
96+
# This prevents LiveView spans from becoming separate transactions when
97+
# they occur within an HTTP request - they should be child spans of the
98+
# HTTP server span instead.
7399
is_transaction_root =
74100
cond do
75101
# No parent = definitely a root
@@ -142,6 +168,9 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
142168
end
143169

144170
# Clean up: remove the transaction root span and all its children
171+
# Note: For distributed tracing, the transaction root span may have been stored
172+
# as a child span (with a remote parent_span_id). In that case, we need to also
173+
# remove it from the child spans, not just look for it as a root span.
145174
:ok = SpanStorage.remove_root_span(span_record.span_id)
146175

147176
# Also clean up any remaining pending children records (shouldn't be any, but be safe)
@@ -156,6 +185,17 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
156185
result
157186
end
158187

188+
# Check if this is a LiveView span from static render that should be filtered out
189+
defp static_render_liveview_span?(span_record) do
190+
# Check for the attribute we set in on_start
191+
Map.get(span_record.attributes, :"sentry.liveview.static_render", false) == true
192+
end
193+
194+
# Check if the LiveViewPropagator module is loaded (only compiled when Phoenix.LiveView is available)
195+
defp liveview_propagator_loaded? do
196+
Code.ensure_loaded?(Sentry.OpenTelemetry.LiveViewPropagator)
197+
end
198+
159199
@impl :otel_span_processor
160200
def force_flush(_config) do
161201
:ok
@@ -170,11 +210,18 @@ if Sentry.OpenTelemetry.VersionChecker.tracing_compatible?() do
170210
end
171211

172212
# Helper function to detect if a span is a server span that should be
173-
# treated as a transaction root for distributed tracing.
174-
# This includes HTTP server request spans (have http.request.method attribute)
175-
defp is_server_span?(%{kind: :server, attributes: attributes}) do
213+
# treated as a transaction root. This includes:
214+
# - HTTP server request spans (have http.request.method attribute)
215+
# - LiveView spans from OpentelemetryPhoenix (have kind: :server and origin: "opentelemetry_phoenix")
216+
# But NOT internal spans created with Tracer.with_span (which have kind: :internal)
217+
defp is_server_span?(%{kind: :server, attributes: attributes} = span_record) do
176218
# Check if it's an HTTP server request span (has http.request.method)
177-
Map.has_key?(attributes, to_string(HTTPAttributes.http_request_method()))
219+
has_http_method = Map.has_key?(attributes, to_string(HTTPAttributes.http_request_method()))
220+
221+
# Check if it's a LiveView span from OpentelemetryPhoenix
222+
is_liveview_span = span_record.origin == "opentelemetry_phoenix"
223+
224+
has_http_method or is_liveview_span
178225
end
179226

180227
defp is_server_span?(_), do: false

test_integrations/phoenix_app/lib/phoenix_app/application.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ defmodule PhoenixApp.Application do
1414
})
1515

1616
OpentelemetryBandit.setup()
17+
18+
# Set up Sentry's LiveView context propagation BEFORE OpentelemetryPhoenix
19+
# This enables distributed tracing context to flow from HTTP requests to LiveView WebSocket processes
20+
Sentry.OpenTelemetry.LiveViewPropagator.setup()
21+
1722
OpentelemetryPhoenix.setup(adapter: :bandit)
1823
OpentelemetryOban.setup()
1924
OpentelemetryEcto.setup([:phoenix_app, :repo], db_statement: :enabled)

test_integrations/phoenix_app/lib/phoenix_app_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule PhoenixAppWeb.Router do
88
plug :put_root_layout, html: {PhoenixAppWeb.Layouts, :root}
99
plug :protect_from_forgery
1010
plug :put_secure_browser_headers
11+
plug Sentry.Plug.LiveViewContext
1112
end
1213

1314
pipeline :api do

0 commit comments

Comments
 (0)