How to Monitor Algae with Vigilmon
Algae brings algebraic data types to Elixir — monoids, semirings, setoids, lattices, and other structures that enable purely functional, composable data modeling. When your business logic is built on these composable structures, correctness and predictability go hand in hand.
But algebraic purity doesn't make your process immune to infrastructure failures. Database connections drop, message queues back up, and nodes become unreachable — all while your supervisor tree cheerfully reports everything is fine. External monitoring fills that gap.
This tutorial adds production observability to an Elixir application that uses Algae:
- A health check endpoint that reflects the state of your critical dependencies
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring for long-running data pipeline workers
- Slack alerts and a public status page
Why Monitor Algae-Based Applications?
Algae encourages building data transformations as pipelines of composable algebraic structures. A typical Algae-based app might:
- Stream data through
MaybeandResultmonadic chains - Accumulate values with monoid-folding pipelines
- Validate input using setoid-based equality and lattice ordering
When these pipelines process real data from external sources (databases, APIs, queues), failures in those sources can cause silent degradation: your pipeline runs, but returns Nothing or empty results for every record because the upstream is down. External monitoring catches that the application is reachable but not the data quality issue — heartbeat monitoring catches both.
Step 1: Add a Health Check Endpoint
Add a lightweight health check using Phoenix or Plug. It should verify that every external dependency your Algae pipelines depend on is alive.
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = %{
database: check_database(),
cache: check_cache(),
pipeline_worker: check_pipeline_worker()
}
status = if Enum.all?(checks, fn {_, v} -> v == :ok end), do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(%{
status: if(status == 200, do: "ok", else: "degraded"),
checks: checks
}))
|> halt()
end
def call(conn, _opts), do: conn
defp check_database do
case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
{:ok, _} -> :ok
_ -> :error
end
end
defp check_cache do
case Redix.command(:redix, ["PING"]) do
{:ok, "PONG"} -> :ok
_ -> :error
end
end
defp check_pipeline_worker do
if Process.whereis(MyApp.Pipeline.Worker) != nil, do: :ok, else: :error
end
end
Register it in your endpoint before the router:
# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
plug MyAppWeb.Plugs.HealthCheck # before router
plug MyAppWeb.Router
end
Test it locally:
mix phx.server
curl http://localhost:4000/health
# {"status":"ok","checks":{"database":"ok","cache":"ok","pipeline_worker":"ok"}}
Step 2: Set Up HTTP Monitoring in Vigilmon
Point Vigilmon at your health endpoint:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set the check interval to 1 minute (paid) or 5 minutes (free)
- Save
Vigilmon probes from multiple geographic regions so a network partition affecting one region doesn't mask a real outage.
Step 3: Heartbeat Monitoring for Data Pipeline Workers
The subtlest failure mode in Algae-based apps is a pipeline that runs but produces no output. Your GenServer is alive, the supervisor is green, but every call to your pipeline returns Nothing because the upstream data source is unreachable.
Use heartbeat monitoring to detect this. Your worker pings a unique Vigilmon URL on every successful processing cycle. If Vigilmon doesn't receive the ping within the expected window, it alerts you.
GenServer Pipeline Worker
# lib/my_app/pipeline/worker.ex
defmodule MyApp.Pipeline.Worker do
use GenServer
require Logger
alias Algae.Maybe
alias MyApp.Pipeline.Transform
@interval :timer.minutes(1)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
@impl true
def init(state) do
schedule_tick()
{:ok, state}
end
@impl true
def handle_info(:tick, state) do
case run_pipeline() do
{:ok, _results} ->
ping_heartbeat()
Logger.info("Pipeline cycle complete")
{:error, reason} ->
Logger.error("Pipeline cycle failed: #{inspect(reason)}")
# No heartbeat ping → Vigilmon alerts after the window expires
end
schedule_tick()
{:noreply, state}
end
defp run_pipeline do
MyApp.DataSource.fetch_batch()
|> Maybe.map(&Transform.normalize/1)
|> Maybe.map(&Transform.validate/1)
|> case do
%Maybe.Just{just: results} -> {:ok, results}
%Maybe.Nothing{} -> {:error, :empty_pipeline}
end
end
defp schedule_tick, do: Process.send_after(self(), :tick, @interval)
defp ping_heartbeat do
url = Application.get_env(:my_app, :vigilmon)[:pipeline_heartbeat_url]
if url do
case Req.get(url, receive_timeout: 5_000) do
{:ok, _} -> :ok
{:error, reason} -> Logger.warning("Heartbeat failed: #{inspect(reason)}")
end
end
end
end
Set the heartbeat URL as an environment variable:
# config/runtime.exs
config :my_app, :vigilmon,
pipeline_heartbeat_url: System.get_env("VIGILMON_PIPELINE_HEARTBEAT_URL")
Create the Heartbeat Monitor in Vigilmon
- Click New Monitor → Heartbeat
- Set the expected interval to match your worker tick (e.g. 1 minute)
- Copy the ping URL and set it as
VIGILMON_PIPELINE_HEARTBEAT_URLin your release environment - Save
Now if your pipeline stops producing output — even if the GenServer is still alive — you get an alert.
Step 4: Alerts via Slack
In Vigilmon, go to Notifications → New Channel → Slack and paste your Slack incoming webhook URL.
To create a Slack webhook:
- Visit api.slack.com/apps → Create New App → From scratch
- Enable Incoming Webhooks → Add New Webhook to Workspace
- Select your alerts channel and copy the webhook URL
Assign the Slack channel to your monitors. On failure, Vigilmon sends:
🔴 DOWN: yourdomain.com/health
Status: 503 Service Unavailable
Detected from: EU-West, US-East
3 minutes ago
And on recovery:
✅ RECOVERED: yourdomain.com/health
Downtime: 8 minutes
Step 5: Status Page and Badge
Status page:
- Go to Status Pages → New Status Page in Vigilmon
- Add your HTTP and heartbeat monitors
- Copy the public URL
Share it with your team and in your README so anyone can check current status before filing a bug report.
README badge:

Key Metrics to Watch
| Metric | What it signals | |--------|----------------| | Health endpoint HTTP status | Overall application reachability | | Database check result | Ecto repo connectivity | | Pipeline heartbeat | End-to-end data processing is running | | Response time trend | Latency increase before timeout failures | | Downtime frequency | Infrastructure instability patterns |
What You've Built
| What | How |
|------|-----|
| Health check endpoint | Plug-based, checks DB + cache + worker |
| HTTP uptime monitoring | Vigilmon HTTP monitor → /health |
| Pipeline heartbeat | GenServer pings Vigilmon on each successful cycle |
| Slack alerts | Vigilmon Slack notification channel |
| Status page | Vigilmon public status page |
| README badge | /badge/{slug} SVG embed |
Algae makes your data modeling correct and composable. Vigilmon makes sure the infrastructure underneath it stays reachable.
Next Steps
- Add per-queue heartbeats if you use Oban to schedule pipeline runs
- Use Vigilmon's response time history to detect slow upstream sources before they cause timeouts
- Add a keyword check on your status page to verify content — not just HTTP 200
Get started free at vigilmon.online.