-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LiveView async wrappers for propagation
- Loading branch information
1 parent
a4ee9fe
commit 4cd43c8
Showing
5 changed files
with
289 additions
and
3 deletions.
There are no files selected for viewing
This file contains 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
97 changes: 97 additions & 0 deletions
97
instrumentation/opentelemetry_phoenix/lib/opentelemetry_phoenix/live_view.ex
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
if Code.ensure_loaded(Phoenix.LiveView) do | ||
defmodule OpentelemetryPhoenix.LiveView do | ||
@moduledoc """ | ||
`OpentelemetryPhoenix.LiveView` provides a extensions to the async functions | ||
in the `Phoenix.LiveView` to reduce boilerplate in propagating OpenTelemetry | ||
contexts across process boundaries. | ||
> #### Module Redefinement {: .info} | ||
> | ||
> This module does not redefine the `Phoenix.Liveview` module, instead | ||
> it provides wrappers for async functions, so this functionality will | ||
> not globally modify the default behavior of the `Phoenix.Liveview` module. | ||
## Usage | ||
Require `OpentelemetryPhoenix.LiveView` in your `live_view` and | ||
`live_component` macros: | ||
```elixir | ||
defmodule MyAppWeb do | ||
# ... | ||
def live_view do | ||
quote do | ||
use Phoenix.LiveView, | ||
layout: {MyAppWeb.Layouts, :app} | ||
require OpentelemetryPhoenix.LiveView | ||
unquote(html_helpers()) | ||
end | ||
end | ||
def live_component do | ||
quote do | ||
use Phoenix.LiveComponent | ||
require OpentelemetryPhoenix.LiveView | ||
unquote(html_helpers()) | ||
end | ||
end | ||
end | ||
``` | ||
Update the references to `assign_async` and `start_async` to use this module:application | ||
```elixir | ||
def mount(%{"slug" => slug}, _, socket) do | ||
{:ok, | ||
socket | ||
|> assign(:foo, "bar") | ||
|> OpentelemetryPhoenix.LiveView.assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end) | ||
|> OpentelemetryPhoenix.LiveView.assign_async([:profile, :rank], fn -> {:ok, %{profile: ..., rank: ...}} end)} | ||
end | ||
``` | ||
""" | ||
require Phoenix.LiveView | ||
|
||
defmacro assign_async(socket, key_or_keys, func, opts \\ []) do | ||
quote do | ||
require OpenTelemetry.Tracer | ||
|
||
ctx = OpenTelemetry.Ctx.get_current() | ||
|
||
Phoenix.LiveView.assign_async( | ||
unquote(socket), | ||
unquote(key_or_keys), | ||
fn -> | ||
OpenTelemetry.Ctx.attach(ctx) | ||
|
||
unquote(func).() | ||
end, | ||
unquote(opts) | ||
) | ||
end | ||
end | ||
|
||
defmacro start_async(socket, name, func, opts \\ []) do | ||
quote do | ||
require OpenTelemetry.Tracer | ||
|
||
ctx = OpenTelemetry.Ctx.get_current() | ||
|
||
Phoenix.LiveView.start_async( | ||
unquote(socket), | ||
unquote(name), | ||
fn -> | ||
OpenTelemetry.Ctx.attach(ctx) | ||
|
||
unquote(func).() | ||
end, | ||
unquote(opts) | ||
) | ||
end | ||
end | ||
end | ||
end |
This file contains 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
This file contains 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
131 changes: 131 additions & 0 deletions
131
instrumentation/opentelemetry_phoenix/test/opentelemetry_phoenix/live_view_test.exs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
defmodule OpentelemetryPhoenix.LiveViewTest do | ||
defmodule ErrorHTML do | ||
def render(template, _assigns) do | ||
Phoenix.Controller.status_message_from_template(template) | ||
end | ||
end | ||
|
||
defmodule TestLive do | ||
use Phoenix.LiveView, layout: false | ||
|
||
require OpenTelemetry.Tracer | ||
require OpentelemetryPhoenix.LiveView | ||
|
||
@impl true | ||
def mount(_params, _session, socket) do | ||
{:ok, socket} | ||
end | ||
|
||
@impl true | ||
def handle_params(_params, _url, socket) do | ||
socket = | ||
OpenTelemetry.Tracer.with_span "parent span" do | ||
socket | ||
|> OpentelemetryPhoenix.LiveView.assign_async(:assign_async, fn -> | ||
OpenTelemetry.Tracer.with_span "assign_async span" do | ||
{:ok, %{assign_async: "assign_async.loaded"}} | ||
end | ||
end) | ||
|> OpentelemetryPhoenix.LiveView.start_async(:start_async, fn -> | ||
OpenTelemetry.Tracer.with_span "start_async span" do | ||
"start_async.loaded" | ||
end | ||
end) | ||
end | ||
|
||
{:noreply, socket} | ||
end | ||
|
||
@impl true | ||
def handle_async(:start_async, {:ok, value}, socket) do | ||
{:noreply, assign(socket, :start_async, Phoenix.LiveView.AsyncResult.ok(value))} | ||
end | ||
|
||
@impl true | ||
def render(assigns) do | ||
~H""" | ||
<%= @assign_async.ok? && @assign_async.result %> | ||
<%= assigns[:start_async] && @start_async.ok? && @start_async.result %> | ||
""" | ||
end | ||
end | ||
|
||
defmodule Router do | ||
use Phoenix.Router, helpers: false | ||
|
||
import Phoenix.LiveView.Router | ||
|
||
live "/test", TestLive, :show | ||
end | ||
|
||
defmodule Endpoint do | ||
use Phoenix.Endpoint, otp_app: :opentelemetry_phoenix | ||
|
||
plug(Router) | ||
end | ||
|
||
use ExUnit.Case, async: false | ||
|
||
import Phoenix.ConnTest | ||
import Phoenix.LiveViewTest | ||
|
||
require Record | ||
|
||
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do | ||
Record.defrecord(name, spec) | ||
end | ||
|
||
@endpoint Endpoint | ||
|
||
setup do | ||
Application.put_env( | ||
:opentelemetry_phoenix, | ||
Endpoint, | ||
[ | ||
secret_key_base: "secret_key_base", | ||
live_view: [signing_salt: "signing_salt"], | ||
render_errors: [formats: [html: ErrorHTML]] | ||
] | ||
) | ||
:otel_simple_processor.set_exporter(:otel_exporter_pid, self()) | ||
|
||
{:ok, _} = start_supervised(Endpoint) | ||
|
||
{:ok, conn: Phoenix.ConnTest.build_conn()} | ||
end | ||
|
||
@tag capture_log: true | ||
test "render_async", %{conn: conn} do | ||
{:ok, view, _html} = live(conn, "/test") | ||
|
||
assert html = render_async(view) | ||
assert html =~ "assign_async.loaded" | ||
assert html =~ "start_async.loaded" | ||
|
||
# Initial parent span from the REST request | ||
assert_receive {:span, span(name: "parent span")} | ||
|
||
# Parent span from the socket | ||
assert_receive {:span, | ||
span( | ||
name: "parent span", | ||
trace_id: trace_id, | ||
span_id: process_span_id | ||
)} | ||
|
||
assert_receive {:span, | ||
span( | ||
name: "assign_async span", | ||
trace_id: ^trace_id, | ||
parent_span_id: ^process_span_id | ||
)} | ||
|
||
|
||
assert_receive {:span, | ||
span( | ||
name: "start_async span", | ||
trace_id: ^trace_id, | ||
parent_span_id: ^process_span_id | ||
)} | ||
end | ||
end |