error_handler.ex (3499B)
1 defmodule Plug.ErrorHandler do 2 @moduledoc """ 3 A module to be used in your existing plugs in order to provide 4 error handling. 5 6 defmodule AppRouter do 7 use Plug.Router 8 use Plug.ErrorHandler 9 10 plug :match 11 plug :dispatch 12 13 get "/hello" do 14 send_resp(conn, 200, "world") 15 end 16 17 @impl Plug.ErrorHandler 18 def handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do 19 send_resp(conn, conn.status, "Something went wrong") 20 end 21 end 22 23 Once this module is used, a callback named `handle_errors/2` should 24 be defined in your plug. This callback will receive the connection 25 already updated with a proper status code for the given exception. 26 The second argument is a map containing: 27 28 * the exception kind (`:throw`, `:error` or `:exit`), 29 * the reason (an exception for errors or a term for others) 30 * the stacktrace 31 32 After the callback is invoked, the error is re-raised. 33 34 It is advised to do as little work as possible when handling errors 35 and avoid accessing data like parameters and session, as the parsing 36 of those is what could have led the error to trigger in the first place. 37 38 Also notice that these pages are going to be shown in production. If 39 you are looking for error handling to help during development, consider 40 using `Plug.Debugger`. 41 42 **Note:** If this module is used with `Plug.Debugger`, it must be used 43 after `Plug.Debugger`. 44 """ 45 46 @doc """ 47 Handle errors from plugs. 48 49 Called when an exception is raised during the processing of a plug. 50 """ 51 @callback handle_errors(Plug.Conn.t(), %{ 52 kind: :error | :throw | :exit, 53 reason: Exception.t() | term(), 54 stack: Exception.stacktrace() 55 }) :: no_return() 56 57 @doc false 58 defmacro __using__(_) do 59 quote location: :keep do 60 @before_compile Plug.ErrorHandler 61 62 @behaviour Plug.ErrorHandler 63 64 @impl Plug.ErrorHandler 65 def handle_errors(conn, assigns) do 66 Plug.Conn.send_resp(conn, conn.status, "Something went wrong") 67 end 68 69 defoverridable handle_errors: 2 70 end 71 end 72 73 @doc false 74 defmacro __before_compile__(_env) do 75 quote location: :keep do 76 defoverridable call: 2 77 78 def call(conn, opts) do 79 try do 80 super(conn, opts) 81 rescue 82 e in Plug.Conn.WrapperError -> 83 %{conn: conn, kind: kind, reason: reason, stack: stack} = e 84 Plug.ErrorHandler.__catch__(conn, kind, e, reason, stack, &handle_errors/2) 85 catch 86 kind, reason -> 87 Plug.ErrorHandler.__catch__( 88 conn, 89 kind, 90 reason, 91 reason, 92 __STACKTRACE__, 93 &handle_errors/2 94 ) 95 end 96 end 97 end 98 end 99 100 @already_sent {:plug_conn, :sent} 101 102 @doc false 103 def __catch__(conn, kind, reason, wrapped_reason, stack, handle_errors) do 104 receive do 105 @already_sent -> 106 send(self(), @already_sent) 107 after 108 0 -> 109 normalized_reason = Exception.normalize(kind, wrapped_reason, stack) 110 111 conn 112 |> Plug.Conn.put_status(status(kind, normalized_reason)) 113 |> handle_errors.(%{kind: kind, reason: normalized_reason, stack: stack}) 114 end 115 116 :erlang.raise(kind, reason, stack) 117 end 118 119 defp status(:error, error), do: Plug.Exception.status(error) 120 defp status(:throw, _throw), do: 500 121 defp status(:exit, _exit), do: 500 122 end