-
-
Notifications
You must be signed in to change notification settings - Fork 210
feat(tracing): nest LiveView spans under root traces #977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
solnic
wants to merge
5
commits into
master
Choose a base branch
from
857-phoenix-live-view-support
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
b23d545 to
8f3ea9f
Compare
26ee0fc to
4e68b1e
Compare
4e68b1e to
8e02c59
Compare
27aa889 to
bcc324c
Compare
bcc324c to
20b2ab4
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This adds support for connecting LiveView spans with their HTTP-based mount span so that transactions that took place on a given LiveView page are nested under the same trace.
Example in UI
LiveView and Phoenix HTTP Span Processing Overview
TL;DR: it works
This document describes how OpenTelemetry spans from Phoenix LiveView and HTTP requests are processed and converted into Sentry transactions by the
Sentry.OpenTelemetry.SpanProcessor.Overview
The span processor receives OTel spans from various instrumentation libraries:
Span Flow Diagram
sequenceDiagram participant Browser participant Bandit as Bandit (HTTP) participant Phoenix as Phoenix LiveView participant Processor as SpanProcessor participant Storage as SpanStorage participant Sentry Note over Browser,Sentry: Phase 1: Static Render (HTTP Request) Browser->>Bandit: GET /tracing-test Bandit->>Processor: on_start(HTTP span) Processor->>Storage: store_span(HTTP) Phoenix->>Processor: on_start(mount span, parent=HTTP) Processor->>Storage: store_span(mount) Phoenix->>Processor: on_end(mount span) Processor->>Storage: update_span(mount) Note over Processor: mount has local parent, not a transaction root Phoenix->>Processor: on_start(handle_params span, parent=HTTP) Processor->>Storage: store_span(handle_params) Phoenix->>Processor: on_end(handle_params span) Processor->>Storage: update_span(handle_params) Note over Processor: handle_params has local parent, not a transaction root Bandit->>Processor: on_end(HTTP span) Processor->>Storage: get_child_spans(HTTP) Storage-->>Processor: [mount, handle_params] Processor->>Sentry: send_transaction(HTTP + children) Processor->>Storage: remove_root_span(HTTP) Bandit-->>Browser: HTML Response Note over Browser,Sentry: Phase 2: WebSocket Upgrade Browser->>Bandit: GET /live/websocket (upgrade) Bandit->>Processor: on_start(WS upgrade span) Processor->>Storage: store_span(WS upgrade) Bandit->>Processor: on_end(WS upgrade span) Processor->>Sentry: send_transaction(WS upgrade, no children) Note over Processor: WebSocket upgrade has no parent, becomes transaction Bandit-->>Browser: 101 Switching Protocols Note over Browser,Sentry: Phase 3: LiveView WebSocket Events Browser->>Phoenix: WebSocket: join Phoenix->>Processor: on_start(mount span, parent=old HTTP span_id) Processor->>Storage: store_span(mount) Phoenix->>Processor: on_end(mount span) Note over Processor: Parent not in storage (already cleaned up) Note over Processor: mount is server span, becomes transaction root Processor->>Sentry: send_transaction(mount) Phoenix->>Processor: on_start(handle_params span, parent=old HTTP span_id) Processor->>Storage: store_span(handle_params) Phoenix->>Processor: on_end(handle_params span) Note over Processor: Parent not in storage Processor->>Sentry: send_transaction(handle_params) Browser->>Phoenix: WebSocket: event (click) Phoenix->>Processor: on_start(handle_event span) Processor->>Storage: store_span(handle_event) Phoenix->>Processor: on_end(handle_event span) Processor->>Sentry: send_transaction(handle_event)Transaction Root Detection Logic
The span processor uses the following logic to determine if a span should become a transaction root:
flowchart TD A[Span Ends] --> B{Has parent_span_id?} B -->|No| C[Transaction Root] B -->|Yes| D{Parent in SpanStorage?} D -->|Yes| E[Child Span - attach to parent] D -->|No| F{Is server span?} F -->|No| G[Orphaned - becomes transaction] F -->|Yes| H{HTTP or LiveView span?} H -->|Yes| C H -->|No| G C --> I[Build transaction with children] I --> J[Send to Sentry] J --> K[Clean up from SpanStorage]Span Types and Their Behavior
1. HTTP Server Spans (opentelemetry_bandit)
Characteristics:
kind: :serverhttp.request.methodattributeparent_span_id: nilfor incoming requestsopentelemetry_banditProcessing:
Example:
{ "name": "GET /tracing-test", "kind": "server", "span_id": "e861c0f9bda78951", "parent_span_id": null, "attributes": { "http.request.method": "GET", "http.route": "/tracing-test", "http.response.status_code": 200 } }2. LiveView Mount Spans (opentelemetry_phoenix)
Characteristics:
kind: :server.mountopentelemetry_phoenixparent_span_idpoints to HTTP spanTwo Contexts:
Static Render Mount (during HTTP request):
Connected Mount (after WebSocket connects):
Example:
{ "name": "PhoenixAppWeb.TracingTestLive.mount", "kind": "server", "span_id": "2fb4aee27d9a781a", "parent_span_id": "e861c0f9bda78951", "origin": "opentelemetry_phoenix" }3. LiveView handle_params Spans
Characteristics:
kind: :server.handle_params4. LiveView handle_event Spans
Characteristics:
kind: :server.handle_event#or ends with.handle_eventExample:
{ "name": "PhoenixAppWeb.TracingTestLive.handle_event#increment", "kind": "server", "span_id": "a31eb359281216c7", "parent_span_id": "8a32e222e14a7e06" }5. Database Spans (opentelemetry_ecto)
Characteristics:
kind: :clientdb.systemanddb.statementattributesopentelemetry_ectoProcessing:
Example:
{ "name": "phoenix_app.repo.query:users", "kind": "client", "span_id": "872aa68385ff20bc", "parent_span_id": "803dea46f1866d76", "attributes": { "db.system": "sqlite", "db.statement": "SELECT ... FROM users" } }Resulting Transaction Structure
Static Render Transaction
graph TD A["GET /tracing-test<br/>(http.server)"] --> B["LiveView.mount<br/>(server)"] A --> C["LiveView.handle_params<br/>(server)"]WebSocket Event Transaction
graph TD A["handle_event#fetch_data<br/>(server)"] --> B["fetch_data<br/>(internal)"] B --> C["repo.query:users<br/>(db)"] B --> D["process_data<br/>(internal)"] D --> E["repo.query:users<br/>(db)"]Key Implementation Details
SpanStorage
The
SpanStoragemodule maintains spans in ETS tables:on_starton_endget_child_spans/1to collect children by parent_span_idremove_root_span/1after transaction is sentTransaction Detection
LiveView Span Detection
LiveView spans are detected by checking the
originfield (derived frominstrumentation_scope.name):The
opentelemetry_phoenixlibrary only creates spans for:[:phoenix, :live_view, :mount, ...][:phoenix, :live_view, :handle_params, ...][:phoenix, :live_view, :handle_event, ...][:phoenix, :live_component, :handle_event, ...]Summary
Trace Continuity via LiveView Propagator
While LiveView WebSocket spans become separate transactions, they share the same trace_id as the original HTTP request. This is enabled by:
Sentry.Plug.LiveViewContext- stores trace context in the session during HTTP requestSentry.OpenTelemetry.LiveViewPropagator- restores context in LiveView processes beforeopentelemetry_phoenixcreates spansExample trace correlation:
Why separate transactions but same trace?
The spans become separate transactions because the parent HTTP span has already completed and been cleaned up from SpanStorage. However, the propagator ensures they share the same
trace_id, so Sentry can correlate them in the trace view.This is the correct behavior because:
Refs #857