defmodule MicrowavepropWeb.MetricsPlug do @moduledoc """ Serves PromEx metrics at `/metrics`. Optionally gated by a bearer token via `config :microwaveprop, :prometheus_auth_token`, sourced from the `PROMETHEUS_AUTH_TOKEN` env var in `runtime.exs`. """ @behaviour Plug import Plug.Conn @impl true def init(opts), do: opts @impl true def call(conn, _opts) do case authorized?(conn) do :ok -> body = PromEx.get_metrics(Microwaveprop.PromEx) # Defer the ETS cron flusher; otherwise it runs every # `ets_flush_interval` (7.5s) concurrently with scrapes and its # `Task.await` (hardcoded 10s) trips under contention, killing # the GenServer. Upstream `PromEx.Plug` does the same. PromEx.ETSCronFlusher.defer_ets_flush(Microwaveprop.PromEx.__ets_cron_flusher_name__()) conn |> put_resp_content_type("text/plain; version=0.0.4") |> send_resp(200, body) |> halt() :denied -> conn |> put_resp_header("www-authenticate", ~s(Bearer realm="metrics")) |> send_resp(401, "unauthorized") |> halt() end end defp authorized?(conn) do case Application.get_env(:microwaveprop, :prometheus_auth_token) do token when is_binary(token) and token != "" -> check_token(conn, token) _ -> :ok end end defp check_token(conn, expected) do conn |> get_req_header("authorization") |> List.first() |> case do "Bearer " <> token -> if Plug.Crypto.secure_compare(token, expected), do: :ok, else: :denied _ -> :denied end end end