Skip to content

docs: refresh SAM examples #1180

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

Merged
merged 16 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions examples/lambda-functions/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Get the DynamoDB table name from environment variables
const tableName = process.env.SAMPLE_TABLE;

export {
tableName
};
29 changes: 29 additions & 0 deletions examples/lambda-functions/common/dynamodb-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import { tracer } from './powertools';

// Create DynamoDB Client and patch it for tracing
const ddbClient = tracer.captureAWSv3Client(new DynamoDBClient({}));

const marshallOptions = {
// Whether to automatically convert empty strings, blobs, and sets to `null`.
convertEmptyValues: false, // false, by default.
// Whether to remove undefined values while marshalling.
removeUndefinedValues: false, // false, by default.
// Whether to convert typeof object to map attribute.
convertClassInstanceToMap: false, // false, by default.
};

const unmarshallOptions = {
// Whether to return numbers as a string instead of converting them to native JavaScript numbers.
wrapNumbers: false, // false, by default.
};

const translateConfig = { marshallOptions, unmarshallOptions };

// Create the DynamoDB Document client.
const docClient = DynamoDBDocumentClient.from(ddbClient, translateConfig);

export {
docClient
};
32 changes: 32 additions & 0 deletions examples/lambda-functions/common/powertools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Logger } from '@aws-lambda-powertools/logger';
import { Metrics } from '@aws-lambda-powertools/metrics';
import { Tracer } from '@aws-lambda-powertools/tracer';

const awsLambdaPowertoolsVersion = '1.4.1';

const defaultValues = {
region: process.env.AWS_REGION || 'N/A',
executionEnv: process.env.AWS_EXECUTION_ENV || 'N/A'
};

const logger = new Logger({
persistentLogAttributes: {
...defaultValues,
logger: {
name: '@aws-lambda-powertools/logger',
version: awsLambdaPowertoolsVersion,
}
},
});

const metrics = new Metrics({
defaultDimensions: defaultValues
});

const tracer = new Tracer();

export {
logger,
metrics,
tracer
};
94 changes: 51 additions & 43 deletions examples/lambda-functions/get-all-items.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
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;

import middy from '@middy/core';
import { tableName } from './common/constants';
import { logger, tracer, metrics } from './common/powertools';
import { logMetrics } from '@aws-lambda-powertools/metrics';
import { injectLambdaContext } from '@aws-lambda-powertools/logger';
import { captureLambdaHandler } from '@aws-lambda-powertools/tracer';
import { docClient } from './common/dynamodb-client';
import { ScanCommand } from '@aws-sdk/lib-dynamodb';
import got from 'got';

/*
*
* This example uses the Middy middleware instrumentation.
* It is the best choice if your existing code base relies on the Middy middleware engine.
* Powertools offers compatible Middy middleware to make this integration seamless.
* Find more Information in the docs: https://awslabs.github.io/aws-lambda-powertools-typescript/
*
*/

/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
Expand All @@ -24,45 +27,47 @@ const tableName = process.env.SAMPLE_TABLE;
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
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: Add awsRequestId as annotation
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Logger: Append awsRequestId to each log statement
logger.appendKeys({
awsRequestId: context.awsRequestId,
});

// Tracer: Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);
// Request a sample random uuid from a webservice
const res = await got('https://httpbin.org/uuid');
const uuid = JSON.parse(res.body).uuid;

// Tracer: Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();
// Logger: Append uuid to each log statement
logger.appendKeys({ uuid });

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

// Metrics: Capture cold start metrics
metrics.captureColdStartMetric();
// Metrics: Add uuid as metadata
metrics.addMetadata('uuid', uuid);

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

// 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({
const data = await docClient.send(new ScanCommand({
TableName: tableName
}).promise();
}));

const items = data.Items;

// Logger: All log statements are written to CloudWatch
Expand All @@ -81,14 +86,17 @@ export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: C
};
}

// 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;
};
};

// Wrap the handler with middy
export const handler = middy(getAllItemsHandler)
// Use the middleware by passing the Metrics instance as a parameter
.use(logMetrics(metrics))
// Use the middleware by passing the Logger instance as a parameter
.use(injectLambdaContext(logger, { logEvent: true }))
// Use the middleware by passing the Tracer instance as a parameter
.use(captureLambdaHandler(tracer, { captureResponse: false })); // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false
171 changes: 94 additions & 77 deletions examples/lambda-functions/get-by-id.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
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;
import { tableName } from './common/constants';
import { logger, tracer, metrics } from './common/powertools';
import { LambdaInterface } from '@aws-lambda-powertools/commons';
import { docClient } from './common/dynamodb-client';
import { GetItemCommand } from '@aws-sdk/lib-dynamodb';
import got from 'got';

/*
*
* This example uses the Method decorator instrumentation.
* Use TypeScript method decorators if you prefer writing your business logic using TypeScript Classes.
* If you aren’t using Classes, this requires the most significant refactoring.
* Find more Information in the docs: https://awslabs.github.io/aws-lambda-powertools-typescript/
*
*/

/**
*
Expand All @@ -25,72 +25,89 @@ const tableName = process.env.SAMPLE_TABLE;
*
*/

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}`);
class Lambda implements LambdaInterface {

@tracer.captureMethod()
public async getUuid(): Promise<string> {
// Request a sample random uuid from a webservice
const res = await got('https://httpbin.org/uuid');

return JSON.parse(res.body).uuid;
}
// 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');

@tracer.captureLambdaHandler({ captureResponse: false }) // by default the tracer would add the response as metadata on the segment, but there is a chance to hit the 64kb segment size limit. Therefore set captureResponse: false
@logger.injectLambdaContext({ logEvent: true })
@metrics.logMetrics({ throwOnEmptyMetrics: false, captureColdStartMetric: true })
public async handler(event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {

if (event.httpMethod !== 'GET') {
throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`);
}

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: Add awsRequestId as annotation
tracer.putAnnotation('awsRequestId', context.awsRequestId);

// Logger: Append awsRequestId to each log statement
logger.appendKeys({
awsRequestId: context.awsRequestId,
});

// Call the getUuid function
const uuid = await this.getUuid();

// Logger: Append uuid to each log statement
logger.appendKeys({ uuid });

// Tracer: Add uuid as annotation
tracer.putAnnotation('uuid', uuid);

// Metrics: Add uuid as metadata
metrics.addMetadata('uuid', uuid);

// Define response object
let response;

// Get the item from the table
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
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.send(new GetItemCommand({
TableName: tableName,
Key: { id:
{
S: event.pathParameters.id
}
},
}));
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)
// All log statements are written to CloudWatch
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);

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

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

return response;
};
const handlerClass = new Lambda();
export const handler = handlerClass.handler.bind(handlerClass);
Loading