lib/exbox/metrics/metric_handler.ex

defmodule Exbox.Metrics.MetricHandler do
  @moduledoc """
  This module is responsible for logging controller metrics to influx
  """
  alias Exbox.Metrics.Client
  alias Exbox.Metrics.ControllerSeries
  require Logger

  @doc """
  This function is called by the Phoenix endpoint when a controller action is
  finished. It will log the controller metrics to influx.

  Examples:

      iex> Exbox.Metrics.MetricHandler.handle_event([:phoenix, :endpoint, :stop], measurements, metadata, config)
      :ok
  """
  @spec handle_event(list(atom), map, map, map) :: any()
  def handle_event([:phoenix, :endpoint, :stop], measurements, metadata, config) do
    status = metadata.conn.status

    point =
      %ControllerSeries{}
      |> ControllerSeries.tag(:method, metadata.conn.method)
      |> ControllerSeries.tag(
        :action,
        Atom.to_string(Map.get(metadata.conn.private, :phoenix_action, nil))
      )
      |> ControllerSeries.tag(:format, metadata.conn.private.phoenix_format)
      |> ControllerSeries.tag(:status, status)
      |> ControllerSeries.tag(
        :controller,
        Atom.to_string(metadata.conn.private.phoenix_controller)
      )
      |> ControllerSeries.field(:count, 1)
      |> ControllerSeries.field(:trace_id, "empty_for_now")
      |> ControllerSeries.field(:success, success?(status))
      |> ControllerSeries.field(:path, metadata.conn.request_path)
      |> ControllerSeries.field(:http_referer, referer(metadata.conn))
      |> ControllerSeries.field(:duration_ms, duration(measurements))

    point
    |> write_metric(config)
  rescue
    exception ->
      Logger.debug("Exception creating controller series: #{inspect(exception)}")
  end

  defp write_metric(metric, %{metric_client: client}) do
    metric
    |> client.write_metric()
  end

  defp write_metric(metric, _config) do
    metric
    |> Client.write_metric()
  end

  defp duration(measurements) do
    System.convert_time_unit(measurements.duration, :native, :millisecond)
  end

  defp success?(status) do
    case status do
      status when status in 200..399 -> 1.0
      _status -> 0.0
    end
  end

  defp referer(conn) do
    case Enum.find(conn.req_headers, fn {k, _} -> k == "referer" end) do
      {_, value} -> value
      nil -> nil
    end
  end
end