Skip to content

Instantly share code, notes, and snippets.

@nicolaracco
Last active December 14, 2020 15:43
Show Gist options
  • Save nicolaracco/ef8febc5ad465ef870071df32e695df2 to your computer and use it in GitHub Desktop.
Save nicolaracco/ef8febc5ad465ef870071df32e695df2 to your computer and use it in GitHub Desktop.
Lambda error reporting via CDK
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { MyLambdaStack } from '../lib/my-lambda-stack'
import { ErrorReportingStack } from '../lib/error-reporting-stack'
const app = new cdk.App()
const errorReportingStack = new ErrorReportingStack(app, 'ErrorReportingStack', {
appName: 'my-app',
})
new MyLambdaStack(app, 'MyLambdaStack', {
errorReporter: errorReportingStack.errorReporter,
})
import * as zlib from 'zlib'
import { promisify } from 'util'
import * as Bluebird from 'bluebird'
import * as _AWS from 'aws-sdk'
import * as AWSXRay from 'aws-xray-sdk'
import { CloudWatchLogsHandler } from 'aws-lambda'
interface LogEventPayload {
id: string
timestamp: number
message: string
}
interface LogTriggerPayload {
messageType: string
owner: string
logGroup: string
logStream: string
subscriptionFilter: string[]
logEvents: LogEventPayload[]
}
const AWS = AWSXRay.captureAWS(_AWS)
const gunzip = promisify(zlib.gunzip)
const sns = new AWS.SNS()
const SNS_TOPIC = process.env.SNS_TOPIC!
const APP_NAME = process.env.APP_NAME!
export const sendSnsNotification = (lambdaName: string, event: LogEventPayload, payload: LogTriggerPayload): Promise<any> => {
const message = `
Lambda error summary
##########################################################
# Account ID: ${payload.owner}
# LogGroup Name: ${payload.logGroup}
# LogStream: ${payload.logStream}
# Log Message: ${event.message.split('\n').join('\n# ')}
##########################################################
`
return sns.publish({
TargetArn: SNS_TOPIC,
Subject: `[${APP_NAME}] FATAL ${lambdaName}`,
Message: message,
}).promise()
}
export const handler: CloudWatchLogsHandler = async (event) => {
const data = JSON.parse((await gunzip(Buffer.from(event.awslogs.data, 'base64')) as Buffer).toString()) as LogTriggerPayload
const lambdaName = data.logGroup.split('/').slice(-1)[0]
await Bluebird.each(data.logEvents, (logEvent) => sendSnsNotification(lambdaName, logEvent, data))
}
import * as cdk from '@aws-cdk/core'
import * as iam from '@aws-cdk/aws-iam'
import * as lambda from '@aws-cdk/aws-lambda'
import * as lambdaNode from '@aws-cdk/aws-lambda-nodejs'
import * as sns from '@aws-cdk/aws-sns'
import * as snsSubscriptions from '@aws-cdk/aws-sns-subscriptions'
export interface ErrorReportingStackProps extends cdk.StackProps {
appName: string
}
export class ErrorReportingStack extends cdk.Stack {
readonly errorReporter: lambda.IFunction
constructor(scope: cdk.Construct, id: string, props: ErrorReportingStackProps) {
super(scope, id, props)
const snsTopic = new sns.Topic(this, 'ErrorTopic')
snsTopic.addSubscription(new snsSubscriptions.EmailSubscription('[email protected]'))
const errorReporter = new lambdaNode.NodejsFunction(this, 'ErrorReporter', {
environment: {
SNS_TOPIC: snsTopic.topicArn,
APP_NAME: props.appName,
}
})
errorReporter.addToRolePolicy(new iam.PolicyStatement({
actions: ['sns:Publish'],
resources: [snsTopic.topicArn],
}))
this.errorReporter = errorReporter
}
}
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'
import * as lambdaNode from '@aws-cdk/aws-lambda-nodejs'
import * as logs from '@aws-cdk/aws-logs'
import * as logsDestinations from '@aws-cdk/aws-logs-destinations'
export interface MyLambdaStackProps extends cdk.StackProps {
errorReporter?: lambda.IFunction
}
export class MyLambdaStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: MyLambdaStackProps) {
super(scope, id, props)
const lambda = new lambdaNode.NodejsFunction(this, 'handler');
const logGroup = lambda.logGroup;
logGroup.addSubscriptionFilter('ErrorSubscription', {
filterPattern: logs.FilterPattern.literal('ERROR'),
destination: new logsDestinations.LambdaDestination(props.errorReporter),
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment