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/dev.exs b/dev.exs
index 895749d..a09f496 100644
--- a/dev.exs
+++ b/dev.exs
@@ -1,169 +1,217 @@
-#######################################
-# 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)
+Mix.install([
+ {:ecto_sqlite3, ">= 0.0.0"},
+ {:error_tracker, path: "."},
+ {:phoenix_playground, "~> 0.1.7"}
+])
-# Get configuration
-Config.Reader.read!("config/config.exs", env: :dev)
+otp_app = :error_tracker_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
+Application.put_all_env(
+ error_tracker_dev: [
+ {ErrorTrackerDev.Repo, [database: "priv/repo/dev.db"]}
+ ],
+ error_tracker: [
+ {:application, otp_app},
+ {:otp_app, otp_app},
+ {:repo, ErrorTrackerDev.Repo}
+ ]
+)
defmodule ErrorTrackerDev.Repo do
- use Ecto.Repo, otp_app: :error_tracker, adapter: adapter
+ require Logger
+ use Ecto.Repo, otp_app: otp_app, adapter: Ecto.Adapters.SQLite3
+
+ defmodule Migration do
+ use Ecto.Migration
+
+ def up, do: ErrorTracker.Migration.up()
+ def down, do: ErrorTracker.Migration.down()
+ end
+
+ def migrate do
+ Ecto.Migrator.run(__MODULE__, [{0, __MODULE__.Migration}], :up, all: true)
+ end
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)$"
- ]
- ]
-)
+defmodule ErrorTrackerDev.Controller do
+ use Phoenix.Controller, formats: [:html]
+ use Phoenix.Component
-# 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")
+ plug :put_layout, false
+ plug :put_view, __MODULE__
-defmodule ErrorTrackerDevWeb.PageController do
- import Plug.Conn
+ def index(conn, _params) do
+ render(conn)
+ end
- def init(opts), do: opts
+ def index(assigns) do
+ ~H"""
+
ErrorTracker Dev server
- 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
+ def noroute(conn, _params) do
+ ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.noroute/2")
+
+ raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDev.Router
end
- def call(_conn, :exception) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exception")
+ def exception(_conn, _params) do
+ ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exception/2")
- raise CustomException,
+ raise ErrorTrackerDev.Exception,
message: "This is a controller exception",
bread_crumbs: ["First", "Second"]
end
- def call(_conn, :exit) do
- ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exit")
+ def exit(_conn, _params) do
+ ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exit/2")
+
exit(:timeout)
end
+end
- defp content(conn, content) do
- conn
- |> put_resp_header("content-type", "text/html")
- |> send_resp(200, "#{content}")
+defmodule ErrorTrackerDev.Live do
+ use Phoenix.LiveView
+
+ def mount(params, _session, socket) do
+ if params["crash_on_mount"] do
+ raise("Crashed on mount/3")
+ end
+
+ {:ok, socket}
end
-end
-defmodule CustomException do
- defexception [:message, :bread_crumbs]
-end
+ def handle_params(params, _uri, socket) do
+ if params["crash_on_handle_params"] do
+ raise "Crashed on handle_params/3"
+ end
-defmodule ErrorTrackerDevWeb.ErrorView do
- def render("404.html", _assigns) do
- "This is a 404"
+ {:noreply, socket}
end
- def render("500.html", _assigns) do
- "This is a 500"
+ def handle_event("crash_on_handle_event", _params, _socket) do
+ raise "Crashed on handle_event/3"
+ end
+
+ def handle_event("crash_on_render", _params, socket) 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"
+ end
+
+ ~H"""
+ ErrorTracker Dev server
+
+ <.link href="/dev/errors" target="_blank">Open the ErrorTracker dashboard
+
+
+ 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
+
+
+
+ <.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
+
+
+ <.link phx-click="genserver-timeout">Crash with a GenServer timeout
+
+
+
+ Controller examples
+
+
+
+ <.link href="/noroute">Generate a 404 error from the controller
+
+
+ <.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
+
+
+ """
end
end
-defmodule ErrorTrackerDevWeb.Router do
+defmodule ErrorTrackerDev.Router do
use Phoenix.Router
use ErrorTracker.Web, :router
+ import Phoenix.LiveView.Router
+
pipeline :browser do
- plug :fetch_session
- plug :protect_from_forgery
+ plug :accepts, [:html]
+ plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}
+ plug :put_secure_browser_headers
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
+
+ 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", csp_nonce_assign_key: :my_csp_nonce
+ error_tracker_dashboard "/errors", csp_nonce_assign_key: :custom_csp_nonce
end
end
end
-defmodule ErrorTrackerDevWeb.Endpoint do
- use Phoenix.Endpoint, otp_app: :error_tracker
+defmodule ErrorTrackerDev.Endpoint do
+ use Phoenix.Endpoint, otp_app: :phoenix_playground
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]]
+ # Default PhoenixPlayground.Endpoint
+ 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
-
- plug Plug.Session, @session_options
+ plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2
- plug Plug.RequestId
- plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
- plug :add_breadcrumb
- plug :maybe_exception
+ # Use a custom Content Security Policy
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
+ # 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
defp set_csp(conn, _opts) do
nonce = 10 |> :crypto.strong_rand_bytes() |> Base.encode64()
@@ -174,50 +222,80 @@ defmodule ErrorTrackerDevWeb.Endpoint do
]
conn
- |> Plug.Conn.assign(:my_csp_nonce, "#{nonce}")
+ |> 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.Telemetry do
- require Logger
+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
- 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,
- []
- )
+ # Client
- Logger.info("Telemtry attached")
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, %{})
end
- def handle_event(event, measure, metadata, _opts) do
- dbg([event, measure, metadata])
+ # Server (callbacks)
+
+ @impl true
+ def init(initial_state) do
+ {:ok, initial_state}
end
-end
-Application.put_env(:phoenix, :serve_endpoints, true)
+ @impl true
+ def handle_call(:timeout, _from, state) do
+ :timer.sleep(5000)
+ {:reply, state, state}
+ end
+end
-Task.async(fn ->
- children = [
- {Phoenix.PubSub, [name: ErrorTrackerDev.PubSub, adapter: Phoenix.PubSub.PG2]},
- ErrorTrackerDev.Repo,
- ErrorTrackerDevWeb.Endpoint
- ]
+defmodule ErrorTrackerDev.Exception do
+ defexception [:message, :bread_crumbs]
+end
- ErrorTrackerDev.Telemetry.start()
+defmodule ErrorTrackerDev.Telemetry do
+ def handle_event(event, measure, metadata, _opts) do
+ dbg([event, measure, metadata])
+ end
+end
- {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one)
+PhoenixPlayground.start(
+ endpoint: ErrorTrackerDev.Endpoint,
+ child_specs: [
+ {ErrorTrackerDev.Repo, []},
+ {ErrorTrackerDev.GenServer, [name: ErrorTrackerDev.GenServer]}
+ ],
+ open_browser: false,
+ debug_errors: false
+)
- # Automatically run the migrations on boot
- Ecto.Migrator.run(ErrorTrackerDev.Repo, :up, all: true, log_migrations_sql: :debug)
+ErrorTrackerDev.Repo.migrate()
- Process.sleep(:infinity)
-end)
+: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,
+ []
+)
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 %>
- +
- -
- Crash on handle_event
- GenServer timeout
-
- <.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
-)