+ """
end
+end
- scope "/" do
- pipe_through :browser
- get "/", ErrorTrackerDevWeb.PageController, :index
- get "/noroute", ErrorTrackerDevWeb.PageController, :noroute
- get "/exception", ErrorTrackerDevWeb.PageController, :exception
- get "/exit", ErrorTrackerDevWeb.PageController, :exit
+defmodule ErrorTrackerDev.Live do
+ use Phoenix.LiveView
- scope "/dev" do
- error_tracker_dashboard "/errors", csp_nonce_assign_key: :my_csp_nonce
+ def mount(params, _session, socket) do
+ if params["crash_on_mount"] do
+ raise("Crashed on mount/3")
end
- end
-end
-defmodule ErrorTrackerDevWeb.Endpoint do
- use Phoenix.Endpoint, otp_app: :error_tracker
- use ErrorTracker.Integrations.Plug
+ {:ok, socket}
+ end
- @session_options [
- store: :cookie,
- key: "_error_tracker_dev",
- signing_salt: "/VEDsdfsffMnp5",
- same_site: "Lax"
- ]
+ def handle_params(params, _uri, socket) do
+ if params["crash_on_handle_params"] do
+ raise "Crashed on handle_params/3"
+ end
- socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
- socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
+ {:noreply, socket}
+ end
- plug Phoenix.LiveReloader
- plug Phoenix.CodeReloader
+ def handle_event("crash_on_handle_event", _params, socket) do
+ raise "Crashed on handle_event/3"
+ end
- plug Plug.Session, @session_options
+ def handle_event("crash_on_render", _params, socket) do
+ {:noreply, assign(socket, crash_on_render: true)}
+ end
- plug Plug.RequestId
- plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
- plug :add_breadcrumb
- plug :maybe_exception
- plug :set_csp
- plug ErrorTrackerDevWeb.Router
+ def render(assigns) do
+ if Map.has_key?(assigns, :crash_on_render) do
+ raise "Crashed on render/1"
+ end
- def add_breadcrumb(conn, _) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.Endpoint.add_breadcrumb")
- conn
+ ~H"""
+
ErrorTracker Dev server
+
+ <.link href="/dev/errors" target="_blank">Open the ErrorTracker dashboard
+
+
LiveView example
+
+
+
+ <.link href="/?crash_on_mount">Crash on mount/3
+
+
+ <.link patch="/?crash_on_handle_params">Crash on handle_params/3
+
+
+ <.link phx-click="crash_on_render">Crash on render/1
+
+
+ <.link phx-click="crash_on_handle_event">Crash on handle_event/3
+
+
+
+
Controller example
+ """
end
+end
- def maybe_exception(%Plug.Conn{path_info: ["plug-exception"]}, _), do: raise("Plug exception")
- def maybe_exception(conn, _), do: conn
-
- defp set_csp(conn, _opts) do
- nonce = 10 |> :crypto.strong_rand_bytes() |> Base.encode64()
+defmodule ErrorTrackerDev.Router do
+ use Phoenix.Router
+ use ErrorTracker.Web, :router
- policies = [
- "script-src 'self' 'nonce-#{nonce}';",
- "style-src 'self' 'nonce-#{nonce}';"
- ]
+ import Phoenix.LiveView.Router
- conn
- |> Plug.Conn.assign(:my_csp_nonce, "#{nonce}")
- |> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " "))
+ pipeline :browser do
+ plug :accepts, [:html]
+ plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
+ plug :put_secure_browser_headers
end
-end
-defmodule ErrorTrackerDev.Telemetry do
- require Logger
+ scope "/" do
+ pipe_through :browser
- def start do
- :telemetry.attach_many(
- "error-tracker-events",
- [
- [:error_tracker, :error, :new],
- [:error_tracker, :error, :resolved],
- [:error_tracker, :error, :unresolved],
- [:error_tracker, :occurrence, :new]
- ],
- &__MODULE__.handle_event/4,
- []
- )
-
- Logger.info("Telemtry attached")
- end
+ live "/", ErrorTrackerDev.Live
- def handle_event(event, measure, metadata, _opts) do
- dbg([event, measure, metadata])
+ scope "/dev" do
+ error_tracker_dashboard "/errors"
+ end
end
end
-Application.put_env(:phoenix, :serve_endpoints, true)
-
-Task.async(fn ->
- children = [
- {Phoenix.PubSub, [name: ErrorTrackerDev.PubSub, adapter: Phoenix.PubSub.PG2]},
- ErrorTrackerDev.Repo,
- ErrorTrackerDevWeb.Endpoint
- ]
-
- ErrorTrackerDev.Telemetry.start()
+defmodule ErrorTrackerDev.Endpoint do
+ # Default PhoenixPlayground.Endpoint
+ use Phoenix.Endpoint, otp_app: :phoenix_playground
+ plug Plug.Logger
+ socket "/live", Phoenix.LiveView.Socket
+ plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix"
+ plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view"
+ socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
+ plug Phoenix.LiveReloader
+ plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2
- {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)
+ # Our custom router which allows us to have regular controllers and live views
+ plug ErrorTrackerDev.Router
+end
- # Automatically run the migrations on boot
- Ecto.Migrator.run(ErrorTrackerDev.Repo, :up, all: true, log_migrations_sql: :debug)
+PhoenixPlayground.start(
+ endpoint: ErrorTrackerDev.Endpoint,
+ child_specs: [{ErrorTrackerDev.Repo, []}]
+)
- Process.sleep(:infinity)
-end)
+ErrorTrackerDev.Repo.migrate()
From 975f7fb0319c7f5f6b413130f3753c1e29128260 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sat, 14 Dec 2024 11:35:33 +0100
Subject: [PATCH 03/11] Ad GenServer timeout example
---
dev.exs | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/dev.exs b/dev.exs
index 6010a66..b7f13f7 100644
--- a/dev.exs
+++ b/dev.exs
@@ -86,6 +86,11 @@ defmodule ErrorTrackerDev.Live do
{:noreply, assign(socket, crash_on_render: true)}
end
+ def handle_event("genserver-timeout", _params, socket) do
+ GenServer.call(ErrorTrackerDev.GenServer, :timeout, 2000)
+ {:noreply, socket}
+ end
+
def render(assigns) do
if Map.has_key?(assigns, :crash_on_render) do
raise "Crashed on render/1"
@@ -111,6 +116,9 @@ defmodule ErrorTrackerDev.Live do
<.link phx-click="crash_on_handle_event">Crash on handle_event/3
+
+ <.link phx-click="genserver-timeout">Crash with a GenServer timeout
+
Controller example
@@ -156,9 +164,35 @@ defmodule ErrorTrackerDev.Endpoint do
plug ErrorTrackerDev.Router
end
+defmodule ErrorTrackerDev.GenServer do
+ use GenServer
+
+ # Client
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, %{})
+ end
+
+ # Server (callbacks)
+
+ @impl true
+ def init(initial_state) do
+ {:ok, initial_state}
+ end
+
+ @impl true
+ def handle_call(:timeout, _from, state) do
+ :timer.sleep(5000)
+ {:reply, state, state}
+ end
+end
+
PhoenixPlayground.start(
endpoint: ErrorTrackerDev.Endpoint,
- child_specs: [{ErrorTrackerDev.Repo, []}]
+ child_specs: [
+ {ErrorTrackerDev.Repo, []},
+ {ErrorTrackerDev.GenServer, [name: ErrorTrackerDev.GenServer]}
+ ]
)
ErrorTrackerDev.Repo.migrate()
From 702ce17b223bb59c42cf30ff60eb180e9aad675c Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sat, 14 Dec 2024 11:40:45 +0100
Subject: [PATCH 04/11] Explain how to drop the dev database
---
dev.exs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/dev.exs b/dev.exs
index b7f13f7..d6a9ed2 100644
--- a/dev.exs
+++ b/dev.exs
@@ -101,7 +101,13 @@ defmodule ErrorTrackerDev.Live do
<.link href="/dev/errors" target="_blank">Open the ErrorTracker dashboard
-
LiveView example
+
+ Errors are stored in the priv/repo/dev.db
+ database, which is automatically created by this script.
+ If you want to clear the state stop the script, run the following command and start it again.
- """)
- end
-
- def call(conn, :noroute) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.no_route")
- raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDevWeb.Router
- end
-
- def call(_conn, :exception) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exception")
-
- raise CustomException,
- message: "This is a controller exception",
- bread_crumbs: ["First", "Second"]
- end
-
- def call(_conn, :exit) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exit")
- exit(:timeout)
- end
-
- defp content(conn, content) do
- conn
- |> put_resp_header("content-type", "text/html")
- |> send_resp(200, "#{content}")
- end
-end
-
-defmodule CustomException do
- defexception [:message, :bread_crumbs]
-end
-
-defmodule ErrorTrackerDevWeb.ErrorView do
- def render("404.html", _assigns) do
- "This is a 404"
- end
-
- def render("500.html", _assigns) do
- "This is a 500"
- end
-end
-
-defmodule ErrorTrackerDevWeb.Router do
- use Phoenix.Router
- use ErrorTracker.Web, :router
-
- pipeline :browser do
- plug :fetch_session
- plug :protect_from_forgery
- end
-
- scope "/" do
- pipe_through :browser
- get "/", ErrorTrackerDevWeb.PageController, :index
- get "/noroute", ErrorTrackerDevWeb.PageController, :noroute
- get "/exception", ErrorTrackerDevWeb.PageController, :exception
- get "/exit", ErrorTrackerDevWeb.PageController, :exit
-
- scope "/dev" do
- error_tracker_dashboard "/errors", csp_nonce_assign_key: :my_csp_nonce
- end
- end
-end
-
-defmodule ErrorTrackerDevWeb.Endpoint do
- use Phoenix.Endpoint, otp_app: :error_tracker
- use ErrorTracker.Integrations.Plug
-
- @session_options [
- store: :cookie,
- key: "_error_tracker_dev",
- signing_salt: "/VEDsdfsffMnp5",
- same_site: "Lax"
- ]
-
- socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
- socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
-
- plug Phoenix.LiveReloader
- plug Phoenix.CodeReloader
-
- plug Plug.Session, @session_options
-
- plug Plug.RequestId
- plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
- plug :add_breadcrumb
- plug :maybe_exception
- plug :set_csp
- plug ErrorTrackerDevWeb.Router
-
- def add_breadcrumb(conn, _) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.Endpoint.add_breadcrumb")
- conn
- end
-
- def maybe_exception(%Plug.Conn{path_info: ["plug-exception"]}, _), do: raise("Plug exception")
- def maybe_exception(conn, _), do: conn
-
- defp set_csp(conn, _opts) do
- nonce = 10 |> :crypto.strong_rand_bytes() |> Base.encode64()
-
- policies = [
- "script-src 'self' 'nonce-#{nonce}';",
- "style-src 'self' 'nonce-#{nonce}';"
- ]
-
- conn
- |> Plug.Conn.assign(:my_csp_nonce, "#{nonce}")
- |> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " "))
- end
-end
-
-defmodule ErrorTrackerDev.Telemetry do
- require Logger
-
- def start do
- :telemetry.attach_many(
- "error-tracker-events",
- [
- [:error_tracker, :error, :new],
- [:error_tracker, :error, :resolved],
- [:error_tracker, :error, :unresolved],
- [:error_tracker, :occurrence, :new]
- ],
- &__MODULE__.handle_event/4,
- []
- )
-
- Logger.info("Telemtry attached")
- end
-
- def handle_event(event, measure, metadata, _opts) do
- dbg([event, measure, metadata])
- end
-end
-
-Application.put_env(:phoenix, :serve_endpoints, true)
-
-Task.async(fn ->
- children = [
- {Phoenix.PubSub, [name: ErrorTrackerDev.PubSub, adapter: Phoenix.PubSub.PG2]},
- ErrorTrackerDev.Repo,
- ErrorTrackerDevWeb.Endpoint
- ]
-
- ErrorTrackerDev.Telemetry.start()
-
- {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)
-
- # Automatically run the migrations on boot
- Ecto.Migrator.run(ErrorTrackerDev.Repo, :up, all: true, log_migrations_sql: :debug)
-
- Process.sleep(:infinity)
-end)
From 64bd2c0e4260876ac6306f71972961b150a62c3c Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sat, 14 Dec 2024 13:07:09 +0100
Subject: [PATCH 10/11] Remove old live.exs script
---
live.exs | 153 -------------------------------------------------------
1 file changed, 153 deletions(-)
delete mode 100644 live.exs
diff --git a/live.exs b/live.exs
deleted file mode 100644
index a5f10bd..0000000
--- a/live.exs
+++ /dev/null
@@ -1,153 +0,0 @@
-Mix.install([
- {:phoenix_playground, "~> 0.1.7"},
- {:postgrex, "~> 0.19.3"},
- {:error_tracker, path: "."}
-])
-
-# Set up the repository for the Error Tracker
-defmodule ErrorTrackerDev.Repo do
- use Ecto.Repo, otp_app: :error_tracker, adapter: Ecto.Adapters.Postgres
-end
-
-Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo)
-Application.put_env(:error_tracker, :application, :error_tracker_dev)
-Application.put_env(:error_tracker, :prefix, "private")
-Application.put_env(:error_tracker, :otp_app, :error_tracker_dev)
-
-Application.put_env(:error_tracker, ErrorTrackerDev.Repo,
- url: "ecto://postgres:postgres@127.0.0.1/error_tracker_dev"
-)
-
-# This migration will set up the database structure
-defmodule Migration0 do
- use Ecto.Migration
-
- def up, do: ErrorTracker.Migration.up(prefix: "private")
- def down, do: ErrorTracker.Migration.down(prefix: "private")
-end
-
-defmodule ErrorTrackerDev.TimeoutGenServer do
- use GenServer
-
- # Client
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, %{})
- end
-
- # Server (callbacks)
-
- @impl true
- def init(initial_state) do
- {:ok, initial_state}
- end
-
- @impl true
- def handle_call(:timeout, _from, state) do
- :timer.sleep(5000)
- {:reply, state, state}
- end
-end
-
-defmodule DemoLive do
- use Phoenix.LiveView
-
- def mount(params, _session, socket) do
- if params["crash"] == "mount" do
- raise "Crashing on mount"
- end
-
- {:ok, assign(socket, count: 0)}
- end
-
- def render(assigns) do
- if assigns.count == 5 do
- raise "Crash on render"
- end
-
- ~H"""
- <%= @count %>
-
-
-
-
-
- <.link href="/?crash=mount">Crash on mount
- <.link patch="/?crash=handle_params">Crash on handle_params
-
-
- """
- end
-
- def handle_event("inc", _params, socket) do
- {:noreply, assign(socket, count: socket.assigns.count + 1)}
- end
-
- def handle_event("dec", _params, socket) do
- {:noreply, assign(socket, count: socket.assigns.count - 1)}
- end
-
- def handle_event("error", _params, _socket) do
- raise "Crash on handle_event"
- end
-
- def handle_event("genserver-timeout", _params, socket) do
- GenServer.call(TimeoutGenServer, :timeout, 2000)
- {:noreply, socket}
- end
-
- def handle_params(params, _uri, socket) do
- if params["crash"] == "handle_params" do
- raise "Crash on handle_params"
- end
-
- {:noreply, socket}
- end
-end
-
-defmodule DemoRouter do
- use Phoenix.Router
- use ErrorTracker.Web, :router
-
- import Phoenix.LiveView.Router
-
- pipeline :browser do
- plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
- end
-
- scope "/" do
- pipe_through :browser
- live "/", DemoLive
- error_tracker_dashboard "/errors"
- end
-end
-
-defmodule DemoEndpoint do
- use Phoenix.Endpoint, otp_app: :phoenix_playground
- plug Plug.Logger
- socket "/live", Phoenix.LiveView.Socket
- plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix"
- plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view"
- socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
- plug Phoenix.LiveReloader
- plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2
- plug DemoRouter
-end
-
-PhoenixPlayground.start(
- endpoint: DemoEndpoint,
- child_specs: [
- {ErrorTrackerDev.Repo, []},
- {ErrorTrackerDev.TimeoutGenServer, [name: TimeoutGenServer]}
- ]
-)
-
-# Create the database if it does not exist and run migrations if needed
-_ = Ecto.Adapters.Postgres.storage_up(ErrorTrackerDev.Repo.config())
-
-Ecto.Migrator.run(ErrorTrackerDev.Repo, [{0, Migration0}], :up,
- all: true,
- log_migrations_sql: :debug
-)
From bd32d5184ff0dbbad450e09345fbe068dafc2ce8 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sun, 15 Dec 2024 11:24:01 +0100
Subject: [PATCH 11/11] Handle endpoint exceptions
---
dev.exs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/dev.exs b/dev.exs
index 0eae520..a09f496 100644
--- a/dev.exs
+++ b/dev.exs
@@ -156,6 +156,9 @@ defmodule ErrorTrackerDev.Live do
<.link href="/exception">Generate an exception from the controller
+
+ <.link href="/plug_exception">Generate an exception from the router
+
<.link href="/exit">Generate an exit from the controller
@@ -205,6 +208,8 @@ defmodule ErrorTrackerDev.Endpoint do
# Use a custom Content Security Policy
plug :set_csp
+ # Raise an exception in the /plug_exception path
+ plug :plug_exception
# Our custom router which allows us to have regular controllers and live views
plug ErrorTrackerDev.Router
@@ -220,6 +225,12 @@ defmodule ErrorTrackerDev.Endpoint do
|> Plug.Conn.assign(:custom_csp_nonce, "#{nonce}")
|> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " "))
end
+
+ defp plug_exception(conn = %Plug.Conn{path_info: path_info}, _opts) when is_list(path_info) do
+ if "plug_exception" in path_info,
+ do: raise("Crashed in Endpoint"),
+ else: conn
+ end
end
defmodule ErrorTrackerDev.ErrorView do