Unclear how to handle errors in Phoenix.Endpoint
mitchellhenke opened this issue ยท 4 comments
Environment
- Elixir version (elixir -v): 1.6.2
- Phoenix version (mix deps): 1.3.1
- Operating system: macOS 10.13.3
Apologies ahead of time as this is an issue that is difficult for me to explain, so this is a bit rambly ๐
This originated from getsentry/sentry-elixir#229
Currently, to catch errors in Phoenix during the request process, using Plug.ErrorHandler in the Router works well. The limitation being if the error happens before the request reaches the Router in the Endpoint, and I haven't been able to find a way I like to catch them.
Since Phoenix.Endpoint
defines an overridable call/2
here I was thinking I could override and include my own error handling code by adding something like:
# lib/my_app_web/endpoint.ex
# ...
def call(conn, opts) do
IO.inspect(conn.private)
conn = put_in conn.secret_key_base, config(:secret_key_base)
conn = put_in conn.script_name, script_name()
conn = Plug.Conn.put_private(conn, :phoenix_endpoint, __MODULE__)
try do
super(conn, opts)
catch
kind, reason ->
stack = System.stacktrace()
MyErrorHandlingService.report(kind, reason, stack)
Phoenix.Endpoint.RenderErrors.__catch__(conn, kind, reason, @phoenix_render_errors)
end
end
# => %{phoenix_endpoint: MyAppWeb.Endpoint}
This works in that my error handling service gets called. The issue with it is it seems like both the original call/2
that Phoenix.Endpoint defines gets called first, and then mine gets called. It seems that way because the inspect above prints out :private
with the endpoint module before my code puts it in there.
I also tried to use Plug.ErrorHandler
in the Endpoint, but any handle_errors/2
that get defined don't get called. I'm guessing because Phoenix.Endpoint
works similarly in defining an overridable call/2
function?
Expected behavior
I'm not sure how I would expect to be able to handle the errors. I'm happy to try solutions I have missed or put together a PR.
If there isn't a good way currently, being able to define a function to handle errors in the Phoenix.Endpoint
catch could work (similar to Plug.ErrorHandler
), but I'm open to anything that makes it easier ๐
Phoenix' call is happening in a @before_compile
, so you need to make sure to register your hook in a before compile too. That's basically what Plug.ErrorHandler does (so I would recommend to even not depend on it and do your own try/catch block, see Plug.ErrorHandler source).
Also note that you shouldn't replicate what Phoenix does, because you would be fiddling with Phoenix internals and that's error prone. :) Although you probably only did that for experimentation purposes.
Here is the building block that you want:
defmodule Sample do
defmacro __using__(_opts) do
quote do
@before_compile Sample
end
end
defmacro __before_compile__(_) do
quote do
defoverridable call: 2
def call(conn, opts) do
try do
super(conn, opts)
catch
kind, reason ->
Sentry.stuff(...)
:erlang.raise(kind, reason, System.stacktrace)
end
end
end
end
end
It should work for plugs, phoenix, etc.
@josevalim thanks for the quick response! that works perfectly ๐
@josevalim Can we use the same to tackle this problem? absinthe-graphql/absinthe#1219
Problem would be Absinthe context would be gone at this point