TypedStruct is an Elixir macro library that brings compile-time rigour to struct definitions — automatic typespecs, enforced required fields, and a clean DSL that eliminates boilerplate. It's a library, not a running service, but the Elixir applications that use TypedStruct — APIs, data pipelines, Phoenix services — absolutely need monitoring. Vigilmon helps you monitor those applications: catching struct validation failures at runtime, confirming data pipeline health, and alerting when the services that produce and consume TypedStruct-typed data go down.
What You'll Set Up
- HTTP health endpoint validating TypedStruct-enforced data integrity
- Cron heartbeat for data pipeline workers using TypedStruct domain models
- Runtime struct validation error alerting via health checks
- API endpoint monitoring for services built around TypedStruct schemas
Prerequisites
- Elixir 1.14+ with
typed_structin yourmix.exs - An application using TypedStruct for domain modelling (Phoenix, GenServer workers, etc.)
- A free Vigilmon account
Step 1: Define a Health Struct with TypedStruct
Use TypedStruct to define a canonical health response shape — this ensures your health endpoint always returns consistent, type-safe data:
defmodule MyApp.Health do
use TypedStruct
typedstruct do
field :status, String.t(), enforce: true
field :version, String.t(), enforce: true
field :database, boolean(), default: false
field :cache, boolean(), default: false
field :checked_at, DateTime.t(), enforce: true
end
end
Build and serialise the health struct in your controller:
defmodule MyAppWeb.HealthController do
use MyAppWeb, :controller
def index(conn, _params) do
health = %MyApp.Health{
status: "ok",
version: Application.spec(:my_app, :vsn) |> to_string(),
database: database_alive?(),
cache: cache_alive?(),
checked_at: DateTime.utc_now()
}
status_code = if health.database and health.cache, do: 200, else: 503
conn |> put_status(status_code) |> json(health)
end
defp database_alive? do
case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
{:ok, _} -> true
_ -> false
end
end
defp cache_alive? do
case Redix.command(:redix, ["PING"]) do
{:ok, "PONG"} -> true
_ -> false
end
end
end
TypedStruct's enforce: true means the struct construction will raise a ArgumentError at compile time if you forget a required field — your health endpoint can never return an incomplete response.
Step 2: Add the Monitor in Vigilmon
- Log in to vigilmon.online and click Add Monitor.
- Set Type to
HTTP / HTTPS. - Enter
https://myapp.example.com/health. - Set Check interval to
1 minute. - Set Expected HTTP status to
200. - Under Response body, enable Contains keyword and enter
"ok". - Click Save.
The keyword check ensures Vigilmon fails the probe if your TypedStruct health response contains "error" or "degraded" in the status field, even if the HTTP layer returns 200.
Step 3: Validate Incoming Data and Surface Failures
TypedStruct is most valuable at domain boundaries — when external data arrives (API calls, message queues, CSV imports). When validation fails, it's a signal that an upstream producer changed their schema. Surface these failures to Vigilmon via a health degradation:
defmodule MyApp.DataIngestion do
require Logger
@typedstruct_errors_key :typedstruct_validation_errors
def ingest(raw_map) do
try do
event = struct!(MyApp.Event, raw_map)
process(event)
rescue
e in [ArgumentError, KeyError] ->
Logger.error("TypedStruct validation failed: #{inspect(e)}")
increment_error_count()
{:error, :validation_failed}
end
end
defp increment_error_count do
current = :persistent_term.get(@typedstruct_errors_key, 0)
:persistent_term.put(@typedstruct_errors_key, current + 1)
end
def error_count, do: :persistent_term.get(@typedstruct_errors_key, 0)
end
Expose the error count in your health endpoint:
def index(conn, _params) do
error_count = MyApp.DataIngestion.error_count()
status = if error_count > 100, do: 503, else: 200
health = %MyApp.Health{
status: if(error_count > 100, do: "degraded", else: "ok"),
version: Application.spec(:my_app, :vsn) |> to_string(),
database: database_alive?(),
cache: cache_alive?(),
checked_at: DateTime.utc_now()
}
conn |> put_status(status) |> json(Map.put(health, :validation_errors, error_count))
end
Vigilmon will detect the 503 and fire your alert when validation error counts spike.
Step 4: Heartbeat for Data Pipeline Workers
If you run Elixir workers that process TypedStruct-typed domain events (GenStage producers, Broadway pipelines, Task.Supervisor jobs), add a Vigilmon heartbeat:
- In Vigilmon, click Add Monitor → Cron Heartbeat.
- Set the interval to match your pipeline's expected throughput (e.g.
10minutes if the pipeline processes events every few minutes). - Copy the heartbeat URL.
Send the ping after each successful processing batch:
defmodule MyApp.EventPipeline do
use GenStage
require Logger
@heartbeat_url System.get_env("VIGILMON_HEARTBEAT_URL")
def handle_events(events, _from, state) do
processed =
Enum.map(events, fn raw ->
struct!(MyApp.Event, raw)
end)
Enum.each(processed, &persist/1)
ping_vigilmon()
{:noreply, [], state}
end
defp ping_vigilmon do
:httpc.request(:get, {String.to_charlist(@heartbeat_url), []}, [{:timeout, 5000}], [])
rescue
e -> Logger.warning("Vigilmon ping failed: #{inspect(e)}")
end
end
The pipeline only pings on successful batch processing. A crash between events means no ping, and Vigilmon alerts after the expected interval.
Step 5: Monitor Your API Surface
Applications built with TypedStruct often expose typed JSON APIs. Add Vigilmon monitors for each critical endpoint:
defmodule MyApp.UserController do
use MyAppWeb, :controller
def show(conn, %{"id" => id}) do
case MyApp.Users.get(id) do
%MyApp.User{} = user ->
json(conn, user)
nil ->
conn |> put_status(404) |> json(%{error: "not_found"})
end
end
end
For each critical API route, add a Vigilmon monitor:
- Add a dedicated health or smoke-test endpoint that exercises your TypedStruct data layer.
- Create an HTTP monitor in Vigilmon for that endpoint.
- Set the keyword assertion to match expected TypedStruct field names (e.g.
"email"for a user endpoint).
This confirms not just that the HTTP server is up, but that the TypedStruct domain layer is returning well-formed data.
Step 6: Configure Alert Channels
- Go to Alert Channels in Vigilmon and connect Slack or PagerDuty.
- Set Consecutive failures before alert to
2for HTTP monitors to absorb deploy restarts. - Set it to
1for heartbeat monitors on data pipelines — a single missed beat usually indicates a real problem. - Use maintenance windows during deploys:
curl -X POST https://vigilmon.online/api/maintenance \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"monitor_id": "abc123", "duration_minutes": 5}'
Summary
| Monitor | Target | What It Catches |
|---|---|---|
| HTTP health check | /health with TypedStruct response | App down, dependency failure |
| Response keyword | "ok" in body | Degraded status from validation errors |
| Cron heartbeat | Heartbeat URL | Pipeline worker stopped processing |
| API endpoint check | Critical route keyword | Domain layer returning malformed data |
TypedStruct enforces correctness at compile time — Vigilmon enforces reliability at runtime. Together they give your Elixir application both the type safety of explicit struct contracts and the operational confidence of continuous uptime monitoring.