Skip to content

Commit a247f50

Browse files
committed
feat(node): Add dataloader instrumentation
1 parent bcf571d commit a247f50

File tree

8 files changed

+150
-56
lines changed

8 files changed

+150
-56
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/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
@@ -27,6 +27,7 @@ export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa
2727
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
2828
export { spotlightIntegration } from './integrations/spotlight';
2929
export { genericPoolIntegration } from './integrations/tracing/genericPool';
30+
export { dataloaderIntegration } from './integrations/tracing/dataloader';
3031

3132
export { SentryContextManager } from './otel/contextManager';
3233
export { generateInstrumentOnce } from './otel/instrument';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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(INTEGRATION_NAME, () => new DataloaderInstrumentation({}));
14+
15+
const _dataloaderIntegration = (() => {
16+
return {
17+
name: INTEGRATION_NAME,
18+
setupOnce() {
19+
instrumentDataloader();
20+
},
21+
22+
setup(client) {
23+
client.on('spanStart', span => {
24+
const spanJSON = spanToJSON(span);
25+
if (spanJSON.description?.startsWith('dataloader')) {
26+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.dataloader');
27+
}
28+
29+
// These are all possible dataloader span descriptions
30+
// Still checking for the future versions
31+
// in case they add support for `clear` and `prime`
32+
if (
33+
spanJSON.description === 'dataloader.load' ||
34+
spanJSON.description === 'dataloader.loadMany' ||
35+
spanJSON.description === 'dataloader.batch'
36+
) {
37+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'cache.get');
38+
// TODO: We can try adding `key` to the `data` attribute upstream.
39+
// Or alternatively, we can add `requestHook` to the dataloader instrumentation.
40+
}
41+
});
42+
},
43+
};
44+
}) satisfies IntegrationFn;
45+
46+
/**
47+
* Dataloader integration
48+
*
49+
* Capture tracing data for Dataloader.
50+
*/
51+
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';
@@ -39,6 +40,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
3940
koaIntegration(),
4041
connectIntegration(),
4142
genericPoolIntegration(),
43+
dataloaderIntegration(),
4244
];
4345
}
4446

@@ -64,5 +66,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
6466
instrumentGraphql,
6567
instrumentRedis,
6668
instrumentGenericPool,
69+
instrumentDataloader,
6770
];
6871
}

yarn.lock

+20-56
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"
@@ -9668,17 +9675,8 @@
96689675
dependencies:
96699676
"@types/unist" "*"
96709677

9671-
"@types/history-4@npm:@types/[email protected]":
9672-
version "4.7.8"
9673-
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
9674-
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
9675-
9676-
"@types/history-5@npm:@types/[email protected]":
9677-
version "4.7.8"
9678-
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
9679-
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
9680-
9681-
"@types/history@*":
9678+
"@types/history-4@npm:@types/[email protected]", "@types/history-5@npm:@types/[email protected]", "@types/history@*":
9679+
name "@types/history-4"
96829680
version "4.7.8"
96839681
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
96849682
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
@@ -10006,15 +10004,7 @@
1000610004
"@types/history" "^3"
1000710005
"@types/react" "*"
1000810006

10009-
"@types/react-router-4@npm:@types/[email protected]":
10010-
version "5.1.14"
10011-
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da"
10012-
integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw==
10013-
dependencies:
10014-
"@types/history" "*"
10015-
"@types/react" "*"
10016-
10017-
"@types/react-router-5@npm:@types/[email protected]":
10007+
"@types/react-router-4@npm:@types/[email protected]", "@types/react-router-5@npm:@types/[email protected]":
1001810008
version "5.1.14"
1001910009
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da"
1002010010
integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw==
@@ -15227,6 +15217,11 @@ data-urls@^4.0.0:
1522715217
whatwg-mimetype "^3.0.0"
1522815218
whatwg-url "^12.0.0"
1522915219

15220+
15221+
version "2.2.2"
15222+
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
15223+
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==
15224+
1523015225
date-fns@^2.29.2:
1523115226
version "2.29.3"
1523215227
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
@@ -28417,7 +28412,8 @@ react-is@^18.0.0:
2841728412
dependencies:
2841828413
"@remix-run/router" "1.0.2"
2841928414

28420-
"react-router-6@npm:[email protected]":
28415+
"react-router-6@npm:[email protected]", [email protected]:
28416+
name react-router-6
2842128417
version "6.3.0"
2842228418
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
2842328419
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
@@ -28432,13 +28428,6 @@ react-router-dom@^6.2.2:
2843228428
history "^5.2.0"
2843328429
react-router "6.3.0"
2843428430

28435-
28436-
version "6.3.0"
28437-
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
28438-
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
28439-
dependencies:
28440-
history "^5.2.0"
28441-
2844228431
react@^18.0.0:
2844328432
version "18.0.0"
2844428433
resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96"
@@ -30917,16 +30906,7 @@ string-template@~0.2.1:
3091730906
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
3091830907
integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
3091930908

30920-
"string-width-cjs@npm:string-width@^4.2.0":
30921-
version "4.2.3"
30922-
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
30923-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
30924-
dependencies:
30925-
emoji-regex "^8.0.0"
30926-
is-fullwidth-code-point "^3.0.0"
30927-
strip-ansi "^6.0.1"
30928-
30929-
[email protected], string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
30909+
"string-width-cjs@npm:string-width@^4.2.0", [email protected], string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
3093030910
version "4.2.3"
3093130911
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
3093230912
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -31038,14 +31018,7 @@ stringify-object@^3.2.1:
3103831018
is-obj "^1.0.1"
3103931019
is-regexp "^1.0.0"
3104031020

31041-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
31042-
version "6.0.1"
31043-
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
31044-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
31045-
dependencies:
31046-
ansi-regex "^5.0.1"
31047-
31048-
[email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
31021+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", [email protected], strip-ansi@^6.0.0, strip-ansi@^6.0.1:
3104931022
version "6.0.1"
3105031023
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
3105131024
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -34014,16 +33987,7 @@ wrangler@^3.67.1:
3401433987
optionalDependencies:
3401533988
fsevents "~2.3.2"
3401633989

34017-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
34018-
version "7.0.0"
34019-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
34020-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
34021-
dependencies:
34022-
ansi-styles "^4.0.0"
34023-
string-width "^4.1.0"
34024-
strip-ansi "^6.0.0"
34025-
34026-
[email protected], wrap-ansi@^7.0.0:
33990+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", [email protected], wrap-ansi@^7.0.0:
3402733991
version "7.0.0"
3402833992
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
3402933993
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==

0 commit comments

Comments
 (0)