tutorial

How to Monitor Honeybadger for Elixir with Vigilmon

Combine Honeybadger exception monitoring and uptime checks with Vigilmon external monitoring to get defense-in-depth observability for your Elixir/Phoenix application.

How to Monitor Honeybadger for Elixir with Vigilmon

Honeybadger is an error monitoring, uptime tracking, and performance monitoring client for Elixir and Phoenix. The honeybadger Hex package automatically captures unhandled exceptions in Phoenix request handlers, integrates with Plug to attach request context (path, params, user) to every error report, supports custom check-ins (cron job monitoring), and can report custom errors with structured metadata.

Honeybadger's built-in uptime checks work from Honeybadger's own infrastructure. Vigilmon gives you a second external probe from different infrastructure and geographic regions — a critical complement when your application needs defense-in-depth: two independent monitoring systems with different failure modes means one is always watching even if the other experiences an outage.

This tutorial sets up Honeybadger for exception monitoring and Vigilmon for uptime and heartbeat monitoring, giving your Phoenix application complete observability coverage.


Why Use Honeybadger and Vigilmon Together?

Honeybadger's built-in uptime monitoring is convenient. Vigilmon adds:

  • Independent infrastructure — if Honeybadger's own systems have an incident, your uptime monitoring continues
  • Multi-region probes — Vigilmon checks from multiple geographic regions, catching CDN and regional routing failures that a single-probe check would miss
  • Heartbeat monitoring — detect when background jobs stop running, even if they produce no exceptions
  • Response time history — track latency trends over time and catch regressions before they become outages
  • Status pages — give your users a public status page showing both uptime and incident history

Key Metrics to Track

| Metric | Source | What it tells you | |--------|--------|-------------------| | Unhandled exception count by class | Honeybadger | Error frequency and new error introductions | | Affected users count per error | Honeybadger | Business impact of each error | | Uptime percentage | Vigilmon | External availability from multiple regions | | Response time p95/p99 | Vigilmon | Whether the app is slow before it goes down | | Heartbeat interval | Vigilmon | Whether scheduled jobs complete on time | | Deploy error rate change | Honeybadger | Whether a release introduced new failures |


Step 1: Add Honeybadger to Your Project

# mix.exs
defp deps do
  [
    {:honeybadger, "~> 0.21"}
  ]
end

Configure in your application:

# config/runtime.exs
config :honeybadger,
  api_key: System.get_env("HONEYBADGER_API_KEY"),
  environment_name: System.get_env("HONEYBADGER_ENV", "production"),
  exclude_envs: [:dev, :test],
  breadcrumbs_enabled: true,
  ecto_repos: [MyApp.Repo]

Add to your supervision tree:

# lib/my_app/application.ex
children = [
  Honeybadger,
  MyApp.Repo,
  MyAppWeb.Endpoint
]

Step 2: Phoenix and Plug Integration

Honeybadger integrates with Phoenix automatically when you add use Honeybadger.Plug to your endpoint or router:

# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  use Honeybadger.Plug

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", MyAppWeb do
    pipe_through :browser
    get "/", PageController, :home
    get "/health", HealthController, :index
  end
end

Honeybadger.Plug catches unhandled exceptions, attaches the Plug connection (path, params, method, IP), and re-raises so Phoenix renders error pages normally.


Step 3: User Context for Better Error Attribution

Attach user context so every exception includes who was affected:

# lib/my_app_web/plugs/set_honeybadger_context.ex
defmodule MyAppWeb.Plugs.SetHoneybadgerContext do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case get_session(conn, :current_user_id) do
      nil ->
        conn

      user_id ->
        Honeybadger.context(
          user_id: user_id,
          user_email: get_session(conn, :current_user_email)
        )

        conn
    end
  end
end

Add to your router pipeline:

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug MyAppWeb.Plugs.SetHoneybadgerContext  # ← add after fetch_session
  plug :fetch_live_flash
  # ...
end

Now every exception includes the affected user, enabling Honeybadger's "affected users" count per error.


Step 4: Custom Exception Reporting

Report caught exceptions with structured context:

# lib/my_app/order_processor.ex
defmodule MyApp.OrderProcessor do
  require Logger

  def process(order_id) do
    order = MyApp.Repo.get!(MyApp.Order, order_id)

    with {:ok, _payment} <- charge_payment(order),
         {:ok, _fulfillment} <- create_fulfillment(order) do
      {:ok, order}
    else
      {:error, :payment_declined} ->
        Logger.info("Payment declined for order", order_id: order_id)
        {:error, :payment_declined}

      {:error, reason} ->
        Honeybadger.notify("Order processing failed", %{
          order_id: order_id,
          customer_id: order.customer_id,
          amount_cents: order.amount_cents,
          reason: inspect(reason),
          context: "order_processor.process"
        })

        Logger.error("Order processing failed", order_id: order_id, reason: inspect(reason))
        {:error, reason}
    end
  rescue
    e ->
      Honeybadger.notify(e, %{
        order_id: order_id,
        context: "order_processor.process"
      })

      reraise e, __STACKTRACE__
  end

  defp charge_payment(_order), do: {:ok, :charged}
  defp create_fulfillment(_order), do: {:ok, :fulfilled}
end

Step 5: Breadcrumbs for Request Tracing

Honeybadger breadcrumbs give you a trail of events leading to an exception:

# lib/my_app/checkout_flow.ex
defmodule MyApp.CheckoutFlow do
  def run(cart, user) do
    Honeybadger.add_breadcrumb("Checkout started", %{
      cart_id: cart.id,
      item_count: length(cart.items),
      user_id: user.id
    })

    with {:ok, total} <- calculate_total(cart) do
      Honeybadger.add_breadcrumb("Total calculated", %{total_cents: total})

      with {:ok, payment} <- charge(user, total) do
        Honeybadger.add_breadcrumb("Payment charged", %{payment_id: payment.id})
        finalize_order(cart, payment)
      end
    end
  end

  defp calculate_total(_cart), do: {:ok, 4999}
  defp charge(_user, _amount), do: {:ok, %{id: "ch_123"}}
  defp finalize_order(_cart, _payment), do: {:ok, :complete}
end

When an exception occurs in this flow, Honeybadger shows the full breadcrumb trail — which step succeeded and which failed.


Step 6: Check-ins for Scheduled Jobs

Honeybadger check-ins notify the Honeybadger service when a scheduled job runs. Use these alongside Vigilmon heartbeats for defense-in-depth job monitoring:

# lib/my_app/workers/nightly_sync.ex
defmodule MyApp.Workers.NightlySync do
  use Oban.Worker, queue: :scheduled

  require Logger

  @impl Oban.Worker
  def perform(%Oban.Job{}) do
    start = System.system_time(:second)

    with :ok <- sync_customers(),
         :ok <- sync_orders(),
         :ok <- sync_inventory() do

      duration_s = System.system_time(:second) - start

      # Report to Honeybadger check-in
      honeybadger_checkin_id = Application.get_env(:my_app, :honeybadger_nightly_checkin_id)
      if honeybadger_checkin_id do
        Honeybadger.checkin(honeybadger_checkin_id)
      end

      # Also ping Vigilmon heartbeat
      ping_vigilmon()

      Logger.info("Nightly sync completed", duration_seconds: duration_s)
      :ok
    else
      {:error, reason} ->
        Honeybadger.notify("NightlySync job failed", %{reason: inspect(reason)})
        Logger.error("Nightly sync failed", reason: inspect(reason))
        {:error, reason}
    end
  end

  defp ping_vigilmon do
    url = Application.get_env(:my_app, :vigilmon)[:nightly_sync_heartbeat_url]

    if url do
      :httpc.request(:get, {String.to_charlist(url), []}, [{:timeout, 5_000}], [])
    end
  end

  defp sync_customers, do: :ok
  defp sync_orders, do: :ok
  defp sync_inventory, do: :ok
end

Step 7: Health Endpoint for Vigilmon

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

  def index(conn, _params) do
    checks = %{
      database: check_database(),
      honeybadger: check_honeybadger()
    }

    all_ok = Enum.all?(checks, fn {_, v} -> v[:status] == "ok" end)
    http_status = if all_ok, do: 200, else: 503

    conn
    |> put_status(http_status)
    |> json(%{
      status: if(all_ok, do: "ok", else: "degraded"),
      checks: checks,
      version: Application.spec(:my_app, :vsn) |> to_string()
    })
  end

  defp check_database do
    case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", []) do
      {:ok, _} -> %{status: "ok"}
      {:error, reason} -> %{status: "error", reason: inspect(reason)}
    end
  rescue
    e -> %{status: "error", reason: Exception.message(e)}
  end

  defp check_honeybadger do
    # Honeybadger is healthy if its sender process is alive
    case Process.whereis(Honeybadger.Sender) do
      nil -> %{status: "degraded", reason: "Honeybadger.Sender not running"}
      _pid -> %{status: "ok"}
    end
  end
end

Step 8: Set Up Monitors in Vigilmon

HTTP uptime monitor:

  1. Sign in at vigilmon.online
  2. Click New Monitor → HTTP
  3. URL: https://yourdomain.com/health
  4. Interval: 1 minute (paid) or 5 minutes (free)
  5. Keyword check: body contains "status":"ok"

Heartbeat monitor for nightly sync:

  1. Click New Monitor → Heartbeat
  2. Set expected interval to 25 hours (24-hour job + 1-hour grace period)
  3. Copy the ping URL and set it as VIGILMON_NIGHTLY_SYNC_HEARTBEAT_URL

SSL monitor:

  1. Click New Monitor → SSL
  2. Enter your domain
  3. Alert 14 days before expiry

Step 9: Alerting

Slack for downtime:

In Vigilmon, configure Notifications → New Channel → Slack. When the health endpoint fails, you receive:

🔴 DOWN: yourdomain.com/health
Status: 503 Service Unavailable
Duration: 2 minutes
Check Honeybadger: https://app.honeybadger.io/projects/YOUR_ID/faults?q=active

PagerDuty for critical alerts:

Route Vigilmon missed heartbeats to PagerDuty for immediate on-call paging. A missed nightly sync heartbeat means business data is out of sync and warrants immediate investigation.

Status page:

  1. Go to Status Pages → New Status Page in Vigilmon
  2. Add your HTTP monitor, heartbeat monitors, and SSL monitor
  3. Share the URL in your README and on your support page

Common Honeybadger + Vigilmon Patterns

Deployment tracking:

Tag Honeybadger errors with the current release version so you can correlate error spikes with deploys:

# config/runtime.exs
config :honeybadger,
  api_key: System.get_env("HONEYBADGER_API_KEY"),
  environment_name: System.get_env("HONEYBADGER_ENV", "production"),
  revision: System.get_env("GIT_COMMIT_SHA", "unknown")

After a deploy, watch Honeybadger for new error classes introduced in the release while watching Vigilmon for any availability degradation.

Error rate alerting in Honeybadger:

Configure Honeybadger uptime checks to complement Vigilmon with an additional probe:

Honeybadger uptime check → https://yourdomain.com/health (from Honeybadger's infra)
Vigilmon HTTP monitor → https://yourdomain.com/health (from Vigilmon's infra)

Two independent uptime probes from different infrastructure means no single monitoring system outage creates a blind spot.


What You've Built

| What | How | |------|-----| | Automatic Phoenix exception capture | Honeybadger.Plug with request context | | User attribution for errors | Honeybadger.context/1 with user session data | | Breadcrumb trail for complex flows | Honeybadger.add_breadcrumb/2 at key steps | | Custom exception reporting | Honeybadger.notify/2 with structured metadata | | Scheduled job monitoring | Honeybadger check-ins + Vigilmon heartbeats | | External uptime monitoring | Vigilmon HTTP monitor from multiple regions | | SSL certificate monitoring | Vigilmon SSL monitor with 14-day expiry alert | | Public status page | Vigilmon status page aggregating all monitors |

Honeybadger shows you every exception that reaches your code. Vigilmon shows you when your infrastructure is unreachable before any exception has a chance to fire. Together, no failure mode goes undetected.


Next Steps

  • Add Honeybadger Insights events to track business metrics alongside errors (order rate, conversion rate) so you can detect business-level anomalies that don't produce exceptions
  • Use Vigilmon's response time history to catch Phoenix endpoint latency regressions before users notice slowness
  • Add Vigilmon monitors for your third-party dependencies (payment provider, email service, external APIs) and use Honeybadger's occurrence timeline to correlate third-party outages with error spikes
  • Set up Vigilmon multi-region monitoring if you deploy to multiple cloud regions or use a CDN

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 →