Pulumi Any Terraform

Heartbeat

Create heartbeat monitors for job and cron health checks

The Heartbeat resource creates monitors that expect regular "pings" from your jobs, cron tasks, or scheduled processes.

Example Usage

Basic Heartbeat

import * as betteruptime from "@pulumi-contrib/better-uptime";

const heartbeat = new betteruptime.Heartbeat("backup-job", {
    name: "Database Backup",
    period: 86400, // 24 hours
    grace: 3600, // 1 hour grace period
});

// Export the heartbeat URL to ping
export const heartbeatUrl = heartbeat.url;

Cron Job Heartbeat

const cronHeartbeat = new betteruptime.Heartbeat("daily-sync", {
    name: "Daily Data Sync",
    period: 86400, // Every 24 hours
    grace: 1800, // 30 minute grace period
    call: true,
    sms: true,
    email: true,
});

Short-Interval Heartbeat

const frequentJob = new betteruptime.Heartbeat("health-check-job", {
    name: "Health Check Job",
    period: 300, // Every 5 minutes
    grace: 60, // 1 minute grace period
});

Heartbeat with Policy

import * as betteruptime from "@pulumi-contrib/better-uptime";

const policy = new betteruptime.Policy("ops-team", {
    name: "Operations Team",
});

const criticalJob = new betteruptime.Heartbeat("critical-batch", {
    name: "Critical Batch Process",
    period: 3600, // Hourly
    grace: 300, // 5 minutes
    policyId: policy.id,
    call: true,
    sms: true,
});

Heartbeat Group

const group = new betteruptime.HeartbeatGroup("batch-jobs", {
    name: "Batch Processing Jobs",
});

const heartbeat1 = new betteruptime.Heartbeat("job1", {
    name: "ETL Job 1",
    period: 43200, // 12 hours
    grace: 1800,
    heartbeatGroupId: group.id,
});

const heartbeat2 = new betteruptime.Heartbeat("job2", {
    name: "ETL Job 2",
    period: 43200,
    grace: 1800,
    heartbeatGroupId: group.id,
});

Argument Reference

Required Arguments

  • name (String) - Name of the heartbeat monitor
  • period (Number) - Expected period between pings in seconds

Optional Arguments

  • grace (Number) - Grace period in seconds before alerting. Default: 0
  • call (Boolean) - Enable phone call notifications. Default: false
  • sms (Boolean) - Enable SMS notifications. Default: false
  • email (Boolean) - Enable email notifications. Default: true
  • push (Boolean) - Enable push notifications. Default: true
  • heartbeatGroupId (Number) - Heartbeat group ID
  • policyId (Number) - Escalation policy ID
  • paused (Boolean) - Whether the heartbeat is paused. Default: false
  • sort_index (Number) - Sort index for ordering

Attribute Reference

  • id (String) - The heartbeat ID
  • url (String) - The unique URL to ping
  • status (String) - Current heartbeat status (up, down, paused)
  • createdAt (String) - Creation timestamp
  • updatedAt (String) - Last update timestamp
  • lastPingAt (String) - Last successful ping timestamp

Using Heartbeat URLs

In Shell Scripts

#!/bin/bash
# Your backup script

# Perform backup
pg_dump mydb > backup.sql

# Ping heartbeat on success
curl -fsS --retry 3 "https://uptime.betterstack.com/api/v1/heartbeat/xxx"

In Python

import requests

def backup_job():
    try:
        # Perform backup
        perform_backup()
        
        # Ping heartbeat
        requests.get("https://uptime.betterstack.com/api/v1/heartbeat/xxx")
    except Exception as e:
        print(f"Backup failed: {e}")
        # Don't ping on failure - will trigger alert

In Node.js

const https = require('https');

async function cronJob() {
    try {
        await performTask();
        
        // Ping heartbeat
        https.get('https://uptime.betterstack.com/api/v1/heartbeat/xxx');
    } catch (error) {
        console.error('Job failed:', error);
        // Missing ping will trigger alert
    }
}

In Docker

FROM alpine:latest

# Install curl
RUN apk add --no-cache curl

# Your cron job
COPY backup.sh /backup.sh
RUN chmod +x /backup.sh

# Add heartbeat ping
RUN echo "*/5 * * * * /backup.sh && curl -fsS https://uptime.betterstack.com/api/v1/heartbeat/xxx" > /etc/crontabs/root

CMD ["crond", "-f"]

Best Practices

Period and Grace Configuration

Job FrequencyPeriodGraceUse Case
Every 5 min300s60sFrequent health checks
Hourly3600s300sRegular maintenance
Every 6 hours21600s1800sPeriodic sync jobs
Daily86400s3600sDaily backups
Weekly604800s7200sWeekly reports

Error Handling

// Only ping on success
const heartbeat = new betteruptime.Heartbeat("reliable-job", {
    name: "Data Processing",
    period: 3600,
    grace: 300,
    call: true, // Alert immediately via call
});

// In your job script:
// curl "$HEARTBEAT_URL" || exit 0  # Don't fail job if ping fails

Monitoring Multiple Environments

// Production heartbeat
const prodHeartbeat = new betteruptime.Heartbeat("prod-backup", {
    name: "Production Backup",
    period: 86400,
    grace: 1800,
    call: true,
    sms: true,
});

// Staging heartbeat
const stagingHeartbeat = new betteruptime.Heartbeat("staging-backup", {
    name: "Staging Backup",
    period: 86400,
    grace: 3600, // More grace for staging
    email: true,
});

Grouped Heartbeats

// Create a group for related jobs
const group = new betteruptime.HeartbeatGroup("etl-pipeline", {
    name: "ETL Pipeline Jobs",
});

const steps = ["extract", "transform", "load"].map((step, i) => 
    new betteruptime.Heartbeat(`etl-${step}`, {
        name: `ETL ${step.toUpperCase()}`,
        period: 7200, // 2 hours
        grace: 600,
        heartbeatGroupId: group.id,
        sortIndex: i,
    })
);

Common Patterns

Database Backup Monitoring

const dbBackup = new betteruptime.Heartbeat("postgres-backup", {
    name: "PostgreSQL Backup",
    period: 86400, // Daily
    grace: 3600, // 1 hour grace
    call: true,
    sms: true,
});

// In backup script:
// pg_dump db | gzip > backup.gz && curl "$HEARTBEAT_URL"

Cron Job Monitoring

const cronJob = new betteruptime.Heartbeat("report-generation", {
    name: "Daily Report Generation",
    period: 86400,
    grace: 1800,
});

// Crontab:
// 0 2 * * * /usr/local/bin/generate-report.sh && curl "$HEARTBEAT_URL"

API Scheduled Task

const scheduledTask = new betteruptime.Heartbeat("cleanup-task", {
    name: "Cleanup Old Records",
    period: 21600, // Every 6 hours
    grace: 900, // 15 minutes
});

// In your API:
// schedule.every(6).hours.do(cleanup_and_ping)

Import

Heartbeats can be imported using their ID:

pulumi import better-uptime:index/heartbeat:Heartbeat example 123456

Troubleshooting

Heartbeat Not Receiving Pings

Causes:

  • Incorrect URL
  • Network/firewall issues
  • Job failing before ping

Solution:

  • Verify heartbeat URL is correct
  • Test with curl manually
  • Add logging to job
# Debug heartbeat ping
curl -v "https://uptime.betterstack.com/api/v1/heartbeat/xxx"

False Alerts

Cause: Grace period too short for job variability

Solution:

// Increase grace period for variable jobs
const variableJob = new betteruptime.Heartbeat("variable", {
    name: "Variable Duration Job",
    period: 3600,
    grace: 1800, // 50% of period as grace
});

Missing Heartbeat Groups

Error: "Heartbeat group not found"

Solution: Ensure group is created before heartbeats:

const group = new betteruptime.HeartbeatGroup("group", {
    name: "My Group",
});

const heartbeat = new betteruptime.Heartbeat("hb", {
    name: "Heartbeat",
    period: 3600,
    heartbeatGroupId: group.id,
}, { dependsOn: [group] });