Skip to content

Commit 2ab7518

Browse files
authored
feat(node): Add dataloader integration (#13664)
Adds integration for `dataloader` using [`@opentelemetry/instrumentation-dataloader`](https://www.npmjs.com/package/@opentelemetry/instrumentation-dataloader) on the background. A few notes: - We currently don't have access to the lookup / request as there is no hook from `@opentelemetry/instrumentation-dataloader`. So, we don't have `cache.hit`, `cache.key`, `cache.item_size` and so on, in this integration. I can try to implement those upstream, but if you have another way in mind to access those please let me know. - `@opentelemetry/instrumentation-dataloader` only records spans for `load`, `loadMany` and `batch`, which all are `cache.get` operations. There are also `prime`, `clear`, `clearAll`. We also can implement those upstream and update the integration in future.
1 parent 37c4c42 commit 2ab7518

File tree

12 files changed

+152
-0
lines changed

12 files changed

+152
-0
lines changed

dev-packages/node-integration-tests/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"connect": "^3.7.0",
4444
"cors": "^2.8.5",
4545
"cron": "^3.1.6",
46+
"dataloader": "2.2.2",
4647
"express": "^4.17.3",
4748
"generic-pool": "^3.9.0",
4849
"graphql": "^16.3.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const { loggingTransport, startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
10+
11+
const PORT = 8008;
12+
13+
// Stop the process from exiting before the transaction is sent
14+
setInterval(() => {}, 1000);
15+
16+
const run = async () => {
17+
const express = require('express');
18+
const Dataloader = require('dataloader');
19+
20+
const app = express();
21+
const dataloader = new Dataloader(async keys => keys.map((_, idx) => idx), {
22+
cache: false,
23+
});
24+
25+
app.get('/', (req, res) => {
26+
const user = dataloader.load('user-1');
27+
res.send(user);
28+
});
29+
30+
startExpressServerAndSendPortToRunner(app, PORT);
31+
};
32+
33+
run();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
2+
3+
describe('dataloader auto-instrumentation', () => {
4+
afterAll(async () => {
5+
cleanupChildProcesses();
6+
});
7+
8+
const EXPECTED_TRANSACTION = {
9+
transaction: 'GET /',
10+
spans: expect.arrayContaining([
11+
expect.objectContaining({
12+
data: expect.objectContaining({
13+
'sentry.origin': 'auto.db.otel.dataloader',
14+
'sentry.op': 'cache.get',
15+
}),
16+
description: 'dataloader.load',
17+
origin: 'auto.db.otel.dataloader',
18+
op: 'cache.get',
19+
status: 'ok',
20+
}),
21+
expect.objectContaining({
22+
data: expect.objectContaining({
23+
'sentry.origin': 'auto.db.otel.dataloader',
24+
'sentry.op': 'cache.get',
25+
}),
26+
description: 'dataloader.batch',
27+
origin: 'auto.db.otel.dataloader',
28+
op: 'cache.get',
29+
status: 'ok',
30+
}),
31+
]),
32+
};
33+
34+
test('should auto-instrument `dataloader` package.', done => {
35+
createRunner(__dirname, 'scenario.js')
36+
.expect({ transaction: EXPECTED_TRANSACTION })
37+
.start(done)
38+
.makeRequest('get', '/');
39+
});
40+
});

packages/astro/src/index.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export {
2929
createGetModuleFromFilename,
3030
createTransport,
3131
cron,
32+
dataloaderIntegration,
3233
debugIntegration,
3334
dedupeIntegration,
3435
DEFAULT_USER_INCLUDES,

packages/aws-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export {
7878
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
7979
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
8080
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
81+
dataloaderIntegration,
8182
expressIntegration,
8283
expressErrorHandler,
8384
setupExpressErrorHandler,

packages/bun/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export {
9999
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
100100
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
101101
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
102+
dataloaderIntegration,
102103
expressIntegration,
103104
expressErrorHandler,
104105
setupExpressErrorHandler,

packages/google-cloud-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export {
7979
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
8080
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
8181
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
82+
dataloaderIntegration,
8283
expressIntegration,
8384
expressErrorHandler,
8485
setupExpressErrorHandler,

packages/node/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"@opentelemetry/core": "^1.25.1",
7171
"@opentelemetry/instrumentation": "^0.53.0",
7272
"@opentelemetry/instrumentation-connect": "0.39.0",
73+
"@opentelemetry/instrumentation-dataloader": "0.12.0",
7374
"@opentelemetry/instrumentation-express": "0.42.0",
7475
"@opentelemetry/instrumentation-fastify": "0.39.0",
7576
"@opentelemetry/instrumentation-fs": "0.15.0",

packages/node/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa
2828
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
2929
export { spotlightIntegration } from './integrations/spotlight';
3030
export { genericPoolIntegration } from './integrations/tracing/genericPool';
31+
export { dataloaderIntegration } from './integrations/tracing/dataloader';
3132

3233
export { SentryContextManager } from './otel/contextManager';
3334
export { generateInstrumentOnce } from './otel/instrument';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { DataloaderInstrumentation } from '@opentelemetry/instrumentation-dataloader';
2+
import {
3+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
4+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
5+
defineIntegration,
6+
spanToJSON,
7+
} from '@sentry/core';
8+
import type { IntegrationFn } from '@sentry/types';
9+
import { generateInstrumentOnce } from '../../otel/instrument';
10+
11+
const INTEGRATION_NAME = 'Dataloader';
12+
13+
export const instrumentDataloader = generateInstrumentOnce(
14+
INTEGRATION_NAME,
15+
() =>
16+
new DataloaderInstrumentation({
17+
requireParentSpan: true,
18+
}),
19+
);
20+
21+
const _dataloaderIntegration = (() => {
22+
return {
23+
name: INTEGRATION_NAME,
24+
setupOnce() {
25+
instrumentDataloader();
26+
},
27+
28+
setup(client) {
29+
client.on('spanStart', span => {
30+
const spanJSON = spanToJSON(span);
31+
if (spanJSON.description?.startsWith('dataloader')) {
32+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.dataloader');
33+
}
34+
35+
// These are all possible dataloader span descriptions
36+
// Still checking for the future versions
37+
// in case they add support for `clear` and `prime`
38+
if (
39+
spanJSON.description === 'dataloader.load' ||
40+
spanJSON.description === 'dataloader.loadMany' ||
41+
spanJSON.description === 'dataloader.batch'
42+
) {
43+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'cache.get');
44+
// TODO: We can try adding `key` to the `data` attribute upstream.
45+
// Or alternatively, we can add `requestHook` to the dataloader instrumentation.
46+
}
47+
});
48+
},
49+
};
50+
}) satisfies IntegrationFn;
51+
52+
/**
53+
* Dataloader integration
54+
*
55+
* Capture tracing data for Dataloader.
56+
*/
57+
export const dataloaderIntegration = defineIntegration(_dataloaderIntegration);

packages/node/src/integrations/tracing/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Integration } from '@sentry/types';
22
import { instrumentHttp } from '../http';
33

44
import { connectIntegration, instrumentConnect } from './connect';
5+
import { dataloaderIntegration, instrumentDataloader } from './dataloader';
56
import { expressIntegration, instrumentExpress } from './express';
67
import { fastifyIntegration, instrumentFastify } from './fastify';
78
import { genericPoolIntegration, instrumentGenericPool } from './genericPool';
@@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
4142
connectIntegration(),
4243
genericPoolIntegration(),
4344
kafkaIntegration(),
45+
dataloaderIntegration(),
4446
];
4547
}
4648

@@ -67,5 +69,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
6769
instrumentGraphql,
6870
instrumentRedis,
6971
instrumentGenericPool,
72+
instrumentDataloader,
7073
];
7174
}

yarn.lock

+12
Original file line numberDiff line numberDiff line change
@@ -7101,6 +7101,13 @@
71017101
"@opentelemetry/semantic-conventions" "^1.27.0"
71027102
"@types/connect" "3.4.36"
71037103

7104+
"@opentelemetry/[email protected]":
7105+
version "0.12.0"
7106+
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz#de03a3948dec4f15fed80aa424d6bd5d6a8d10c7"
7107+
integrity sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==
7108+
dependencies:
7109+
"@opentelemetry/instrumentation" "^0.53.0"
7110+
71047111
"@opentelemetry/[email protected]":
71057112
version "0.42.0"
71067113
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz#279f195aa66baee2b98623a16666c6229c8e7564"
@@ -15235,6 +15242,11 @@ data-urls@^4.0.0:
1523515242
whatwg-mimetype "^3.0.0"
1523615243
whatwg-url "^12.0.0"
1523715244

15245+
15246+
version "2.2.2"
15247+
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
15248+
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
15249+
1523815250
date-fns@^2.29.2:
1523915251
version "2.29.3"
1524015252
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"

0 commit comments

Comments
 (0)