Skip to content

Commit 00f8271

Browse files
authored
Safely convert payloads to strings (#110)
For non-error tuples `Exception.normalize/3` returns the original payload, which may be anything. Some payloads such as tuples don't implement the `String.Chars` protocol. So calling `to_string/1` will fail. In such cases we fall back to `inspect/1`. I made a few updates to the `live.exs` script to reproduce this problem. You can run `elixir live.exs` and click the button called «GenServer timeout». This will cause an error that should be recorded by the error tracker. If you revert the changes that I made and try this same button you will se that the error payload cannot be converted to string and detaches the ErrorTracker telemetry handler. Closes #109
1 parent cc6aacb commit 00f8271

File tree

2 files changed

+77
-9
lines changed

2 files changed

+77
-9
lines changed

lib/error_tracker.ex

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,14 +280,18 @@ defmodule ErrorTracker do
280280

281281
defp normalize_exception({kind, ex}, stacktrace) do
282282
case Exception.normalize(kind, ex, stacktrace) do
283-
%struct{} = ex ->
284-
{to_string(struct), Exception.message(ex)}
285-
286-
other ->
287-
{to_string(kind), to_string(other)}
283+
%struct{} = ex -> {to_string(struct), Exception.message(ex)}
284+
payload -> {to_string(kind), safe_to_string(payload)}
288285
end
289286
end
290287

288+
defp safe_to_string(term) do
289+
to_string(term)
290+
rescue
291+
Protocol.UndefinedError ->
292+
inspect(term)
293+
end
294+
291295
defp exception_breadcrumbs(exception) do
292296
case exception do
293297
{_kind, exception} -> exception_breadcrumbs(exception)

live.exs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
Mix.install([
2-
{:phoenix_playground,
3-
github: "phoenix-playground/phoenix_playground",
4-
ref: "ee6da0fc3b141f78b9f967ce71a4fb015c6764a6"},
2+
{:phoenix_playground, "~> 0.1.7"},
3+
{:postgrex, "~> 0.19.3"},
54
{:error_tracker, path: "."}
65
])
76

@@ -13,6 +12,7 @@ end
1312
Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo)
1413
Application.put_env(:error_tracker, :application, :error_tracker_dev)
1514
Application.put_env(:error_tracker, :prefix, "private")
15+
Application.put_env(:error_tracker, :otp_app, :error_tracker_dev)
1616

1717
Application.put_env(:error_tracker, ErrorTrackerDev.Repo,
1818
url: "ecto://postgres:postgres@127.0.0.1/error_tracker_dev"
@@ -26,6 +26,29 @@ defmodule Migration0 do
2626
def down, do: ErrorTracker.Migration.down(prefix: "private")
2727
end
2828

29+
defmodule ErrorTrackerDev.TimeoutGenServer do
30+
use GenServer
31+
32+
# Client
33+
34+
def start_link(_) do
35+
GenServer.start_link(__MODULE__, %{})
36+
end
37+
38+
# Server (callbacks)
39+
40+
@impl true
41+
def init(initial_state) do
42+
{:ok, initial_state}
43+
end
44+
45+
@impl true
46+
def handle_call(:timeout, _from, state) do
47+
:timer.sleep(5000)
48+
{:reply, state, state}
49+
end
50+
end
51+
2952
defmodule DemoLive do
3053
use Phoenix.LiveView
3154

@@ -47,6 +70,7 @@ defmodule DemoLive do
4770
<button phx-click="inc">+</button>
4871
<button phx-click="dec">-</button>
4972
<button phx-click="error">Crash on handle_event</button>
73+
<button phx-click="genserver-timeout">GenServer timeout</button>
5074
5175
<.link href="/?crash=mount">Crash on mount</.link>
5276
<.link patch="/?crash=handle_params">Crash on handle_params</.link>
@@ -69,6 +93,11 @@ defmodule DemoLive do
6993
raise "Crash on handle_event"
7094
end
7195

96+
def handle_event("genserver-timeout", _params, socket) do
97+
GenServer.call(TimeoutGenServer, :timeout, 2000)
98+
{:noreply, socket}
99+
end
100+
72101
def handle_params(params, _uri, socket) do
73102
if params["crash"] == "handle_params" do
74103
raise "Crash on handle_params"
@@ -78,7 +107,42 @@ defmodule DemoLive do
78107
end
79108
end
80109

81-
PhoenixPlayground.start(live: DemoLive, child_specs: [ErrorTrackerDev.Repo])
110+
defmodule DemoRouter do
111+
use Phoenix.Router
112+
use ErrorTracker.Web, :router
113+
114+
import Phoenix.LiveView.Router
115+
116+
pipeline :browser do
117+
plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
118+
end
119+
120+
scope "/" do
121+
pipe_through :browser
122+
live "/", DemoLive
123+
error_tracker_dashboard "/errors"
124+
end
125+
end
126+
127+
defmodule DemoEndpoint do
128+
use Phoenix.Endpoint, otp_app: :phoenix_playground
129+
plug Plug.Logger
130+
socket "/live", Phoenix.LiveView.Socket
131+
plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix"
132+
plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view"
133+
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
134+
plug Phoenix.LiveReloader
135+
plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2
136+
plug DemoRouter
137+
end
138+
139+
PhoenixPlayground.start(
140+
endpoint: DemoEndpoint,
141+
child_specs: [
142+
{ErrorTrackerDev.Repo, []},
143+
{ErrorTrackerDev.TimeoutGenServer, [name: TimeoutGenServer]}
144+
]
145+
)
82146

83147
# Create the database if it does not exist and run migrations if needed
84148
_ = Ecto.Adapters.Postgres.storage_up(ErrorTrackerDev.Repo.config())

0 commit comments

Comments
 (0)