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.
2. Store the ping URL in Secrets Manager
Section titled “2. Store the ping URL in Secrets Manager”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:
read -rs URLaws secretsmanager create-secret --name pingstack/nightly-report \ --secret-string "$URL"unset URLGive the function’s execution role, or the ECS task’s execution role, permission to read that secret.
3. Report after the job succeeds
Section titled “3. Report after the job succeeds”Lambda
Section titled “Lambda”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.
ECS task
Section titled “ECS task”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.
4. Confirm the first report
Section titled “4. Confirm the first report”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.
Optional: report a number
Section titled “Optional: report a number”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) });Why a healthy job might still alert
Section titled “Why a healthy job might still alert”- 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
DISABLEDstate 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.