Skip to content

Commit 3559e7b

Browse files
authored
fix(tracer): properly return DynamoDB.DocumentClient (#528)
* fix: properly return DynamoDB.DocumentClient * added comment to explain fix * updated e2e tests * fix: applied proper generic
1 parent e96c9ba commit 3559e7b

7 files changed

+30
-31
lines changed

Diff for: packages/tracing/src/Tracer.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,12 @@ class Tracer implements TracerInterface {
261261
return this.provider.captureAWSClient(service);
262262
} catch (error) {
263263
try {
264-
return this.provider.captureAWSClient((service as unknown as T & { service: T }).service);
264+
// This is needed because some aws-sdk clients like AWS.DynamoDB.DocumentDB don't comply with the same
265+
// instrumentation contract like most base clients.
266+
// For detailed explanation see: https://github.com/awslabs/aws-lambda-powertools-typescript/issues/524#issuecomment-1024493662
267+
this.provider.captureAWSClient((service as T & { service: T }).service);
268+
269+
return service;
265270
} catch {
266271
throw error;
267272
}

Diff for: packages/tracing/tests/e2e/tracer.test.Decorator.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Tracer } from '../../src';
22
import { Callback, Context } from 'aws-lambda';
3-
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
3+
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
44
// eslint-disable-next-line @typescript-eslint/no-var-requires
55
let AWS = require('aws-sdk');
66

@@ -52,8 +52,8 @@ export class MyFunctionWithDecorator {
5252
}
5353

5454
return Promise.all([
55-
dynamoDBv2.scan({ TableName: testTableName }).promise(),
56-
dynamoDBv3.send(new ScanCommand({ TableName: testTableName })),
55+
dynamoDBv2.put({ TableName: testTableName, Item: { id: `${serviceName}-${event.invocation}-sdkv2` } }).promise(),
56+
dynamoDBv3.send(new PutItemCommand({ TableName: testTableName, Item: { id: { 'S': `${serviceName}-${event.invocation}-sdkv3` } } })),
5757
new Promise((resolve, reject) => {
5858
setTimeout(() => {
5959
const res = this.myMethod();

Diff for: packages/tracing/tests/e2e/tracer.test.DecoratorWithAsyncHandler.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Tracer } from '../../src';
22
import { Context } from 'aws-lambda';
3-
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
3+
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
44
// eslint-disable-next-line @typescript-eslint/no-var-requires
55
let AWS = require('aws-sdk');
66

@@ -52,13 +52,13 @@ export class MyFunctionWithDecorator {
5252
}
5353

5454
try {
55-
await dynamoDBv2.scan({ TableName: testTableName }).promise();
55+
await dynamoDBv2.put({ TableName: testTableName, Item: { id: `${serviceName}-${event.invocation}-sdkv2` } }).promise();
5656
} catch (err) {
5757
console.error(err);
5858
}
5959

6060
try {
61-
await dynamoDBv3.send(new ScanCommand({ TableName: testTableName }));
61+
await dynamoDBv3.send(new PutItemCommand({ TableName: testTableName, Item: { id: { 'S': `${serviceName}-${event.invocation}-sdkv3` } } }));
6262
} catch (err) {
6363
console.error(err);
6464
}

Diff for: packages/tracing/tests/e2e/tracer.test.Manual.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Tracer } from '../../src';
22
import { Context } from 'aws-lambda';
3-
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
3+
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
44
// eslint-disable-next-line @typescript-eslint/no-var-requires
55
let AWS = require('aws-sdk');
66

@@ -55,13 +55,13 @@ export const handler = async (event: CustomEvent, _context: Context): Promise<vo
5555
dynamoDBv2 = new AWS.DynamoDB.DocumentClient();
5656
}
5757
try {
58-
await dynamoDBv2.scan({ TableName: testTableName }).promise();
58+
await dynamoDBv2.put({ TableName: testTableName, Item: { id: `${serviceName}-${event.invocation}-sdkv2` } }).promise();
5959
} catch (err) {
6060
console.error(err);
6161
}
6262

6363
try {
64-
await dynamoDBv3.send(new ScanCommand({ TableName: testTableName }));
64+
await dynamoDBv3.send(new PutItemCommand({ TableName: testTableName, Item: { id: { 'S': `${serviceName}-${event.invocation}-sdkv3` } } }));
6565
} catch (err) {
6666
console.error(err);
6767
}

Diff for: packages/tracing/tests/e2e/tracer.test.Middleware.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import middy from '@middy/core';
22
import { captureLambdaHandler, Tracer } from '../../src';
33
import { Context } from 'aws-lambda';
4-
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
4+
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
55
// eslint-disable-next-line @typescript-eslint/no-var-requires
66
let AWS = require('aws-sdk');
77

@@ -49,13 +49,13 @@ export const handler = middy(async (event: CustomEvent, _context: Context): Prom
4949
dynamoDBv2 = new AWS.DynamoDB.DocumentClient();
5050
}
5151
try {
52-
await dynamoDBv2.scan({ TableName: testTableName }).promise();
52+
await dynamoDBv2.put({ TableName: testTableName, Item: { id: `${serviceName}-${event.invocation}-sdkv2` } }).promise();
5353
} catch (err) {
5454
console.error(err);
5555
}
5656

5757
try {
58-
await dynamoDBv3.send(new ScanCommand({ TableName: testTableName }));
58+
await dynamoDBv3.send(new PutItemCommand({ TableName: testTableName, Item: { id: { 'S': `${serviceName}-${event.invocation}-sdkv3` } } }));
5959
} catch (err) {
6060
console.error(err);
6161
}

Diff for: packages/tracing/tests/e2e/tracer.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ describe('Tracer integration tests', () => {
9292
},
9393
timeout: Duration.seconds(30),
9494
});
95-
table.grantReadData(fn);
95+
table.grantWriteData(fn);
9696
invocationsMap[functionName] = {
9797
serviceName: expectedServiceName,
9898
resourceArn: `arn:aws:lambda:${region}:${account}:function:${functionName}`, // ARN is still a token at this point, so we construct the ARN manually

Diff for: packages/tracing/tests/unit/Tracer.test.ts

+11-17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { Tracer } from '../../src';
88
import { Callback, Context, Handler } from 'aws-lambda/handler';
99
import { Segment, setContextMissingStrategy, Subsegment } from 'aws-xray-sdk-core';
10+
import { DynamoDB } from 'aws-sdk';
1011

1112
interface LambdaInterface {
1213
handler: Handler
@@ -1057,29 +1058,27 @@ describe('Class: Tracer', () => {
10571058
const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient');
10581059

10591060
// Act
1060-
tracer.captureAWSClient({});
1061+
const client = tracer.captureAWSClient(new DynamoDB());
10611062

10621063
// Assess
10631064
expect(captureAWSClientSpy).toBeCalledTimes(0);
1065+
expect(client).toBeInstanceOf(DynamoDB);
10641066

10651067
});
10661068

1067-
test('when called with a simple AWS SDK v2 client, it returns it back instrumented', () => {
1069+
test('when called with a base AWS SDK v2 client, it returns it back instrumented', () => {
10681070

10691071
// Prepare
10701072
const tracer: Tracer = new Tracer();
10711073
const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient');
1072-
// Minimum shape required for a regular AWS v2 client (i.e. AWS.S3) to be instrumented
1073-
const dummyClient = {
1074-
customizeRequests: () => null,
1075-
};
10761074

10771075
// Act
1078-
tracer.captureAWSClient(dummyClient);
1076+
const client = tracer.captureAWSClient(new DynamoDB());
10791077

10801078
// Assess
10811079
expect(captureAWSClientSpy).toBeCalledTimes(1);
1082-
expect(captureAWSClientSpy).toBeCalledWith(dummyClient);
1080+
expect(captureAWSClientSpy).toBeCalledWith(client);
1081+
expect(client).toBeInstanceOf(DynamoDB);
10831082

10841083
});
10851084

@@ -1088,20 +1087,15 @@ describe('Class: Tracer', () => {
10881087
// Prepare
10891088
const tracer: Tracer = new Tracer();
10901089
const captureAWSClientSpy = jest.spyOn(tracer.provider, 'captureAWSClient');
1091-
// Minimum shape required for a complex AWS v2 client (i.e. AWS.DocumentClient) to be instrumented
1092-
const dummyClient = {
1093-
service: {
1094-
customizeRequests: () => null,
1095-
}
1096-
};
10971090

10981091
// Act
1099-
tracer.captureAWSClient(dummyClient);
1092+
const client = tracer.captureAWSClient(new DynamoDB.DocumentClient());
11001093

11011094
// Assess
11021095
expect(captureAWSClientSpy).toBeCalledTimes(2);
1103-
expect(captureAWSClientSpy).toHaveBeenNthCalledWith(1, dummyClient);
1104-
expect(captureAWSClientSpy).toHaveBeenNthCalledWith(2, dummyClient.service);
1096+
expect(captureAWSClientSpy).toHaveBeenNthCalledWith(1, client);
1097+
expect(captureAWSClientSpy).toHaveBeenNthCalledWith(2, (client as unknown as DynamoDB & { service: DynamoDB }).service);
1098+
expect(client).toBeInstanceOf(DynamoDB.DocumentClient);
11051099

11061100
});
11071101

0 commit comments

Comments
 (0)