Skip to content
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

fix(tracer): captureMethod detects proper method name when used with external decorators #1109

Merged
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
8 changes: 5 additions & 3 deletions packages/tracer/src/Tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ class Tracer extends Utility implements TracerInterface {
* @decorator Class
*/
public captureMethod(options?: HandlerOptions): MethodDecorator {
return (_target, _propertyKey, descriptor) => {
return (_target, propertyKey, descriptor) => {
// The descriptor.value is the method this decorator decorates, it cannot be undefined.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const originalMethod = descriptor.value!;
Expand All @@ -434,12 +434,14 @@ class Tracer extends Utility implements TracerInterface {
return originalMethod.apply(this, [...args]);
}

return tracerRef.provider.captureAsyncFunc(`### ${originalMethod.name}`, async subsegment => {
const methodName = String(propertyKey);

return tracerRef.provider.captureAsyncFunc(`### ${methodName}`, async subsegment => {
let result;
try {
result = await originalMethod.apply(this, [...args]);
if (options?.captureResponse ?? true) {
tracerRef.addResponseAsMetadata(result, originalMethod.name);
tracerRef.addResponseAsMetadata(result, methodName);
}
} catch (error) {
tracerRef.addErrorAsMetadata(error as Error);
Expand Down
67 changes: 65 additions & 2 deletions packages/tracer/tests/unit/Tracer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ interface LambdaInterface {
}

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

});

test('when used as decorator together with another external decorator, the method name is detected properly', async () => {

// Prepare
const tracer: Tracer = new Tracer();
const newSubsegment: Segment | Subsegment | undefined = new Subsegment('### dummyMethod');
jest.spyOn(tracer.provider, 'getSegment')
.mockImplementation(() => newSubsegment);
setContextMissingStrategy(() => null);
createCaptureAsyncFuncMock(tracer.provider, newSubsegment);

// Creating custom external decorator
// eslint-disable-next-line func-style
function passThrough() {
// A decorator that calls the original method.
return (
_target: unknown,
_propertyKey: string,
descriptor: PropertyDescriptor
) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const originalMethod = descriptor.value!;
descriptor.value = function (...args: unknown[]) {
return originalMethod.apply(this, [...args]);
};
};
}

class Lambda implements LambdaInterface {
@tracer.captureMethod()
@passThrough()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
public async dummyMethod(): Promise<string> {
return `foo`;
}

public async handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): Promise<void> {
await this.dummyMethod();

return;
}

}

// Act / Assess
const lambda = new Lambda();
const handler = lambda.handler.bind(lambda);
await handler({}, context, () => console.log('Lambda invoked!'));

// Assess
expect(newSubsegment).toEqual(expect.objectContaining({
metadata: {
'hello-world': {
// Assess that the method name is added correctly
'dummyMethod response': 'foo',
},
}
}));

});

});

describe('Method: captureAWS', () => {
Expand Down