Skip to content

Commit a574406

Browse files
authored
fix: captureMethod correctly detect method name when used with external decorators (#1109)
1 parent f25f7bf commit a574406

File tree

2 files changed

+70
-5
lines changed

2 files changed

+70
-5
lines changed

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ class Tracer extends Utility implements TracerInterface {
420420
* @decorator Class
421421
*/
422422
public captureMethod(options?: HandlerOptions): MethodDecorator {
423-
return (_target, _propertyKey, descriptor) => {
423+
return (_target, propertyKey, descriptor) => {
424424
// The descriptor.value is the method this decorator decorates, it cannot be undefined.
425425
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
426426
const originalMethod = descriptor.value!;
@@ -434,12 +434,14 @@ class Tracer extends Utility implements TracerInterface {
434434
return originalMethod.apply(this, [...args]);
435435
}
436436

437-
return tracerRef.provider.captureAsyncFunc(`### ${originalMethod.name}`, async subsegment => {
437+
const methodName = String(propertyKey);
438+
439+
return tracerRef.provider.captureAsyncFunc(`### ${methodName}`, async subsegment => {
438440
let result;
439441
try {
440442
result = await originalMethod.apply(this, [...args]);
441443
if (options?.captureResponse ?? true) {
442-
tracerRef.addResponseAsMetadata(result, originalMethod.name);
444+
tracerRef.addResponseAsMetadata(result, methodName);
443445
}
444446
} catch (error) {
445447
tracerRef.addErrorAsMetadata(error as Error);

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

+65-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ interface LambdaInterface {
1515
}
1616

1717
type CaptureAsyncFuncMock = jest.SpyInstance<unknown, [name: string, fcn: (subsegment?: Subsegment) => unknown, parent?: Segment | Subsegment]>;
18-
const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface): CaptureAsyncFuncMock {
18+
const createCaptureAsyncFuncMock = function(provider: ProviderServiceInterface, subsegment?: Subsegment): CaptureAsyncFuncMock {
1919
return jest.spyOn(provider, 'captureAsyncFunc')
2020
.mockImplementation(async (methodName, callBackFn) => {
21-
const subsegment = new Subsegment(`### ${methodName}`);
21+
if (!subsegment) {
22+
subsegment = new Subsegment(`### ${methodName}`);
23+
}
2224
jest.spyOn(subsegment, 'flush').mockImplementation(() => null);
2325
await callBackFn(subsegment);
2426
});
@@ -1239,6 +1241,67 @@ describe('Class: Tracer', () => {
12391241

12401242
});
12411243

1244+
test('when used as decorator together with another external decorator, the method name is detected properly', async () => {
1245+
1246+
// Prepare
1247+
const tracer: Tracer = new Tracer();
1248+
const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod');
1249+
jest.spyOn(tracer.provider, 'getSegment')
1250+
.mockImplementation(() => newSubsegment);
1251+
setContextMissingStrategy(() => null);
1252+
createCaptureAsyncFuncMock(tracer.provider, newSubsegment);
1253+
1254+
// Creating custom external decorator
1255+
// eslint-disable-next-line func-style
1256+
function passThrough() {
1257+
// A decorator that calls the original method.
1258+
return (
1259+
_target: unknown,
1260+
_propertyKey: string,
1261+
descriptor: PropertyDescriptor
1262+
) => {
1263+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1264+
const originalMethod = descriptor.value!;
1265+
descriptor.value = function (...args: unknown[]) {
1266+
return originalMethod.apply(this, [...args]);
1267+
};
1268+
};
1269+
}
1270+
1271+
class Lambda implements LambdaInterface {
1272+
@tracer.captureMethod()
1273+
@passThrough()
1274+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1275+
// @ts-ignore
1276+
public async dummyMethod(): Promise<string> {
1277+
return `foo`;
1278+
}
1279+
1280+
public async handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): Promise<void> {
1281+
await this.dummyMethod();
1282+
1283+
return;
1284+
}
1285+
1286+
}
1287+
1288+
// Act / Assess
1289+
const lambda = new Lambda();
1290+
const handler = lambda.handler.bind(lambda);
1291+
await handler({}, context, () => console.log('Lambda invoked!'));
1292+
1293+
// Assess
1294+
expect(newSubsegment).toEqual(expect.objectContaining({
1295+
metadata: {
1296+
'hello-world': {
1297+
// Assess that the method name is added correctly
1298+
'dummyMethod response': 'foo',
1299+
},
1300+
}
1301+
}));
1302+
1303+
});
1304+
12421305
});
12431306

12441307
describe('Method: captureAWS', () => {

0 commit comments

Comments
 (0)