Skip to content

chore(tracer): cdk examples + e2e tests #347

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 19 commits into from
Dec 28, 2021
Merged
Show file tree
Hide file tree
Changes from 12 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
58 changes: 38 additions & 20 deletions docs/core/tracer.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://gi

## Getting started

Tracer has three global settings that can be used to configure its behavior:

Setting | Description | Environment variable | Constructor parameter
------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------
**Trace Enabled** | Explicitly enables/disables tracing | `POWERTOOLS_TRACE_ENABLED` | `enabled`
**Capture Responses** | Captures Lambda or method return as metadata | `POWERTOOLS_TRACER_CAPTURE_RESPONSE` | N/A
**Capture Errors** | Captures Lambda or method exception as metadata | `POWERTOOLS_TRACER_CAPTURE_ERROR` | N/A

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this bit has been changed in main, getting started section + settings. How will it look like once you merge?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will resolve the conflict & accept the version on main since I made this change several days ago before your PR was merged.

### Permissions

Before your use this utility, your AWS Lambda function [must have permissions](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html#services-xray-permissions) to send traces to AWS X-Ray.
Expand Down Expand Up @@ -63,7 +71,7 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
// OR tracer = Tracer({ service: 'example' });

class Lambda {
@tracer.captureLambdaHanlder()
@tracer.captureLambdaHandler()
public handler(event: any, context: any) {
...
}
Expand All @@ -79,28 +87,34 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
import { Tracer } from '@aws-lambda-powertools/tracer';

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

export const handler = async (_event: any, context: any) => {
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
// Create subsegment for the function
const handlerSegment = segment.addNewSubsegment(`## ${context.functionName}`);
// Create subsegment for the function & set it as active
const subsegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(subsegment);

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

let res;
try {
res = ...
// Add the response as metadata
tracer.addResponseAsMetadata(res, context.functionName);
tracer.addResponseAsMetadata(res, process.env._HANDLER);
} catch (err) {
// Add the error as metadata
tracer.addErrorAsMetadata(err as Error);
throw err;
} finally {
// Close subsegment (the AWS Lambda one is closed automatically)
subsegment.close();
// Set the facade segment as active again
tracer.setSegment(segment);
}

// Close subsegment (the AWS Lambda one is closed automatically)
handlerSegment.close();

return res;
}
```
Expand All @@ -109,6 +123,7 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t

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

### Annotations & Metadata
Expand All @@ -123,10 +138,10 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t
```typescript hl_lines="6"
import { Tracer } from '@aws-lambda-powertools/tracer';

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

export const handler = async (_event: any, _context: any) => {
tracer.putAnnotation('PaymentStatus', "SUCCESS");
tracer.putAnnotation('successfulBooking', true);
}
```
=== "Metadata"
Expand All @@ -135,11 +150,11 @@ When using the `captureLambdaHandler` decorator or middleware, Tracer performs t
```typescript hl_lines="7"
import { Tracer } from '@aws-lambda-powertools/tracer';

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

export const handler = async (_event: any, _context: any) => {
const res = someLogic();
tracer.putMetadata('PaymentResponse', res);
tracer.putMetadata('paymentResponse', res);
}
```

Expand Down Expand Up @@ -178,29 +193,32 @@ You can trace other methods using the `captureMethod` decorator or manual instru

=== "Manual"

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

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

const chargeId = async () => {
// Create subsegment & set it as active
const subsegment = new Subsegment(`### chargeId`);
const parentSubsegment = tracer.getSegment(); // This is the subsegment currently active
// Create subsegment for the function & set it as active
const subsegment = parentSubsegment.addNewSubsegment(`### chargeId`);
tracer.setSegment(subsegment);

let res;
try {
res = await someLogic(); // Do something
// Add the response as metadata
tracer.putMetadata(`chargeId response`, data);
tracer.addResponseAsMetadata(res, 'chargeId');
} catch (err) {
// Add the error as metadata
subsegment.addError(err, false);
tracer.addErrorAsMetadata(err as Error);
throw err;
}

// Close subsegment
// Close subsegment (the AWS Lambda one is closed automatically)
subsegment.close();
// Set the facade segment as active again
tracer.setSegment(parentSubsegment);

return res;
}
Expand Down
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ This project separates core utilities that will be available in other runtimes v
| ------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- |
| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `"service_undefined"` |
| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | `None` |
| **POWERTOOLS_TRACE_DISABLED** | Explicitly disables tracing | [Tracing](./core/tracer) | `false` |
| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata. | [Tracing](./core/tracer) | `true` |
| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata. | [Tracing](./core/tracer) | `true` |
| **POWERTOOLS_TRACE_ENABLED** | Explicitly enables/disables tracing | [Tracing](./core/tracer) | `true` |
| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata | [Tracing](./core/tracer) | `true` |
| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata | [Tracing](./core/tracer) | `true` |
| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logging](./core/logger) | `false` |
| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logging](./core/logger) | `0` |
| **POWERTOOLS_LOG_DEDUPLICATION_DISABLED** | Disables log deduplication filter protection to use Pytest Live Log feature | [Logging](./core/logger) | `false` |
Expand Down
16 changes: 1 addition & 15 deletions examples/cdk/bin/cdk-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,4 @@ import * as cdk from 'aws-cdk-lib';
import { CdkAppStack } from '../lib/example-stack';

const app = new cdk.App();
new CdkAppStack(app, 'CdkAppStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */

/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },

/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
new CdkAppStack(app, 'CdkAppStack', {});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const metrics = new Metrics({ namespace: namespace, service: serviceName });
const logger = new Logger({ logLevel: 'INFO', serviceName: serviceName });
const tracer = new Tracer({ serviceName: serviceName });

export const handler = async (_event: unknown, context: Context): Promise<unknown> => {
export const handler = async (_event: unknown, context: Context): Promise<void> => {
// 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)
// we do it at the beginning because we want to trace the whole duration of the handler
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by Lambda & that can't be manipulated)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import middy from '@middy/core'
import middy from '@middy/core';
import { Callback, Context } from 'aws-lambda';
import { Metrics, MetricUnits } from '@aws-lambda-powertools/metrics';

Expand Down
27 changes: 27 additions & 0 deletions examples/cdk/lib/example-function.Tracer.CaptureErrorDisabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import middy from '@middy/core'
import { Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';

// Set environment variable to disable capture response
process.env.POWERTOOLS_TRACER_ERROR_RESPONSE = "false";
const tracer = new Tracer({ serviceName: 'tracerCaptureResponseDisabledFn' });

// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res;
try {
res = { foo: 'bar' };

// We are throwing an error only for testing purposes to make sure the error response is not captured in the subsegment metadata
throw new Error('An error occurred.');
} catch (err) {
// The error won't be in the subsegment metadata
throw err;
}

return res;
}).use(captureLambdaHandler(tracer));
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import middy from '@middy/core'
import { Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';

// Set environment variable to disable capture response
process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = "false";
const tracer = new Tracer({ serviceName: 'tracerCaptureResponseDisabledFn' });

// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res;
try {
res = { foo: 'bar' };
} catch (err) {
throw err;
}

// The response won't be captured in the subsegment metadata
return res;
}).use(captureLambdaHandler(tracer));
34 changes: 34 additions & 0 deletions examples/cdk/lib/example-function.Tracer.Decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Callback, Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { Tracer } from '@aws-lambda-powertools/tracer';

// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
const tracer = new Tracer({ serviceName: 'tracerDecoratorFn' });

export class MyFunctionWithDecorator {
// We instrument the handler with the decorator and the tracer will automatically create a subsegment and capture relevant annotations and metadata
@tracer.captureLambdaHandler()
public handler(event: typeof Events.Custom.CustomEvent, context: Context, _callback: Callback<unknown>): void | Promise<unknown> {
// Add custom annotation & metadata
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res: { foo: string };
try {
res = { foo: this.myMethod() };
} catch (err) {
throw err;
}

return new Promise((resolve, _reject) => resolve(res as unknown));
}

// We can also use decorators to create a subsegment for the method & capture response and errors as metadata
@tracer.captureMethod()
public myMethod(): string {
return 'bar';
}
}

export const handlerClass = new MyFunctionWithDecorator();
export const handler = handlerClass.handler;
23 changes: 23 additions & 0 deletions examples/cdk/lib/example-function.Tracer.Disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import middy from '@middy/core';
import { Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';

// process.env.POWERTOOLS_TRACE_ENABLED = 'false'; // Alternative to disabling tracing in the constructor
const tracer = new Tracer({ serviceName: 'tracerDisabledFn', enabled: false });

// In this example we are using the middleware pattern but you could use also the captureLambdaHandler decorator or the manual mode
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
// No tracing will be done and the commands will be ignored, this is useful for testing
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res;
try {
res = { foo: 'bar' };
} catch (err) {
throw err;
}

return res;
}).use(captureLambdaHandler(tracer));
39 changes: 39 additions & 0 deletions examples/cdk/lib/example-function.Tracer.Manual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { Tracer } from '@aws-lambda-powertools/tracer';

// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
const tracer = new Tracer({ serviceName: 'tracerManualFn' });

export const handler = async (event: typeof Events.Custom.CustomEvent, context: Context): Promise<unknown> => {
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
// Create subsegment for the function & set it as active
const subsegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(subsegment);

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

// Add custom annotation & metadata
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res;
try {
res = { foo: 'bar' };
// Add the response as metadata
tracer.addResponseAsMetadata(res, process.env._HANDLER);
} catch (err) {
// Add the error as metadata
tracer.addErrorAsMetadata(err as Error);
throw err;
} finally {
// Close subsegment (the AWS Lambda one is closed automatically)
subsegment.close();
// Set the facade segment as active again
tracer.setSegment(segment);
}

return res;
};
23 changes: 23 additions & 0 deletions examples/cdk/lib/example-function.Tracer.Middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import middy from '@middy/core';
import { Context } from 'aws-lambda';
import { Events } from '@aws-lambda-powertools/commons';
import { captureLambdaHandler, Tracer } from '@aws-lambda-powertools/tracer';

// process.env.POWERTOOLS_SERVICE_NAME = 'tracerManualFn'; // Alternative to setting the service name in the constructor
const tracer = new Tracer({ serviceName: 'tracerMiddlewareFn' });

// We instrument the handler with the middy middleware and the tracer will automatically create a subsegment and capture relevant annotations and metadata
export const handler = middy(async (event: typeof Events.Custom.CustomEvent, context: Context) => {
// Add custom annotation & metadata
tracer.putAnnotation('awsRequestId', context.awsRequestId);
tracer.putMetadata('eventPayload', event);

let res;
try {
res = { foo: 'bar' };
} catch (err) {
throw err;
}

return res;
}).use(captureLambdaHandler(tracer));
Loading