Skip to content

Monitor AWS EventBridge scheduled jobs

EventBridge Scheduler only decides when a job starts. It can start a Lambda function or an ECS task, and either can fail without anyone looking: the function times out, the task can’t pull its image, or the schedule was disabled during an incident and never turned back on. A heartbeat check catches all of these: the job reports to Pingstack after it succeeds, and Pingstack alerts you when that report doesn’t arrive on time.

This recipe covers both targets. Only step 3 differs.

1. Create a heartbeat check that matches the schedule

Section titled “1. Create a heartbeat check that matches the schedule”

Find the schedule’s expression. EventBridge cron has six fields (minutes, hours, day of month, month, day of week, year), and one of the two day fields must be ?:

cron(0 2 * * ? *)

Pingstack uses standard five-field cron. Drop the year field and replace ? with *:

EventBridge Pingstack
cron(0 2 * * ? *) 0 2 * * *
cron(30 6 ? * MON-FRI *) 30 6 * * 1-5
cron(0 */4 * * ? *) 0 */4 * * *
rate(15 minutes) Interval of 15 minutes

Create a heartbeat check with the converted schedule. EventBridge Scheduler uses the timezone set on the schedule, or UTC if none is set; choose the same one in Pingstack. See schedules and timezones.

Set a grace period that covers the job’s runtime, the schedule’s flexible time window if you use one, and any retries in its retry policy. Attach a notification group.

Copy the location’s ping URL from the check in Pingstack and store it as a secret, so it stays out of your infrastructure code:

Terminal window
read -rs URL
aws secretsmanager create-secret --name pingstack/nightly-report \
--secret-string "$URL"
unset URL

Give the function’s execution role, or the ECS task’s execution role, permission to read that secret.

Read the secret once per container, then report at the end of the handler:

// index.mjs (Node.js 20 runtime)
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const secrets = new SecretsManagerClient({});
let pingUrl;
async function getPingUrl() {
pingUrl ??= (await secrets.send(
new GetSecretValueCommand({ SecretId: 'pingstack/nightly-report' })
)).SecretString;
return pingUrl;
}
export async function handler() {
await runNightlyReport();
try {
await fetch(await getPingUrl(), { signal: AbortSignal.timeout(10_000) });
} catch (error) {
console.warn(`Pingstack heartbeat failed: ${error.name}`);
}
}

The report runs only after runNightlyReport() resolves. If the handler throws or times out, nothing is sent and Pingstack alerts you when the deadline passes. A failure to reach Pingstack is logged, not thrown, so it can’t make EventBridge retry a successful run. Set the function’s timeout to leave a few seconds for the report.

Inject the secret into the container and chain the report after the job:

{
"containerDefinitions": [
{
"name": "nightly-report",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/reports:1.4.0",
"command": [
"sh", "-c",
"./run-report && curl --fail --silent --show-error --max-time 10 --retry 3 \"$PINGSTACK_PING_URL\""
],
"secrets": [
{
"name": "PINGSTACK_PING_URL",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:pingstack/nightly-report"
}
]
}
]
}

&& runs the report only if ./run-report exits successfully. If your image has no shell or curl, report from the end of your program instead, as in the Lambda example.

Run the job once by hand: use Test on the Lambda function, or aws ecs run-task with the same task definition and network settings as the schedule. Open the check in Pingstack and confirm the location received a heartbeat and shows a next deadline.

Append ?value= with a count the job produces to send it as a numeric value, then set warning and critical thresholds on the check:

const sent = await runNightlyReport();
const target = new URL(await getPingUrl());
target.searchParams.set('value', String(sent));
await fetch(target, { signal: AbortSignal.timeout(10_000) });
  • Flexible time window. EventBridge can start the job anywhere inside the window. Make the grace period at least as long.
  • Retries. A run that succeeds after a retry reports late.
  • Timezone mismatch. Schedules without a timezone run in UTC.
  • Disabled schedule. A schedule in the DISABLED state never runs. Pingstack alerts, which is usually what you want.
  • ECS capacity. A task waiting for Fargate capacity or an image pull starts late. Allow for it in the grace period.

If alerts still don’t match what you expect, see troubleshooting monitoring.