Packages
sentry
10.7.1
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/live_view_hook.ex
if Code.ensure_loaded?(Phoenix.LiveView) do
defmodule Sentry.LiveViewHook do
@moduledoc """
A module that provides a `Phoenix.LiveView` hook to add Sentry context and breadcrumbs.
*Available since v10.5.0.*
This module sets context and breadcrumbs for the live view process through
`Sentry.Context`. It sets things like:
* The request URL
* The user agent and user's IP address
* Breadcrumbs for events that happen within LiveView
To make this module work best, you'll need to fetch information from the LiveView's
WebSocket. You can do that when calling the `socket/3` macro in your Phoenix endpoint.
For example:
socket "/live", Phoenix.LiveView.Socket,
websocket: [connect_info: [:peer_data, :uri, :user_agent]]
## Examples
defmodule MyApp.UserLive do
use Phoenix.LiveView
on_mount Sentry.LiveViewHook
# ...
end
You can do the same at the router level:
live_session :default, on_mount: Sentry.LiveViewHook do
scope "..." do
# ...
end
end
You can also set this in your `MyAppWeb` module, so that all LiveViews that
`use MyAppWeb, :live_view` will have this hook.
"""
@moduledoc since: "10.5.0"
import Phoenix.LiveView, only: [attach_hook: 4, get_connect_info: 2]
alias Sentry.Context
require Logger
# See also:
# https://develop.sentry.dev/sdk/event-payloads/request/
@doc false
@spec on_mount(:default, map() | :not_mounted_at_router, map(), struct()) :: {:cont, struct()}
def on_mount(:default, %{} = params, _session, socket), do: on_mount(params, socket)
def on_mount(:default, :not_mounted_at_router, _session, socket), do: {:cont, socket}
## Helpers
defp on_mount(params, %Phoenix.LiveView.Socket{} = socket) do
Context.set_extra_context(%{socket_id: socket.id})
Context.set_request_context(%{url: socket.host_uri})
Context.add_breadcrumb(%{
category: "web.live_view.mount",
message: "Mounted live view",
data: params
})
if uri = get_connect_info_if_root(socket, :uri) do
Context.set_request_context(%{url: URI.to_string(uri)})
end
if user_agent = get_connect_info_if_root(socket, :user_agent) do
Context.set_extra_context(%{user_agent: user_agent})
end
# :peer_data returns t:Plug.Conn.Adapter.peer_data/0.
# https://hexdocs.pm/plug/Plug.Conn.Adapter.html#t:peer_data/0
if ip_address = socket |> get_connect_info_if_root(:peer_data) |> get_safe_ip_address() do
Context.set_user_context(%{ip_address: ip_address})
end
socket
|> maybe_attach_hook_handle_params()
|> attach_hook(__MODULE__, :handle_event, &handle_event_hook/3)
|> attach_hook(__MODULE__, :handle_info, &handle_info_hook/2)
catch
# We must NEVER raise an error in a hook, as it will crash the LiveView process
# and we don't want Sentry to be responsible for that.
kind, reason ->
Logger.error(
"Sentry.LiveView.on_mount hook errored out: #{Exception.format(kind, reason)}",
event_source: :logger
)
{:cont, socket}
else
socket -> {:cont, socket}
end
defp handle_event_hook(event, params, socket) do
Context.add_breadcrumb(%{
category: "web.live_view.event",
message: inspect(event),
data: %{event: event, params: params}
})
{:cont, socket}
end
defp handle_info_hook(message, socket) do
Context.add_breadcrumb(%{
category: "web.live_view.info",
message: inspect(message, pretty: true)
})
{:cont, socket}
end
defp handle_params_hook(params, uri, socket) do
Context.set_extra_context(%{socket_id: socket.id})
Context.set_request_context(%{url: uri})
Context.add_breadcrumb(%{
category: "web.live_view.params",
message: "#{uri}",
data: %{params: params, uri: uri}
})
{:cont, socket}
end
defp get_connect_info_if_root(socket, key) do
case socket.parent_pid do
nil -> get_connect_info(socket, key)
pid when is_pid(pid) -> nil
end
end
defp maybe_attach_hook_handle_params(socket) do
case socket.parent_pid do
nil -> attach_hook(socket, __MODULE__, :handle_params, &handle_params_hook/3)
pid when is_pid(pid) -> socket
end
end
defp get_safe_ip_address(%{ip_address: ip} = _peer_data) do
case :inet.ntoa(ip) do
ip_address when is_list(ip_address) -> List.to_string(ip_address)
{:error, _reason} -> nil
end
catch
_kind, _reason -> nil
end
defp get_safe_ip_address(_peer_data) do
nil
end
end
end