From 9bb3d3b435b9dcb8a5549f6c120b370863bcb414 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 10:49:08 +0100 Subject: [PATCH 01/11] Rename dev script --- old_dev.exs | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 old_dev.exs diff --git a/old_dev.exs b/old_dev.exs new file mode 100644 index 0000000..895749d --- /dev/null +++ b/old_dev.exs @@ -0,0 +1,223 @@ +####################################### +# Development Server for ErrorTracker. +# +# Based on PhoenixLiveDashboard code. +# +# Usage: +# +# $ iex -S mix dev +####################################### +Logger.configure(level: :debug) + +# Get configuration +Config.Reader.read!("config/config.exs", env: :dev) + +# Prepare the repo +adapter = + case Application.get_env(:error_tracker, :ecto_adapter) do + :postgres -> Ecto.Adapters.Postgres + :mysql -> Ecto.Adapters.MyXQL + :sqlite3 -> Ecto.Adapters.SQLite3 + end + +defmodule ErrorTrackerDev.Repo do + use Ecto.Repo, otp_app: :error_tracker, adapter: adapter +end + +_ = adapter.storage_up(ErrorTrackerDev.Repo.config()) + +# Configures the endpoint +Application.put_env(:error_tracker, ErrorTrackerDevWeb.Endpoint, + url: [host: "localhost"], + secret_key_base: "Hu4qQN3iKzTV4fJxhorPQlA/osH9fAMtbtjVS58PFgfw3ja5Z18Q/WSNR9wP4OfW", + live_view: [signing_salt: "hMegieSe"], + http: [port: System.get_env("PORT") || 4000], + debug_errors: true, + check_origin: false, + pubsub_server: ErrorTrackerDev.PubSub, + watchers: [ + tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} + ], + live_reload: [ + patterns: [ + ~r"dev.exs$", + ~r"dist/.*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"lib/error_tracker/web/(live|views)/.*(ex)$", + ~r"lib/error_tracker/web/templates/.*(ex)$" + ] + ] +) + +# Setup up the ErrorTracker configuration +Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo) +Application.put_env(:error_tracker, :otp_app, :error_tracker_dev) +Application.put_env(:error_tracker, :prefix, "private") + +defmodule ErrorTrackerDevWeb.PageController do + import Plug.Conn + + def init(opts), do: opts + + def call(conn, :index) do + content(conn, """ +

ErrorTracker Dev Server

+
Open ErrorTracker
+
Generate Plug exception
+
Generate Router 404
+
Raise NoRouteError from a controller
+
Generate Exception
+
Generate Exit
+ """) + 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 d58ff7771a4ee3137cc33ae4713e6786bceb3b21 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 10:49:08 +0100 Subject: [PATCH 02/11] Create new dev.exs script with LiveView examples --- dev.exs | 299 +++++++++++++++++++++++--------------------------------- 1 file changed, 120 insertions(+), 179 deletions(-) diff --git a/dev.exs b/dev.exs index 895749d..6010a66 100644 --- a/dev.exs +++ b/dev.exs @@ -1,223 +1,164 @@ -####################################### -# Development Server for ErrorTracker. +# This is the development server for Errortracker built on the PhoenixLiveDashboard project. +# To start the development server run: +# $ iex dev.exs # -# Based on PhoenixLiveDashboard code. -# -# Usage: -# -# $ iex -S mix dev -####################################### -Logger.configure(level: :debug) - -# Get configuration -Config.Reader.read!("config/config.exs", env: :dev) - -# Prepare the repo -adapter = - case Application.get_env(:error_tracker, :ecto_adapter) do - :postgres -> Ecto.Adapters.Postgres - :mysql -> Ecto.Adapters.MyXQL - :sqlite3 -> Ecto.Adapters.SQLite3 - end +Mix.install([ + {:ecto_sqlite3, ">= 0.0.0"}, + {:error_tracker, path: "."}, + {:phoenix_playground, "~> 0.1.7"} +]) -defmodule ErrorTrackerDev.Repo do - use Ecto.Repo, otp_app: :error_tracker, adapter: adapter -end +otp_app = :error_tracker_dev -_ = adapter.storage_up(ErrorTrackerDev.Repo.config()) - -# Configures the endpoint -Application.put_env(:error_tracker, ErrorTrackerDevWeb.Endpoint, - url: [host: "localhost"], - secret_key_base: "Hu4qQN3iKzTV4fJxhorPQlA/osH9fAMtbtjVS58PFgfw3ja5Z18Q/WSNR9wP4OfW", - live_view: [signing_salt: "hMegieSe"], - http: [port: System.get_env("PORT") || 4000], - debug_errors: true, - check_origin: false, - pubsub_server: ErrorTrackerDev.PubSub, - watchers: [ - tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} +Application.put_all_env( + error_tracker_dev: [ + {ErrorTrackerDev.Repo, [database: "priv/repo/dev.db"]} ], - live_reload: [ - patterns: [ - ~r"dev.exs$", - ~r"dist/.*(js|css|png|jpeg|jpg|gif|svg)$", - ~r"lib/error_tracker/web/(live|views)/.*(ex)$", - ~r"lib/error_tracker/web/templates/.*(ex)$" - ] + error_tracker: [ + {:application, otp_app}, + {:otp_app, otp_app}, + {:repo, ErrorTrackerDev.Repo} ] ) -# Setup up the ErrorTracker configuration -Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo) -Application.put_env(:error_tracker, :otp_app, :error_tracker_dev) -Application.put_env(:error_tracker, :prefix, "private") - -defmodule ErrorTrackerDevWeb.PageController do - import Plug.Conn - - def init(opts), do: opts - - def call(conn, :index) do - content(conn, """ -

ErrorTracker Dev Server

-
Open ErrorTracker
-
Generate Plug exception
-
Generate Router 404
-
Raise NoRouteError from a controller
-
Generate Exception
-
Generate Exit
- """) - 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") +defmodule ErrorTrackerDev.Repo do + require Logger + use Ecto.Repo, otp_app: otp_app, adapter: Ecto.Adapters.SQLite3 - raise CustomException, - message: "This is a controller exception", - bread_crumbs: ["First", "Second"] - end + defmodule Migration do + use Ecto.Migration - def call(_conn, :exit) do - ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exit") - exit(:timeout) + def up, do: ErrorTracker.Migration.up() + def down, do: ErrorTracker.Migration.down() end - defp content(conn, content) do - conn - |> put_resp_header("content-type", "text/html") - |> send_resp(200, "#{content}") + def migrate do + Ecto.Migrator.run(__MODULE__, [{0, __MODULE__.Migration}], :up, all: true) end end -defmodule CustomException do - defexception [:message, :bread_crumbs] -end +defmodule ErrorTrackerDev.Controller do + use Phoenix.Controller, formats: [:html] + use Phoenix.Component -defmodule ErrorTrackerDevWeb.ErrorView do - def render("404.html", _assigns) do - "This is a 404" - end + plug :put_layout, false + plug :put_view, __MODULE__ - def render("500.html", _assigns) do - "This is a 500" + def index(conn, _opts) do + render(conn) end -end -defmodule ErrorTrackerDevWeb.Router do - use Phoenix.Router - use ErrorTracker.Web, :router + def index(assigns) do + ~H""" +

ErrorTracker Dev server

- pipeline :browser do - plug :fetch_session - plug :protect_from_forgery + + """ 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

+ + + +

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.

    rm priv/repo/dev.db priv/repo/dev.db-shm priv/repo/dev.db-wal
    +

    + +

    LiveView examples

    • From 560316d5ae4e125cf8175bec52e68b9a0d20810b Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 11:40:45 +0100 Subject: [PATCH 05/11] Cleanup unused params --- dev.exs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dev.exs b/dev.exs index d6a9ed2..92d8db4 100644 --- a/dev.exs +++ b/dev.exs @@ -44,7 +44,7 @@ defmodule ErrorTrackerDev.Controller do plug :put_layout, false plug :put_view, __MODULE__ - def index(conn, _opts) do + def index(conn, _params) do render(conn) end @@ -78,7 +78,7 @@ defmodule ErrorTrackerDev.Live do {:noreply, socket} end - def handle_event("crash_on_handle_event", _params, socket) do + def handle_event("crash_on_handle_event", _params, _socket) do raise "Crashed on handle_event/3" end @@ -198,7 +198,9 @@ PhoenixPlayground.start( child_specs: [ {ErrorTrackerDev.Repo, []}, {ErrorTrackerDev.GenServer, [name: ErrorTrackerDev.GenServer]} - ] + ], + open_browser: false, + debug_errors: false ) ErrorTrackerDev.Repo.migrate() From 7cc6c8149ff5c886828b8e6fb29adbb5ab39efe6 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 11:40:45 +0100 Subject: [PATCH 06/11] Add controller examples --- dev.exs | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/dev.exs b/dev.exs index 92d8db4..60a5049 100644 --- a/dev.exs +++ b/dev.exs @@ -57,6 +57,26 @@ defmodule ErrorTrackerDev.Controller do
    """ end + + def noroute(conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.noroute/2") + + raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDev.Router + end + + def exception(_conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exception/2") + + raise ErrorTrackerDev.Exception, + message: "This is a controller exception", + bread_crumbs: ["First", "Second"] + end + + def exit(_conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exit/2") + + exit(:timeout) + end end defmodule ErrorTrackerDev.Live do @@ -127,7 +147,19 @@ defmodule ErrorTrackerDev.Live do -

    Controller example

    +

    Controller examples

    + +
      +
    • + <.link href="/noroute">Generate a 404 error from the controller +
    • +
    • + <.link href="/exception">Generate an exception from the controller +
    • +
    • + <.link href="/exit">Generate an exit from the controller +
    • +
    """ end end @@ -148,6 +180,9 @@ defmodule ErrorTrackerDev.Router do pipe_through :browser live "/", ErrorTrackerDev.Live + get "/noroute", ErrorTrackerDev.Controller, :noroute + get "/exception", ErrorTrackerDev.Controller, :exception + get "/exit", ErrorTrackerDev.Controller, :exit scope "/dev" do error_tracker_dashboard "/errors" @@ -156,8 +191,10 @@ defmodule ErrorTrackerDev.Router do end defmodule ErrorTrackerDev.Endpoint do - # Default PhoenixPlayground.Endpoint use Phoenix.Endpoint, otp_app: :phoenix_playground + use ErrorTracker.Integrations.Plug + + # Default PhoenixPlayground.Endpoint plug Plug.Logger socket "/live", Phoenix.LiveView.Socket plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix" @@ -170,6 +207,16 @@ defmodule ErrorTrackerDev.Endpoint do plug ErrorTrackerDev.Router end +defmodule ErrorTrackerDev.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 ErrorTrackerDev.GenServer do use GenServer @@ -193,6 +240,10 @@ defmodule ErrorTrackerDev.GenServer do end end +defmodule ErrorTrackerDev.Exception do + defexception [:message, :bread_crumbs] +end + PhoenixPlayground.start( endpoint: ErrorTrackerDev.Endpoint, child_specs: [ From db720f9f3d84a5d57bb433b34eb15aa87dc6191e Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 12:24:06 +0100 Subject: [PATCH 07/11] Log ErrorTracker telemetry events --- dev.exs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dev.exs b/dev.exs index 60a5049..e18d7a2 100644 --- a/dev.exs +++ b/dev.exs @@ -244,6 +244,12 @@ defmodule ErrorTrackerDev.Exception do defexception [:message, :bread_crumbs] end +defmodule ErrorTrackerDev.Telemetry do + def handle_event(event, measure, metadata, _opts) do + dbg([event, measure, metadata]) + end +end + PhoenixPlayground.start( endpoint: ErrorTrackerDev.Endpoint, child_specs: [ @@ -255,3 +261,15 @@ PhoenixPlayground.start( ) ErrorTrackerDev.Repo.migrate() + +:telemetry.attach_many( + "error-tracker-events", + [ + [:error_tracker, :error, :new], + [:error_tracker, :error, :resolved], + [:error_tracker, :error, :unresolved], + [:error_tracker, :occurrence, :new] + ], + &ErrorTrackerDev.Telemetry.handle_event/4, + [] +) From acd83aa47ac4a7f3a0d92fc718f09c260e842c45 Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 12:24:06 +0100 Subject: [PATCH 08/11] Use custom CSP nonce --- dev.exs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/dev.exs b/dev.exs index e18d7a2..0eae520 100644 --- a/dev.exs +++ b/dev.exs @@ -185,7 +185,7 @@ defmodule ErrorTrackerDev.Router do get "/exit", ErrorTrackerDev.Controller, :exit scope "/dev" do - error_tracker_dashboard "/errors" + error_tracker_dashboard "/errors", csp_nonce_assign_key: :custom_csp_nonce end end end @@ -203,8 +203,23 @@ defmodule ErrorTrackerDev.Endpoint do plug Phoenix.LiveReloader plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2 + # Use a custom Content Security Policy + plug :set_csp # Our custom router which allows us to have regular controllers and live views plug ErrorTrackerDev.Router + + 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(:custom_csp_nonce, "#{nonce}") + |> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " ")) + end end defmodule ErrorTrackerDev.ErrorView do From ed0c51e56de6010d42509e39c51f51ec211609ab Mon Sep 17 00:00:00 2001 From: crbelaus Date: Sat, 14 Dec 2024 12:24:06 +0100 Subject: [PATCH 09/11] Remove old dev script and update README --- README.md | 6 +- old_dev.exs | 223 ---------------------------------------------------- 2 files changed, 2 insertions(+), 227 deletions(-) delete mode 100644 old_dev.exs diff --git a/README.md b/README.md index 98b1de1..5e79930 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,8 @@ mix assets.watch ### Development server -We have a `dev.exs` script that starts a development server. - -To run it together with an `IEx` console you can do: +We have a `dev.exs` script based on [Phoenix Playground](https://github.com/phoenix-playground/phoenix_playground) that starts a development server. ``` -iex -S mix dev +iex dev.exs ``` diff --git a/old_dev.exs b/old_dev.exs deleted file mode 100644 index 895749d..0000000 --- a/old_dev.exs +++ /dev/null @@ -1,223 +0,0 @@ -####################################### -# Development Server for ErrorTracker. -# -# Based on PhoenixLiveDashboard code. -# -# Usage: -# -# $ iex -S mix dev -####################################### -Logger.configure(level: :debug) - -# Get configuration -Config.Reader.read!("config/config.exs", env: :dev) - -# Prepare the repo -adapter = - case Application.get_env(:error_tracker, :ecto_adapter) do - :postgres -> Ecto.Adapters.Postgres - :mysql -> Ecto.Adapters.MyXQL - :sqlite3 -> Ecto.Adapters.SQLite3 - end - -defmodule ErrorTrackerDev.Repo do - use Ecto.Repo, otp_app: :error_tracker, adapter: adapter -end - -_ = adapter.storage_up(ErrorTrackerDev.Repo.config()) - -# Configures the endpoint -Application.put_env(:error_tracker, ErrorTrackerDevWeb.Endpoint, - url: [host: "localhost"], - secret_key_base: "Hu4qQN3iKzTV4fJxhorPQlA/osH9fAMtbtjVS58PFgfw3ja5Z18Q/WSNR9wP4OfW", - live_view: [signing_salt: "hMegieSe"], - http: [port: System.get_env("PORT") || 4000], - debug_errors: true, - check_origin: false, - pubsub_server: ErrorTrackerDev.PubSub, - watchers: [ - tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} - ], - live_reload: [ - patterns: [ - ~r"dev.exs$", - ~r"dist/.*(js|css|png|jpeg|jpg|gif|svg)$", - ~r"lib/error_tracker/web/(live|views)/.*(ex)$", - ~r"lib/error_tracker/web/templates/.*(ex)$" - ] - ] -) - -# Setup up the ErrorTracker configuration -Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo) -Application.put_env(:error_tracker, :otp_app, :error_tracker_dev) -Application.put_env(:error_tracker, :prefix, "private") - -defmodule ErrorTrackerDevWeb.PageController do - import Plug.Conn - - def init(opts), do: opts - - def call(conn, :index) do - content(conn, """ -

    ErrorTracker Dev Server

    - - - - - - - """) - 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