Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unclear how to handle errors in Phoenix.Endpoint #2791

Closed
mitchellhenke opened this issue Mar 8, 2018 · 4 comments
Closed

Unclear how to handle errors in Phoenix.Endpoint #2791

mitchellhenke opened this issue Mar 8, 2018 · 4 comments

Comments

@mitchellhenke
Copy link
Contributor

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 🙂

@josevalim
Copy link
Member

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.

@josevalim
Copy link
Member

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.

@mitchellhenke
Copy link
Contributor Author

@josevalim thanks for the quick response! that works perfectly 💖

@kasvith
Copy link

kasvith commented Jan 3, 2023

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants