tutorial

How to Monitor Norm with Vigilmon

Norm is a data contract and schema validation library for Elixir. Learn how to monitor validation pipelines, track conformance failures, and alert on data quality degradation using Vigilmon.

Norm is a composable data contract library for Elixir inspired by Clojure's spec. It lets you define specs for data structures, validate and conform incoming data, and generate example data for testing. When Norm sits at the boundary between external inputs and your business logic — parsing API payloads, validating user-submitted forms, conforming event streams — silent validation failures or a crashed conformance pipeline cause corrupt data to propagate downstream. Vigilmon gives you uptime monitoring, heartbeat checks, and alerting so those failures surface immediately rather than days later during an audit.

What You'll Set Up

  • HTTP health endpoint that exercises your Norm validation pipeline
  • Cron heartbeat for background conformance workers
  • Alerting when validation error rates spike
  • SSL certificate monitoring for the services feeding data into your Norm specs

Prerequisites

  • Elixir 1.14+ with the norm package added to mix.exs
  • A Phoenix or Plug-based application with at least one Norm spec in production use
  • A free Vigilmon account

Step 1: Add a Health Endpoint That Exercises Your Norm Specs

Rather than a trivial ping endpoint, exercise a representative Norm spec so the health check fails if your validation logic is broken:

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

  alias MyApp.Specs

  def index(conn, _params) do
    case validate_norm_pipeline() do
      :ok ->
        json(conn, %{status: "ok", norm: "operational"})

      {:error, errors} ->
        conn
        |> put_status(503)
        |> json(%{status: "error", norm: "degraded", errors: inspect(errors)})
    end
  end

  defp validate_norm_pipeline do
    # Conform a known-good fixture through your core spec
    sample = %{id: 1, email: "healthcheck@example.com", role: "user"}

    case Norm.conform(sample, Specs.user_spec()) do
      {:ok, _conformed} -> :ok
      {:error, errors} -> {:error, errors}
    end
  end
end

Define a minimal spec that exercises the critical path:

# lib/my_app/specs.ex
defmodule MyApp.Specs do
  import Norm

  def user_spec do
    schema(%{
      id: spec(is_integer() and (&(&1 > 0))),
      email: spec(is_binary()),
      role: spec(&(&1 in ["user", "admin", "viewer"]))
    })
  end
end

Wire it to a route:

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

Verify locally:

mix phx.server
curl http://localhost:4000/health
# {"status":"ok","norm":"operational"}

Step 2: Add the Monitor in Vigilmon

  1. Log in to vigilmon.online and click Add Monitor.
  2. Set Type to HTTP / HTTPS.
  3. Enter your health URL: https://myapp.example.com/health.
  4. Set Check interval to 1 minute.
  5. Set Expected HTTP status to 200.
  6. Under Response body, enable Contains keyword and enter "operational".
  7. Click Save.

The keyword check catches the case where your app returns 200 but the Norm pipeline is reporting "degraded" — a common pattern when health checks mask inner failures with a generic 200 response.


Step 3: Track Validation Errors with Telemetry

Norm doesn't emit Telemetry events by default, but you can wrap your Norm.conform/2 calls in a module that emits metrics, then alert when the failure rate climbs:

defmodule MyApp.Validation do
  require Logger

  def conform(data, spec, name \\ "unknown") do
    start = System.monotonic_time(:millisecond)
    result = Norm.conform(data, spec)
    duration_ms = System.monotonic_time(:millisecond) - start

    case result do
      {:ok, conformed} ->
        :telemetry.execute(
          [:my_app, :norm, :conform],
          %{duration_ms: duration_ms, failures: 0},
          %{spec: name}
        )
        {:ok, conformed}

      {:error, errors} ->
        Logger.warning("Norm conformance failed for #{name}: #{inspect(errors)}")
        :telemetry.execute(
          [:my_app, :norm, :conform],
          %{duration_ms: duration_ms, failures: length(errors)},
          %{spec: name}
        )
        {:error, errors}
    end
  end
end

Attach a handler to log when failure counts spike:

# In your Application.start/2
:telemetry.attach(
  "norm-conformance-failures",
  [:my_app, :norm, :conform],
  fn _event, %{failures: count}, %{spec: name}, _config ->
    if count > 5 do
      Logger.error("High Norm failure count (#{count}) for spec: #{name}")
    end
  end,
  nil
)

Expose an aggregated failure count in your health endpoint so Vigilmon can alert on data quality degradation before it reaches critical levels.


Step 4: Heartbeat for Background Conformance Workers

If your app uses Norm to validate events from a message queue or external feed — conforming Kafka messages, validating webhook payloads, processing CSV imports — add a Vigilmon cron heartbeat to confirm the worker keeps running:

  1. In Vigilmon, click Add MonitorCron Heartbeat.
  2. Set Expected ping interval to match your worker schedule (e.g. 5 minutes for a continuous consumer).
  3. Copy the heartbeat URL (e.g. https://vigilmon.online/heartbeat/abc123).

Ping Vigilmon after each successful batch:

defmodule MyApp.EventConsumer do
  require Logger

  def process_batch(events) do
    results = Enum.map(events, fn event ->
      MyApp.Validation.conform(event, MyApp.Specs.event_spec(), "event")
    end)

    errors = Enum.filter(results, fn
      {:error, _} -> true
      _ -> false
    end)

    Logger.info("Processed #{length(events)} events, #{length(errors)} errors")

    if length(errors) < length(events) do
      # At least some succeeded — ping heartbeat
      ping_vigilmon()
    end

    results
  end

  defp ping_vigilmon do
    url = System.get_env("VIGILMON_HEARTBEAT_URL")
    if url, do: :httpc.request(:get, {String.to_charlist(url), []}, [], [])
  end
end

If the consumer crashes mid-batch and never pings, Vigilmon alerts after the expected window expires.


Step 5: Monitor Upstream Data Sources

Norm specs protect you from bad data, but the root cause is usually an upstream service sending malformed payloads. Monitor the APIs or services feeding your Norm pipeline directly:

  1. In Vigilmon, click Add MonitorHTTP / HTTPS.
  2. Enter the upstream API health endpoint: https://upstream-service.example.com/status.
  3. Set Check interval to 1 minute.
  4. Enable SSL certificate monitoring with a 21 day expiry alert.

Correlate Norm conformance failure spikes with upstream monitor downtime events. A sudden jump in {:error, _} returns often traces back to the upstream service returning a schema-breaking response during a deployment.


Step 6: Configure Alert Channels

  1. Go to Alert Channels in Vigilmon and add Slack, email, or PagerDuty.
  2. Set Consecutive failures before alert to 2 to suppress transient spikes.
  3. Use Maintenance windows when you intentionally change a spec's shape during a deployment that will temporarily fail the health check:
curl -X POST https://vigilmon.online/api/maintenance \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{"monitor_id": "abc123", "duration_minutes": 15}'

Summary

| Monitor | Target | What It Catches | |---|---|---| | HTTP health check | /health on your app | Norm spec failures, broken validation pipeline | | Response keyword | "operational" in body | App up but conformance degraded | | Cron heartbeat | Heartbeat URL | Silent background consumer failure | | Upstream HTTP monitor | Upstream API health | Bad data source causing conformance errors | | SSL certificate | Upstream API endpoint | TLS expiry breaking the data feed |

Norm makes data contracts explicit at the Elixir boundary — Vigilmon keeps watch over the pipeline those contracts protect. With a spec-exercising health endpoint, a heartbeat for background consumers, and upstream source monitoring, you get full visibility from the data source through to the conformance layer.

Monitor your app with Vigilmon

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

Start free →