prop/lib/microwaveprop_web/metrics_plug.ex
Graham McIntire c7bc3ed5d0
feat(telemetry): PromEx Prometheus exporter at /metrics
Wires PromEx with its built-in Application / BEAM / Phoenix / Ecto /
Oban plugins plus a custom `Microwaveprop.PromEx.InstrumentPlugin`
that registers Prometheus histograms for every
`Microwaveprop.Instrument` span (HRRR/NEXRAD/IEM/GEFS/NARR/UWYO
/Elevation fetches, DB batch upserts, terrain analysis, radar
aggregation, propagation score band). Queue-depth gauges from the
10-second poller land at `microwaveprop_oban_queue_count{queue,state}`.

Router forwards `/metrics` to MicrowavepropWeb.MetricsPlug which
optionally requires a bearer token (`PROMETHEUS_AUTH_TOKEN` env var in
runtime.exs); leave it unset to expose metrics publicly and restrict
at the ingress / Cloudflare Access layer instead.

Example external Prometheus scrape config:

    scrape_configs:
      - job_name: microwaveprop
        scrape_interval: 30s
        metrics_path: /metrics
        scheme: https
        authorization:
          type: Bearer
          credentials: <token>
        static_configs:
          - targets: ['prop.w5isp.com:443']
2026-04-18 16:39:39 -05:00

51 lines
1.2 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 ->
PromEx.Plug.call(conn, prom_ex_module: Microwaveprop.PromEx, path: "/metrics")
: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