diff --git a/packages/node-integration-tests/package.json b/packages/node-integration-tests/package.json index f1661102a963..e63a6e940dca 100644 --- a/packages/node-integration-tests/package.json +++ b/packages/node-integration-tests/package.json @@ -9,6 +9,7 @@ "scripts": { "clean": "rimraf -g **/node_modules", "prisma:init": "(cd suites/tracing/prisma-orm && ts-node ./setup.ts)", + "prisma:init:new": "(cd suites/tracing-new/prisma-orm && ts-node ./setup.ts)", "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --format stylish", "lint:prettier": "prettier --check \"{suites,utils}/**/*.ts\"", @@ -16,7 +17,7 @@ "fix:eslint": "eslint . --format stylish --fix", "fix:prettier": "prettier --write \"{suites,utils}/**/*.ts\"", "type-check": "tsc", - "pretest": "run-s --silent prisma:init", + "pretest": "run-s --silent prisma:init prisma:init:new", "test": "ts-node ./utils/run-tests.ts", "test:watch": "yarn test --watch" }, diff --git a/packages/node-integration-tests/suites/express/tracing/server.ts b/packages/node-integration-tests/suites/express/tracing/server.ts index faf5a50f95ed..e857621ad22e 100644 --- a/packages/node-integration-tests/suites/express/tracing/server.ts +++ b/packages/node-integration-tests/suites/express/tracing/server.ts @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -8,7 +7,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, }); diff --git a/packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts b/packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts new file mode 100644 index 000000000000..5bd8aa815cbe --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts @@ -0,0 +1,43 @@ +import * as Sentry from '@sentry/node'; +import { ApolloServer, gql } from 'apollo-server'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + integrations: [new Sentry.Integrations.GraphQL(), new Sentry.Integrations.Apollo()], +}); + +const typeDefs = gql` + type Query { + hello: String + } +`; + +const resolvers = { + Query: { + hello: () => { + return 'Hello world!'; + }, + }, +}; + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +const transaction = Sentry.startTransaction({ name: 'test_transaction', op: 'transaction' }); + +Sentry.configureScope(scope => { + scope.setSpan(transaction); +}); + +void (async () => { + // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation + await server.executeOperation({ + query: '{hello}', + }); + + transaction.finish(); +})(); diff --git a/packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts b/packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts new file mode 100644 index 000000000000..128f8a2f164b --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts @@ -0,0 +1,35 @@ +import { assertSentryTransaction, conditionalTest, TestEnv } from '../../../utils'; + +// Node 10 is not supported by `graphql-js` +// Ref: https://github.com/graphql/graphql-js/blob/main/package.json +conditionalTest({ min: 12 })('GraphQL/Apollo Tests', () => { + test('should instrument GraphQL and Apollo Server.', async () => { + const env = await TestEnv.init(__dirname); + const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); + + expect(envelope).toHaveLength(3); + + const transaction = envelope[2]; + const parentSpanId = (transaction as any)?.contexts?.trace?.span_id; + const graphqlSpanId = (transaction as any)?.spans?.[0].span_id; + + expect(parentSpanId).toBeDefined(); + expect(graphqlSpanId).toBeDefined(); + + assertSentryTransaction(transaction, { + transaction: 'test_transaction', + spans: [ + { + description: 'execute', + op: 'graphql.execute', + parent_span_id: parentSpanId, + }, + { + description: 'Query.hello', + op: 'graphql.resolve', + parent_span_id: graphqlSpanId, + }, + ], + }); + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts new file mode 100644 index 000000000000..31d7356765e9 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts @@ -0,0 +1,46 @@ +import * as Sentry from '@sentry/node'; +import { MongoClient } from 'mongodb'; + +// suppress logging of the mongo download +global.console.log = () => null; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], +}); + +const client = new MongoClient(process.env.MONGO_URL || '', { + useUnifiedTopology: true, +}); + +async function run(): Promise { + const transaction = Sentry.startTransaction({ + name: 'Test Transaction', + op: 'transaction', + }); + + Sentry.configureScope(scope => { + scope.setSpan(transaction); + }); + + try { + await client.connect(); + + const database = client.db('admin'); + const collection = database.collection('movies'); + + await collection.insertOne({ title: 'Rick and Morty' }); + await collection.findOne({ title: 'Back to the Future' }); + await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); + await collection.findOne({ title: 'South Park' }); + + await collection.find({ title: 'South Park' }).toArray(); + } finally { + if (transaction) transaction.finish(); + await client.close(); + } +} + +void run(); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts new file mode 100644 index 000000000000..5664aac9422b --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts @@ -0,0 +1,85 @@ +import { MongoMemoryServer } from 'mongodb-memory-server-global'; + +import { assertSentryTransaction, conditionalTest, TestEnv } from '../../../../utils'; + +// This test can take longer. +jest.setTimeout(15000); + +conditionalTest({ min: 12 })('MongoDB Test', () => { + let mongoServer: MongoMemoryServer; + + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + process.env.MONGO_URL = mongoServer.getUri(); + }, 10000); + + afterAll(async () => { + if (mongoServer) { + await mongoServer.stop(); + } + }); + + test('should auto-instrument `mongodb` package.', async () => { + const env = await TestEnv.init(__dirname); + const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); + + expect(envelope).toHaveLength(3); + + assertSentryTransaction(envelope[2], { + transaction: 'Test Transaction', + spans: [ + { + data: { + collectionName: 'movies', + dbName: 'admin', + namespace: 'admin.movies', + doc: '{"title":"Rick and Morty"}', + }, + description: 'insertOne', + op: 'db', + }, + { + data: { + collectionName: 'movies', + dbName: 'admin', + namespace: 'admin.movies', + query: '{"title":"Back to the Future"}', + }, + description: 'findOne', + op: 'db', + }, + { + data: { + collectionName: 'movies', + dbName: 'admin', + namespace: 'admin.movies', + filter: '{"title":"Back to the Future"}', + update: '{"$set":{"title":"South Park"}}', + }, + description: 'updateOne', + op: 'db', + }, + { + data: { + collectionName: 'movies', + dbName: 'admin', + namespace: 'admin.movies', + query: '{"title":"South Park"}', + }, + description: 'findOne', + op: 'db', + }, + { + data: { + collectionName: 'movies', + dbName: 'admin', + namespace: 'admin.movies', + query: '{"title":"South Park"}', + }, + description: 'find', + op: 'db', + }, + ], + }); + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/scenario.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/scenario.ts new file mode 100644 index 000000000000..0f576cb793aa --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/scenario.ts @@ -0,0 +1,36 @@ +import * as Sentry from '@sentry/node'; +import mysql from 'mysql'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], +}); + +const connection = mysql.createConnection({ + user: 'root', + password: 'docker', +}); + +connection.connect(function (err: unknown) { + if (err) { + return; + } +}); + +const transaction = Sentry.startTransaction({ + op: 'transaction', + name: 'Test Transaction', +}); + +Sentry.configureScope(scope => { + scope.setSpan(transaction); +}); + +connection.query('SELECT 1 + 1 AS solution', function () { + connection.query('SELECT NOW()', ['1', '2'], () => { + if (transaction) transaction.finish(); + connection.end(); + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/test.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/test.ts new file mode 100644 index 000000000000..3b96f2cafec0 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/test.ts @@ -0,0 +1,23 @@ +import { assertSentryTransaction, TestEnv } from '../../../../utils'; + +test('should auto-instrument `mysql` package.', async () => { + const env = await TestEnv.init(__dirname); + const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); + + expect(envelope).toHaveLength(3); + + assertSentryTransaction(envelope[2], { + transaction: 'Test Transaction', + spans: [ + { + description: 'SELECT 1 + 1 AS solution', + op: 'db', + }, + + { + description: 'SELECT NOW()', + op: 'db', + }, + ], + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts new file mode 100644 index 000000000000..a7859fd562a3 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts @@ -0,0 +1,25 @@ +import * as Sentry from '@sentry/node'; +import pg from 'pg'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], +}); + +const transaction = Sentry.startTransaction({ + op: 'transaction', + name: 'Test Transaction', +}); + +Sentry.configureScope(scope => { + scope.setSpan(transaction); +}); + +const client = new pg.Client(); +client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => + client.query('SELECT * FROM bazz', () => { + client.query('SELECT NOW()', () => transaction.finish()); + }), +); diff --git a/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts b/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts new file mode 100644 index 000000000000..edfa67cee9d7 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts @@ -0,0 +1,54 @@ +import { assertSentryTransaction, TestEnv } from '../../../../utils'; + +class PgClient { + // https://node-postgres.com/api/client#clientquery + public query(_text: unknown, values: unknown, callback?: () => void) { + if (typeof callback === 'function') { + callback(); + return; + } + + if (typeof values === 'function') { + values(); + return; + } + + return Promise.resolve(); + } +} + +beforeAll(() => { + jest.mock('pg', () => { + return { + Client: PgClient, + native: { + Client: PgClient, + }, + }; + }); +}); + +test('should auto-instrument `pg` package.', async () => { + const env = await TestEnv.init(__dirname); + const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); + + expect(envelope).toHaveLength(3); + + assertSentryTransaction(envelope[2], { + transaction: 'Test Transaction', + spans: [ + { + description: 'SELECT * FROM foo where bar ilike "baz%"', + op: 'db', + }, + { + description: 'SELECT * FROM bazz', + op: 'db', + }, + { + description: 'SELECT NOW()', + op: 'db', + }, + ], + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml b/packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml new file mode 100644 index 000000000000..45caa4bb3179 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma + ports: + - '5433:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json b/packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json new file mode 100644 index 000000000000..f8b24d7d0465 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json @@ -0,0 +1,22 @@ +{ + "name": "sentry-prisma-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=12" + }, + "scripts": { + "db-up": "docker-compose up -d", + "generate": "prisma generate", + "migrate": "prisma migrate dev -n sentry-test", + "setup": "run-s --silent db-up generate migrate" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@prisma/client": "3.12.0", + "prisma": "^3.12.0" + } +} diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000000..fbffa92c2bb7 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql new file mode 100644 index 000000000000..8619aaceb2b0 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "email" TEXT NOT NULL, + "name" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma new file mode 100644 index 000000000000..4363c97738ee --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma @@ -0,0 +1,15 @@ +datasource db { + url = "postgresql://prisma:prisma@localhost:5433/tests" + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + email String @unique + name String? +} diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts b/packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts new file mode 100644 index 000000000000..0eb40d9c83ee --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { PrismaClient } from '@prisma/client'; +import * as Sentry from '@sentry/node'; +import { randomBytes } from 'crypto'; + +const client = new PrismaClient(); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + integrations: [new Sentry.Integrations.Prisma({ client })], +}); + +async function run(): Promise { + const transaction = Sentry.startTransaction({ + name: 'Test Transaction', + op: 'transaction', + }); + + Sentry.configureScope(scope => { + scope.setSpan(transaction); + }); + + try { + await client.user.create({ + data: { + name: 'Tilda', + email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, + }, + }); + + await client.user.findMany(); + + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, + }, + }); + } finally { + if (transaction) transaction.finish(); + } +} + +void run(); diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts b/packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts new file mode 100755 index 000000000000..3c40d12f7337 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts @@ -0,0 +1,16 @@ +import { parseSemver } from '@sentry/utils'; +import { execSync } from 'child_process'; + +const NODE_VERSION = parseSemver(process.versions.node); + +if (NODE_VERSION.major && NODE_VERSION.major < 12) { + // eslint-disable-next-line no-console + console.warn(`Skipping Prisma tests on Node: ${NODE_VERSION.major}`); + process.exit(0); +} + +try { + execSync('yarn && yarn setup'); +} catch (_) { + process.exit(1); +} diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts b/packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts new file mode 100644 index 000000000000..e3393f5fe2f8 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts @@ -0,0 +1,17 @@ +import { assertSentryTransaction, conditionalTest, TestEnv } from '../../../utils'; + +conditionalTest({ min: 12 })('Prisma ORM Integration', () => { + test('should instrument Prisma client for tracing.', async () => { + const env = await TestEnv.init(__dirname); + const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); + + assertSentryTransaction(envelope[2], { + transaction: 'Test Transaction', + spans: [ + { description: 'User create', op: 'db.sql.prisma' }, + { description: 'User findMany', op: 'db.sql.prisma' }, + { description: 'User deleteMany', op: 'db.sql.prisma' }, + ], + }); + }); +}); diff --git a/packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock b/packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock new file mode 100644 index 000000000000..d228adebd621 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock @@ -0,0 +1,27 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@3.12.0": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.12.0.tgz#a0eb49ffea5c128dd11dffb896d7139a60073d12" + integrity sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw== + dependencies: + "@prisma/engines-version" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + +"@prisma/engines-version@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": + version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#829ca3d9d0d92555f44644606d4edfd45b2f5886" + integrity sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw== + +"@prisma/engines@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": + version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#e52e364084c4d05278f62768047b788665e64a45" + integrity sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ== + +prisma@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.12.0.tgz#9675e0e72407122759d3eadcb6d27cdccd3497bd" + integrity sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg== + dependencies: + "@prisma/engines" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" diff --git a/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts b/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts new file mode 100644 index 000000000000..a6197e5ab743 --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts @@ -0,0 +1,26 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import * as Sentry from '@sentry/node'; +import * as http from 'http'; + +Sentry.addTracingExtensions(); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + tracePropagationTargets: [/\/v0/, 'v1'], + integrations: [new Sentry.Integrations.Http({ tracing: true })], +}); + +const transaction = Sentry.startTransaction({ name: 'test_transaction' }); + +Sentry.configureScope(scope => { + scope.setSpan(transaction); +}); + +http.get('http://match-this-url.com/api/v0'); +http.get('http://match-this-url.com/api/v1'); +http.get('http://dont-match-this-url.com/api/v2'); +http.get('http://dont-match-this-url.com/api/v3'); + +transaction.finish(); diff --git a/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts b/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts new file mode 100644 index 000000000000..1209c59da46a --- /dev/null +++ b/packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts @@ -0,0 +1,42 @@ +import nock from 'nock'; + +import { runScenario, TestEnv } from '../../../utils'; + +test('HttpIntegration should instrument correct requests when tracePropagationTargets option is provided', async () => { + const match1 = nock('http://match-this-url.com') + .get('/api/v0') + .matchHeader('baggage', val => typeof val === 'string') + .matchHeader('sentry-trace', val => typeof val === 'string') + .reply(200); + + const match2 = nock('http://match-this-url.com') + .get('/api/v1') + .matchHeader('baggage', val => typeof val === 'string') + .matchHeader('sentry-trace', val => typeof val === 'string') + .reply(200); + + const match3 = nock('http://dont-match-this-url.com') + .get('/api/v2') + .matchHeader('baggage', val => val === undefined) + .matchHeader('sentry-trace', val => val === undefined) + .reply(200); + + const match4 = nock('http://dont-match-this-url.com') + .get('/api/v3') + .matchHeader('baggage', val => val === undefined) + .matchHeader('sentry-trace', val => val === undefined) + .reply(200); + + const env = await TestEnv.init(__dirname); + await runScenario(env.url); + + env.server.close(); + nock.cleanAll(); + + await new Promise(resolve => env.server.close(resolve)); + + expect(match1.isDone()).toBe(true); + expect(match2.isDone()).toBe(true); + expect(match3.isDone()).toBe(true); + expect(match4.isDone()).toBe(true); +}); diff --git a/packages/node/package.json b/packages/node/package.json index 7386be3912bb..e29e7290c495 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -19,6 +19,7 @@ "@sentry/core": "7.44.2", "@sentry/types": "7.44.2", "@sentry/utils": "7.44.2", + "@sentry-internal/tracing": "7.44.2", "cookie": "^0.4.1", "https-proxy-agent": "^5.0.0", "lru_map": "^0.3.3", diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index 0b3a925d775e..d0d0ae7424be 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -1,5 +1,5 @@ import type { Scope } from '@sentry/core'; -import { BaseClient, SDK_VERSION, SessionFlusher } from '@sentry/core'; +import { addTracingExtensions, BaseClient, SDK_VERSION, SessionFlusher } from '@sentry/core'; import type { Event, EventHint, Severity, SeverityLevel } from '@sentry/types'; import { logger, resolvedSyncPromise } from '@sentry/utils'; import * as os from 'os'; @@ -40,6 +40,9 @@ export class NodeClient extends BaseClient { ...options.transportOptions, }; + // The Node client always supports tracing + addTracingExtensions(); + super(options); } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 92d3cfcf835f..07db6be2f07d 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -45,6 +45,7 @@ export { setUser, withScope, } from '@sentry/core'; +export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; export { NodeClient } from './client'; export { makeNodeTransport } from './transports'; @@ -57,10 +58,12 @@ import * as domain from 'domain'; import * as Handlers from './handlers'; import * as NodeIntegrations from './integrations'; +import * as TracingIntegrations from './tracing/integrations'; const INTEGRATIONS = { ...CoreIntegrations, ...NodeIntegrations, + ...TracingIntegrations, }; export { INTEGRATIONS as Integrations, Handlers }; diff --git a/packages/node/src/tracing/index.ts b/packages/node/src/tracing/index.ts new file mode 100644 index 000000000000..15c4e2889b3f --- /dev/null +++ b/packages/node/src/tracing/index.ts @@ -0,0 +1,24 @@ +import { lazyLoadedNodePerformanceMonitoringIntegrations } from '@sentry-internal/tracing'; +import type { Integration } from '@sentry/types'; +import { logger } from '@sentry/utils'; + +/** + * Automatically detects and returns integrations that will work with your dependencies. + */ +export function autoDiscoverNodePerformanceMonitoringIntegrations(): Integration[] { + const loadedIntegrations = lazyLoadedNodePerformanceMonitoringIntegrations + .map(tryLoad => { + try { + return tryLoad(); + } catch (_) { + return undefined; + } + }) + .filter(integration => !!integration) as Integration[]; + + if (loadedIntegrations.length === 0) { + logger.warn('Performance monitoring integrations could not be automatically loaded.'); + } + + return loadedIntegrations; +} diff --git a/packages/node/src/tracing/integrations.ts b/packages/node/src/tracing/integrations.ts new file mode 100644 index 000000000000..a37bf6bfd494 --- /dev/null +++ b/packages/node/src/tracing/integrations.ts @@ -0,0 +1 @@ +export { Apollo, Express, GraphQL, Mongo, Mysql, Postgres, Prisma } from '@sentry-internal/tracing'; diff --git a/packages/tracing-internal/src/index.ts b/packages/tracing-internal/src/index.ts index c735942fbe19..c4e17c25294f 100644 --- a/packages/tracing-internal/src/index.ts +++ b/packages/tracing-internal/src/index.ts @@ -1,6 +1,15 @@ export * from './exports'; -export { Apollo, Express, GraphQL, Mongo, Mysql, Postgres, Prisma } from './node/integrations'; +export { + Apollo, + Express, + GraphQL, + Mongo, + Mysql, + Postgres, + Prisma, + lazyLoadedNodePerformanceMonitoringIntegrations, +} from './node'; export { BrowserTracing, diff --git a/packages/tracing-internal/src/node/integrations/index.ts b/packages/tracing-internal/src/node/integrations/index.ts index 607a3e129984..0b69f4440f3a 100644 --- a/packages/tracing-internal/src/node/integrations/index.ts +++ b/packages/tracing-internal/src/node/integrations/index.ts @@ -5,3 +5,4 @@ export { Mongo } from './mongo'; export { Prisma } from './prisma'; export { GraphQL } from './graphql'; export { Apollo } from './apollo'; +export * from './lazy'; diff --git a/packages/tracing-internal/src/node/integrations/lazy.ts b/packages/tracing-internal/src/node/integrations/lazy.ts new file mode 100644 index 000000000000..f53ff756cd48 --- /dev/null +++ b/packages/tracing-internal/src/node/integrations/lazy.ts @@ -0,0 +1,47 @@ +import type { Integration, IntegrationClass } from '@sentry/types'; +import { dynamicRequire } from '@sentry/utils'; + +export const lazyLoadedNodePerformanceMonitoringIntegrations: (() => Integration)[] = [ + () => { + const integration = dynamicRequire(module, './apollo') as { + Apollo: IntegrationClass; + }; + return new integration.Apollo(); + }, + () => { + const integration = dynamicRequire(module, './apollo') as { + Apollo: IntegrationClass; + }; + return new integration.Apollo({ useNestjs: true }); + }, + () => { + const integration = dynamicRequire(module, './graphql') as { + GraphQL: IntegrationClass; + }; + return new integration.GraphQL(); + }, + () => { + const integration = dynamicRequire(module, './mongo') as { + Mongo: IntegrationClass; + }; + return new integration.Mongo(); + }, + () => { + const integration = dynamicRequire(module, './mongo') as { + Mongo: IntegrationClass; + }; + return new integration.Mongo({ mongoose: true }); + }, + () => { + const integration = dynamicRequire(module, './mysql') as { + Mysql: IntegrationClass; + }; + return new integration.Mysql(); + }, + () => { + const integration = dynamicRequire(module, './postgres') as { + Postgres: IntegrationClass; + }; + return new integration.Postgres(); + }, +];