Back to sensacat

Home  /  Integration guides

· SensaCat Team

How to Monitor Scheduled AWS Lambda Functions

CloudWatch tells you about invocations that happened. The expensive failure is a scheduled rule that stopped invoking anything.

Monitoring a scheduled Lambda means detecting an invocation that did not happen. CloudWatch metrics are built around invocations that did, which makes the interesting failure invisible by default.

A CloudWatch alarm on the Errors metric fires when a function runs and fails. If the EventBridge rule is disabled, the function never runs, Errors stays at zero, and the alarm stays green forever.

You can build this in CloudWatch, with a caveat

An alarm on the Invocations metric using the treatMissingData setting of breaching will fire when no data points arrive. It works, and it is fiddly to get right because missing data and zero are handled differently across metric math.

The external heartbeat is simpler and has one property CloudWatch cannot match: it keeps working when the problem is with your AWS account itself, such as a suspended account or a region-wide event.

Ping in a finally block

Put the success ping where it only runs on a clean completion, and the failure ping where it runs on any exception. A try/finally structure gets both without duplicating the call.

Swallowing exceptions from the ping itself is deliberate. Monitoring should never be the reason a working function reports a failure.

If your Lambda runs inside a VPC with no NAT gateway, it has no outbound internet access and the ping will time out on every invocation. That is a five-second penalty per run and no monitoring. Either add a NAT gateway, use a VPC endpoint, or accept CloudWatch-only coverage for that function.

Know how your invocation type retries

Retry behaviour differs by how the function is invoked, and it changes what a heartbeat means.

Invocation type Retries on failure Implication for monitoring
Asynchronous (EventBridge, S3, SNS) 2 retries by default, configurable 0-2 One failure can ping success on attempt three
Synchronous (API Gateway, direct) None; the caller decides Failure surfaces immediately
Stream (Kinesis, DynamoDB) Retries until success or record expiry A poison record can block the shard silently

The stream row is a genuine trap. A single unprocessable record can stall an entire shard while the function reports no errors, because it never gets past the record to fail on anything else. Configure a failure destination or bisect-on-error to avoid it.

Timeouts are not exceptions

When a Lambda hits its configured timeout, it is killed. No exception is raised inside your code, no except block runs, and no failure ping is sent. The function simply stops mid-execution.

Lambda's ceiling is 15 minutes, and functions are frequently left at the 3-second default by an infrastructure template nobody revisited. A job that grew from 2 seconds to 4 over a year will start timing out on the day it crosses the line, with no code change to blame.

The heartbeat still catches it, because the success ping never fires either. But if you were relying on the failure ping to tell you what went wrong, a timeout will look identical to the function never having been invoked.

Use context.get_remaining_time_in_millis() to send a warning ping when a run is approaching its limit, which turns a future incident into a piece of information.

Check the rule, not just the function

The most common cause of a scheduled Lambda not running is the EventBridge rule being disabled, usually by a Terraform or CloudFormation change that nobody connected to this function.

Worth running in CI against production. A rule silently flipping to DISABLED during an unrelated deploy is the kind of thing that is obvious in hindsight and invisible at the time.

For the general pattern see heartbeat monitoring, and for scheduled work generally what is a cron job.