tutorial

How to Monitor Vex with Vigilmon

Vex is Elixir's flexible data validation library — but validation failures in production are silent unless you track them. Here's how to monitor Vex-powered applications with Vigilmon to catch validation regressions before users do.

How to Monitor Vex with Vigilmon

Vex is a data validation library for Elixir that works independently of Ecto, supporting struct and map validation with built-in validators (presence, format, length, inclusion) and custom validator functions. It's common in Elixir applications that process external data — API ingestion pipelines, form processing, event consumers — where input quality is unpredictable.

The monitoring challenge: Vex validation failures don't crash your app. They return {:error, errors} tuples, which your code handles gracefully. But when validation failure rates spike — because an upstream API changed its schema, a frontend form was updated, or a data migration introduced unexpected formats — you want to know before users do.

This tutorial wires up monitoring for a Vex-powered application:

  • A health endpoint that reports validation pipeline status
  • HTTP uptime monitoring with Vigilmon
  • Heartbeat monitoring tied to validation error rates
  • Alerts on validation failure spikes

Why Monitor Vex?

| Signal | What it catches | |---|---| | Validation error rate | Upstream schema changes, bad data ingestion | | Validation pipeline availability | Application crashes in processing workers | | Data quality regression | New form fields missing validation rules | | Processing throughput | Validation bottlenecks under load | | Rule coverage | Missing validators on new fields |

Vex validations are a contract between your application and its data sources. Monitoring that contract gives you early warning when the contract breaks.


Step 1: Track Validation Metrics in Your Application

Add a simple ETS-based counter for validation outcomes:

# lib/my_app/validation_metrics.ex
defmodule MyApp.ValidationMetrics do
  use GenServer

  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)

  def init(_) do
    :ets.new(:validation_counts, [:named_table, :public, :set])
    :ets.insert(:validation_counts, {:ok, 0})
    :ets.insert(:validation_counts, {:error, 0})
    {:ok, nil}
  end

  def record(:ok) do
    :ets.update_counter(:validation_counts, :ok, 1)
  end

  def record(:error) do
    :ets.update_counter(:validation_counts, :error, 1)
  end

  def stats do
    ok = :ets.lookup_element(:validation_counts, :ok, 2)
    error = :ets.lookup_element(:validation_counts, :error, 2)
    total = ok + error
    error_rate = if total > 0, do: Float.round(error / total * 100, 2), else: 0.0
    %{ok: ok, error: error, total: total, error_rate_pct: error_rate}
  end

  def reset do
    :ets.insert(:validation_counts, {:ok, 0})
    :ets.insert(:validation_counts, {:error, 0})
  end
end

Wrap your Vex validation calls to record outcomes:

# lib/my_app/user_validator.ex
defmodule MyApp.UserValidator do
  use Vex.Struct

  defstruct [:name, :email, :age]

  validates :name, presence: true, length: [min: 2, max: 100]
  validates :email, presence: true, format: ~r/@/
  validates :age, presence: true, inclusion: 18..120

  def validate(attrs) do
    struct = struct(__MODULE__, attrs)
    result = Vex.validate(struct)
    case result do
      {:ok, _} -> MyApp.ValidationMetrics.record(:ok)
      {:error, _} -> MyApp.ValidationMetrics.record(:error)
    end
    result
  end
end

Add ValidationMetrics to your supervision tree:

# lib/my_app/application.ex
children = [
  MyApp.ValidationMetrics,
  # ... other children
]

Step 2: Expose a Health Endpoint

Add an HTTP health endpoint that reports validation stats:

# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
  use MyAppWeb, :controller

  @error_rate_threshold 20.0

  def index(conn, _params) do
    stats = MyApp.ValidationMetrics.stats()
    status = if stats.error_rate_pct < @error_rate_threshold, do: "ok", else: "degraded"
    http_status = if status == "ok", do: 200, else: 503

    conn
    |> put_status(http_status)
    |> json(%{
      status: status,
      validation_stats: stats,
      timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
    })
  end
end

Add the route:

# lib/my_app_web/router.ex
scope "/", MyAppWeb do
  pipe_through :api
  get "/health", HealthController, :index
end

Test it:

curl http://localhost:4000/health
# => {"status":"ok","validation_stats":{"ok":142,"error":3,"total":145,"error_rate_pct":2.07},...}

The endpoint returns 503 when the validation error rate exceeds 20%, giving Vigilmon a clear signal.


Step 3: Add Vigilmon HTTP Monitoring

  1. Log in to vigilmon.online and click Add Monitor.
  2. Set Type to HTTP / HTTPS.
  3. Enter your health endpoint: https://yourapp.com/health.
  4. Set Check interval to 1 minute.
  5. Set Expected HTTP status to 200.
  6. Under Advanced, add Response body contains: "status":"ok".
  7. Click Save.

Vigilmon now polls your application every minute. When validation failures spike past 20%, the health endpoint returns 503 and Vigilmon alerts you within two check cycles (2 minutes).


Step 4: Heartbeat Monitoring for Processing Workers

If Vex runs inside a GenServer or Task-based processing pipeline, add a heartbeat to confirm the pipeline is alive:

# lib/my_app/processing_heartbeat.ex
defmodule MyApp.ProcessingHeartbeat do
  use GenServer

  @interval :timer.minutes(1)
  @vigilmon_heartbeat_url System.get_env("VIGILMON_PROCESSING_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(:ping, state) do
    stats = MyApp.ValidationMetrics.stats()
    if stats.error_rate_pct < 20.0 do
      :httpc.request(:get, {String.to_charlist(@vigilmon_heartbeat_url), []}, [], [])
    end
    MyApp.ValidationMetrics.reset()
    schedule()
    {:noreply, state}
  end

  defp schedule, do: Process.send_after(self(), :ping, @interval)
end

Add ProcessingHeartbeat to your supervision tree.

To create the heartbeat in Vigilmon:

  1. Click Add MonitorCron / Heartbeat.
  2. Set expected interval to 1 minute.
  3. Copy the heartbeat URL into VIGILMON_PROCESSING_HEARTBEAT_URL.

Step 5: Test Your Validation Rules

Use Vex's built-in test helpers to confirm your validators behave as expected — and catch regressions before they reach production:

# test/my_app/user_validator_test.exs
defmodule MyApp.UserValidatorTest do
  use ExUnit.Case

  test "valid user passes" do
    assert {:ok, _} = MyApp.UserValidator.validate(%{
      name: "Alice",
      email: "alice@example.com",
      age: 30
    })
  end

  test "missing email fails" do
    assert {:error, errors} = MyApp.UserValidator.validate(%{
      name: "Alice",
      age: 30
    })
    assert Enum.any?(errors, fn {_, :email, _, _} -> true; _ -> false end)
  end

  test "underage user fails" do
    assert {:error, _} = MyApp.UserValidator.validate(%{
      name: "Alice",
      email: "alice@example.com",
      age: 16
    })
  end
end

Run tests in CI to ensure validation rules never silently regress.


Step 6: Set Up Alerts

Configure alert channels for both monitors:

  1. Go to Alert ChannelsAdd Channel.
  2. Choose Slack, Email, or PagerDuty.
  3. Set alert threshold to 2 consecutive failures for the HTTP monitor.
  4. For the heartbeat monitor, set Missing heartbeat to alert immediately (1 missed ping).
  5. Assign the channel to both monitors.

For data processing pipelines, consider also enabling Escalation — if the validation error rate alert is not acknowledged within 10 minutes, escalate to a second channel.


Key Metrics to Watch

| Metric | Vigilmon feature | What to alert on | |---|---|---| | Application availability | HTTP monitor on /health | Any non-200 or timeout | | Validation error rate | HTTP monitor body check | "status":"degraded" (>20% errors) | | Processing pipeline liveness | Heartbeat monitor | Any missed heartbeat | | Response time | Response time chart | P95 > 500ms over 5 minutes | | SSL certificate | Cert expiry monitor | Expires in < 14 days |


Conclusion

Vex makes it easy to validate data in Elixir without coupling to Ecto — but validation failures are business logic errors that happen silently unless you track them. By exposing validation metrics through a health endpoint, tying a heartbeat to your processing pipeline's error rate, and alerting on spikes, you get early warning when upstream data quality degrades.

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →