tutorial

Monitor Your Gin (Go) Application with Vigilmon

Add external uptime monitoring to your Go Gin API — health endpoints, goroutine heartbeats, Docker deployment checks, and graceful shutdown that doesn't lose requests.

Production APIs go dark in ways that don't trip your tests: a database connection pool exhausted at 2 AM, a goroutine leak that slowly starves your workers, a misconfigured Docker healthcheck that kills the container after a restart. Vigilmon catches these from outside your infrastructure by actively probing your service every minute and alerting you within seconds of the first failure.

In this guide you'll wire a Go Gin API into Vigilmon for uptime monitoring, goroutine heartbeats, and alert routing — and set up a Docker deployment with proper health check configuration.

What You'll Build

  • A /health endpoint in Gin that checks the database and reports status
  • A Vigilmon HTTP monitor pointed at your API
  • A background goroutine heartbeat that pings Vigilmon every N minutes
  • A Dockerfile with a proper HEALTHCHECK directive
  • Graceful shutdown so in-flight requests finish before the process exits
  • 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 gives Vigilmon (and Docker) a reliable signal that your service is alive and ready. Return structured JSON so that assertion rules on Vigilmon can check the body — not just the status code.

// internal/health/handler.go
package health

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

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

type Handler struct {
	DB *sql.DB
}

type Response 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{}
	overall := "ok"

	// Ping the DB with a tight deadline so a hung connection doesn't stall the check.
	ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
	defer cancel()

	if err := h.DB.PingContext(ctx); err != nil {
		checks["database"] = "error: " + err.Error()
		overall = "degraded"
	} else {
		checks["database"] = "ok"
	}

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

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

Register it in your router setup:

// cmd/api/main.go
package main

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

	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
	"yourapp/internal/health"
)

func main() {
	db, err := sql.Open("postgres", "postgres://user:pass@localhost/mydb?sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	r := gin.Default()

	// Health endpoint — no auth middleware, reachable by Vigilmon
	healthHandler := &health.Handler{DB: db}
	r.GET("/health", healthHandler.Check)

	// ... rest of your routes ...

	r.Run(":8080")
}

Test it:

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

Step 2: Add the Vigilmon HTTP Monitor

  1. Log in to Vigilmon and click New Monitor → HTTP.
  2. Enter https://your-domain.com/health as the URL.
  3. Set the interval to 60 seconds (free) or 30 seconds (paid).
  4. Under Assertions, add:
    • Status code equals 200
    • Response body contains "status":"ok"
  5. Save.

Vigilmon now checks your service from multiple regions and fires an alert within seconds if it starts returning errors or timing out.


Step 3: Configure Alerts

In the Vigilmon dashboard, go to Alerts → Channels.

Email: Add your on-call address. Vigilmon sends a failure alert within 30 seconds of the first failed check and a recovery notification when the service comes back.

Slack: Click Add Channel → Slack, paste your incoming webhook URL, and save. Route all CRITICAL monitors to both channels via Alerts → Routing.


Step 4: Goroutine Heartbeat for Background Workers

HTTP uptime checks verify your API is responding, but they won't catch a background goroutine that silently stopped running. Use a Vigilmon heartbeat monitor to detect this.

  1. In Vigilmon, go to New Monitor → Heartbeat.
  2. Set the expected period (e.g., 5 minutes).
  3. Copy the ping URL (https://vigilmon.online/ping/YOUR_ID).

Now ping it from your background goroutine using time.Ticker:

// internal/worker/processor.go
package worker

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

const vigilmonHeartbeat = "https://vigilmon.online/ping/YOUR_HEARTBEAT_ID"

func RunProcessor(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

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

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			if err := processJobs(); err != nil {
				log.Printf("processor error: %v", err)
				continue // Don't ping on failure — let Vigilmon detect the miss
			}
			// Signal success
			if _, err := httpClient.Get(vigilmonHeartbeat); err != nil {
				log.Printf("vigilmon ping failed: %v", err)
			}
		}
	}
}

Start the worker in main.go:

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

go worker.RunProcessor(ctx)

If the goroutine panics and is never restarted, Vigilmon will alert you after one missed interval.


Step 5: Docker Deployment with HEALTHCHECK

Add a HEALTHCHECK instruction to your Dockerfile so Docker itself knows when to restart a container and when to mark it unhealthy:

FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /api ./cmd/api

FROM alpine:3.19
RUN apk --no-cache add ca-certificates curl
WORKDIR /root/
COPY --from=builder /api .

EXPOSE 8080

# Docker will probe /health every 30s.
# 3 consecutive failures → container status = "unhealthy" → restart policy kicks in.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

CMD ["./api"]

With restart: unless-stopped in your docker-compose.yml, Docker will restart an unhealthy container automatically. Vigilmon catches the window of unavailability and alerts you even when Docker recovers on its own.


Step 6: Graceful Shutdown

Graceful shutdown prevents in-flight requests from being cut off when you deploy a new version. Wire it up alongside the health endpoint:

// cmd/api/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 the server in a goroutine
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %v", err)
		}
	}()

	// Wait for SIGINT or SIGTERM (sent by Docker, Kubernetes, systemd, etc.)
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("shutting down gracefully")

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

During a rolling deploy your old container receives SIGTERM, drains its requests in ≤10 seconds, and exits cleanly. Vigilmon sees a brief gap at most and won't fire a false-positive alert if the new container comes up within the check interval.


Step 7: Verify End-to-End

  1. Confirm the Vigilmon dashboard shows the monitor as UP.
  2. Return a 503 from your health handler temporarily and verify an alert arrives.
  3. Restore the 200 and confirm the recovery notification.

To simulate a database failure locally:

// Intentionally break the DB URL
db, _ := sql.Open("postgres", "postgres://bad:creds@localhost/none")

The health check returns 503 with "database": "error: ...", and Vigilmon catches it within the next check interval.


Production Checklist

  • [ ] /health route registered and returning 200 with JSON body
  • [ ] DB PingContext using a 2s deadline to avoid hanging checks
  • [ ] Vigilmon HTTP monitor with status code + body assertions
  • [ ] Email and Slack alert channels configured
  • [ ] Goroutine heartbeat monitor for background workers
  • [ ] HEALTHCHECK in Dockerfile
  • [ ] Graceful shutdown wired to SIGTERM
  • [ ] Health endpoint excluded from auth middleware

Summary

You now have a Go Gin API that:

  • Exposes a structured /health endpoint with active dependency checks
  • Is probed every minute by Vigilmon from multiple regions
  • Fires Slack and email alerts within seconds of a failure
  • Runs background goroutines with heartbeat monitoring
  • Deploys cleanly with Docker health checks and graceful shutdown

The setup takes under 30 minutes and runs on Vigilmon's free tier. Your on-call rotation gets an alert in Slack before any user files a bug report.

Monitor your app with Vigilmon

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

Start free →