lib/honeybadger/plug.ex

if Code.ensure_loaded?(Plug) do
  defmodule Honeybadger.Plug do
    @moduledoc """
    The `Honeybadger.Plug` adds automatic error handling to a plug pipeline.

    Within a `Plug.Router` or `Phoenix.Router` use the module and crashes will
    be reported to Honeybadger. It's best to `use Honeybadger.Plug` **after
    the Router plugs** so that exceptions due to non-matching routes are not
    reported to Honeybadger.

    ### Example

        defmodule MyPhoenixApp.Router do
          use Crywolf.Web, :router
          use Honeybadger.Plug

          pipeline :browser do
            [...]
          end
        end

    ## Customizing

    Data reporting may be customized by passing an alternate `:plug_data`
    module. This is useful when working with alternate frameworks, such as
    Absinthe for GraphQL APIs.

    Any module with a `metadata/2` function that accepts a `Plug.Conn` and a
    `module` name can be used to generate metadata.

    ### Example

        defmodule MyPhoenixApp.Router do
          use Crywolf.Web, :router
          use Honeybadger.Plug, plug_data: MyAbsinthePlugData
        end
    """

    alias Honeybadger.PlugData
    alias Honeybadger.Breadcrumbs.{Breadcrumb, Collector}

    @doc false
    defmacro __using__(opts) do
      quote location: :keep do
        use Plug.ErrorHandler

        @plug_data Keyword.get(unquote(opts), :plug_data, PlugData)

        @doc """
        Called by `Plug.ErrorHandler` when an error is caught.

        By default this ignores "Not Found" errors for `Plug` or `Phoenix`
        pipelines. It may be overridden to ignore additional errors or to
        customize the data that is used for notifications.
        """
        @impl Plug.ErrorHandler
        def handle_errors(_conn, %{reason: %FunctionClauseError{function: :do_match}}), do: :ok

        def handle_errors(conn, %{reason: reason, stack: stack}) do
          if Plug.Exception.status(reason) == 404 do
            # 404 errors are not reported
            :ok
          else
            Collector.add(Breadcrumb.from_error(reason))
            metadata = @plug_data.metadata(conn, __MODULE__)
            Honeybadger.notify(reason, metadata: metadata, stacktrace: stack)
          end
        end

        defoverridable handle_errors: 2
      end
    end
  end
end