Current section

Files

Jump to
new_relic_agent lib new_relic telemetry phoenix_live_view.ex
Raw

lib/new_relic/telemetry/phoenix_live_view.ex

defmodule NewRelic.Telemetry.PhoenixLiveView do
use GenServer
alias NewRelic.Transaction
alias NewRelic.Tracer
@moduledoc """
Provides Phoenix `LiveView` instrumentation via `telemetry`.
This instrumentation collects Transactions for the initial LiveView lifecycle events
that occur inside the WebSocket process, as well as metrics for subsequent instances
of lifecycle events.
You can opt-out of this instrumentation as a whole with `:phoenix_live_view_instrumentation_enabled`,
and specifically out of params collection with `:function_argument_collection_enabled` via configuration.
See `NewRelic.Config` for details.
"""
def start_link(_) do
config = %{
handler_id: {:new_relic, :phoenix_live_view},
enabled?: NewRelic.Config.feature?(:phoenix_live_view_instrumentation),
collect_args?: NewRelic.Config.feature?(:function_argument_collection)
}
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
@live_view_mount_start [:phoenix, :live_view, :mount, :start]
@live_view_mount_stop [:phoenix, :live_view, :mount, :stop]
@live_view_mount_exception [:phoenix, :live_view, :mount, :exception]
@live_view_handle_params_start [:phoenix, :live_view, :handle_params, :start]
@live_view_handle_params_stop [:phoenix, :live_view, :handle_params, :stop]
@live_view_handle_params_exception [:phoenix, :live_view, :handle_params, :exception]
@live_view_handle_event_start [:phoenix, :live_view, :handle_event, :start]
@live_view_handle_event_stop [:phoenix, :live_view, :handle_event, :stop]
@live_view_handle_event_exception [:phoenix, :live_view, :handle_event, :exception]
@live_view_render_start [:phoenix, :live_view, :render, :start]
@live_view_render_stop [:phoenix, :live_view, :render, :stop]
@live_view_render_exception [:phoenix, :live_view, :render, :exception]
@live_view_events [
@live_view_mount_start,
@live_view_mount_stop,
@live_view_mount_exception,
@live_view_handle_params_start,
@live_view_handle_params_stop,
@live_view_handle_params_exception,
@live_view_handle_event_start,
@live_view_handle_event_stop,
@live_view_handle_event_exception,
@live_view_render_start,
@live_view_render_stop,
@live_view_render_exception
]
@doc false
def init(%{enabled?: false}), do: :ignore
def init(%{enabled?: true} = config) do
:telemetry.attach_many(
config.handler_id,
@live_view_events,
&__MODULE__.handle_event/4,
config
)
{:ok, config}
end
@doc false
def terminate(_reason, %{handler_id: handler_id}) do
:telemetry.detach(handler_id)
end
def handle_event(@live_view_mount_start, meas, meta, config) do
if meta.socket.transport_pid do
uri = URI.parse(meta.uri)
# We're in the LiveView WebSocket process, collect a Transaction
with :collect <- Transaction.Reporter.start_transaction(:web, uri.path) do
NewRelic.DistributedTrace.start(:other)
framework_name = "/Phoenix.LiveView/Live/#{inspect(meta.socket.view)}/#{meta.socket.assigns.live_action}"
NewRelic.add_attributes(
pid: inspect(self()),
start_time: meas.system_time,
framework_name: framework_name,
host: uri.host,
path: uri.path,
"live_view.router": inspect(meta.socket.router),
"live_view.endpoint": inspect(meta.socket.endpoint),
"live_view.action": meta.socket.assigns[:live_action],
"live_view.socket_id": meta.socket.id
)
end
end
Tracer.Direct.start_span(
meta.telemetry_span_context,
"#{inspect(meta.socket.view)}:#{meta.socket.assigns.live_action}.mount",
system_time: meas.system_time,
attributes: [
"live_view.params": config.collect_args? && meta[:params] && inspect(meta[:params])
]
)
end
def handle_event([_, _, type, _] = event, meas, meta, config)
when event in [
@live_view_handle_params_start,
@live_view_handle_event_start,
@live_view_render_start
] do
Tracer.Direct.start_span(
meta.telemetry_span_context,
"#{inspect(meta.socket.view)}:#{meta.socket.assigns.live_action}.#{type}",
system_time: meas.system_time,
attributes: [
event: meta[:event],
params: config.collect_args? && meta[:params] && inspect(meta[:params]),
"live_view.render.changed?": meta[:changed?],
"live_view.render.force?": meta[:force?]
]
)
end
def handle_event(event, meas, meta, _config)
when event in [
@live_view_mount_stop,
@live_view_handle_params_stop,
@live_view_handle_event_stop
] do
Tracer.Direct.stop_span(meta.telemetry_span_context, duration: meas.duration)
end
def handle_event(event, meas, meta, _config)
when event in [
@live_view_mount_exception,
@live_view_handle_params_exception,
@live_view_handle_event_exception
] do
Tracer.Direct.stop_span(
meta.telemetry_span_context,
duration: meas.duration,
attributes: [error: true, "error.kind": meta.kind, "error.reason": inspect(meta.reason)]
)
end
def handle_event(@live_view_render_stop, meas, meta, _config) do
Tracer.Direct.stop_span(meta.telemetry_span_context, duration: meas.duration)
if meta.socket.transport_pid do
Transaction.Reporter.stop_transaction(:web)
end
end
def handle_event(@live_view_render_exception, meas, meta, _config) do
Tracer.Direct.stop_span(
meta.telemetry_span_context,
duration: meas.duration,
attributes: [error: true, "error.kind": meta.kind, "error.reason": inspect(meta.reason)]
)
if meta.socket.transport_pid do
Transaction.Reporter.stop_transaction(:web)
end
end
def handle_event(_event, _meas, _meta, _config) do
:ignore
end
end