Skip to content

Commit

Permalink
feat(node): Add dataloader integration (#13664)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
onurtemizkan committed Sep 19, 2024
1 parent 37c4c42 commit 2ab7518
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 0 deletions.
1 change: 1 addition & 0 deletions dev-packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"connect": "^3.7.0",
"cors": "^2.8.5",
"cron": "^3.1.6",
"dataloader": "2.2.2",
"express": "^4.17.3",
"generic-pool": "^3.9.0",
"graphql": "^16.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { loggingTransport, startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,
});

const PORT = 8008;

// Stop the process from exiting before the transaction is sent
setInterval(() => {}, 1000);

const run = async () => {
const express = require('express');
const Dataloader = require('dataloader');

const app = express();
const dataloader = new Dataloader(async keys => keys.map((_, idx) => idx), {
cache: false,
});

app.get('/', (req, res) => {
const user = dataloader.load('user-1');
res.send(user);
});

startExpressServerAndSendPortToRunner(app, PORT);
};

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('dataloader auto-instrumentation', () => {
afterAll(async () => {
cleanupChildProcesses();
});

const EXPECTED_TRANSACTION = {
transaction: 'GET /',
spans: expect.arrayContaining([
expect.objectContaining({
data: expect.objectContaining({
'sentry.origin': 'auto.db.otel.dataloader',
'sentry.op': 'cache.get',
}),
description: 'dataloader.load',
origin: 'auto.db.otel.dataloader',
op: 'cache.get',
status: 'ok',
}),
expect.objectContaining({
data: expect.objectContaining({
'sentry.origin': 'auto.db.otel.dataloader',
'sentry.op': 'cache.get',
}),
description: 'dataloader.batch',
origin: 'auto.db.otel.dataloader',
op: 'cache.get',
status: 'ok',
}),
]),
};

test('should auto-instrument `dataloader` package.', done => {
createRunner(__dirname, 'scenario.js')
.expect({ transaction: EXPECTED_TRANSACTION })
.start(done)
.makeRequest('get', '/');
});
});
1 change: 1 addition & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
createGetModuleFromFilename,
createTransport,
cron,
dataloaderIntegration,
debugIntegration,
dedupeIntegration,
DEFAULT_USER_INCLUDES,
Expand Down
1 change: 1 addition & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
dataloaderIntegration,
expressIntegration,
expressErrorHandler,
setupExpressErrorHandler,
Expand Down
1 change: 1 addition & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
dataloaderIntegration,
expressIntegration,
expressErrorHandler,
setupExpressErrorHandler,
Expand Down
1 change: 1 addition & 0 deletions packages/google-cloud-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
dataloaderIntegration,
expressIntegration,
expressErrorHandler,
setupExpressErrorHandler,
Expand Down
1 change: 1 addition & 0 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@opentelemetry/core": "^1.25.1",
"@opentelemetry/instrumentation": "^0.53.0",
"@opentelemetry/instrumentation-connect": "0.39.0",
"@opentelemetry/instrumentation-dataloader": "0.12.0",
"@opentelemetry/instrumentation-express": "0.42.0",
"@opentelemetry/instrumentation-fastify": "0.39.0",
"@opentelemetry/instrumentation-fs": "0.15.0",
Expand Down
1 change: 1 addition & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
export { spotlightIntegration } from './integrations/spotlight';
export { genericPoolIntegration } from './integrations/tracing/genericPool';
export { dataloaderIntegration } from './integrations/tracing/dataloader';

export { SentryContextManager } from './otel/contextManager';
export { generateInstrumentOnce } from './otel/instrument';
Expand Down
57 changes: 57 additions & 0 deletions packages/node/src/integrations/tracing/dataloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { DataloaderInstrumentation } from '@opentelemetry/instrumentation-dataloader';
import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
defineIntegration,
spanToJSON,
} from '@sentry/core';
import type { IntegrationFn } from '@sentry/types';
import { generateInstrumentOnce } from '../../otel/instrument';

const INTEGRATION_NAME = 'Dataloader';

export const instrumentDataloader = generateInstrumentOnce(
INTEGRATION_NAME,
() =>
new DataloaderInstrumentation({
requireParentSpan: true,
}),
);

const _dataloaderIntegration = (() => {
return {
name: INTEGRATION_NAME,
setupOnce() {
instrumentDataloader();
},

setup(client) {
client.on('spanStart', span => {
const spanJSON = spanToJSON(span);
if (spanJSON.description?.startsWith('dataloader')) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.dataloader');
}

// These are all possible dataloader span descriptions
// Still checking for the future versions
// in case they add support for `clear` and `prime`
if (
spanJSON.description === 'dataloader.load' ||
spanJSON.description === 'dataloader.loadMany' ||
spanJSON.description === 'dataloader.batch'
) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'cache.get');
// TODO: We can try adding `key` to the `data` attribute upstream.
// Or alternatively, we can add `requestHook` to the dataloader instrumentation.
}
});
},
};
}) satisfies IntegrationFn;

/**
* Dataloader integration
*
* Capture tracing data for Dataloader.
*/
export const dataloaderIntegration = defineIntegration(_dataloaderIntegration);
3 changes: 3 additions & 0 deletions packages/node/src/integrations/tracing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Integration } from '@sentry/types';
import { instrumentHttp } from '../http';

import { connectIntegration, instrumentConnect } from './connect';
import { dataloaderIntegration, instrumentDataloader } from './dataloader';
import { expressIntegration, instrumentExpress } from './express';
import { fastifyIntegration, instrumentFastify } from './fastify';
import { genericPoolIntegration, instrumentGenericPool } from './genericPool';
Expand Down Expand Up @@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
connectIntegration(),
genericPoolIntegration(),
kafkaIntegration(),
dataloaderIntegration(),
];
}

Expand All @@ -67,5 +69,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
instrumentGraphql,
instrumentRedis,
instrumentGenericPool,
instrumentDataloader,
];
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7101,6 +7101,13 @@
"@opentelemetry/semantic-conventions" "^1.27.0"
"@types/connect" "3.4.36"

"@opentelemetry/instrumentation-dataloader@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz#de03a3948dec4f15fed80aa424d6bd5d6a8d10c7"
integrity sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==
dependencies:
"@opentelemetry/instrumentation" "^0.53.0"

"@opentelemetry/instrumentation-express@0.42.0":
version "0.42.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz#279f195aa66baee2b98623a16666c6229c8e7564"
Expand Down Expand Up @@ -15235,6 +15242,11 @@ data-urls@^4.0.0:
whatwg-mimetype "^3.0.0"
whatwg-url "^12.0.0"

dataloader@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0"
integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==

date-fns@^2.29.2:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
Expand Down

0 comments on commit 2ab7518

Please sign in to comment.