prop/lib/microwaveprop_web/metrics_plug.ex
Graham McIntire 9af50a9e34
fix(metrics): serve scrape body via PromEx.get_metrics
PromEx.Plug's internal path check doesn't match when forwarded under
/metrics (conn.path_info is [] after the forward strips the prefix),
so it silently returned without sending a response. Call
PromEx.get_metrics/1 directly and send the text body ourselves —
tested, returns 200 with all 23 registered event handlers producing
proper Prometheus histogram output.
2026-04-18 16:50:37 -05:00

56 lines
1.3 KiB
Elixir

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)
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