Skip to content

Commit

Permalink
feat(node): Add knex integration (#13526)
Browse files Browse the repository at this point in the history
Implement Knex OTEL instrumentation in `packages/node`.

This integration is not enabled by default.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Co-authored-by: Abhijeet Prasad <aprasad@sentry.io>
  • Loading branch information
Zen-cronic and AbhiPrasad authored Nov 11, 2024
1 parent 68781c5 commit 1f56ffa
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 8 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 @@ -50,6 +50,7 @@
"graphql": "^16.3.0",
"http-terminator": "^3.2.0",
"ioredis": "^5.4.1",
"knex": "^2.5.1",
"kafkajs": "2.2.4",
"lru-memoizer": "2.3.0",
"mongodb": "^3.7.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: '3.9'

services:
db_postgres:
image: postgres:13
restart: always
container_name: integration-tests-knex-postgres
ports:
- '5445:5432'
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: tests

db_mysql2:
image: mysql:8
restart: always
container_name: integration-tests-knex-mysql2
ports:
- '3307:3306'
environment:
MYSQL_ROOT_PASSWORD: docker
MYSQL_DATABASE: tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { loggingTransport } = 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,
integrations: [Sentry.knexIntegration()],
});

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

const knex = require('knex').default;

const mysql2Client = knex({
client: 'mysql2',
connection: {
host: 'localhost',
port: 3307,
user: 'root',
password: 'docker',
database: 'tests',
},
});

async function run() {
await Sentry.startSpan(
{
name: 'Test Transaction',
op: 'transaction',
},
async () => {
try {
await mysql2Client.schema.createTable('User', table => {
table.increments('id').notNullable().primary({ constraintName: 'User_pkey' });
table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(mysql2Client.fn.now(3));
table.text('email').notNullable();
table.text('name').notNullable();
});

await mysql2Client('User').insert({ name: 'jane', email: 'jane@domain.com' });
await mysql2Client('User').select('*');
} finally {
await mysql2Client.destroy();
}
},
);
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { loggingTransport } = 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,
integrations: [Sentry.knexIntegration()],
});

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

const knex = require('knex').default;

const pgClient = knex({
client: 'pg',
connection: {
host: 'localhost',
port: 5445,
user: 'test',
password: 'test',
database: 'tests',
},
});

async function run() {
await Sentry.startSpan(
{
name: 'Test Transaction',
op: 'transaction',
},
async () => {
try {
await pgClient.schema.createTable('User', table => {
table.increments('id').notNullable().primary({ constraintName: 'User_pkey' });
table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(pgClient.fn.now(3));
table.text('email').notNullable();
table.text('name').notNullable();
});

await pgClient('User').insert({ name: 'bob', email: 'bob@domain.com' });
await pgClient('User').select('*');
} finally {
await pgClient.destroy();
}
},
);
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();
129 changes: 129 additions & 0 deletions dev-packages/node-integration-tests/suites/tracing/knex/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { createRunner } from '../../../utils/runner';

// When running docker compose, we need a larger timeout, as this takes some time...
jest.setTimeout(90000);

describe('knex auto instrumentation', () => {
// Update this if another knex version is installed
const KNEX_VERSION = '2.5.1';

test('should auto-instrument `knex` package when using `pg` client', done => {
const EXPECTED_TRANSACTION = {
transaction: 'Test Transaction',
spans: expect.arrayContaining([
expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.system': 'postgresql',
'db.name': 'tests',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
'net.peer.name': 'localhost',
'net.peer.port': 5445,
}),
status: 'ok',
description:
'create table "User" ("id" serial primary key, "createdAt" timestamptz(3) not null default CURRENT_TIMESTAMP(3), "email" text not null, "name" text not null)',
origin: 'auto.db.otel.knex',
}),
expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.system': 'postgresql',
'db.name': 'tests',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
'net.peer.name': 'localhost',
'net.peer.port': 5445,
}),
status: 'ok',
// In the knex-otel spans, the placeholders (e.g., `$1`) are replaced by a `?`.
description: 'insert into "User" ("email", "name") values (?, ?)',
origin: 'auto.db.otel.knex',
}),

expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.operation': 'select',
'db.sql.table': 'User',
'db.system': 'postgresql',
'db.name': 'tests',
'db.statement': 'select * from "User"',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
}),
status: 'ok',
description: 'select * from "User"',
origin: 'auto.db.otel.knex',
}),
]),
};

createRunner(__dirname, 'scenario-withPostgres.js')
.withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] })
.expect({ transaction: EXPECTED_TRANSACTION })
.start(done);
});

test('should auto-instrument `knex` package when using `mysql2` client', done => {
const EXPECTED_TRANSACTION = {
transaction: 'Test Transaction',
spans: expect.arrayContaining([
expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.system': 'mysql2',
'db.name': 'tests',
'db.user': 'root',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
'net.peer.name': 'localhost',
'net.peer.port': 3307,
}),
status: 'ok',
description:
'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(3) not null default CURRENT_TIMESTAMP(3), `email` text not null, `name` text not null)',
origin: 'auto.db.otel.knex',
}),
expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.system': 'mysql2',
'db.name': 'tests',
'db.user': 'root',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
'net.peer.name': 'localhost',
'net.peer.port': 3307,
}),
status: 'ok',
description: 'insert into `User` (`email`, `name`) values (?, ?)',
origin: 'auto.db.otel.knex',
}),

expect.objectContaining({
data: expect.objectContaining({
'knex.version': KNEX_VERSION,
'db.operation': 'select',
'db.sql.table': 'User',
'db.system': 'mysql2',
'db.name': 'tests',
'db.statement': 'select * from `User`',
'db.user': 'root',
'sentry.origin': 'auto.db.otel.knex',
'sentry.op': 'db',
}),
status: 'ok',
description: 'select * from `User`',
origin: 'auto.db.otel.knex',
}),
]),
};

createRunner(__dirname, 'scenario-withMysql2.js')
.withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port: 3306'] })
.expect({ transaction: EXPECTED_TRANSACTION })
.start(done);
});
});
1 change: 1 addition & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export {
isInitialized,
kafkaIntegration,
koaIntegration,
knexIntegration,
lastEventId,
linkedErrorsIntegration,
localVariablesIntegration,
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 @@ -92,6 +92,7 @@ export {
fsIntegration,
genericPoolIntegration,
graphqlIntegration,
knexIntegration,
kafkaIntegration,
lruMemoizerIntegration,
mongoIntegration,
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 @@ -113,6 +113,7 @@ export {
setupConnectErrorHandler,
genericPoolIntegration,
graphqlIntegration,
knexIntegration,
kafkaIntegration,
lruMemoizerIntegration,
mongoIntegration,
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 @@ -92,6 +92,7 @@ export {
fastifyIntegration,
genericPoolIntegration,
graphqlIntegration,
knexIntegration,
kafkaIntegration,
lruMemoizerIntegration,
mongoIntegration,
Expand Down
9 changes: 3 additions & 6 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
"engines": {
"node": ">=14.18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/types/index.d.ts",
Expand Down Expand Up @@ -56,9 +54,7 @@
},
"typesVersions": {
"<4.9": {
"build/types/index.d.ts": [
"build/types-ts3.8/index.d.ts"
]
"build/types/index.d.ts": ["build/types-ts3.8/index.d.ts"]
}
},
"publishConfig": {
Expand All @@ -81,6 +77,7 @@
"@opentelemetry/instrumentation-http": "0.53.0",
"@opentelemetry/instrumentation-ioredis": "0.43.0",
"@opentelemetry/instrumentation-kafkajs": "0.4.0",
"@opentelemetry/instrumentation-knex": "0.41.0",
"@opentelemetry/instrumentation-koa": "0.43.0",
"@opentelemetry/instrumentation-lru-memoizer": "0.40.0",
"@opentelemetry/instrumentation-mongodb": "0.48.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 { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h
export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa';
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
export { spotlightIntegration } from './integrations/spotlight';
export { knexIntegration } from './integrations/tracing/knex';
export { tediousIntegration } from './integrations/tracing/tedious';
export { genericPoolIntegration } from './integrations/tracing/genericPool';
export { dataloaderIntegration } from './integrations/tracing/dataloader';
Expand Down
47 changes: 47 additions & 0 deletions packages/node/src/integrations/tracing/knex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
import type { IntegrationFn } from '@sentry/types';
import { generateInstrumentOnce } from '../../otel/instrument';

const INTEGRATION_NAME = 'Knex';

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

const _knexIntegration = (() => {
return {
name: INTEGRATION_NAME,
setupOnce() {
instrumentKnex();
},

setup(client) {
client.on('spanStart', span => {
const { data } = spanToJSON(span);
// knex.version is always set in the span data
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/0309caeafc44ac9cb13a3345b790b01b76d0497d/plugins/node/opentelemetry-instrumentation-knex/src/instrumentation.ts#L138
if (data && 'knex.version' in data) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.knex');
}
});
},
};
}) satisfies IntegrationFn;

/**
* Knex integration
*
* Capture tracing data for [Knex](https://knexjs.org/).
*
* @example
* ```javascript
* import * as Sentry from '@sentry/node';
*
* Sentry.init({
* integrations: [Sentry.knexIntegration()],
* });
* ```
*/
export const knexIntegration = defineIntegration(_knexIntegration);
1 change: 1 addition & 0 deletions packages/remix/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export {
inboundFiltersIntegration,
initOpenTelemetry,
isInitialized,
knexIntegration,
kafkaIntegration,
koaIntegration,
lastEventId,
Expand Down
Loading

0 comments on commit 1f56ffa

Please sign in to comment.