PrePlug

By default, if you’re using an error handler and an error is thrown inside Plug, you lose your entire chain of things registered to happen in :before_send (because you don’t have changes reflected in the connection) - meaning that for things like request logging and monitoring, Plug is extremely awkward. Please see the example a little further down for more information. This module provides a workaround for this issue. My hope is that this eventually gets into Plug somehow, but until then I quite simply must have a way to do this.

Installation

If available in Hex, the package can be installed as:

  1. Add pre_plug to your list of dependencies in mix.exs:

    def deps do
      [{:pre_plug, "~> 1.0"}]
    end
  2. Ensure pre_plug is started before your application:

    def application do
      [applications: [:pre_plug]]
    end

Usage

Pretty easy, just use PrePlug and use pre_plug instead of plug for all of the Plugs which you require always take effect regardless of error. Be vigilant with this, and only use pre_plug when you absolutely must in order to avoid any unintended side effects (not that I know of any, but better safe than sorry).

defmodule MyApp.Router do
  use Plug.Router
  use PrePlug

  # pre_plug as it must always fire properly
  pre_plug Plug.Logger

  # plug for anything else
  plug :match
  plug :dispatch
end

Examples

In order to explain this properly, there needs to be an example (as Plug is very misleading here).

Consider the router below, and pay close attention to the Logger:

defmodule PlugTest.Router do
  # import Conn
  import Plug.Conn

  # pull in any Plug dependencies
  use Plug.ErrorHandler
  use Plug.Router

  # add first plug
  plug Plug.Logger

  # plug requirements
  plug :match
  plug :dispatch

  get "/" do
    raise Plug.BadRequestError
  end

  defp handle_errors(conn, _) do
    send_resp(conn, conn.status, "Something went wrong!")
  end
end

All looks typical right? Nothing special, just the built in Plug routing tools. However, if you call this endpoint, you’ll see this in your terminal output:

00:06:35.396 [info]  GET /

The Logger fired the initial log which happens on request start, but the log intended to fire at the end of the request (which contains things such as response times, codes, etc.) did not. This means that for all you know, the request is on-going. There are a number of problems here, particularly if you’re relying on monitoring tools to fire on errors. You could have an error which hangs for 10 minutes before it exits - how can you tell with the current implementation?

Using this module, you would simply swap out plug Plug.Logger with pre_plug Plug.Logger. Make sure to also use PrePlug before your usage of pre_plug, and voila, here is your output:

00:09:31.481 [info]  GET /
00:09:31.487 [info]  Sent 400 in 6ms

Ok, perfect. Now we can see the response code as well as the execution time in our logs. This means we can error handle as necessary and do basically whatever we want.

Warnings

I should point out that this does not mean that you should change all of your plugs to use pre_plug, only those which make sense to (e.g. logging, monitoring, other critical components).

I have been using this safely in a production app with no issue for a few months now, but it goes without saying to test your implementations thoroughly as this is pretty raw code.