Skip to content

Commit 95b40d1

Browse files
bpauwelsdreamorosiPauwels
authored
docs: refresh SAM examples (#1180)
* SAM Example: Symlink for src folder * SAM Example: Switched Instrumentations for 2 functions * various changes * fixed link in readme * fixed eslint errors * comments from @dreamorosi * added constants.ts, fixed versions * removed samconfig.toml * fixed package.lock.json * moved to nodejs18.x runtime, excluded aws-sdk, fixed get functions * Update examples/sam/template.yaml * Update examples/sam/template.yaml * fixed tests * fixed package-lock.json Co-authored-by: Andrea Amorosi <[email protected]> Co-authored-by: Pauwels <[email protected]>
1 parent 999a86c commit 95b40d1

18 files changed

+5831
-4499
lines changed

Diff for: examples/lambda-functions/common/constants.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Get the DynamoDB table name from environment variables
2+
const tableName = process.env.SAMPLE_TABLE;
3+
4+
export {
5+
tableName
6+
};

Diff for: examples/lambda-functions/common/dynamodb-client.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
3+
import { tracer } from './powertools';
4+
5+
// Create DynamoDB Client and patch it for tracing
6+
const ddbClient = tracer.captureAWSv3Client(new DynamoDBClient({}));
7+
8+
const marshallOptions = {
9+
// Whether to automatically convert empty strings, blobs, and sets to `null`.
10+
convertEmptyValues: false, // false, by default.
11+
// Whether to remove undefined values while marshalling.
12+
removeUndefinedValues: false, // false, by default.
13+
// Whether to convert typeof object to map attribute.
14+
convertClassInstanceToMap: false, // false, by default.
15+
};
16+
17+
const unmarshallOptions = {
18+
// Whether to return numbers as a string instead of converting them to native JavaScript numbers.
19+
wrapNumbers: false, // false, by default.
20+
};
21+
22+
const translateConfig = { marshallOptions, unmarshallOptions };
23+
24+
// Create the DynamoDB Document client.
25+
const docClient = DynamoDBDocumentClient.from(ddbClient, translateConfig);
26+
27+
export {
28+
docClient
29+
};

Diff for: examples/lambda-functions/common/powertools.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
import { Metrics } from '@aws-lambda-powertools/metrics';
3+
import { Tracer } from '@aws-lambda-powertools/tracer';
4+
5+
const awsLambdaPowertoolsVersion = '1.4.1';
6+
7+
const defaultValues = {
8+
region: process.env.AWS_REGION || 'N/A',
9+
executionEnv: process.env.AWS_EXECUTION_ENV || 'N/A'
10+
};
11+
12+
const logger = new Logger({
13+
persistentLogAttributes: {
14+
...defaultValues,
15+
logger: {
16+
name: '@aws-lambda-powertools/logger',
17+
version: awsLambdaPowertoolsVersion,
18+
}
19+
},
20+
});
21+
22+
const metrics = new Metrics({
23+
defaultDimensions: defaultValues
24+
});
25+
26+
const tracer = new Tracer();
27+
28+
export {
29+
logger,
30+
metrics,
31+
tracer
32+
};

Diff for: examples/lambda-functions/get-all-items.ts

+51-43
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
2-
import { Metrics } from '@aws-lambda-powertools/metrics';
3-
import { Logger } from '@aws-lambda-powertools/logger';
4-
import { Tracer } from '@aws-lambda-powertools/tracer';
5-
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
6-
7-
// Create the PowerTools clients
8-
const metrics = new Metrics();
9-
const logger = new Logger();
10-
const tracer = new Tracer();
11-
12-
// Create DynamoDB DocumentClient and patch it for tracing
13-
const docClient = tracer.captureAWSClient(new DocumentClient());
14-
15-
// Get the DynamoDB table name from environment variables
16-
const tableName = process.env.SAMPLE_TABLE;
17-
2+
import middy from '@middy/core';
3+
import { tableName } from './common/constants';
4+
import { logger, tracer, metrics } from './common/powertools';
5+
import { logMetrics } from '@aws-lambda-powertools/metrics';
6+
import { injectLambdaContext } from '@aws-lambda-powertools/logger';
7+
import { captureLambdaHandler } from '@aws-lambda-powertools/tracer';
8+
import { docClient } from './common/dynamodb-client';
9+
import { ScanCommand } from '@aws-sdk/lib-dynamodb';
10+
import got from 'got';
11+
12+
/*
13+
*
14+
* This example uses the Middy middleware instrumentation.
15+
* It is the best choice if your existing code base relies on the Middy middleware engine.
16+
* Powertools offers compatible Middy middleware to make this integration seamless.
17+
* Find more Information in the docs: https://awslabs.github.io/aws-lambda-powertools-typescript/
18+
*
19+
*/
20+
1821
/**
1922
*
2023
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
@@ -24,45 +27,47 @@ const tableName = process.env.SAMPLE_TABLE;
2427
* @returns {Object} object - API Gateway Lambda Proxy Output Format
2528
*
2629
*/
27-
export const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
30+
const getAllItemsHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
2831
if (event.httpMethod !== 'GET') {
2932
throw new Error(`getAllItems only accepts GET method, you tried: ${event.httpMethod}`);
3033
}
3134

32-
// Tracer: Get facade segment created by AWS Lambda
33-
const segment = tracer.getSegment();
35+
// Tracer: Add awsRequestId as annotation
36+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
37+
38+
// Logger: Append awsRequestId to each log statement
39+
logger.appendKeys({
40+
awsRequestId: context.awsRequestId,
41+
});
3442

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

39-
// Tracer: Annotate the subsegment with the cold start & serviceName
40-
tracer.annotateColdStart();
41-
tracer.addServiceNameAnnotation();
47+
// Logger: Append uuid to each log statement
48+
logger.appendKeys({ uuid });
4249

43-
// Tracer: Add annotation for the awsRequestId
44-
tracer.putAnnotation('awsRequestId', context.awsRequestId);
50+
// Tracer: Add uuid as annotation
51+
tracer.putAnnotation('uuid', uuid);
4552

46-
// Metrics: Capture cold start metrics
47-
metrics.captureColdStartMetric();
53+
// Metrics: Add uuid as metadata
54+
metrics.addMetadata('uuid', uuid);
4855

49-
// Logger: Add persistent attributes to each log statement
50-
logger.addPersistentLogAttributes({
51-
awsRequestId: context.awsRequestId,
52-
});
56+
// Define response object
57+
let response;
5358

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

63-
const data = await docClient.scan({
67+
const data = await docClient.send(new ScanCommand({
6468
TableName: tableName
65-
}).promise();
69+
}));
70+
6671
const items = data.Items;
6772

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

84-
// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
85-
handlerSegment.close(); // (## index.handler)
86-
87-
// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
88-
tracer.setSegment(segment);
89-
9089
// All log statements are written to CloudWatch
9190
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
9291

9392
return response;
94-
};
93+
};
94+
95+
// Wrap the handler with middy
96+
export const handler = middy(getAllItemsHandler)
97+
// Use the middleware by passing the Metrics instance as a parameter
98+
.use(logMetrics(metrics))
99+
// Use the middleware by passing the Logger instance as a parameter
100+
.use(injectLambdaContext(logger, { logEvent: true }))
101+
// Use the middleware by passing the Tracer instance as a parameter
102+
.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

Diff for: examples/lambda-functions/get-by-id.ts

+94-77
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
2-
import { Metrics } from '@aws-lambda-powertools/metrics';
3-
import { Logger } from '@aws-lambda-powertools/logger';
4-
import { Tracer } from '@aws-lambda-powertools/tracer';
5-
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
6-
7-
// Create the PowerTools clients
8-
const metrics = new Metrics();
9-
const logger = new Logger();
10-
const tracer = new Tracer();
11-
12-
// Create DynamoDB DocumentClient and patch it for tracing
13-
const docClient = tracer.captureAWSClient(new DocumentClient());
14-
15-
// Get the DynamoDB table name from environment variables
16-
const tableName = process.env.SAMPLE_TABLE;
2+
import { tableName } from './common/constants';
3+
import { logger, tracer, metrics } from './common/powertools';
4+
import { LambdaInterface } from '@aws-lambda-powertools/commons';
5+
import { docClient } from './common/dynamodb-client';
6+
import { GetItemCommand } from '@aws-sdk/lib-dynamodb';
7+
import got from 'got';
8+
9+
/*
10+
*
11+
* This example uses the Method decorator instrumentation.
12+
* Use TypeScript method decorators if you prefer writing your business logic using TypeScript Classes.
13+
* If you aren’t using Classes, this requires the most significant refactoring.
14+
* Find more Information in the docs: https://awslabs.github.io/aws-lambda-powertools-typescript/
15+
*
16+
*/
1717

1818
/**
1919
*
@@ -25,72 +25,89 @@ const tableName = process.env.SAMPLE_TABLE;
2525
*
2626
*/
2727

28-
export const getByIdHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
29-
if (event.httpMethod !== 'GET') {
30-
throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`);
28+
class Lambda implements LambdaInterface {
29+
30+
@tracer.captureMethod()
31+
public async getUuid(): Promise<string> {
32+
// Request a sample random uuid from a webservice
33+
const res = await got('https://httpbin.org/uuid');
34+
35+
return JSON.parse(res.body).uuid;
3136
}
32-
// Tracer: Get facade segment created by AWS Lambda
33-
const segment = tracer.getSegment();
34-
35-
// Tracer: Create subsegment for the function & set it as active
36-
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
37-
tracer.setSegment(handlerSegment);
38-
39-
// Tracer: Annotate the subsegment with the cold start & serviceName
40-
tracer.annotateColdStart();
41-
tracer.addServiceNameAnnotation();
42-
43-
// Tracer: Add annotation for the awsRequestId
44-
tracer.putAnnotation('awsRequestId', context.awsRequestId);
45-
46-
// Metrics: Capture cold start metrics
47-
metrics.captureColdStartMetric();
48-
49-
// Logger: Add persistent attributes to each log statement
50-
logger.addPersistentLogAttributes({
51-
awsRequestId: context.awsRequestId,
52-
});
53-
54-
// Get the item from the table
55-
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
56-
let response;
57-
try {
58-
if (!tableName) {
59-
throw new Error('SAMPLE_TABLE environment variable is not set');
60-
}
61-
if (!event.pathParameters) {
62-
throw new Error('event does not contain pathParameters');
63-
}
64-
if (!event.pathParameters.id) {
65-
throw new Error('PathParameter id is missing');
37+
38+
@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
39+
@logger.injectLambdaContext({ logEvent: true })
40+
@metrics.logMetrics({ throwOnEmptyMetrics: false, captureColdStartMetric: true })
41+
public async handler(event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> {
42+
43+
if (event.httpMethod !== 'GET') {
44+
throw new Error(`getById only accepts GET method, you tried: ${event.httpMethod}`);
6645
}
6746

68-
const data = await docClient.get({
69-
TableName: tableName,
70-
Key: { id: event.pathParameters.id },
71-
}).promise();
72-
const item = data.Item;
73-
response = {
74-
statusCode: 200,
75-
body: JSON.stringify(item)
76-
};
77-
} catch (err) {
78-
tracer.addErrorAsMetadata(err as Error);
79-
logger.error('Error reading from table. ' + err);
80-
response = {
81-
statusCode: 500,
82-
body: JSON.stringify({ 'error': 'Error reading from table.' })
83-
};
84-
}
47+
// Tracer: Add awsRequestId as annotation
48+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
49+
50+
// Logger: Append awsRequestId to each log statement
51+
logger.appendKeys({
52+
awsRequestId: context.awsRequestId,
53+
});
54+
55+
// Call the getUuid function
56+
const uuid = await this.getUuid();
57+
58+
// Logger: Append uuid to each log statement
59+
logger.appendKeys({ uuid });
60+
61+
// Tracer: Add uuid as annotation
62+
tracer.putAnnotation('uuid', uuid);
63+
64+
// Metrics: Add uuid as metadata
65+
metrics.addMetadata('uuid', uuid);
66+
67+
// Define response object
68+
let response;
69+
70+
// Get the item from the table
71+
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html#get-property
72+
try {
73+
if (!tableName) {
74+
throw new Error('SAMPLE_TABLE environment variable is not set');
75+
}
76+
if (!event.pathParameters) {
77+
throw new Error('event does not contain pathParameters');
78+
}
79+
if (!event.pathParameters.id) {
80+
throw new Error('PathParameter id is missing');
81+
}
82+
const data = await docClient.send(new GetItemCommand({
83+
TableName: tableName,
84+
Key: { id:
85+
{
86+
S: event.pathParameters.id
87+
}
88+
},
89+
}));
90+
const item = data.Item;
91+
response = {
92+
statusCode: 200,
93+
body: JSON.stringify(item)
94+
};
95+
} catch (err) {
96+
tracer.addErrorAsMetadata(err as Error);
97+
logger.error('Error reading from table. ' + err);
98+
response = {
99+
statusCode: 500,
100+
body: JSON.stringify({ 'error': 'Error reading from table.' })
101+
};
102+
}
85103

86-
// Tracer: Close subsegment (the AWS Lambda one is closed automatically)
87-
handlerSegment.close(); // (## index.handler)
104+
// All log statements are written to CloudWatch
105+
logger.info(`response from: ${event.path} statusCode: ${response.statusCode} body: ${response.body}`);
88106

89-
// Tracer: Set the facade segment as active again (the one created by AWS Lambda)
90-
tracer.setSegment(segment);
107+
return response;
108+
}
91109

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

95-
return response;
96-
};
112+
const handlerClass = new Lambda();
113+
export const handler = handlerClass.handler.bind(handlerClass);

0 commit comments

Comments
 (0)