How to Monitor Absinthe with Vigilmon
Absinthe brings a full-featured GraphQL server to Elixir and Phoenix — subscriptions over WebSockets, batched field resolution via Dataloader, and a composable plug pipeline. But GraphQL has a monitoring blind spot: every request hits the same /graphql endpoint and returns HTTP 200, even when resolvers error out. Standard uptime monitors see a healthy endpoint when your query is actually returning {"errors": [...]}.
This tutorial wires up layered monitoring for an Absinthe API:
- A dedicated GraphQL health query that exercises the schema end-to-end
- HTTP uptime monitoring with Vigilmon
- Resolver error-rate alerting via cron heartbeats
- Subscription channel health checks
- Slack alerts when queries degrade
Why Monitor Absinthe?
| Signal | What it catches | |---|---| | Schema availability | Deploy regressions that break introspection | | Resolver latency | N+1 queries, Dataloader bottlenecks | | Subscription channels | WebSocket transport failures, PubSub outages | | Error rate | Authorization bugs, missing fields, bad variables | | Batch performance | Dataloader batching configuration issues |
A plain TCP check misses all of these. You need to actually execute queries.
Step 1: Add a Health Query to Your Schema
Create a dedicated health query that Vigilmon can execute. This exercises your schema, the Absinthe plug pipeline, and any configured middleware.
# lib/my_app_web/schema.ex
defmodule MyAppWeb.Schema do
use Absinthe.Schema
query do
field :health, :health_result do
resolve fn _args, _info ->
{:ok, %{status: "ok", timestamp: DateTime.utc_now() |> DateTime.to_iso8601()}}
end
end
# ... your other queries
end
object :health_result do
field :status, non_null(:string)
field :timestamp, non_null(:string)
end
end
Test it locally:
curl -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ health { status timestamp } }"}'
# => {"data":{"health":{"status":"ok","timestamp":"2026-07-03T10:00:00Z"}}}
Step 2: Add an HTTP Health Endpoint
Vigilmon monitors HTTP endpoints, not GraphQL queries directly. Add a lightweight REST health endpoint alongside your GraphQL route so Vigilmon has a clean target:
# lib/my_app_web/plugs/graphql_health.ex
defmodule MyAppWeb.Plugs.GraphQLHealth do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health/graphql"} = conn, _opts) do
result = run_health_query()
{status, body} = case result do
{:ok, %{"data" => %{"health" => %{"status" => "ok"}}}} ->
{200, %{status: "ok", graphql: "reachable"}}
_ ->
{503, %{status: "error", graphql: "unreachable"}}
end
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(body))
|> halt()
end
def call(conn, _opts), do: conn
defp run_health_query do
query = ~s|{ health { status timestamp } }|
Absinthe.run(query, MyAppWeb.Schema)
end
end
Register the plug in your endpoint before the router:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.GraphQLHealth
plug MyAppWeb.Router
Now GET /health/graphql returns 200 when Absinthe is healthy and 503 when it's not.
Step 3: Add Vigilmon Uptime Monitoring
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter your health endpoint URL:
https://api.yourapp.com/health/graphql. - Set Check interval to
1 minute. - Set Expected HTTP status to
200. - Under Advanced, add a Response body contains check:
"status":"ok". - Click Save.
Vigilmon now hits your GraphQL health endpoint every minute and alerts you if the schema becomes unreachable.
Step 4: Monitor Resolver Error Rates with a Heartbeat
GraphQL resolvers can fail silently — HTTP 200 with {"errors": [...]} in the body. Use Absinthe's telemetry events to track error rates and emit a heartbeat to Vigilmon.
First, attach a telemetry handler in your application:
# lib/my_app/graphql_telemetry.ex
defmodule MyApp.GraphQLTelemetry do
def setup do
:telemetry.attach(
"absinthe-execute",
[:absinthe, :execute, :operation, :stop],
&handle_event/4,
nil
)
end
def handle_event(_event, _measurements, metadata, _config) do
case metadata do
%{blueprint: %{result: %{errors: errors}}} when errors != [] ->
:telemetry.execute([:my_app, :graphql, :errors], %{count: length(errors)}, %{})
_ ->
:ok
end
end
end
Attach it in application.ex:
def start(_type, _args) do
MyApp.GraphQLTelemetry.setup()
# ...
end
Then add a periodic heartbeat worker that pings Vigilmon only when the error rate is acceptable:
# lib/my_app/graphql_heartbeat.ex
defmodule MyApp.GraphQLHeartbeat do
use GenServer
@interval :timer.minutes(1)
@vigilmon_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, %{error_count: 0}}
end
def handle_info(:ping, state) do
if state.error_count < 10 do
HTTPoison.get!(@vigilmon_heartbeat_url)
end
schedule()
{:noreply, %{state | error_count: 0}}
end
defp schedule, do: Process.send_after(self(), :ping, @interval)
end
Add it to your supervision tree. If GraphQL errors spike, the heartbeat stops and Vigilmon alerts you.
To create the heartbeat monitor in Vigilmon:
- Click Add Monitor → Cron / Heartbeat.
- Set the expected ping interval to
1 minute. - Copy the generated heartbeat URL into
@vigilmon_heartbeat_urlabove.
Step 5: Monitor Absinthe Subscriptions
Subscriptions run over Phoenix Channels. Monitor the channel endpoint as well:
- Click Add Monitor → HTTP / HTTPS.
- Enter your WebSocket upgrade URL as an HTTP check:
https://api.yourapp.com/socket/websocket. - Set Expected HTTP status to
200(Phoenix returns 200 on HTTP upgrade requests). - Set the interval to
2 minutes.
For deeper subscription health, add a subscription-specific health query:
# In your schema
subscription do
field :health_ping, :string do
config fn _args, _info ->
{:ok, topic: "health"}
end
resolve fn ping, _, _ -> {:ok, ping} end
end
end
Then periodically publish a test ping from a GenServer:
defmodule MyApp.SubscriptionHealthWorker do
use GenServer
def handle_info(:ping, state) do
Absinthe.Subscription.publish(MyAppWeb.Endpoint, "pong", health_ping: "health")
Process.send_after(self(), :ping, 30_000)
{:noreply, state}
end
end
Step 6: Set Up Alerts
In Vigilmon, configure alert channels for your Absinthe monitors:
- Go to Alert Channels → Add Channel.
- Choose Slack, Email, or PagerDuty.
- Set alert thresholds: notify after 2 consecutive failures (avoids flapping on transient errors).
- Assign the alert channel to both your HTTP monitor and your heartbeat monitor.
For Absinthe APIs used by mobile clients, also enable Status Page so users can check GraphQL availability themselves:
- Go to Status Pages → Create Page.
- Add your GraphQL health monitor.
- Share the public URL with API consumers.
Key Metrics to Watch
| Metric | Vigilmon feature | What to alert on |
|---|---|---|
| Schema availability | HTTP monitor on /health/graphql | Any 5xx or timeout |
| Resolver error rate | Heartbeat monitor | Heartbeat missing for > 2 min |
| Response time | Response time chart | P95 > 2s over 5 minutes |
| SSL certificate | Cert expiry monitor | Expires in < 14 days |
| Subscription endpoint | HTTP monitor on /socket/websocket | Any non-200 response |
Conclusion
Absinthe's flexibility makes it a great GraphQL server — but that same flexibility means you need layered monitoring: an HTTP health endpoint for basic availability, a heartbeat that pauses on error spikes, and subscription endpoint checks. With these three Vigilmon monitors in place, you'll catch schema regressions, resolver failures, and transport outages before your API consumers do.
Get started free at vigilmon.online.