tutorial

Monitoring Baselime-Instrumented Apps with Vigilmon: Serverless Health Checks, Heartbeats & External Uptime

Guide to pairing Baselime's serverless observability (now Cloudflare) with Vigilmon's external uptime monitoring — health endpoints, Lambda heartbeats, and independent alerting.

Baselime brought structured log-based observability to serverless applications — and its integration into Cloudflare's platform means that paradigm now lives natively at the edge. Whether you're running AWS Lambda functions or Cloudflare Workers, Baselime/Cloudflare gives you rich structured logs, traces, and alerts from inside your functions. But internal observability has a blind spot: it depends on your functions actually receiving invocations and emitting logs. When a function is broken at the routing layer, behind a misconfigured domain, or simply not being triggered at all, the logs are silent. Vigilmon catches those silent failures with an external probe that checks reachability independently of your logging pipeline.

This tutorial covers adding external uptime monitoring to serverless applications already instrumented with Baselime.

What You'll Build

  • A /health route in your serverless function for external probing
  • A Vigilmon HTTP monitor targeting your function's public URL
  • A heartbeat monitor for event-driven and scheduled serverless functions
  • An alert strategy that keeps Baselime/Cloudflare observability alerts and Vigilmon uptime alerts distinct

Prerequisites

  • A Baselime account (or Cloudflare Observability if you've migrated) with at least one instrumented application
  • A deployed serverless function reachable via a public HTTPS URL
  • A free account at vigilmon.online

Step 1: Add a Health Route to Your Serverless Function

Serverless functions don't have a persistent process to probe. Every health check is itself a function invocation. That's fine — add a dedicated route that validates the function's runtime environment and upstream dependencies.

AWS Lambda (Node.js)

// handlers/health.mjs
export async function handler(event) {
  const checks = {};
  let ok = true;

  // Environment variable sanity check
  const requiredEnvVars = ["DATABASE_URL", "API_KEY", "QUEUE_URL"];
  const missingVars = requiredEnvVars.filter((v) => !process.env[v]);
  if (missingVars.length > 0) {
    checks.env = `missing: ${missingVars.join(", ")}`;
    ok = false;
  } else {
    checks.env = "ok";
  }

  // Upstream HTTP dependency probe
  try {
    const resp = await fetch(process.env.UPSTREAM_API + "/ping", {
      signal: AbortSignal.timeout(3000),
    });
    checks.upstream = resp.ok ? "ok" : `http_${resp.status}`;
    if (!resp.ok) ok = false;
  } catch (err) {
    checks.upstream = `error: ${err.message}`;
    ok = false;
  }

  return {
    statusCode: ok ? 200 : 503,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      status: ok ? "ok" : "degraded",
      region: process.env.AWS_REGION,
      checks,
    }),
  };
}

Wire the Lambda to a GET /health route in your API Gateway or function URL configuration.

Cloudflare Worker

// src/index.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return handleHealth(env);
    }

    // ... rest of your routing
    return new Response("Not found", { status: 404 });
  },
};

async function handleHealth(env: Env): Promise<Response> {
  const checks: Record<string, string> = {};
  let ok = true;

  // KV connectivity check
  try {
    const probe = await env.APP_KV.get("__health_probe__");
    checks.kv = "ok";
  } catch (err) {
    checks.kv = `error: ${(err as Error).message}`;
    ok = false;
  }

  // Upstream API check
  try {
    const resp = await fetch(env.UPSTREAM_API + "/ping", {
      signal: AbortSignal.timeout(3000),
    });
    checks.upstream = resp.ok ? "ok" : `http_${resp.status}`;
    if (!resp.ok) ok = false;
  } catch (err) {
    checks.upstream = `error: ${(err as Error).message}`;
    ok = false;
  }

  return Response.json(
    { status: ok ? "ok" : "degraded", checks },
    { status: ok ? 200 : 503 }
  );
}

Verify the endpoint after deployment:

curl -s https://your-function.workers.dev/health | jq .
# {"status":"ok","checks":{"kv":"ok","upstream":"ok"}}

Step 2: Set Up Vigilmon HTTP Monitor

  1. Log in to VigilmonAdd Monitor → HTTP.
  2. URL: https://your-function.workers.dev/health (or your Lambda function URL / custom domain).
  3. Check interval: 60 seconds.
  4. Response timeout: 10 seconds — generous enough for cold-start Lambda invocations.
  5. Expected status code: 200.
  6. JSON body assertion:
    • Path: status
    • Expected value: ok
  7. Save.

Vigilmon now probes your function from multiple external locations every 60 seconds. If the function is unreachable, returns a non-200 status, or the JSON assertion fails, you get an alert immediately.

Securing the health endpoint

Baselime captures all function logs including health check invocations. If your health response contains sensitive runtime data, add a token check:

// Lambda example
export async function handler(event) {
  const token = event.headers?.["x-health-token"] || event.headers?.["X-Health-Token"];
  if (token !== process.env.HEALTH_TOKEN) {
    return { statusCode: 401, body: JSON.stringify({ error: "unauthorized" }) };
  }
  // ... health checks
}

In Vigilmon's monitor settings, add Custom header: X-Health-Token: <your-secret>.


Step 3: Heartbeat Monitor for Scheduled and Event-Driven Functions

Baselime (and Cloudflare Observability) captures logs from your functions when they run. If a scheduled function stops being invoked — say, an EventBridge rule is accidentally disabled, or a Cloudflare Cron Trigger misfires — the logs simply stop. Silence in the logs doesn't trigger an alert.

Vigilmon's heartbeat monitor inverts this: instead of alerting on noise, it alerts on silence.

AWS Lambda with EventBridge

// handlers/scheduled-job.mjs
export async function handler(event) {
  try {
    await runDataSync();

    // Signal success to Vigilmon — only fires when job completes
    await fetch(process.env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
    console.log("Data sync complete — heartbeat sent");
  } catch (err) {
    // Deliberate silence — missed heartbeat triggers Vigilmon alert
    console.error("Data sync failed — heartbeat withheld:", err);
    throw err;
  }
}

Cloudflare Worker Cron Trigger

export default {
  // ... fetch handler ...

  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    try {
      await runScheduledTask(env);

      await fetch(env.VIGILMON_HEARTBEAT_URL, { method: "POST" });
      console.log("Scheduled task complete — heartbeat sent");
    } catch (err) {
      console.error("Scheduled task failed:", err);
      // Do not ping heartbeat — Vigilmon fires an alert
    }
  },
};

In Vigilmon, create a Heartbeat monitor and set the grace period to 10 minutes for a 5-minute cron, or 70 minutes for an hourly job. Store the heartbeat URL in AWS Secrets Manager or Cloudflare Secrets and inject it as VIGILMON_HEARTBEAT_URL.


Step 4: Correlating Baselime Logs with Vigilmon Alerts

When Vigilmon fires a downtime alert, you want to jump immediately to the Baselime (or Cloudflare Observability) log view for the same time window. Set up a runbook link:

  1. Note the Vigilmon alert body — it includes the timestamp of the first failed check.
  2. Open Baselime / Cloudflare Observability and filter logs to the same time window and function name.
  3. Look for error logs, cold-start timeouts, or missing invocations (no logs at all = function not being invoked).
  4. Check whether the issue is in the function code or at the routing / trigger layer.

Add a note in your Vigilmon monitor's Notes field linking directly to the relevant Baselime namespace or Cloudflare dashboard URL so on-call engineers can jump straight there.


Step 5: Public Status Page

Baselime and Cloudflare Observability dashboards are internal tools. When customers want to check service health, point them to a public status page.

  1. In Vigilmon, go to Status Pages → Create.
  2. Add your HTTP monitor and any heartbeat monitors.
  3. Configure a custom domain (e.g., status.example.com) or use the Vigilmon subdomain.
  4. Add an uptime badge to your documentation:
<a href="https://status.example.com">
  <img src="https://vigilmon.online/badge/<monitor-id>.svg" alt="API Status" />
</a>

What Vigilmon Adds to a Baselime/Cloudflare Observability Stack

| Scenario | Baselime / Cloudflare Obs | Vigilmon | |---|---|---| | Function throws an unhandled error | Captured in structured logs | HTTP monitor sees 5xx, fires alert | | Function URL is unreachable (DNS, routing) | No logs — silent gap | External probe catches immediately | | EventBridge rule disabled accidentally | No invocations — logs go quiet | Heartbeat silence triggers alert | | Cold start causes user-facing timeout | Logs show cold start duration | Response timeout fires alert | | Cron Trigger misfires | No execution — no logs | Heartbeat missed alert | | Your cloud provider has a regional issue | Logs may not be ingested | Vigilmon is external — probing from outside |


Baselime and Cloudflare Observability give you rich insight into what happens inside your serverless functions. Vigilmon tells you whether those functions are reachable and running on schedule. The two tools are complementary — observability tells you the what, external monitoring tells you the whether.

Add independent uptime monitoring to your serverless stack today — register 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 →