Skip to content

Commit a92dd07

Browse files
committed
feat(instrumentation-dataloader): Enhance dataloader instrumentation.
1 parent c2ad0af commit a92dd07

File tree

2 files changed

+506
-32
lines changed

2 files changed

+506
-32
lines changed

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

Lines changed: 148 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ const MODULE_NAME = 'dataloader';
3636
type DataloaderInternal = typeof Dataloader.prototype & {
3737
_batchLoadFn: Dataloader.BatchLoadFn<unknown, unknown>;
3838
_batch: { spanLinks?: Link[] } | null;
39-
40-
// TODO: Remove this once types on Dataloader get fixed https://github.com/graphql/dataloader/pull/334
41-
name?: string | null;
4239
};
4340

4441
type LoadFn = (typeof Dataloader.prototype)['load'];
4542
type LoadManyFn = (typeof Dataloader.prototype)['loadMany'];
43+
type PrimeFn = (typeof Dataloader.prototype)['prime'];
44+
type ClearFn = (typeof Dataloader.prototype)['clear'];
45+
type ClearAllFn = (typeof Dataloader.prototype)['clearAll'];
4646

4747
export class DataloaderInstrumentation extends InstrumentationBase<DataloaderInstrumentationConfig> {
4848
constructor(config: DataloaderInstrumentationConfig = {}) {
@@ -57,17 +57,18 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
5757
dataloader => {
5858
this._patchLoad(dataloader.prototype);
5959
this._patchLoadMany(dataloader.prototype);
60+
this._patchPrime(dataloader.prototype);
61+
this._patchClear(dataloader.prototype);
62+
this._patchClearAll(dataloader.prototype);
6063

6164
return this._getPatchedConstructor(dataloader);
6265
},
6366
dataloader => {
64-
if (isWrapped(dataloader.prototype.load)) {
65-
this._unwrap(dataloader.prototype, 'load');
66-
}
67-
68-
if (isWrapped(dataloader.prototype.loadMany)) {
69-
this._unwrap(dataloader.prototype, 'loadMany');
70-
}
67+
['load', 'loadMany', 'prime', 'clear', 'clearAll'].forEach(method => {
68+
if (isWrapped(dataloader.prototype[method])) {
69+
this._unwrap(dataloader.prototype, method);
70+
}
71+
});
7172
}
7273
) as InstrumentationNodeModuleDefinition,
7374
];
@@ -81,7 +82,7 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
8182

8283
private getSpanName(
8384
dataloader: DataloaderInternal,
84-
operation: 'load' | 'loadMany' | 'batch'
85+
operation: 'load' | 'loadMany' | 'batch' | 'prime' | 'clear' | 'clearAll'
8586
): string {
8687
const dataloaderName = dataloader.name;
8788
if (dataloaderName === undefined || dataloaderName === null) {
@@ -185,6 +186,13 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
185186
const result = original
186187
.call(this, ...args)
187188
.then(value => {
189+
span.setAttribute('cache.key', [args[0]]);
190+
span.setAttribute('cache.hit', value !== undefined);
191+
span.setAttribute(
192+
'cache.item_size',
193+
value ? JSON.stringify(value).length : 0
194+
);
195+
188196
span.end();
189197
return value;
190198
})
@@ -243,10 +251,139 @@ export class DataloaderInstrumentation extends InstrumentationBase<DataloaderIns
243251
// .loadMany never rejects, as errors from internal .load
244252
// calls are caught by dataloader lib
245253
return original.call(this, ...args).then(value => {
254+
span.setAttribute('cache.key', Array.from(args[0]));
255+
span.setAttribute(
256+
'cache.hit',
257+
value.every((v: unknown) => v !== undefined)
258+
);
259+
span.setAttribute(
260+
'cache.item_size',
261+
value.reduce(
262+
(acc: number, v: unknown) => acc + JSON.stringify(v).length,
263+
0
264+
)
265+
);
266+
246267
span.end();
247268
return value;
248269
});
249270
});
250271
};
251272
}
273+
274+
private _patchPrime(proto: typeof Dataloader.prototype) {
275+
if (isWrapped(proto.prime)) {
276+
this._unwrap(proto, 'prime');
277+
}
278+
279+
this._wrap(proto, 'prime', this._getPatchedPrime.bind(this));
280+
}
281+
282+
private _getPatchedPrime(original: PrimeFn): PrimeFn {
283+
const instrumentation = this;
284+
285+
return function patchedPrime(
286+
this: DataloaderInternal,
287+
...args: Parameters<typeof original>
288+
) {
289+
if (!instrumentation.shouldCreateSpans()) {
290+
return original.call(this, ...args);
291+
}
292+
293+
const parent = context.active();
294+
const span = instrumentation.tracer.startSpan(
295+
instrumentation.getSpanName(this, 'prime'),
296+
{ kind: SpanKind.CLIENT },
297+
parent
298+
);
299+
300+
const ret = context.with(trace.setSpan(parent, span), () => {
301+
return original.call(this, ...args);
302+
});
303+
304+
span.setAttribute('cache.key', [args[0]]);
305+
span.setAttribute(
306+
'cache.item_size',
307+
args[1] ? JSON.stringify(args[1]).length : 0
308+
);
309+
310+
span.end();
311+
312+
return ret;
313+
};
314+
}
315+
316+
private _patchClear(proto: typeof Dataloader.prototype) {
317+
if (isWrapped(proto.clear)) {
318+
this._unwrap(proto, 'clear');
319+
}
320+
321+
this._wrap(proto, 'clear', this._getPatchedClear.bind(this));
322+
}
323+
324+
private _getPatchedClear(original: ClearFn): ClearFn {
325+
const instrumentation = this;
326+
327+
return function patchedClear(
328+
this: DataloaderInternal,
329+
...args: Parameters<typeof original>
330+
) {
331+
if (!instrumentation.shouldCreateSpans()) {
332+
return original.call(this, ...args);
333+
}
334+
335+
const parent = context.active();
336+
const span = instrumentation.tracer.startSpan(
337+
instrumentation.getSpanName(this, 'clear'),
338+
{ kind: SpanKind.CLIENT },
339+
parent
340+
);
341+
342+
const ret = context.with(trace.setSpan(parent, span), () => {
343+
span.setAttribute('cache.key', [args[0]]);
344+
345+
return original.call(this, ...args);
346+
});
347+
348+
span.end();
349+
350+
return ret;
351+
};
352+
}
353+
354+
private _patchClearAll(proto: typeof Dataloader.prototype) {
355+
if (isWrapped(proto.clearAll)) {
356+
this._unwrap(proto, 'clearAll');
357+
}
358+
359+
this._wrap(proto, 'clearAll', this._getPatchedClearAll.bind(this));
360+
}
361+
362+
private _getPatchedClearAll(original: ClearAllFn): ClearAllFn {
363+
const instrumentation = this;
364+
365+
return function patchedClearAll(
366+
this: DataloaderInternal,
367+
...args: Parameters<typeof original>
368+
) {
369+
if (!instrumentation.shouldCreateSpans()) {
370+
return original.call(this, ...args);
371+
}
372+
373+
const parent = context.active();
374+
const span = instrumentation.tracer.startSpan(
375+
instrumentation.getSpanName(this, 'clearAll'),
376+
{ kind: SpanKind.CLIENT },
377+
parent
378+
);
379+
380+
const ret = context.with(trace.setSpan(parent, span), () => {
381+
return original.call(this, ...args);
382+
});
383+
384+
span.end();
385+
386+
return ret;
387+
};
388+
}
252389
}

0 commit comments

Comments
 (0)