tutorial

Monitoring Your Go Gin API with Vigilmon: Uptime, Heartbeats & Alerts

Learn how to add production-grade monitoring to your Go Gin API — health endpoints, uptime checks, goroutine heartbeats, and alert routing.

Production APIs go down in unexpected ways: a misconfigured deployment, a database connection pool exhausted at 2 AM, a goroutine leak that silently starves your workers. Vigilmon catches these before your customers do. In this tutorial you'll wire a Go Gin API into Vigilmon for uptime monitoring, heartbeat pings, and alert routing — all in about 20 minutes.

What You'll Build

  • A /health endpoint in Gin that returns JSON status
  • A Vigilmon HTTP monitor pointed at your API
  • A background goroutine heartbeat using time.Ticker
  • Graceful shutdown that doesn't lose in-flight requests
  • Email and Slack webhook alerts

Prerequisites

  • Go 1.21+
  • A running Gin project (or follow along from scratch)
  • A free Vigilmon account

Step 1: Add the Health Endpoint

A health endpoint lets Vigilmon (and your load balancer) confirm your service is alive and ready to serve traffic. The key is to return a meaningful status, not just 200 OK.

// internal/health/handler.go
package health

import (
    "database/sql"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

type Handler struct {
    DB *sql.DB
}

type HealthResponse struct {
    Status    string            `json:"status"`
    Timestamp time.Time         `json:"timestamp"`
    Checks    map[string]string `json:"checks"`
}

func (h *Handler) Check(c *gin.Context) {
    checks := map[string]string{}
    overallStatus := "ok"

    // Ping the database with a 2-second deadline
    if err := h.DB.PingContext(c.Request.Context()); err != nil {
        checks["database"] = "error: " + err.Error()
        overallStatus = "degraded"
    } else {
        checks["database"] = "ok"
    }

    statusCode := http.StatusOK
    if overallStatus != "ok" {
        statusCode = http.StatusServiceUnavailable
    }

    c.JSON(statusCode, HealthResponse{
        Status:    overallStatus,
        Timestamp: time.Now().UTC(),
        Checks:    checks,
    })
}

Register it on your router:

// main.go (excerpt)
r := gin.Default()

healthHandler := &health.Handler{DB: db}
r.GET("/health", healthHandler.Check)

Test it locally:

curl -s localhost:8080/health | jq
# {
#   "status": "ok",
#   "timestamp": "2025-06-29T10:00:00Z",
#   "checks": { "database": "ok" }
# }

When your database is unreachable, the endpoint returns 503 — Vigilmon treats any non-2xx response as a failure and immediately starts the alerting chain.


Step 2: Create a Vigilmon HTTP Monitor

Log in to Vigilmon and create a new HTTP Monitor:

| Field | Value | |---|---| | URL | https://api.yourdomain.com/health | | Method | GET | | Check interval | 60 seconds | | Expected status | 200 | | Timeout | 10 seconds | | Regions | Select 2–3 for triangulation |

Under Alert Channels, add your email address. You'll add Slack in Step 5.

Click Save — Vigilmon immediately begins polling. Within a minute you'll see the first green tick on your dashboard.

Pro tip: If your API requires authentication, add an Authorization header in the monitor config. Create a dedicated read-only monitoring token rather than reusing a user token.


Step 3: Goroutine Heartbeat with time.Ticker

HTTP uptime checks confirm the process is responding, but they don't tell you whether your background workers are healthy. The heartbeat pattern fixes this: your worker pings Vigilmon every N seconds to confirm it's alive. If pings stop, Vigilmon alerts you.

First, grab your Heartbeat URL from Vigilmon (Dashboard → Heartbeat Monitors → New). It looks like:

https://vigilmon.online/api/heartbeats/YOUR-UUID/ping

Now wire it into your worker:

// internal/worker/heartbeat.go
package worker

import (
    "context"
    "log"
    "net/http"
    "time"
)

const heartbeatInterval = 60 * time.Second

func RunWithHeartbeat(ctx context.Context, heartbeatURL string, work func(ctx context.Context) error) {
    ticker := time.NewTicker(heartbeatInterval)
    defer ticker.Stop()

    httpClient := &http.Client{Timeout: 5 * time.Second}

    ping := func() {
        resp, err := httpClient.Get(heartbeatURL)
        if err != nil {
            log.Printf("[heartbeat] ping failed: %v", err)
            return
        }
        resp.Body.Close()
        log.Printf("[heartbeat] pinged, status %d", resp.StatusCode)
    }

    // Ping immediately on start so Vigilmon knows the worker is up
    ping()

    for {
        select {
        case <-ctx.Done():
            log.Println("[heartbeat] worker shutting down")
            return
        case <-ticker.C:
            // Do the actual work first, then ping on success
            if err := work(ctx); err != nil {
                log.Printf("[worker] error: %v", err)
                // Don't ping — let Vigilmon detect the silence
                continue
            }
            ping()
        }
    }
}

Launch it from main.go:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go worker.RunWithHeartbeat(ctx, os.Getenv("VIGILMON_HEARTBEAT_URL"), processQueue)

Store the heartbeat URL in an environment variable — never hard-code it:

VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeats/YOUR-UUID/ping

Vigilmon's heartbeat monitor expects a ping within a configurable window (e.g. 90 seconds). If the window expires without a ping — because the worker panicked, the goroutine deadlocked, or the process restarted — you get alerted immediately.


Step 4: Graceful Shutdown

Monitoring is only useful if your deployment process doesn't generate false alerts. A non-graceful shutdown kills in-flight requests mid-response, triggers 5xx errors during the check interval, and floods your alert inbox during every deploy.

// main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    // ... register routes ...

    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    // Start server in background
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %v", err)
        }
    }()

    // Block until signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("shutting down...")

    // Give in-flight requests 30 seconds to complete
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("forced shutdown: %v", err)
    }
    log.Println("server exited cleanly")
}

Pair this with a Vigilmon maintenance window during planned deployments: the dashboard lets you mute alerts for a set duration so a rolling restart doesn't page your on-call.


Step 5: Alert Routing

Email Alerts

Already configured in Step 2. Vigilmon sends alerts when:

  • The endpoint returns a non-2xx status
  • The endpoint doesn't respond within the timeout
  • The heartbeat window expires without a ping

You can configure escalation policies — first alert goes to your personal email, if unacknowledged for 15 minutes it escalates to the team channel.

Slack Webhook Alerts

  1. Create a Slack incoming webhook in your workspace (Slack docs)
  2. In Vigilmon → Alert Channels → Add Channel → Webhook
  3. Paste the Slack webhook URL
  4. Assign the channel to your monitors

The alert payload Vigilmon posts looks like:

{
  "text": "🔴 *api.yourdomain.com/health* is DOWN\nStatus: 503 | Region: eu-west-1\nDuration: 2m 14s"
}

You can also send to PagerDuty, Microsoft Teams, or any webhook-capable endpoint.


Step 6: Test the Full Loop

  1. Simulate a DB failure: stop your local database and curl /health — you should see 503.
  2. Verify Vigilmon detects it: within one check interval, your Vigilmon dashboard shows the monitor red and an alert fires.
  3. Test the heartbeat gap: stop your worker process and wait for the heartbeat window to expire — you should get an alert.
  4. Recover: bring the DB back up. Vigilmon auto-recovers and sends a "back online" notification.

Production Checklist

  • [ ] /health checks all critical dependencies (DB, cache, message broker)
  • [ ] Heartbeat URL stored in environment variable, not code
  • [ ] Graceful shutdown with SIGTERM handling
  • [ ] Alert channels tested end-to-end
  • [ ] Maintenance windows configured for your deploy pipeline
  • [ ] Monitor check interval tuned to your SLA (60s for most APIs)

Wrapping Up

You now have a production-grade monitoring setup for your Go Gin API:

  • Uptime monitoring that catches HTTP failures within 60 seconds
  • Heartbeat monitoring that catches silent worker failures
  • Alert routing to email and Slack with escalation

Sign up for Vigilmon and get your first monitor running in under five minutes — no credit card required.

If you hit issues or have questions, drop a comment below. Happy monitoring!

Monitor your app with Vigilmon

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

Start free →