lib/plug/logger.ex

defmodule ExJsonLogger.Plug.Logger do
  @moduledoc """
  A plug for logging request information through metadata.

  Logger Metadata available:
   * method - request method
   * path - request path
   * status - request status (integer)
   * duration - time in milliseconds from connection to response (float)
   * format - (phoenix specific)
   * controller - (phoenix specific)
   * action - (phoenix specific)
   * query_string - raw unparsed request query string

  Metadata is filtered by default so keys will need to be whitelisted.

  ## Usage

  To use with a Phoenix application, replace `plug Plug.Logger` in the projects endpoint.ex file with `ExJsonLogger.Plug.Logger`:

      # plug Plug.Logger
      plug ExJsonLogger.Plug.Logger

  ## Options
  * `:log` - The log level at which this plug should log its request info.
  Default is `:info`.
  """

  @behaviour Plug

  require Logger

  alias Plug.Conn

  @spec init(Keyword.t()) :: Logger.level()
  def init(opts) do
    Keyword.get(opts, :log, :info)
  end

  @spec call(Conn.t(), Logger.level()) :: Conn.t()
  def call(conn, level) do
    start_time = current_time()

    Conn.register_before_send(conn, fn conn ->
      stop_time = current_time()
      diff = stop_time - start_time
      duration = System.convert_time_unit(diff, :native, :microsecond) / 1000

      metadata =
        []
        |> Keyword.put(:method, conn.method)
        |> Keyword.put(:path, conn.request_path)
        |> Keyword.put(:status, conn.status)
        |> Keyword.put(:duration, duration)
        |> Keyword.put(:query_string, conn.query_string)
        |> Keyword.merge(formatted_phoenix_info(conn))

      Logger.log(level, fn -> {"", metadata} end)

      conn
    end)
  end

  defp formatted_phoenix_info(%{
         private: %{
           phoenix_format: format,
           phoenix_controller: controller,
           phoenix_action: action
         }
       }) do
    [
      {:format, format},
      {:controller, inspect(controller)},
      {:action, Atom.to_string(action)}
    ]
  end

  defp formatted_phoenix_info(_), do: []

  defp current_time, do: System.monotonic_time()
end