Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@ config :error_tracker,
repo: MyApp.Repo
```

Attach to Oban events:
And you are ready to go!

```elixir
defmodule MyApp.Application do
def start(_type, _args) do
ErrorTracker.Integrations.Oban.attach()
end
end
```
By default Phoenix and Oban integrations will start registering exceptions.

Attach to Plug errors:
If you want to also catch exceptions before your Phoenix Router (in plugs used
on your Endpoint) or your application just use `Plug` but not `Phoenix`, you can
attach to those errors with:

```elixir
defmodule MyApp.Endpoint do
use ErrorTracker.Plug
use ErrorTracker.Integrations.Plug
end
```
27 changes: 24 additions & 3 deletions dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,19 @@ defmodule ErrorTrackerDevWeb.PageController do
content(conn, """
<h2>ErrorTracker Dev Server</h2>
<div><a href="/errors">Open ErrorTracker</a></div>
<div><a href="/404">Generate 404 Error</a></div>
<div><a href="/plug-exception">Generate Plug exception</a></div>
<div><a href="/404">Generate Router 404</a></div>
<div><a href="/noroute">Raise NoRouteError from a controller</a></div>
<div><a href="/exception">Generate Exception</a></div>
""")
end

def call(conn, :noroute) do
raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDevWeb.Router
end

def call(_conn, :exception) do
raise "This is an error"
raise "This is a controller exception"
end

defp content(conn, content) do
Expand All @@ -71,9 +77,18 @@ defmodule ErrorTrackerDevWeb.PageController do
end
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.Integrations.Plug

pipeline :browser do
plug :fetch_session
Expand All @@ -83,12 +98,14 @@ defmodule ErrorTrackerDevWeb.Router do
scope "/" do
pipe_through :browser
get "/", ErrorTrackerDevWeb.PageController, :index
get "/noroute", ErrorTrackerDevWeb.PageController, :noroute
get "/exception", ErrorTrackerDevWeb.PageController, :exception
end
end

defmodule ErrorTrackerDevWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :error_tracker
use ErrorTracker.Integrations.Plug

@session_options [
store: :cookie,
Expand All @@ -107,7 +124,11 @@ defmodule ErrorTrackerDevWeb.Endpoint do

plug Plug.RequestId
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
plug :maybe_exception
plug ErrorTrackerDevWeb.Router

def maybe_exception(%Plug.Conn{path_info: ["plug-exception"]}, _), do: raise("Plug exception")
def maybe_exception(conn, _), do: conn
end

defmodule Migration0 do
Expand Down
1 change: 1 addition & 0 deletions lib/error_tracker/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ defmodule ErrorTracker.Application do

defp attach_handlers do
ErrorTracker.Integrations.Oban.attach()
ErrorTracker.Integrations.Phoenix.attach()
end
end
32 changes: 32 additions & 0 deletions lib/error_tracker/integrations/phoenix.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule ErrorTracker.Integrations.Phoenix do
alias ErrorTracker.Integrations.Plug, as: PlugIntegration

def attach do
if Application.spec(:phoenix) do
:telemetry.attach(
__MODULE__,
[:phoenix, :router_dispatch, :exception],
&__MODULE__.handle_exception/4,
[]
)
end
end

def handle_exception(
[:phoenix, :router_dispatch, :exception],
_measurements,
%{reason: %Plug.Conn.WrapperError{conn: conn, reason: reason, stack: stack}},
_opts
) do
PlugIntegration.report_error(conn, reason, stack)
end

def handle_exception(
[:phoenix, :router_dispatch, :exception],
_measurements,
%{reason: reason, stacktrace: stack, conn: conn},
_opts
) do
PlugIntegration.report_error(conn, reason, stack)
end
end
43 changes: 36 additions & 7 deletions lib/error_tracker/integrations/plug.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
defmodule ErrorTracker.Integrations.Plug do
defmacro __using__(_opts) do
quote do
use Plug.ErrorHandler
@before_compile unquote(__MODULE__)
end
end

defmacro __before_compile__(_) do
quote do
defoverridable call: 2

def call(conn, opts) do
try do
super(conn, opts)
rescue
e in Plug.Conn.WrapperError ->
unquote(__MODULE__).report_error(conn, e, e.stack)

@impl Plug.ErrorHandler
def handle_errors(conn, %{kind: :error, reason: exception, stack: stack}) do
ErrorTracker.report(exception, stack)
Plug.Conn.WrapperError.reraise(e)

:ok
e ->
stack = __STACKTRACE__
unquote(__MODULE__).report_error(conn, e, stack)

:erlang.raise(:error, e, stack)
catch
kind, reason ->
stack = __STACKTRACE__
unquote(__MODULE__).report_error(conn, reason, stack)

:erlang.raise(kind, reason, stack)
end
end
end
end

def handle_errors(conn, _throw_or_exit) do
:ok
def report_error(_conn, reason, stack) do
unless Process.get(:error_tracker_router_exception_reported) do
# TODO: Add metadata from conn when implemented
try do
ErrorTracker.report(reason, stack)
after
Process.put(:error_tracker_router_exception_reported, true)
end
end
end
Expand Down