Skip to content

Commit b6d293a

Browse files
authored
fix(instrumentation-dataloader): Patch batchLoadFn without creating an instance (#2498)
1 parent 2aef158 commit b6d293a

File tree

2 files changed

+79
-47
lines changed

2 files changed

+79
-47
lines changed

plugins/node/instrumentation-dataloader/src/instrumentation.ts

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -92,64 +92,75 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
9292
return `${MODULE_NAME}.${operation} ${dataloaderName}`;
9393
}
9494

95+
private _wrapBatchLoadFn(
96+
batchLoadFn: Dataloader.BatchLoadFn<unknown, unknown>
97+
): Dataloader.BatchLoadFn<unknown, unknown> {
98+
const instrumentation = this;
99+
100+
return function patchedBatchLoadFn(
101+
this: DataloaderInternal,
102+
...args: Parameters<Dataloader.BatchLoadFn<unknown, unknown>>
103+
) {
104+
if (
105+
!instrumentation.isEnabled() ||
106+
!instrumentation.shouldCreateSpans()
107+
) {
108+
return batchLoadFn.call(this, ...args);
109+
}
110+
111+
const parent = context.active();
112+
const span = instrumentation.tracer.startSpan(
113+
instrumentation.getSpanName(this, 'batch'),
114+
{ links: this._batch?.spanLinks as Link[] | undefined },
115+
parent
116+
);
117+
118+
return context.with(trace.setSpan(parent, span), () => {
119+
return (batchLoadFn.apply(this, args) as Promise<unknown[]>)
120+
.then(value => {
121+
span.end();
122+
return value;
123+
})
124+
.catch(err => {
125+
span.recordException(err);
126+
span.setStatus({
127+
code: SpanStatusCode.ERROR,
128+
message: err.message,
129+
});
130+
span.end();
131+
throw err;
132+
});
133+
});
134+
};
135+
}
136+
95137
private _getPatchedConstructor(
96138
constructor: typeof Dataloader
97139
): typeof Dataloader {
98-
const prototype = constructor.prototype;
99140
const instrumentation = this;
141+
const prototype = constructor.prototype;
142+
143+
if (!instrumentation.isEnabled()) {
144+
return constructor;
145+
}
100146

101147
function PatchedDataloader(
148+
this: DataloaderInternal,
102149
...args: ConstructorParameters<typeof constructor>
103150
) {
104-
const inst = new constructor(...args) as DataloaderInternal;
105-
106-
if (!instrumentation.isEnabled()) {
107-
return inst;
108-
}
151+
// BatchLoadFn is the first constructor argument
152+
// https://github.com/graphql/dataloader/blob/77c2cd7ca97e8795242018ebc212ce2487e729d2/src/index.js#L47
153+
if (typeof args[0] === 'function') {
154+
if (isWrapped(args[0])) {
155+
instrumentation._unwrap(args, 0);
156+
}
109157

110-
if (isWrapped(inst._batchLoadFn)) {
111-
instrumentation._unwrap(inst, '_batchLoadFn');
158+
args[0] = instrumentation._wrapBatchLoadFn(
159+
args[0]
160+
) as Dataloader.BatchLoadFn<unknown, unknown>;
112161
}
113162

114-
instrumentation._wrap(inst, '_batchLoadFn', original => {
115-
return function patchedBatchLoadFn(
116-
this: DataloaderInternal,
117-
...args: Parameters<Dataloader.BatchLoadFn<unknown, unknown>>
118-
) {
119-
if (
120-
!instrumentation.isEnabled() ||
121-
!instrumentation.shouldCreateSpans()
122-
) {
123-
return original.call(this, ...args);
124-
}
125-
126-
const parent = context.active();
127-
const span = instrumentation.tracer.startSpan(
128-
instrumentation.getSpanName(inst, 'batch'),
129-
{ links: this._batch?.spanLinks as Link[] | undefined },
130-
parent
131-
);
132-
133-
return context.with(trace.setSpan(parent, span), () => {
134-
return (original.apply(this, args) as Promise<unknown[]>)
135-
.then(value => {
136-
span.end();
137-
return value;
138-
})
139-
.catch(err => {
140-
span.recordException(err);
141-
span.setStatus({
142-
code: SpanStatusCode.ERROR,
143-
message: err.message,
144-
});
145-
span.end();
146-
throw err;
147-
});
148-
});
149-
};
150-
});
151-
152-
return inst;
163+
return constructor.apply(this, args);
153164
}
154165

155166
PatchedDataloader.prototype = prototype;

plugins/node/instrumentation-dataloader/test/dataloader.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,4 +619,25 @@ describe('DataloaderInstrumentation', () => {
619619
assert.deepStrictEqual(await alternativeDataloader.loadMany(['test']), [1]);
620620
assert.strictEqual(memoryExporter.getFinishedSpans().length, 5);
621621
});
622+
623+
it('should not prune custom methods', async () => {
624+
class CustomDataLoader extends Dataloader<string, string> {
625+
constructor() {
626+
super(async keys => keys.map((_, idx) => getMd5HashFromIdx(idx)));
627+
}
628+
629+
public async customLoad() {
630+
return this.load('test');
631+
}
632+
}
633+
634+
const customDataloader = new CustomDataLoader();
635+
await customDataloader.customLoad();
636+
637+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 2);
638+
const [batchSpan, loadSpan] = memoryExporter.getFinishedSpans();
639+
640+
assert.strictEqual(loadSpan.name, 'dataloader.load');
641+
assert.strictEqual(batchSpan.name, 'dataloader.batch');
642+
});
622643
});

0 commit comments

Comments
 (0)