Skip to content

Commit b69cfdd

Browse files
authored
chore(tracer): cdk examples + e2e tests (#347)
* Simplified base CDK examples * Added Tracer examples * Added back LambdaInterface in Tracer.test * Updated package.json * Updates to examples & docs * Updated docs example * Updated examples + added unit test groups * Added base e2e tests * Added back folder to jest config * Added disabled tracer e2e test * Added patch AWS SDK clients to manual e2e test * Reduced test timeouts * Added captureAWS feature to e2e * Added additional e2e test cases * Removed redundant e2e test files * Relaxed dependency version on cdk examples
1 parent 6dc9320 commit b69cfdd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+9088
-5005
lines changed

docs/core/tracer.md

+30-20
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
9393
// OR tracer = Tracer({ service: 'example' });
9494

9595
class Lambda {
96-
@tracer.captureLambdaHanlder()
96+
@tracer.captureLambdaHandler()
9797
public handler(event: any, context: any) {
9898
...
9999
}
@@ -109,28 +109,34 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
109109
import { Tracer } from '@aws-lambda-powertools/tracer';
110110

111111
const tracer = Tracer(); // Sets service via env var
112-
// OR tracer = Tracer({ service: 'example' });
112+
// OR tracer = Tracer({ service: 'serverlessAirline' });
113113

114114
export const handler = async (_event: any, context: any) => {
115115
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
116-
// Create subsegment for the function
117-
const handlerSegment = segment.addNewSubsegment(`## ${context.functionName}`);
116+
// Create subsegment for the function & set it as active
117+
const subsegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
118+
tracer.setSegment(subsegment);
119+
120+
// Annotate the subsegment with the cold start & serviceName
118121
tracer.annotateColdStart();
119122
tracer.addServiceNameAnnotation();
120123

121124
let res;
122125
try {
123126
res = ...
124127
// Add the response as metadata
125-
tracer.addResponseAsMetadata(res, context.functionName);
128+
tracer.addResponseAsMetadata(res, process.env._HANDLER);
126129
} catch (err) {
127130
// Add the error as metadata
128131
tracer.addErrorAsMetadata(err as Error);
132+
throw err;
133+
} finally {
134+
// Close subsegment (the AWS Lambda one is closed automatically)
135+
subsegment.close();
136+
// Set the facade segment as active again
137+
tracer.setSegment(segment);
129138
}
130139

131-
// Close subsegment (the AWS Lambda one is closed automatically)
132-
handlerSegment.close();
133-
134140
return res;
135141
}
136142
```
@@ -139,6 +145,7 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t
139145

140146
* Handles the lifecycle of the subsegment
141147
* Creates a `ColdStart` annotation to easily filter traces that have had an initialization overhead
148+
* Creates a `ServiceName` annotation to easily filter traces that have a specific service name
142149
* Captures any response, or full exceptions generated by the handler, and include as tracing metadata
143150

144151
### Annotations & Metadata
@@ -153,10 +160,10 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t
153160
```typescript hl_lines="6"
154161
import { Tracer } from '@aws-lambda-powertools/tracer';
155162

156-
const tracer = new Tracer({ serviceName: 'my-service' });
163+
const tracer = new Tracer({ serviceName: 'serverlessAirline' });
157164

158165
export const handler = async (_event: any, _context: any) => {
159-
tracer.putAnnotation('PaymentStatus', "SUCCESS");
166+
tracer.putAnnotation('successfulBooking', true);
160167
}
161168
```
162169
=== "Metadata"
@@ -165,11 +172,11 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t
165172
```typescript hl_lines="7"
166173
import { Tracer } from '@aws-lambda-powertools/tracer';
167174

168-
const tracer = new Tracer({ serviceName: 'my-service' });
175+
const tracer = new Tracer({ serviceName: 'serverlessAirline' });
169176

170177
export const handler = async (_event: any, _context: any) => {
171178
const res = someLogic();
172-
tracer.putMetadata('PaymentResponse', res);
179+
tracer.putMetadata('paymentResponse', res);
173180
}
174181
```
175182

@@ -208,29 +215,32 @@ You can trace other methods using the `captureMethod` decorator or manual instru
208215

209216
=== "Manual"
210217

211-
```typescript hl_lines="2 8-9 15 18 22"
218+
```typescript hl_lines="6 8-9 15 23 25"
212219
import { Tracer } from '@aws-lambda-powertools/tracer';
213-
import { Segment } from 'aws-xray-sdk-core';
214220

215-
const tracer = new Tracer({ serviceName: 'my-service' });
221+
const tracer = new Tracer({ serviceName: 'serverlessAirline' });
216222

217223
const chargeId = async () => {
218-
// Create subsegment & set it as active
219-
const subsegment = new Subsegment(`### chargeId`);
224+
const parentSubsegment = tracer.getSegment(); // This is the subsegment currently active
225+
// Create subsegment for the function & set it as active
226+
const subsegment = parentSubsegment.addNewSubsegment(`### chargeId`);
220227
tracer.setSegment(subsegment);
221228

222229
let res;
223230
try {
224231
res = await someLogic(); // Do something
225232
// Add the response as metadata
226-
tracer.putMetadata(`chargeId response`, data);
233+
tracer.addResponseAsMetadata(res, 'chargeId');
227234
} catch (err) {
228235
// Add the error as metadata
229-
subsegment.addError(err, false);
236+
tracer.addErrorAsMetadata(err as Error);
237+
throw err;
230238
}
231239

232-
// Close subsegment
240+
// Close subsegment (the AWS Lambda one is closed automatically)
233241
subsegment.close();
242+
// Set the facade segment as active again
243+
tracer.setSegment(parentSubsegment);
234244

235245
return res;
236246
}

docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ Each TypeScript utility is installed as standalone NPM package.
7979
## Connect
8080

8181
* **AWS Developers Slack**: `#lambda-powertools`** - **[Invite, if you don't have an account](https://join.slack.com/t/awsdevelopers/shared_invite/zt-yryddays-C9fkWrmguDv0h2EEDzCqvw){target="_blank"}**
82-
* **Email**: [email protected]
82+
* **Email**: [email protected]

examples/cdk/bin/cdk-app.ts

+1-15
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,4 @@ import * as cdk from 'aws-cdk-lib';
44
import { CdkAppStack } from '../lib/example-stack';
55

66
const app = new cdk.App();
7-
new CdkAppStack(app, 'CdkAppStack', {
8-
/* If you don't specify 'env', this stack will be environment-agnostic.
9-
* Account/Region-dependent features and context lookups will not work,
10-
* but a single synthesized template can be deployed anywhere. */
11-
12-
/* Uncomment the next line to specialize this stack for the AWS Account
13-
* and Region that are implied by the current CLI configuration. */
14-
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
15-
16-
/* Uncomment the next line if you know exactly what Account and Region you
17-
* want to deploy the stack to. */
18-
// env: { account: '123456789012', region: 'us-east-1' },
19-
20-
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
21-
});
7+
new CdkAppStack(app, 'CdkAppStack', {});

examples/cdk/lib/example-stack.MyFunction.ts renamed to examples/cdk/lib/example-function.MyFunction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const metrics = new Metrics({ namespace: namespace, service: serviceName });
1010
const logger = new Logger({ logLevel: 'INFO', serviceName: serviceName });
1111
const tracer = new Tracer({ serviceName: serviceName });
1212

13-
export const handler = async (_event: unknown, context: Context): Promise<unknown> => {
13+
export const handler = async (_event: unknown, context: Context): Promise<void> => {
1414
// Since we are in manual mode we need to create the handler segment (the 4 lines below would be done for you by decorator/middleware)
1515
// we do it at the beginning because we want to trace the whole duration of the handler
1616
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by Lambda & that can't be manipulated)

examples/cdk/lib/example-stack.MyFunctionWithMiddy.ts renamed to examples/cdk/lib/example-function.MyFunctionWithMiddy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import middy from '@middy/core'
1+
import middy from '@middy/core';
22
import { Callback, Context } from 'aws-lambda';
33
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';
44

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import middy from '@middy/core';
2+
import { Context } from 'aws-lambda';
3+
import { Events } from '@aws-lambda-powertools/commons';
4+
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';
5+
6+
// Set environment variable to disable capture response
7+
process.env.POWERTOOLS_TRACER_ERROR_RESPONSE = 'false';
8+
const tracer = new Tracer({ serviceName: 'tracerCaptureErrorDisabledFn' });
9+
10+
// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator
11+
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
12+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
13+
tracer.putMetadata('eventPayload', event);
14+
15+
let res;
16+
try {
17+
res = { foo: 'bar' };
18+
19+
// We are throwing an error only for testing purposes to make sure the error response is not captured in the subsegment metadata
20+
throw new Error('An error occurred.');
21+
} catch (err) {
22+
// The error won't be in the subsegment metadata
23+
throw err;
24+
}
25+
26+
return res;
27+
}).use(captureLambdaHandler(tracer));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import middy from '@middy/core';
2+
import { Context } from 'aws-lambda';
3+
import { Events } from '@aws-lambda-powertools/commons';
4+
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';
5+
6+
// Set environment variable to disable capture response
7+
process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false';
8+
const tracer = new Tracer({ serviceName: 'tracerCaptureResponseDisabledFn' });
9+
10+
// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator
11+
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
12+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
13+
tracer.putMetadata('eventPayload', event);
14+
15+
let res;
16+
try {
17+
res = { foo: 'bar' };
18+
} catch (err) {
19+
throw err;
20+
}
21+
22+
// The response won't be captured in the subsegment metadata
23+
return res;
24+
}).use(captureLambdaHandler(tracer));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Callback, Context } from 'aws-lambda';
2+
import { Events } from '@aws-lambda-powertools/commons';
3+
import { Tracer } from '@aws-lambda-powertools/tracer';
4+
5+
// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
6+
const tracer = new Tracer({ serviceName: 'tracerDecoratorFn' });
7+
8+
export class MyFunctionWithDecorator {
9+
// We instrument the handler with the decorator and the tracer will automatically create a subsegment and capture relevant annotations and metadata
10+
@tracer.captureLambdaHandler()
11+
public handler(event: typeof Events.Custom.CustomEvent, context: Context, _callback: Callback<unknown>): void | Promise<unknown> {
12+
// Add custom annotation & metadata
13+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
14+
tracer.putMetadata('eventPayload', event);
15+
16+
let res: { foo: string };
17+
try {
18+
res = { foo: this.myMethod() };
19+
} catch (err) {
20+
throw err;
21+
}
22+
23+
return new Promise((resolve, _reject) => resolve(res as unknown));
24+
}
25+
26+
// We can also use decorators to create a subsegment for the method & capture response and errors as metadata
27+
@tracer.captureMethod()
28+
public myMethod(): string {
29+
return 'bar';
30+
}
31+
}
32+
33+
export const handlerClass = new MyFunctionWithDecorator();
34+
export const handler = handlerClass.handler;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import middy from '@middy/core';
2+
import { Context } from 'aws-lambda';
3+
import { Events } from '@aws-lambda-powertools/commons';
4+
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';
5+
6+
// process.env.POWERTOOLS_TRACE_ENABLED = 'false'; // Alternative to disabling tracing in the constructor
7+
const tracer = new Tracer({ serviceName: 'tracerDisabledFn', enabled: false });
8+
9+
// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator or the manual mode
10+
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
11+
// No tracing will be done and the commands will be ignored, this is useful for testing
12+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
13+
tracer.putMetadata('eventPayload', event);
14+
15+
let res;
16+
try {
17+
res = { foo: 'bar' };
18+
} catch (err) {
19+
throw err;
20+
}
21+
22+
return res;
23+
}).use(captureLambdaHandler(tracer));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Context } from 'aws-lambda';
2+
import { Events } from '@aws-lambda-powertools/commons';
3+
import { Tracer } from '@aws-lambda-powertools/tracer';
4+
5+
// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
6+
const tracer = new Tracer({ serviceName: 'tracerManualFn' });
7+
8+
export const handler = async (event: typeof Events.Custom.CustomEvent, context: Context): Promise<unknown> => {
9+
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
10+
// Create subsegment for the function & set it as active
11+
const subsegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
12+
tracer.setSegment(subsegment);
13+
14+
// Annotate the subsegment with the cold start & serviceName
15+
tracer.annotateColdStart();
16+
tracer.addServiceNameAnnotation();
17+
18+
// Add custom annotation & metadata
19+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
20+
tracer.putMetadata('eventPayload', event);
21+
22+
let res;
23+
try {
24+
res = { foo: 'bar' };
25+
// Add the response as metadata
26+
tracer.addResponseAsMetadata(res, process.env._HANDLER);
27+
} catch (err) {
28+
// Add the error as metadata
29+
tracer.addErrorAsMetadata(err as Error);
30+
throw err;
31+
} finally {
32+
// Close subsegment (the AWS Lambda one is closed automatically)
33+
subsegment.close();
34+
// Set the facade segment as active again
35+
tracer.setSegment(segment);
36+
}
37+
38+
return res;
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import middy from '@middy/core';
2+
import { Context } from 'aws-lambda';
3+
import { Events } from '@aws-lambda-powertools/commons';
4+
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';
5+
6+
// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
7+
const tracer = new Tracer({ serviceName: 'tracerMiddlewareFn' });
8+
9+
// We instrument the handler with the middy middleware and the tracer will automatically create a subsegment and capture relevant annotations and metadata
10+
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
11+
// Add custom annotation & metadata
12+
tracer.putAnnotation('awsRequestId', context.awsRequestId);
13+
tracer.putMetadata('eventPayload', event);
14+
15+
let res;
16+
try {
17+
res = { foo: 'bar' };
18+
} catch (err) {
19+
throw err;
20+
}
21+
22+
return res;
23+
}).use(captureLambdaHandler(tracer));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Context } from 'aws-lambda';
2+
import { Events } from '@aws-lambda-powertools/commons';
3+
import { Tracer } from '@aws-lambda-powertools/tracer';
4+
import { STS } from 'aws-sdk';
5+
6+
// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
7+
const tracer = new Tracer({ serviceName: 'tracerPatchAWSSDKv2Fn' });
8+
// To patch a specific AWS SDK Client, we pass it to the Tracer that will return an instrumented version of it
9+
const sts = tracer.captureAWSClient(new STS());
10+
11+
export const handler = async (_event: typeof Events.Custom.CustomEvent, _context: Context): Promise<unknown> => {
12+
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
13+
// Create subsegment for the function & set it as active
14+
const subsegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
15+
tracer.setSegment(subsegment);
16+
17+
let res;
18+
try {
19+
// Tracer will create a subsegment for the AWS SDK call and capture relevant annotations and metadata
20+
res = await sts.getCallerIdentity({}).promise();
21+
// Add custom metadata with the response object
22+
tracer.putMetadata('awsResponse', res);
23+
} catch (err) {
24+
throw err;
25+
}
26+
27+
// Close subsegment (the AWS Lambda one is closed automatically)
28+
subsegment.close();
29+
// Set the facade segment as active again
30+
tracer.setSegment(segment);
31+
32+
return res;
33+
};

0 commit comments

Comments
 (0)