Skip to content

Commit 6110fa8

Browse files
committed
fix(instrumentation-dataloder): Patch batchLoadFn without creating an instance
1 parent d056d21 commit 6110fa8

File tree

2 files changed

+83
-47
lines changed

2 files changed

+83
-47
lines changed

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

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -90,64 +90,74 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
9090
return `${MODULE_NAME}.${operation} ${dataloaderName}`;
9191
}
9292

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

99148
function PatchedDataloader(
149+
this: DataloaderInternal,
100150
...args: ConstructorParameters<typeof constructor>
101151
) {
102-
const inst = new constructor(...args) as DataloaderInternal;
103-
104-
if (!instrumentation.isEnabled()) {
105-
return inst;
106-
}
107-
108-
if (isWrapped(inst._batchLoadFn)) {
109-
instrumentation._unwrap(inst, '_batchLoadFn');
152+
if (isWrapped(args[0])) {
153+
instrumentation._unwrap(args, 0);
110154
}
111155

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

150-
return inst;
160+
return constructor.apply(this, args);
151161
}
152162

153163
PatchedDataloader.prototype = prototype;

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,26 @@ extraInstrumentation.disable();
3131

3232
import * as assert from 'assert';
3333
import * as Dataloader from 'dataloader';
34+
import * as crypto from 'crypto';
35+
36+
const getMd5HashFromIdx = (idx: number) =>
37+
crypto.createHash('md5').update(String(idx)).digest('hex');
38+
3439

3540
describe('DataloaderInstrumentation', () => {
3641
let dataloader: Dataloader<string, number>;
3742
let contextManager: AsyncHooksContextManager;
3843

44+
class CustomDataLoader extends Dataloader<string, string> {
45+
constructor() {
46+
super(async keys => keys.map((_, idx) => getMd5HashFromIdx(idx)));
47+
}
48+
49+
public async customLoad() {
50+
return this.load('test');
51+
}
52+
}
53+
3954
const memoryExporter = new InMemorySpanExporter();
4055
const provider = new NodeTracerProvider();
4156
const tracer = provider.getTracer('default');
@@ -334,4 +349,15 @@ describe('DataloaderInstrumentation', () => {
334349
assert.deepStrictEqual(await alternativeDataloader.loadMany(['test']), [1]);
335350
assert.strictEqual(memoryExporter.getFinishedSpans().length, 5);
336351
});
352+
353+
it('should not prune custom methods', async () => {
354+
const customDataloader = new CustomDataLoader();
355+
await customDataloader.customLoad();
356+
357+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 2);
358+
const [batchSpan, loadSpan] = memoryExporter.getFinishedSpans();
359+
360+
assert.strictEqual(loadSpan.name, 'dataloader.load');
361+
assert.strictEqual(batchSpan.name, 'dataloader.batch');
362+
});
337363
});

0 commit comments

Comments
 (0)