My hangup was that I wanted to find a single method of capturing any ERROR pattern match in all lambda-related CloudWatch logs, and get immediate notification of it. The kind of thing you'd setup in LogDNA or LogEntries really simply through their UI. I didn't like the idea of setting up individual lambdas to subscribe to other lambdas' logs because I thought that'd be overkill. I was overthinking it.
The best way to do this is to create a lambda that is triggered by CloudWatch Logs and deliberately subscribes to the log groups and patterns that it cares about. Here's a generic example:
It's easy to do this in a CloudFormation template (or whatever format), e.g.:
Resources:
CloudWatchLogProcessorFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: CloudWatchLogProcessor
...
Events:
Foo:
Type: CloudWatchLogs
Properties:
LogGroupName: /aws/lambda/Foo
FilterPattern: ERROR
Bar:
Type: CloudWatchLogs
Properties:
LogGroupName: /aws/lambda/Bar
FilterPattern: ERROR
Baz:
Type: CloudWatchLogs
Properties:
LogGroupName: /aws/lambda/Baz
FilterPattern: ERROR
Bof:
Type: CloudWatchLogs
Properties:
LogGroupName: /aws/lambda/Bof
FilterPattern: ERROR
You can then have this lambda perform the tiny task of decoding the log and doing whatever you want with the results, like publishing a message containing the log group and log entries to an SNS topic:
const AWS = require('aws-sdk');
const zlib = require('zlib');
const sns = new AWS.SNS();
exports.handler = async (event) => {
if (!event.awslogs || !event.awslogs.data) {
throw new Error("Unexpected event.");
}
const payload = Buffer.from(event.awslogs.data, 'base64');
const log = JSON.parse(zlib.unzipSync(payload).toString());
let msg = '';
msg += `Log Group: ${log.logGroup}\n`;
msg += `Log Stream: ${log.logStream}\n\n`;
log.logEvents.forEach(e => {
msg += `${e.message}\n\n`;
});
await sns.publish({
Message: msg,
TopicArn: process.env.SNS_TOPIC_ARN
}).promise();
return null;
};