How to Monitor Req with Vigilmon
Req is Elixir's modern HTTP client — built on Finch, with automatic retries, response caching, compression, JSON encoding/decoding, and a composable plugin system baked in. It's the go-to client for Elixir applications that call third-party APIs, internal microservices, or webhooks. But Req's reliability features can mask real problems: automatic retries hide intermittent failures, response caching can serve stale data after an upstream change, and base URL configuration mistakes silently route requests to the wrong endpoint.
This tutorial sets up monitoring for applications that use Req as their HTTP client:
- A health endpoint that exercises your Req-based integrations
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring for scheduled Req jobs
- Request failure rate tracking via telemetry
- Slack alerts when upstream APIs degrade
Why Monitor Req-based Integrations?
| Signal | What it catches | |---|---| | Upstream API availability | Third-party API outages, rate limiting, auth token expiry | | Retry rate | Intermittent failures hidden by automatic retries | | Cache freshness | Stale cached responses after upstream schema changes | | Request latency | Upstream slowdowns affecting your response times | | Error response rate | Non-retried 4xx errors, unexpected 5xx from APIs |
Req's retry and cache features are production lifesavers — and monitoring blind spots.
Step 1: Create a Req Client Module
Structure your Req usage as a named client with a shared base configuration:
# lib/my_app/http_client.ex
defmodule MyApp.HttpClient do
@base_url Application.compile_env(:my_app, :external_api_url, "https://api.example.com")
def new do
Req.new(
base_url: @base_url,
retry: :transient,
retry_delay: &retry_delay/1,
max_retries: 3,
receive_timeout: 10_000,
connect_options: [timeout: 5_000]
)
|> Req.Request.append_request_steps(telemetry: &attach_telemetry/1)
end
def get(path, opts \\ []) do
Req.get(new(), [url: path] ++ opts)
end
def post(path, body, opts \\ []) do
Req.post(new(), [url: path, json: body] ++ opts)
end
defp retry_delay(n), do: Integer.pow(2, n) * 1000
defp attach_telemetry(request) do
start = System.monotonic_time()
%{request | private: Map.put(request.private, :start_time, start)}
end
end
Step 2: Add a Health Endpoint for Your Integrations
Create a health endpoint that makes a lightweight Req call to each external API your application depends on:
# lib/my_app_web/plugs/integrations_health.ex
defmodule MyAppWeb.Plugs.IntegrationsHealth do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health/integrations"} = conn, _opts) do
checks = run_integration_checks()
all_ok = Enum.all?(checks, fn {_name, status} -> status == :ok end)
{http_status, body} = if all_ok do
{200, %{status: "ok", checks: format_checks(checks)}}
else
{503, %{status: "degraded", checks: format_checks(checks)}}
end
conn
|> put_resp_content_type("application/json")
|> send_resp(http_status, Jason.encode!(body))
|> halt()
end
def call(conn, _opts), do: conn
defp run_integration_checks do
[
{"external_api", check_external_api()},
{"payment_gateway", check_payment_gateway()}
]
end
defp check_external_api do
case MyApp.HttpClient.get("/health") do
{:ok, %{status: 200}} -> :ok
_ -> :error
end
end
defp check_payment_gateway do
case Req.get("https://api.stripe.com/v1/charges",
headers: [{"Authorization", "Bearer #{System.get_env("STRIPE_KEY")}"}],
params: [limit: 1]) do
{:ok, %{status: 200}} -> :ok
_ -> :error
end
end
defp format_checks(checks) do
Map.new(checks, fn {name, status} -> {name, to_string(status)} end)
end
end
Register the plug:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.IntegrationsHealth
plug MyAppWeb.Router
Test it:
curl http://localhost:4000/health/integrations
# => {"status":"ok","checks":{"external_api":"ok","payment_gateway":"ok"}}
Step 3: Add Vigilmon Uptime Monitoring
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter your integration health URL:
https://yourapp.com/health/integrations. - Set Check interval to
2 minutes. - Set Expected HTTP status to
200. - Under Advanced, add a Response body contains check:
"status":"ok". - Click Save.
Vigilmon now verifies all your Req-based integrations are reachable on every check.
Step 4: Track Req Request Metrics via Telemetry
Add a telemetry handler to capture Req request outcomes:
# lib/my_app/req_telemetry.ex
defmodule MyApp.ReqTelemetry do
require Logger
def setup do
:telemetry.attach_many(
"req-requests",
[
[:req, :request, :stop],
[:req, :request, :exception]
],
&handle_event/4,
nil
)
end
def handle_event([:req, :request, :stop], measurements, metadata, _config) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
status = get_in(metadata, [:result, :status]) || 0
if status >= 400 do
Logger.warning("Req request failed: status=#{status} duration=#{duration_ms}ms url=#{metadata.url}")
end
if duration_ms > 5000 do
Logger.warning("Slow Req request: #{duration_ms}ms url=#{metadata.url}")
end
:telemetry.execute(
[:my_app, :req, :request],
%{duration_ms: duration_ms, status: status},
%{url: metadata[:url]}
)
end
def handle_event([:req, :request, :exception], _measurements, metadata, _config) do
Logger.error("Req request exception: #{inspect(metadata.reason)}")
end
end
Attach in application.ex:
def start(_type, _args) do
MyApp.ReqTelemetry.setup()
# ...
end
Step 5: Heartbeat for Scheduled Req Jobs
Many applications use Req for scheduled jobs — polling external APIs, syncing data, sending webhooks. Monitor these with a Vigilmon heartbeat:
# lib/my_app/api_sync_worker.ex
defmodule MyApp.ApiSyncWorker do
use GenServer
@sync_interval :timer.minutes(15)
@heartbeat_url "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_ID"
def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)
def init(_) do
schedule()
{:ok, nil}
end
def handle_info(:sync, state) do
case run_sync() do
{:ok, _count} ->
Req.get!(@heartbeat_url)
{:error, reason} ->
require Logger
Logger.error("API sync failed: #{inspect(reason)}")
# Do NOT ping heartbeat — Vigilmon will alert on the miss
end
schedule()
{:noreply, state}
end
defp schedule, do: Process.send_after(self(), :sync, @sync_interval)
defp run_sync do
case MyApp.HttpClient.get("/api/v1/records", params: [since: last_sync_time()]) do
{:ok, %{status: 200, body: %{"records" => records}}} ->
MyApp.Records.upsert_all(records)
{:ok, length(records)}
{:ok, %{status: status}} ->
{:error, {:http_error, status}}
{:error, reason} ->
{:error, reason}
end
end
defp last_sync_time do
DateTime.utc_now() |> DateTime.add(-15, :minute) |> DateTime.to_iso8601()
end
end
Create the heartbeat in Vigilmon:
- Click Add Monitor → Cron / Heartbeat.
- Set expected interval to
15 minutes. - Set grace period to
5 minutes. - Copy the generated URL into
@heartbeat_url.
Step 6: Set Up Alerts
- Go to Alert Channels → Add Channel.
- Choose Slack, Email, or PagerDuty.
- For integration health HTTP monitor: alert after 2 consecutive failures.
- For sync heartbeats: alert after first miss.
- Assign channels to all Req monitors.
For customer-facing features backed by third-party APIs, also create a Status Page:
- Go to Status Pages → Create Page.
- Add your integration health monitor under a label like "API Integrations".
- Share the public URL in your documentation.
Key Metrics to Watch
| Metric | Vigilmon feature | What to alert on |
|---|---|---|
| Integration availability | HTTP monitor on /health/integrations | Any 503 or timeout |
| Scheduled sync completion | Heartbeat monitor | Missing for > 20 minutes |
| API response time | Response time chart | P95 > 5s |
| SSL certificate | Cert expiry monitor | Expires in < 14 days |
| Retry storm | Telemetry + heartbeat gap | Repeated heartbeat delays |
Conclusion
Req makes HTTP in Elixir reliable and ergonomic — but its reliability features (retries, caching) need to be paired with observability so you know when your upstream APIs are actually struggling. A health endpoint that exercises your integrations, a heartbeat for scheduled jobs, and telemetry for request metrics give you the full picture. With these Vigilmon monitors in place, you'll catch third-party API degradation before it cascades into user-facing failures.
Get started free at vigilmon.online.