From da8520881960fdb8ea4b61709f003bc1bd260710 Mon Sep 17 00:00:00 2001 From: Andrea Amorosi Date: Wed, 24 Sep 2025 20:03:09 +0200 Subject: [PATCH] style(tracer): apply stricter linting --- packages/tracer/src/Tracer.ts | 53 +++++++++---------- packages/tracer/src/middleware/middy.ts | 27 ++++------ .../tracer/src/provider/ProviderService.ts | 4 +- packages/tracer/src/types/Tracer.ts | 20 +++---- packages/tracer/tests/e2e/decorator.test.ts | 2 +- packages/tracer/tests/e2e/manual.test.ts | 2 +- packages/tracer/tests/helpers/mockRequests.ts | 5 +- .../tracer/tests/unit/ProviderService.test.ts | 20 +++---- packages/tracer/tests/unit/Tracer.test.ts | 18 +++++-- packages/tracer/tests/unit/middy.test.ts | 23 ++------ packages/tracer/vitest.config.ts | 4 +- 11 files changed, 82 insertions(+), 96 deletions(-) diff --git a/packages/tracer/src/Tracer.ts b/packages/tracer/src/Tracer.ts index 3c3cfb94e1..b498f7e74e 100644 --- a/packages/tracer/src/Tracer.ts +++ b/packages/tracer/src/Tracer.ts @@ -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 @@ -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 @@ -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. * @@ -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. @@ -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. diff --git a/packages/tracer/src/middleware/middy.ts b/packages/tracer/src/middleware/middy.ts index 90207d7664..793bd94a93 100644 --- a/packages/tracer/src/middleware/middy.ts +++ b/packages/tracer/src/middleware/middy.ts @@ -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 @@ -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, @@ -83,9 +82,7 @@ const captureLambdaHandler = ( target.setSegment(lambdaSegment); }; - const captureLambdaHandlerBefore = async ( - request: MiddyLikeRequest - ): Promise => { + const before = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { open(); setCleanupFunction(request); @@ -94,9 +91,7 @@ const captureLambdaHandler = ( } }; - const captureLambdaHandlerAfter = async ( - request: MiddyLikeRequest - ): Promise => { + const after = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { if (options?.captureResponse ?? true) { target.addResponseAsMetadata(request.response, process.env._HANDLER); @@ -105,9 +100,7 @@ const captureLambdaHandler = ( } }; - const captureLambdaHandlerError = async ( - request: MiddyLikeRequest - ): Promise => { + const onError = (request: MiddyLikeRequest) => { if (target.isTracingEnabled()) { target.addErrorAsMetadata(request.error as Error); close(); @@ -115,9 +108,9 @@ const captureLambdaHandler = ( }; return { - before: captureLambdaHandlerBefore, - after: captureLambdaHandlerAfter, - onError: captureLambdaHandlerError, + before, + after, + onError, }; }; diff --git a/packages/tracer/src/provider/ProviderService.ts b/packages/tracer/src/provider/ProviderService.ts index a17331e3f1..44a1488af5 100644 --- a/packages/tracer/src/provider/ProviderService.ts +++ b/packages/tracer/src/provider/ProviderService.ts @@ -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, @@ -172,7 +172,7 @@ class ProviderService implements ProviderServiceInterface { response: { status, ...(contentLenght && { - content_length: Number.parseInt(contentLenght), + content_length: Number.parseInt(contentLenght, 10), }), }, }; diff --git a/packages/tracer/src/types/Tracer.ts b/packages/tracer/src/types/Tracer.ts index 8978e0bd6a..3767f7b10c 100644 --- a/packages/tracer/src/types/Tracer.ts +++ b/packages/tracer/src/types/Tracer.ts @@ -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'; @@ -48,7 +46,8 @@ type TracerOptions = { * .use(captureLambdaHandler(tracer, { captureResponse: false })); * ``` * - * Decorator usage: + * **Decorator usage** + * * @example * ```typescript * const tracer = new Tracer(); @@ -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; @@ -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 { * this.getChargeId(); * } @@ -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; diff --git a/packages/tracer/tests/e2e/decorator.test.ts b/packages/tracer/tests/e2e/decorator.test.ts index 2c5b14a6af..64ee1cbf5c 100644 --- a/packages/tracer/tests/e2e/decorator.test.ts +++ b/packages/tracer/tests/e2e/decorator.test.ts @@ -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; diff --git a/packages/tracer/tests/e2e/manual.test.ts b/packages/tracer/tests/e2e/manual.test.ts index c553a7e181..c2fdf8b12f 100644 --- a/packages/tracer/tests/e2e/manual.test.ts +++ b/packages/tracer/tests/e2e/manual.test.ts @@ -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; diff --git a/packages/tracer/tests/helpers/mockRequests.ts b/packages/tracer/tests/helpers/mockRequests.ts index 4385d06be4..3568646f00 100644 --- a/packages/tracer/tests/helpers/mockRequests.ts +++ b/packages/tracer/tests/helpers/mockRequests.ts @@ -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, @@ -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, diff --git a/packages/tracer/tests/unit/ProviderService.test.ts b/packages/tracer/tests/unit/ProviderService.test.ts index d10ddd4eb1..9cea095984 100644 --- a/packages/tracer/tests/unit/ProviderService.test.ts +++ b/packages/tracer/tests/unit/ProviderService.test.ts @@ -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(); @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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'); @@ -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'); diff --git a/packages/tracer/tests/unit/Tracer.test.ts b/packages/tracer/tests/unit/Tracer.test.ts index a59a125a9c..aa20322f75 100644 --- a/packages/tracer/tests/unit/Tracer.test.ts +++ b/packages/tracer/tests/unit/Tracer.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import type { LambdaInterface } from '@aws-lambda-powertools/commons/types'; import context from '@aws-lambda-powertools/testing-utils/context'; import type { Context } from 'aws-lambda'; @@ -884,7 +885,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer, { shouldThrow: true }); // Act & Assess - expect(lambda.handler({}, context)).rejects.toThrow(Error); + await expect(lambda.handler({}, context)).rejects.toThrow(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(addErrorFlagSpy).toHaveBeenCalledTimes(1); expect(addErrorSpy).toHaveBeenCalledTimes(0); @@ -905,7 +906,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer, { shouldThrow: true }); // Act & Assess - expect(lambda.handler({}, context)).rejects.toThrow(Error); + await expect(lambda.handler({}, context)).rejects.toThrow(Error); expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); expect(addErrorAsMetadataSpy).toHaveBeenCalledTimes(1); expect(addErrorAsMetadataSpy).toHaveBeenCalledWith(expect.any(Error)); @@ -922,7 +923,7 @@ describe('Class: Tracer', () => { const lambda = getLambdaClass(tracer); // Act - lambda.handler(event, context); + await lambda.handler(event, context); // Assess expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1); @@ -978,6 +979,7 @@ describe('Class: Tracer', () => { } public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return this.memberVariable; } @@ -1057,6 +1059,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1087,6 +1090,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1126,6 +1130,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod({ captureResponse: false }) public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1161,6 +1166,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1197,6 +1203,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(_some: string): Promise { + await setTimeout(0); // Simulate some async operation throw new Error('Exception thrown!'); } @@ -1245,6 +1252,7 @@ describe('Class: Tracer', () => { @tracer.captureMethod() public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return `memberVariable:${this.memberVariable}`; } @@ -1279,6 +1287,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return; } @@ -1336,6 +1345,7 @@ describe('Class: Tracer', () => { @tracer.captureMethod() @passThrough() public async dummyMethod(): Promise { + await setTimeout(0); // Simulate some async operation return 'foo'; } @@ -1374,6 +1384,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod({ subSegmentName: '#### myCustomMethod' }) public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } @@ -1419,6 +1430,7 @@ describe('Class: Tracer', () => { class Lambda implements LambdaInterface { @tracer.captureMethod() public async dummyMethod(some: string): Promise { + await setTimeout(0); // Simulate some async operation return some; } diff --git a/packages/tracer/tests/unit/middy.test.ts b/packages/tracer/tests/unit/middy.test.ts index bb529a3af7..93e37b6309 100644 --- a/packages/tracer/tests/unit/middy.test.ts +++ b/packages/tracer/tests/unit/middy.test.ts @@ -1,3 +1,4 @@ +import { setTimeout } from 'node:timers/promises'; import { cleanupMiddlewares } from '@aws-lambda-powertools/commons'; import context from '@aws-lambda-powertools/testing-utils/context'; import middy from '@middy/core'; @@ -13,6 +14,10 @@ import { captureLambdaHandler } from '../../src/middleware/middy.js'; describe('Middy middleware', () => { const ENVIRONMENT_VARIABLES = process.env; + const lambdaHandler: Handler = async (_event: unknown, _context: Context) => { + await setTimeout(0); // Simulate some async operation + throw new Error('Exception thrown!'); + }; beforeEach(() => { vi.clearAllMocks(); @@ -64,12 +69,6 @@ describe('Middy middleware', () => { () => new Segment('facade', process.env._X_AMZN_TRACE_ID || null) ) .mockImplementationOnce(() => new Subsegment('## index.handler')); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess @@ -173,12 +172,6 @@ describe('Middy middleware', () => { setContextMissingStrategy(() => null); const addErrorSpy = vi.spyOn(newSubsegment, 'addError'); const addErrorFlagSpy = vi.spyOn(newSubsegment, 'addErrorFlag'); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess @@ -208,12 +201,6 @@ describe('Middy middleware', () => { ); setContextMissingStrategy(() => null); const addErrorSpy = vi.spyOn(newSubsegment, 'addError'); - const lambdaHandler: Handler = async ( - _event: unknown, - _context: Context - ) => { - throw new Error('Exception thrown!'); - }; const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer)); // Act & Assess diff --git a/packages/tracer/vitest.config.ts b/packages/tracer/vitest.config.ts index baa5cf7463..e962bb6d1c 100644 --- a/packages/tracer/vitest.config.ts +++ b/packages/tracer/vitest.config.ts @@ -4,7 +4,7 @@ export default defineProject({ test: { environment: 'node', setupFiles: ['../testing/src/setupEnv.ts'], - hookTimeout: 1_000 * 60 * 10, // 10 minutes - testTimeout: 1_000 * 60 * 3, // 3 minutes + hookTimeout: 1000 * 60 * 10, // 10 minutes + testTimeout: 1000 * 60 * 3, // 3 minutes }, });