Skip to content
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
53 changes: 24 additions & 29 deletions packages/tracer/src/Tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,24 @@ import type {
const { Subsegment: XraySubsegment } = xraySdk;

/**
* ## Intro
* Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://github.com/aws/aws-xray-sdk-node).
*
* Tracing data can be visualized through AWS X-Ray Console.
*
* ## Key features
* * Auto capture cold start as annotation, and responses or full exceptions as metadata
* * Auto-disable when not running in AWS Lambda environment
* * Automatically trace HTTP(s) clients and generate segments for each request
* * Support tracing functions via decorators, middleware, and manual instrumentation
* * Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js
* **Key features**
* - Auto capture cold start as annotation, and responses or full exceptions as metadata
* - Auto-disable when not running in AWS Lambda environment
* - Automatically trace HTTP(s) clients and generate segments for each request
* - Support tracing functions via decorators, middleware, and manual instrumentation
* - Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js
*
* ## Usage
*
* For more usage examples, see [our documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/tracer/).
*
* ### Functions usage with middleware
* **Functions usage with middleware**
*
* If you use function-based Lambda handlers you can use the {@link Tracer.captureLambdaHandler} middy middleware to automatically:
* * handle the subsegment lifecycle
* * add the `ServiceName` and `ColdStart` annotations
* * add the function response as metadata
* * add the function error as metadata (if any)
* - handle the subsegment lifecycle
* - add the `ServiceName` and `ColdStart` annotations
* - add the function response as metadata
* - add the function error as metadata (if any)
*
* @example
* ```typescript
Expand All @@ -80,13 +75,13 @@ const { Subsegment: XraySubsegment } = xraySdk;
* export const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer));
* ```
*
* ### Object oriented usage with decorators
* **Object oriented usage with decorators**
*
* If instead you use TypeScript Classes to wrap your Lambda handler you can use the {@link Tracer.captureLambdaHandler} decorator to automatically:
* * handle the subsegment lifecycle
* * add the `ServiceName` and `ColdStart` annotations
* * add the function response as metadata
* * add the function error as metadata (if any)
* - handle the subsegment lifecycle
* - add the `ServiceName` and `ColdStart` annotations
* - add the function response as metadata
* - add the function error as metadata (if any)
*
* @example
* ```typescript
Expand All @@ -106,7 +101,7 @@ const { Subsegment: XraySubsegment } = xraySdk;
* export const handler = handlerClass.handler.bind(handlerClass);
* ```
*
* ### Functions usage with manual instrumentation
* **Functions usage with manual instrumentation**
*
* If you prefer to manually instrument your Lambda handler you can use the methods in the tracer class directly.
*
Expand Down Expand Up @@ -391,10 +386,10 @@ class Tracer extends Utility implements TracerInterface {
* A decorator automating capture of metadata and annotations on segments or subsegments for a Lambda Handler.
*
* Using this decorator on your handler function will automatically:
* * handle the subsegment lifecycle
* * add the `ColdStart` annotation
* * add the function response as metadata
* * add the function error as metadata (if any)
* - handle the subsegment lifecycle
* - add the `ColdStart` annotation
* - add the function response as metadata
* - add the function error as metadata (if any)
*
* Note: Currently TypeScript only supports decorators on classes and methods. If you are using the
* function syntax, you should use the middleware instead.
Expand Down Expand Up @@ -475,9 +470,9 @@ class Tracer extends Utility implements TracerInterface {
* A decorator automating capture of metadata and annotations on segments or subsegments for an arbitrary function.
*
* Using this decorator on your function will automatically:
* * handle the subsegment lifecycle
* * add the function response as metadata
* * add the function error as metadata (if any)
* - handle the subsegment lifecycle
* - add the function response as metadata
* - add the function error as metadata (if any)
*
* Note: Currently TypeScript only supports decorators on classes and methods. If you are using the
* function syntax, you should use the middleware instead.
Expand Down
27 changes: 10 additions & 17 deletions packages/tracer/src/middleware/middy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import type { CaptureLambdaHandlerOptions } from '../types/Tracer.js';
* A middy middleware automating capture of metadata and annotations on segments or subsegments for a Lambda Handler.
*
* Using this middleware on your handler function will automatically:
* * handle the subsegment lifecycle
* * add the `ColdStart` annotation
* * add the function response as metadata
* * add the function error as metadata (if any)
* - handle the subsegment lifecycle
* - add the `ColdStart` annotation
* - add the function response as metadata
* - add the function error as metadata (if any)
*
* @example
* ```typescript
Expand All @@ -33,7 +33,6 @@ import type { CaptureLambdaHandlerOptions } from '../types/Tracer.js';
*
* @param target - The Tracer instance to use for tracing
* @param options - (_optional_) Options for the middleware
* @returns middleware - The middy middleware object
*/
const captureLambdaHandler = (
target: Tracer,
Expand Down Expand Up @@ -83,9 +82,7 @@ const captureLambdaHandler = (
target.setSegment(lambdaSegment);
};

const captureLambdaHandlerBefore = async (
request: MiddyLikeRequest
): Promise<void> => {
const before = (request: MiddyLikeRequest) => {
if (target.isTracingEnabled()) {
open();
setCleanupFunction(request);
Expand All @@ -94,9 +91,7 @@ const captureLambdaHandler = (
}
};

const captureLambdaHandlerAfter = async (
request: MiddyLikeRequest
): Promise<void> => {
const after = (request: MiddyLikeRequest) => {
if (target.isTracingEnabled()) {
if (options?.captureResponse ?? true) {
target.addResponseAsMetadata(request.response, process.env._HANDLER);
Expand All @@ -105,19 +100,17 @@ const captureLambdaHandler = (
}
};

const captureLambdaHandlerError = async (
request: MiddyLikeRequest
): Promise<void> => {
const onError = (request: MiddyLikeRequest) => {
if (target.isTracingEnabled()) {
target.addErrorAsMetadata(request.error as Error);
close();
}
};

return {
before: captureLambdaHandlerBefore,
after: captureLambdaHandlerAfter,
onError: captureLambdaHandlerError,
before,
after,
onError,
};
};

Expand Down
4 changes: 2 additions & 2 deletions packages/tracer/src/provider/ProviderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import { subscribe } from 'node:diagnostics_channel';
import http from 'node:http';
import https from 'node:https';
import { addUserAgentMiddleware } from '@aws-lambda-powertools/commons';
import type { DiagnosticsChannel } from 'undici-types';
import { getXRayTraceIdFromEnv } from '@aws-lambda-powertools/commons/utils/env';
import type { DiagnosticsChannel } from 'undici-types';
import {
findHeaderAndDecode,
getRequestURL,
Expand Down Expand Up @@ -172,7 +172,7 @@ class ProviderService implements ProviderServiceInterface {
response: {
status,
...(contentLenght && {
content_length: Number.parseInt(contentLenght),
content_length: Number.parseInt(contentLenght, 10),
}),
},
};
Expand Down
20 changes: 10 additions & 10 deletions packages/tracer/src/types/Tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ type TracerOptions = {
/**
* Options for handler decorators and middleware.
*
* Options supported:
* * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata
* **Middleware usage**
*
* Middleware usage:
* @example
* ```typescript
* import middy from '@middy/core';
Expand All @@ -48,7 +46,8 @@ type TracerOptions = {
* .use(captureLambdaHandler(tracer, { captureResponse: false }));
* ```
*
* Decorator usage:
* **Decorator usage**
*
* @example
* ```typescript
* const tracer = new Tracer();
Expand All @@ -61,6 +60,8 @@ type TracerOptions = {
* const handlerClass = new Lambda();
* export const handler = handlerClass.handler.bind(handlerClass);
* ```
*
* @property captureResponse - Whether to capture the Lambda handler response as subsegment metadata (default: true)
*/
type CaptureLambdaHandlerOptions = {
captureResponse?: boolean;
Expand All @@ -69,22 +70,18 @@ type CaptureLambdaHandlerOptions = {
/**
* Options for method decorators.
*
* Options supported:
* * `subSegmentName` - (_optional_) - Set a custom name for the subsegment
* * `captureResponse` - (_optional_) - Disable response serialization as subsegment metadata
*
* Usage:
* @example
* ```typescript
* const tracer = new Tracer();
*
* class Lambda implements LambdaInterface {
* @tracer.captureMethod({ subSegmentName: 'gettingChargeId', captureResponse: false })
* @tracer.captureMethod({ subSegmentName: 'gettingChargeId', captureResponse: false })
* private getChargeId(): string {
* return 'foo bar';
* }
*
* @tracer.captureLambdaHandler({ captureResponse: false })
* @tracer.captureLambdaHandler({ captureResponse: false })
* public async handler(_event: any, _context: any): Promise<void> {
* this.getChargeId();
* }
Expand All @@ -93,6 +90,9 @@ type CaptureLambdaHandlerOptions = {
* const handlerClass = new Lambda();
* export const handler = handlerClass.handler.bind(handlerClass);
* ```
*
* @property subSegmentName - (_optional_) - Set a custom name for the subsegment
* @property captureResponse - (_optional_) - Disable response serialization as subsegment metadata (default: true)
*/
type CaptureMethodOptions = {
subSegmentName?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/tracer/tests/e2e/decorator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('Tracer E2E tests, decorator instrumentation', () => {
}
});

it('should generate all trace data correctly', async () => {
it('should generate all trace data correctly', () => {
// Assess
const mainSubsegment = traceData[0];
const { subsegments, annotations, metadata } = mainSubsegment;
Expand Down
2 changes: 1 addition & 1 deletion packages/tracer/tests/e2e/manual.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('Tracer E2E tests, manual instantiation', () => {
}
});

it('should generate all trace data correctly', async () => {
it('should generate all trace data correctly', () => {
// Assess
const mainSubsegment = traceData[0];
const { subsegments, annotations, metadata } = mainSubsegment;
Expand Down
5 changes: 2 additions & 3 deletions packages/tracer/tests/helpers/mockRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const mockFetch = ({
});

if (throwError) {
const error = new AggregateError('Mock fetch error');
const error = new AggregateError([], 'Mock fetch error');

errorChannel.publish({
request,
Expand All @@ -62,8 +62,7 @@ const mockFetch = ({
const encoder = new TextEncoder();
const encodedHeaders = [];
for (const [key, value] of Object.entries(headers ?? {})) {
encodedHeaders.push(encoder.encode(key));
encodedHeaders.push(encoder.encode(value));
encodedHeaders.push(encoder.encode(key), encoder.encode(value));
}
responseHeadersChannel.publish({
request,
Expand Down
20 changes: 10 additions & 10 deletions packages/tracer/tests/unit/ProviderService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ describe('Class: ProviderService', () => {
});

describe('Method: instrumentFetch', () => {
it('subscribes to the diagnostics channel', async () => {
it('subscribes to the diagnostics channel', () => {
// Prepare
const provider: ProviderService = new ProviderService();

Expand All @@ -319,7 +319,7 @@ describe('Class: ProviderService', () => {
expect(channel('undici:request:error').hasSubscribers).toBe(true);
});

it('traces a successful request', async () => {
it('traces a successful request', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -367,7 +367,7 @@ describe('Class: ProviderService', () => {
);
});

it('excludes the content_length header when invalid or not found', async () => {
it('excludes the content_length header when invalid or not found', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -406,7 +406,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('adds a throttle flag to the segment when the status code is 429', async () => {
it('adds a throttle flag to the segment when the status code is 429', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -442,7 +442,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('adds an error flag to the segment when the status code is 4xx', async () => {
it('adds an error flag to the segment when the status code is 4xx', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -478,7 +478,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('adds a fault flag to the segment when the status code is 5xx', async () => {
it('adds a fault flag to the segment when the status code is 5xx', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -514,7 +514,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('skips the segment creation when the request has no origin', async () => {
it('skips the segment creation when the request has no origin', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand All @@ -531,7 +531,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenCalledTimes(0);
});

it('does not add any path to the segment when the request has no path', async () => {
it('does not add any path to the segment when the request has no path', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -564,7 +564,7 @@ describe('Class: ProviderService', () => {
});
});

it('closes the segment and adds a fault flag when the connection fails', async () => {
it('closes the segment and adds a fault flag when the connection fails', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down Expand Up @@ -595,7 +595,7 @@ describe('Class: ProviderService', () => {
expect(provider.setSegment).toHaveBeenLastCalledWith(segment);
});

it('forwards the correct sampling decision in the request header', async () => {
it('forwards the correct sampling decision in the request header', () => {
// Prepare
const provider: ProviderService = new ProviderService();
const segment = new Subsegment('## dummySegment');
Expand Down
Loading
Loading