Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(examples): add example for AWS SAM #674

Merged
merged 26 commits into from
May 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
01324a8
added example for AWS SAM
Mar 18, 2022
9c3d999
removed unnecessary hello-world folder
Apr 5, 2022
ed0abd2
tsconfig now in line with other tsconfigs in this repo
Apr 5, 2022
fd6acbc
added try/catch block + metric annotation + error logging for dynamod…
Apr 5, 2022
644a76a
Importing only DynamoDB instead of whole aws-sdk. Patching only the D…
Apr 5, 2022
58a3ca4
removed prettier; aligned dependency versions with others in this repo
Apr 5, 2022
ef8239a
Update examples/sam/README.md
bpauwels Apr 12, 2022
cf2f9c6
Update examples/sam/README.md
bpauwels Apr 12, 2022
8ce19c7
Merge branch 'awslabs:main' into examples-sam
bpauwels Apr 12, 2022
cb28daf
seperated lambda function code
Apr 12, 2022
5266452
Updated README.md
Apr 20, 2022
007c990
Updated README.md removed powertools example folder
Apr 20, 2022
f6bc21c
Updated README.md changed cleanup to sam delete
Apr 20, 2022
caf39e2
fixed eslint issues
Apr 20, 2022
b6953dc
added copied files to .gitignore
Apr 20, 2022
d5969b8
multiple changes to template.yaml
Apr 20, 2022
a35bb80
changed policy to DynamoDBReadPolicy for all get* Lambdas
Apr 20, 2022
7a355cf
fixed escaped DynamodDB description in template.yaml
Apr 20, 2022
5fd51c6
fixed README.md; changed try catch to capture missing env; import onl…
Apr 26, 2022
17831a3
fixed tracer.captureAWSClient to capture DocumentClient
Apr 26, 2022
3b68bdb
moved package.json to sam folder
Apr 27, 2022
de14ec6
changed author in package.json; various changes to README.md; changed…
Apr 27, 2022
48425c0
removed redundant comments
Apr 27, 2022
6b55f2d
added additional steps to readme.md
Apr 27, 2022
310c39f
removed null assertions
Apr 27, 2022
ae2dddf
removed manual copy step and created a symlink instead
May 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ site

# Generated API documentation (from TypeDoc)
/api

# SAM Example copies files
/examples/sam/src/handlers/*
!/examples/sam/src/handlers/COPY_LAMBDA_FUNCTIONS_HERE
94 changes: 94 additions & 0 deletions examples/lambda-functions/get-all-items.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'GET') {
throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`);
}

// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// get all items from the table (only first 1MB data, you can use `LastEvaluatedKey` to get the rest of data)
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#scan-property
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}

const data = await docClient.scan({
TableName: tableName
}).promise();
const items = data.Items;

// Logger: All log statements are written to CloudWatch
logger.debug(`retrieved items: ${items?.length || 0}`);

response = {
statusCode: 200,
body: JSON.stringify(items)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error reading from table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error reading from table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
96 changes: 96 additions & 0 deletions examples/lambda-functions/get-by-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/

export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'GET') {
throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`);
}
// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// Get the item from the table
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}
if (!event.pathParameters) {
throw new Error('event does not contain pathParameters')
}
if (!event.pathParameters.id) {
throw new Error('PathParameter id is missing')
}

const data = await docClient.get({
TableName: tableName,
Key: { id: event.pathParameters.id },
}).promise();
const item = data.Item;
response = {
statusCode: 200,
body: JSON.stringify(item)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error reading from table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error reading from table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
97 changes: 97 additions & 0 deletions examples/lambda-functions/put-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Logger } from '@aws-lambda-powertools/logger';
import { Tracer } from '@aws-lambda-powertools/tracer';
import { DocumentClient } from 'aws-sdk/clients/dynamodb';

// Create the PowerTools clients
const metrics = new Metrics();
const logger = new Logger();
const tracer = new Tracer();

// Create DynamoDB DocumentClient and patch it for tracing
const docClient = tracer.captureAWSClient(new DocumentClient());

// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/

export const putItemHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
if (event.httpMethod !== 'POST') {
throw new Error(`putItem only accepts POST method, you tried: ${event.httpMethod}`);
}
// Tracer: Get facade segment created by AWS Lambda
const segment = tracer.getSegment();

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();

// Tracer: Add annotation for the awsRequestId
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();

// Logger: Add persistent attributes to each log statement
logger.addPersistentLogAttributes({
awsRequestId: context.awsRequestId,
});

// Creates a new item, or replaces an old item with a new item
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#put-property
let response;
try {
if (!tableName) {
throw new Error('SAMPLE_TABLE environment variable is not set');
}
if (!event.body) {
throw new Error('Event does not contain body')
}

// Get id and name from the body of the request
const body = JSON.parse(event.body);
const id = body.id;
const name = body.name;

await docClient.put({
TableName: tableName,
Item: { id: id, name: name }
}).promise();
response = {
statusCode: 200,
body: JSON.stringify(body)
};
} catch (err) {
tracer.addErrorAsMetadata(err as Error);
logger.error('Error writing data to table. ' + err);
response = {
statusCode: 500,
body: JSON.stringify({ 'error': 'Error writing data to table.' })
};
}

// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close(); // (## index.handler)

// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
tracer.setSegment(segment);

// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

return response;
};
28 changes: 28 additions & 0 deletions examples/lambda-functions/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"noImplicitAny": true,
"target": "ES2020",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"outDir": "lib",
"removeComments": false,
"strict": true,
"inlineSourceMap": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"pretty": true,
"esModuleInterop": true
},
"exclude": [ "./node_modules"],
"watchOptions": {
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
"fallbackPolling": "dynamicPriority"
},
"lib": [ "es2020" ],
"types": [
"node"
]
}
Loading