diff --git a/.size-limit.js b/.size-limit.js index 685b40b00fbe..13b963bacd8d 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -217,6 +217,15 @@ module.exports = [ gzip: true, limit: '41 KB', }, + // Node-Core SDK (ESM) + { + name: '@sentry/node-core', + path: 'packages/node-core/build/esm/index.js', + import: createImport('init'), + ignore: [...builtinModules, ...nodePrefixedBuiltinModules], + gzip: true, + limit: '116 KB', + }, // Node SDK (ESM) { name: '@sentry/node', @@ -224,7 +233,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '170 KB', + limit: '144 KB', }, { name: '@sentry/node - without tracing', diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json index e7f854cb7943..cab490640de9 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json @@ -16,11 +16,11 @@ "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.202.0", - "@opentelemetry/instrumentation-http": "^0.202.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-http": "^0.203.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.30.0", + "@opentelemetry/semantic-conventions": "^1.34.0", "@types/express": "^4.17.21", "@types/node": "^18.19.1", "express": "^4.21.2", diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts index 75e718016a90..a403a23bebda 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts @@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'query', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'expressInit', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts index a1b596072a6a..d919c75ea61b 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts @@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'query', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'expressInit', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts index 2f3208a33bbb..6a4283a9caa9 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts @@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'query', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request } data: { 'express.name': 'expressInit', 'express.type': 'middleware', - 'http.route': '/', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 7f9b18b4cc50..4d3ee36c9778 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -66,7 +66,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -85,7 +84,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -144,7 +142,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -163,7 +160,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -192,8 +188,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), - status: 'ok', + status: 'unknown_error', timestamp: expect.any(Number), trace_id: expect.stringMatching(/[a-f0-9]{32}/), + measurements: {}, }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json index c35bcef4da90..92770106970e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/sdk-trace-node": "^1.25.1", + "@opentelemetry/sdk-trace-node": "^2.0.0", "@sentry/node": "latest || *", "@sentry/opentelemetry": "latest || *", "@types/express": "4.17.17", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts index d0aed916864b..de09f0965baa 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts @@ -19,10 +19,9 @@ Sentry.init({ const provider = new NodeTracerProvider({ sampler: new CustomSampler(), + spanProcessors: [new SentrySpanProcessor()], }); -provider.addSpanProcessor(new SentrySpanProcessor()); - provider.register({ propagator: new SentryPropagator(), contextManager: new Sentry.SentryContextManager(), diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts index c3e40d06d6b0..5ca9077634d2 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts @@ -50,7 +50,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -69,7 +68,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json index f5013b83598a..d26dc2db5843 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json @@ -11,8 +11,8 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/sdk-node": "0.52.1", - "@opentelemetry/exporter-trace-otlp-http": "0.52.1", + "@opentelemetry/sdk-node": "0.203.0", + "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts index 0f978f72cf57..3e12007c0d75 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts @@ -74,7 +74,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -93,7 +92,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -152,7 +150,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -171,7 +168,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -200,8 +196,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), - status: 'ok', + status: 'unknown_error', timestamp: expect.any(Number), trace_id: expect.stringMatching(/[a-f0-9]{32}/), + measurements: {}, }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index 4e83198da45c..11b5509fb637 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -11,11 +11,11 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/sdk-trace-node": "1.26.0", - "@opentelemetry/exporter-trace-otlp-http": "0.53.0", - "@opentelemetry/instrumentation-undici": "0.6.0", - "@opentelemetry/instrumentation-http": "0.53.0", - "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/sdk-trace-node": "2.0.0", + "@opentelemetry/exporter-trace-otlp-http": "0.203.0", + "@opentelemetry/instrumentation-undici": "0.13.2", + "@opentelemetry/instrumentation-http": "0.203.0", + "@opentelemetry/instrumentation": "0.203.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts index ebabd499fee5..b833af11ef83 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts @@ -20,15 +20,15 @@ Sentry.init({ }); // Create and configure NodeTracerProvider -const provider = new NodeTracerProvider({}); - -provider.addSpanProcessor( - new BatchSpanProcessor( - new OTLPTraceExporter({ - url: 'http://localhost:3032/', - }), - ), -); +const provider = new NodeTracerProvider({ + spanProcessors: [ + new BatchSpanProcessor( + new OTLPTraceExporter({ + url: 'http://localhost:3032/', + }), + ), + ], +}); // Initialize the provider provider.register({ diff --git a/dev-packages/e2e-tests/test-applications/node-otel/package.json b/dev-packages/e2e-tests/test-applications/node-otel/package.json index af285767a655..1a554ece3bf7 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json @@ -11,8 +11,8 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/sdk-node": "0.52.1", - "@opentelemetry/exporter-trace-otlp-http": "0.52.1", + "@opentelemetry/sdk-node": "0.203.0", + "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts index de68adf681b7..c6abde474439 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts @@ -74,7 +74,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -93,7 +92,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -152,7 +150,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', }, @@ -171,7 +168,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) data: { 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', - 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', }, @@ -200,8 +196,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), - status: 'ok', + status: 'unknown_error', timestamp: expect.any(Number), trace_id: expect.stringMatching(/[a-f0-9]{32}/), + measurements: {}, }); }); diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts index 7562297b2d4d..e9b2c9409154 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts @@ -31,7 +31,11 @@ test.describe('Trace propagation', () => { const clientTx = await clientTxPromise; expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id); + + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); + + expect(requestHandlerSpan).toBeDefined(); + expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); }); test('should not have trace connection for prerendered pages', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts index 7562297b2d4d..e9b2c9409154 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts @@ -31,7 +31,11 @@ test.describe('Trace propagation', () => { const clientTx = await clientTxPromise; expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id); + + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); + + expect(requestHandlerSpan).toBeDefined(); + expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); }); test('should not have trace connection for prerendered pages', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts index 7562297b2d4d..e9b2c9409154 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts @@ -31,7 +31,11 @@ test.describe('Trace propagation', () => { const clientTx = await clientTxPromise; expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id); - expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id); + + const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express'); + + expect(requestHandlerSpan).toBeDefined(); + expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id); }); test('should not have trace connection for prerendered pages', async ({ page }) => { diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json index 1aa77efcf44c..b27f7db9584e 100644 --- a/dev-packages/node-core-integration-tests/package.json +++ b/dev-packages/node-core-integration-tests/package.json @@ -27,12 +27,12 @@ "@nestjs/core": "10.4.6", "@nestjs/platform-express": "10.4.6", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "^0.57.2", - "@opentelemetry/instrumentation-http": "0.57.2", - "@opentelemetry/resources": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-http": "0.203.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@sentry/core": "9.40.0", "@sentry/node-core": "9.40.0", diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts index b131eb421b61..a6fdc2ce88af 100644 --- a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts +++ b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts @@ -14,7 +14,7 @@ const EXPECTED_TRANSCATION = { 'rpc.system': 'aws-api', 'rpc.method': 'PutObject', 'rpc.service': 'S3', - 'aws.region': 'us-east-1', + 'cloud.region': 'us-east-1', 'aws.s3.bucket': 'ot-demo-test', 'otel.kind': 'CLIENT', }), diff --git a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts index 5ed3878572b8..b8be6a003fda 100644 --- a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts @@ -82,6 +82,22 @@ describe('express v5 tracing', () => { .expect({ transaction: { transaction: 'GET /', + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'http.response.status_code': 200, + url: expect.stringMatching(/\/$/), + 'http.method': 'GET', + 'http.url': expect.stringMatching(/\/$/), + 'http.route': '/', + 'http.target': '/', + }, + op: 'http.server', + status: 'ok', + }, + }, }, }) .start(); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/test.ts index f5a772dbf096..4476c76a3933 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts @@ -83,6 +83,22 @@ describe('express tracing', () => { .expect({ transaction: { transaction: 'GET /', + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'http.response.status_code': 200, + url: expect.stringMatching(/\/$/), + 'http.method': 'GET', + 'http.url': expect.stringMatching(/\/$/), + 'http.route': '/', + 'http.target': '/', + }, + op: 'http.server', + status: 'ok', + }, + }, }, }) .start(); @@ -327,8 +343,7 @@ describe('express tracing', () => { const runner = createRunner() .expect({ transaction: { - // TODO(v10): This is incorrect on OpenTelemetry v1 but can be fixed in v2 - transaction: `GET ${status_code === 404 ? '/' : url}`, + transaction: `GET ${url}`, contexts: { trace: { span_id: expect.stringMatching(/[a-f0-9]{16}/), diff --git a/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts index 6e03921c4b73..176d947e1ecf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts @@ -15,14 +15,14 @@ describe('kafkajs', () => { }) .expect({ transaction: { - transaction: 'test-topic', + transaction: 'send test-topic', contexts: { trace: expect.objectContaining({ op: 'message', status: 'ok', data: expect.objectContaining({ 'messaging.system': 'kafka', - 'messaging.destination': 'test-topic', + 'messaging.destination.name': 'test-topic', 'otel.kind': 'PRODUCER', 'sentry.op': 'message', 'sentry.origin': 'auto.kafkajs.otel.producer', @@ -33,14 +33,14 @@ describe('kafkajs', () => { }) .expect({ transaction: { - transaction: 'test-topic', + transaction: 'process test-topic', contexts: { trace: expect.objectContaining({ op: 'message', status: 'ok', data: expect.objectContaining({ 'messaging.system': 'kafka', - 'messaging.destination': 'test-topic', + 'messaging.destination.name': 'test-topic', 'otel.kind': 'CONSUMER', 'sentry.op': 'message', 'sentry.origin': 'auto.kafkajs.otel.consumer', diff --git a/dev-packages/opentelemetry-v2-tests/.eslintrc.js b/dev-packages/opentelemetry-v2-tests/.eslintrc.js deleted file mode 100644 index fdb9952bae52..000000000000 --- a/dev-packages/opentelemetry-v2-tests/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - env: { - node: true, - }, - extends: ['../../.eslintrc.js'], -}; diff --git a/dev-packages/opentelemetry-v2-tests/README.md b/dev-packages/opentelemetry-v2-tests/README.md deleted file mode 100644 index e5ae255c830c..000000000000 --- a/dev-packages/opentelemetry-v2-tests/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# OpenTelemetry v2 Tests - -This package contains tests for `@sentry/opentelemetry` when using OpenTelemetry v2. It is used to ensure compatibility with OpenTelemetry v2 APIs. - -## Running Tests - -To run the tests: - -```bash -yarn test -``` - -## Structure - -The tests are copied from `packages/opentelemetry/test` with adjusted imports to work with OpenTelemetry v2 dependencies. The main differences are: - -1. Uses OpenTelemetry v2 as devDependencies -2. Imports from `@sentry/opentelemetry` instead of relative paths -3. Tests the same functionality but with v2 APIs diff --git a/dev-packages/opentelemetry-v2-tests/package.json b/dev-packages/opentelemetry-v2-tests/package.json deleted file mode 100644 index 5d091acc5674..000000000000 --- a/dev-packages/opentelemetry-v2-tests/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@sentry-internal/opentelemetry-v2-tests", - "version": "9.40.0", - "private": true, - "description": "Tests for @sentry/opentelemetry with OpenTelemetry v2", - "engines": { - "node": ">=18" - }, - "scripts": { - "lint": "eslint . --format stylish", - "fix": "eslint . --format stylish --fix", - "test": "vitest run", - "test:watch": "vitest --watch" - }, - "devDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.0.0", - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/sdk-trace-base": "^2.0.0", - "@opentelemetry/semantic-conventions": "^1.34.0" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts b/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts deleted file mode 100644 index 0df183362633..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts +++ /dev/null @@ -1,442 +0,0 @@ -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import type { Scope } from '@sentry/core'; -import { - getCurrentScope, - getIsolationScope, - Scope as ScopeClass, - setAsyncContextStrategy, - withIsolationScope, - withScope, -} from '@sentry/core'; -import { afterAll, afterEach, beforeEach, describe, expect, it, test } from 'vitest'; -import { setOpenTelemetryContextAsyncContextStrategy } from '../../../packages/opentelemetry/src/asyncContextStrategy'; -import { setupOtel } from './helpers/initOtel'; -import { cleanupOtel } from './helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from './helpers/TestClient'; - -describe('asyncContextStrategy', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - getCurrentScope().clear(); - getIsolationScope().clear(); - - const options = getDefaultTestClientOptions(); - const client = new TestClient(options); - [provider] = setupOtel(client); - setOpenTelemetryContextAsyncContextStrategy(); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - afterAll(() => { - // clear the strategy - setAsyncContextStrategy(undefined); - }); - - test('scope inheritance', () => { - const initialScope = getCurrentScope(); - const initialIsolationScope = getIsolationScope(); - - initialScope.setExtra('a', 'a'); - initialIsolationScope.setExtra('aa', 'aa'); - - withIsolationScope(() => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - scope1.setExtra('b', 'b'); - isolationScope1.setExtra('bb', 'bb'); - - withScope(() => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - scope2.setExtra('c', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b: 'b', - c: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb: 'bb', - }); - }); - }); - }); - - test('async scope inheritance', async () => { - const initialScope = getCurrentScope(); - const initialIsolationScope = getIsolationScope(); - - async function asyncSetExtra(scope: Scope, key: string, value: string): Promise { - await new Promise(resolve => setTimeout(resolve, 1)); - scope.setExtra(key, value); - } - - initialScope.setExtra('a', 'a'); - initialIsolationScope.setExtra('aa', 'aa'); - - await withIsolationScope(async () => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - await asyncSetExtra(scope1, 'b', 'b'); - await asyncSetExtra(isolationScope1, 'bb', 'bb'); - - await withScope(async () => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - await asyncSetExtra(scope2, 'c', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b: 'b', - c: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb: 'bb', - }); - }); - }); - }); - - test('concurrent scope contexts', () => { - const initialScope = getCurrentScope(); - const initialIsolationScope = getIsolationScope(); - - initialScope.setExtra('a', 'a'); - initialIsolationScope.setExtra('aa', 'aa'); - - withIsolationScope(() => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - scope1.setExtra('b', 'b'); - isolationScope1.setExtra('bb', 'bb'); - - withScope(() => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - scope2.setExtra('c', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b: 'b', - c: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb: 'bb', - }); - }); - }); - - withIsolationScope(() => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - scope1.setExtra('b2', 'b'); - isolationScope1.setExtra('bb2', 'bb'); - - withScope(() => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - scope2.setExtra('c2', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b2: 'b', - c2: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb2: 'bb', - }); - }); - }); - }); - - test('concurrent async scope contexts', async () => { - const initialScope = getCurrentScope(); - const initialIsolationScope = getIsolationScope(); - - async function asyncSetExtra(scope: Scope, key: string, value: string): Promise { - await new Promise(resolve => setTimeout(resolve, 1)); - scope.setExtra(key, value); - } - - initialScope.setExtra('a', 'a'); - initialIsolationScope.setExtra('aa', 'aa'); - - await withIsolationScope(async () => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - await asyncSetExtra(scope1, 'b', 'b'); - await asyncSetExtra(isolationScope1, 'bb', 'bb'); - - await withScope(async () => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - await asyncSetExtra(scope2, 'c', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b: 'b', - c: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb: 'bb', - }); - }); - }); - - await withIsolationScope(async () => { - const scope1 = getCurrentScope(); - const isolationScope1 = getIsolationScope(); - - expect(scope1).not.toBe(initialScope); - expect(isolationScope1).not.toBe(initialIsolationScope); - - expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); - expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); - - scope1.setExtra('b2', 'b'); - isolationScope1.setExtra('bb2', 'bb'); - - await withScope(async () => { - const scope2 = getCurrentScope(); - const isolationScope2 = getIsolationScope(); - - expect(scope2).not.toBe(scope1); - expect(isolationScope2).toBe(isolationScope1); - - expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - - scope2.setExtra('c2', 'c'); - - expect(scope2.getScopeData().extra).toEqual({ - a: 'a', - b2: 'b', - c2: 'c', - }); - - expect(isolationScope2.getScopeData().extra).toEqual({ - aa: 'aa', - bb2: 'bb', - }); - }); - }); - }); - - describe('withScope()', () => { - it('will make the passed scope the active scope within the callback', () => - new Promise(done => { - withScope(scope => { - expect(getCurrentScope()).toBe(scope); - done(); - }); - })); - - it('will pass a scope that is different from the current active isolation scope', () => - new Promise(done => { - withScope(scope => { - expect(getIsolationScope()).not.toBe(scope); - done(); - }); - })); - - it('will always make the inner most passed scope the current scope when nesting calls', () => - new Promise(done => { - withIsolationScope(_scope1 => { - withIsolationScope(scope2 => { - expect(getIsolationScope()).toBe(scope2); - done(); - }); - }); - })); - - it('forks the scope when not passing any scope', () => - new Promise(done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); - - withScope(scope => { - expect(getCurrentScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - })); - - it('forks the scope when passing undefined', () => - new Promise(done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); - - withScope(undefined, scope => { - expect(getCurrentScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - })); - - it('sets the passed in scope as active scope', () => - new Promise(done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); - - const customScope = new ScopeClass(); - - withScope(customScope, scope => { - expect(getCurrentScope()).toBe(customScope); - expect(scope).toBe(customScope); - done(); - }); - })); - }); - - describe('withIsolationScope()', () => { - it('will make the passed isolation scope the active isolation scope within the callback', () => - new Promise(done => { - withIsolationScope(scope => { - expect(getIsolationScope()).toBe(scope); - done(); - }); - })); - - it('will pass an isolation scope that is different from the current active scope', () => - new Promise(done => { - withIsolationScope(scope => { - expect(getCurrentScope()).not.toBe(scope); - done(); - }); - })); - - it('will always make the inner most passed scope the current scope when nesting calls', () => - new Promise(done => { - withIsolationScope(_scope1 => { - withIsolationScope(scope2 => { - expect(getIsolationScope()).toBe(scope2); - done(); - }); - }); - })); - - it('forks the isolation scope when not passing any isolation scope', () => - new Promise(done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); - - withIsolationScope(scope => { - expect(getIsolationScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - })); - - it('forks the isolation scope when passing undefined', () => - new Promise(done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); - - withIsolationScope(undefined, scope => { - expect(getIsolationScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - })); - - it('sets the passed in isolation scope as active isolation scope', () => - new Promise(done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); - - const customScope = new ScopeClass(); - - withIsolationScope(customScope, scope => { - expect(getIsolationScope()).toBe(customScope); - expect(scope).toBe(customScope); - done(); - }); - })); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts b/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts deleted file mode 100644 index b39f45d4919e..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ProxyTracer } from '@opentelemetry/api'; -import { describe, expect, it } from 'vitest'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('OpenTelemetryClient', () => { - it('exposes a tracer', () => { - const options = getDefaultTestClientOptions(); - const client = new TestClient(options); - - const tracer = client.tracer; - expect(tracer).toBeDefined(); - expect(tracer).toBeInstanceOf(ProxyTracer); - - // Ensure we always get the same tracer instance - const tracer2 = client.tracer; - - expect(tracer2).toBe(tracer); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts deleted file mode 100644 index f67cc361d73e..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ClientOptions, Event, Options, SeverityLevel } from '@sentry/core'; -import { Client, createTransport, getCurrentScope, resolvedSyncPromise } from '@sentry/core'; -import { wrapClientClass } from '../../../../packages/opentelemetry/src/custom/client'; -import type { OpenTelemetryClient } from '../../../../packages/opentelemetry/src/types'; - -class BaseTestClient extends Client { - public constructor(options: ClientOptions) { - super(options); - } - - public eventFromException(exception: any): PromiseLike { - return resolvedSyncPromise({ - exception: { - values: [ - { - type: exception.name, - value: exception.message, - }, - ], - }, - }); - } - - public eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike { - return resolvedSyncPromise({ message, level }); - } -} - -export const TestClient = wrapClientClass(BaseTestClient); - -export type TestClientInterface = Client & OpenTelemetryClient; - -export function init(options: Partial = {}): void { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1, ...options })); - - // The client is on the current scope, from where it generally is inherited - getCurrentScope().setClient(client); - client.init(); -} - -export function getDefaultTestClientOptions(options: Partial = {}): ClientOptions { - return { - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), - stackParser: () => [], - ...options, - } as ClientOptions; -} diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts deleted file mode 100644 index b45e49e28d79..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; -import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; -import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'; -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { - ATTR_SERVICE_NAME, - ATTR_SERVICE_VERSION, - SEMRESATTRS_SERVICE_NAMESPACE, -} from '@opentelemetry/semantic-conventions'; -import { debug as debugLogger, getClient, SDK_VERSION } from '@sentry/core'; -import { wrapContextManagerClass } from '../../../../packages/opentelemetry/src/contextManager'; -import { DEBUG_BUILD } from '../../../../packages/opentelemetry/src/debug-build'; -import { SentryPropagator } from '../../../../packages/opentelemetry/src/propagator'; -import { SentrySampler } from '../../../../packages/opentelemetry/src/sampler'; -import { setupEventContextTrace } from '../../../../packages/opentelemetry/src/setupEventContextTrace'; -import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor'; -import { enhanceDscWithOpenTelemetryRootSpanName } from '../../../../packages/opentelemetry/src/utils/enhanceDscWithOpenTelemetryRootSpanName'; -import type { TestClientInterface } from './TestClient'; - -/** - * Initialize OpenTelemetry for Node. - */ -export function initOtel(): void { - const client = getClient(); - - if (!client) { - DEBUG_BUILD && - debugLogger.warn( - 'No client available, skipping OpenTelemetry setup. This probably means that `Sentry.init()` was not called before `initOtel()`.', - ); - return; - } - - if (client.getOptions().debug) { - diag.setLogger( - { - error: debugLogger.error, - warn: debugLogger.warn, - info: debugLogger.log, - debug: debugLogger.log, - verbose: debugLogger.log, - }, - DiagLogLevel.DEBUG, - ); - } - - setupEventContextTrace(client); - enhanceDscWithOpenTelemetryRootSpanName(client); - - const [provider, spanProcessor] = setupOtel(client); - client.traceProvider = provider; - client.spanProcessor = spanProcessor; -} - -/** Just exported for tests. */ -export function setupOtel(client: TestClientInterface): [BasicTracerProvider, SentrySpanProcessor] { - const spanProcessor = new SentrySpanProcessor(); - // Create and configure NodeTracerProvider - const provider = new BasicTracerProvider({ - sampler: new SentrySampler(client), - resource: defaultResource().merge( - resourceFromAttributes({ - [ATTR_SERVICE_NAME]: 'opentelemetry-test', - // eslint-disable-next-line deprecation/deprecation - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_VERSION]: SDK_VERSION, - }), - ), - forceFlushTimeoutMillis: 500, - spanProcessors: [spanProcessor], - }); - - // We use a custom context manager to keep context in sync with sentry scope - const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager); - - trace.setGlobalTracerProvider(provider); - propagation.setGlobalPropagator(new SentryPropagator()); - context.setGlobalContextManager(new SentryContextManager()); - - return [provider, spanProcessor]; -} diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts deleted file mode 100644 index 12372f60ea85..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api'; -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import type { ClientOptions, Options } from '@sentry/core'; -import { flush, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; -import { setOpenTelemetryContextAsyncContextStrategy } from '../../../../packages/opentelemetry/src/asyncContextStrategy'; -import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor'; -import type { OpenTelemetryClient } from '../../../../packages/opentelemetry/src/types'; -import { clearOpenTelemetrySetupCheck } from '../../../../packages/opentelemetry/src/utils/setupCheck'; -import { initOtel } from './initOtel'; -import { init as initTestClient } from './TestClient'; - -const PUBLIC_DSN = 'https://username@domain/123'; - -/** - * Initialize Sentry for Node. - */ -function init(options: Partial | undefined = {}): void { - setOpenTelemetryContextAsyncContextStrategy(); - initTestClient(options); - initOtel(); -} - -function resetGlobals(): void { - getCurrentScope().clear(); - getCurrentScope().setClient(undefined); - getIsolationScope().clear(); - getGlobalScope().clear(); - delete (global as any).__SENTRY__; -} - -export function mockSdkInit(options?: Partial) { - resetGlobals(); - - init({ dsn: PUBLIC_DSN, ...options }); -} - -export async function cleanupOtel(_provider?: BasicTracerProvider): Promise { - clearOpenTelemetrySetupCheck(); - - const provider = getProvider(_provider); - - if (provider) { - await provider.forceFlush(); - await provider.shutdown(); - } - - // Disable all globally registered APIs - trace.disable(); - context.disable(); - propagation.disable(); - - await flush(); -} - -export function getSpanProcessor(): SentrySpanProcessor | undefined { - const client = getClient(); - if (!client) { - return undefined; - } - - const spanProcessor = client.spanProcessor; - if (spanProcessor instanceof SentrySpanProcessor) { - return spanProcessor; - } - - return undefined; -} - -export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined { - let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider(); - - if (provider instanceof ProxyTracerProvider) { - provider = provider.getDelegate(); - } - - if (!(provider instanceof BasicTracerProvider)) { - return undefined; - } - - return provider; -} diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts deleted file mode 100644 index 800c2dbbeba1..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { addBreadcrumb, captureException, getClient, withIsolationScope, withScope } from '@sentry/core'; -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { startSpan } from '../../../../packages/opentelemetry/src/trace'; -import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; - -describe('Integration | breadcrumbs', () => { - const beforeSendTransaction = vi.fn(() => null); - - afterEach(async () => { - await cleanupOtel(); - }); - - describe('without tracing', () => { - it('correctly adds & retrieves breadcrumbs', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb }); - - const client = getClient() as TestClientInterface; - - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } }); - addBreadcrumb({ timestamp: 123455, message: 'test3' }); - - const error = new Error('test'); - captureException(error); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(3); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test1', timestamp: 123456 }, - { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 }, - { message: 'test3', timestamp: 123455 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - - it('handles parallel isolation scopes', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - addBreadcrumb({ timestamp: 123456, message: 'test0' }); - - withIsolationScope(() => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - }); - - withIsolationScope(() => { - addBreadcrumb({ timestamp: 123456, message: 'test2' }); - captureException(error); - }); - - withIsolationScope(() => { - addBreadcrumb({ timestamp: 123456, message: 'test3' }); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(4); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test0', timestamp: 123456 }, - { message: 'test2', timestamp: 123456 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - }); - - it('correctly adds & retrieves breadcrumbs', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - startSpan({ name: 'test' }, () => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - - startSpan({ name: 'inner1' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } }); - }); - - startSpan({ name: 'inner2' }, () => { - addBreadcrumb({ timestamp: 123455, message: 'test3' }); - }); - - captureException(error); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(3); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test1', timestamp: 123456 }, - { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 }, - { message: 'test3', timestamp: 123455 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - - it('correctly adds & retrieves breadcrumbs for the current isolation scope only', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - withIsolationScope(() => { - startSpan({ name: 'test1' }, () => { - addBreadcrumb({ timestamp: 123456, message: 'test1-a' }); - - startSpan({ name: 'inner1' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test1-b' }); - }); - }); - }); - - withIsolationScope(() => { - startSpan({ name: 'test2' }, () => { - addBreadcrumb({ timestamp: 123456, message: 'test2-a' }); - - startSpan({ name: 'inner2' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); - }); - - captureException(error); - }); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(4); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test2-a', timestamp: 123456 }, - { message: 'test2-b', timestamp: 123457 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - - it('ignores scopes inside of root span', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - startSpan({ name: 'test1' }, () => { - withScope(() => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - }); - startSpan({ name: 'inner1' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test2' }); - }); - - captureException(error); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(2); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test1', timestamp: 123456 }, - { message: 'test2', timestamp: 123457 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - - it('handles deep nesting of scopes', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - startSpan({ name: 'test1' }, () => { - withScope(() => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - }); - startSpan({ name: 'inner1' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test2' }); - - startSpan({ name: 'inner2' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test3' }); - - startSpan({ name: 'inner3' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test4' }); - - captureException(error); - - startSpan({ name: 'inner4' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test5' }); - }); - - addBreadcrumb({ timestamp: 123457, message: 'test6' }); - }); - }); - }); - - addBreadcrumb({ timestamp: 123456, message: 'test99' }); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test1', timestamp: 123456 }, - { message: 'test2', timestamp: 123457 }, - { message: 'test3', timestamp: 123457 }, - { message: 'test4', timestamp: 123457 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); - - it('correctly adds & retrieves breadcrumbs in async isolation scopes', async () => { - const beforeSend = vi.fn(() => null); - const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); - - mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 }); - - const client = getClient() as TestClientInterface; - - const error = new Error('test'); - - const promise1 = withIsolationScope(() => { - return startSpan({ name: 'test' }, async () => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); - - await startSpan({ name: 'inner1' }, async () => { - addBreadcrumb({ timestamp: 123457, message: 'test2' }); - }); - - await startSpan({ name: 'inner2' }, async () => { - addBreadcrumb({ timestamp: 123455, message: 'test3' }); - }); - - await new Promise(resolve => setTimeout(resolve, 10)); - - captureException(error); - }); - }); - - const promise2 = withIsolationScope(() => { - return startSpan({ name: 'test-b' }, async () => { - addBreadcrumb({ timestamp: 123456, message: 'test1-b' }); - - await startSpan({ name: 'inner1' }, async () => { - addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); - }); - - await startSpan({ name: 'inner2' }, async () => { - addBreadcrumb({ timestamp: 123455, message: 'test3-b' }); - }); - }); - }); - - await Promise.all([promise1, promise2]); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeBreadcrumb).toHaveBeenCalledTimes(6); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test1', timestamp: 123456 }, - { message: 'test2', timestamp: 123457 }, - { message: 'test3', timestamp: 123455 }, - ], - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts deleted file mode 100644 index 3e237b749d5e..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts +++ /dev/null @@ -1,387 +0,0 @@ -import { - captureException, - getCapturedScopesOnSpan, - getClient, - getCurrentScope, - getIsolationScope, - setTag, - withIsolationScope, - withScope, -} from '@sentry/core'; -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { startSpan } from '../../../../packages/opentelemetry/src/trace'; -import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; - -describe('Integration | Scope', () => { - afterEach(async () => { - await cleanupOtel(); - }); - - describe.each([ - ['with tracing', true], - ['without tracing', false], - ])('%s', (_name, tracingEnabled) => { - it('correctly syncs OTEL context & Sentry hub/scope', async () => { - const beforeSend = vi.fn(() => null); - const beforeSendTransaction = vi.fn(() => null); - - mockSdkInit({ - tracesSampleRate: tracingEnabled ? 1 : 0, - beforeSend, - beforeSendTransaction, - }); - - const client = getClient() as TestClientInterface; - - const rootScope = getCurrentScope(); - - const error = new Error('test error'); - let spanId: string | undefined; - let traceId: string | undefined; - - rootScope.setTag('tag1', 'val1'); - - withScope(scope1 => { - scope1.setTag('tag2', 'val2'); - - withScope(scope2b => { - scope2b.setTag('tag3-b', 'val3-b'); - }); - - withScope(scope2 => { - scope2.setTag('tag3', 'val3'); - - startSpan({ name: 'outer' }, span => { - expect(getCapturedScopesOnSpan(span).scope).toBe(tracingEnabled ? scope2 : undefined); - - spanId = span.spanContext().spanId; - traceId = span.spanContext().traceId; - - setTag('tag4', 'val4'); - - captureException(error); - }); - }); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - - if (spanId) { - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: { - trace: { - span_id: spanId, - trace_id: traceId, - }, - }, - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - } - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - tags: { - tag1: 'val1', - tag2: 'val2', - tag3: 'val3', - tag4: 'val4', - }, - }), - { - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }, - ); - - if (tracingEnabled) { - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - // Note: Scope for transaction is taken at `start` time, not `finish` time - expect(beforeSendTransaction).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - data: { - 'sentry.origin': 'manual', - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - }, - span_id: spanId, - status: 'ok', - trace_id: traceId, - origin: 'manual', - }, - }), - spans: [], - start_timestamp: expect.any(Number), - tags: { - tag1: 'val1', - tag2: 'val2', - tag3: 'val3', - tag4: 'val4', - }, - timestamp: expect.any(Number), - transaction_info: { source: 'custom' }, - type: 'transaction', - }), - { - event_id: expect.any(String), - }, - ); - } - }); - - it('isolates parallel scopes', async () => { - const beforeSend = vi.fn(() => null); - const beforeSendTransaction = vi.fn(() => null); - - mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; - const rootScope = getCurrentScope(); - - const error1 = new Error('test error 1'); - const error2 = new Error('test error 2'); - let spanId1: string | undefined; - let spanId2: string | undefined; - let traceId1: string | undefined; - let traceId2: string | undefined; - - rootScope.setTag('tag1', 'val1'); - - const initialIsolationScope = getIsolationScope(); - - withScope(scope1 => { - scope1.setTag('tag2', 'val2a'); - - expect(getIsolationScope()).toBe(initialIsolationScope); - - withScope(scope2 => { - scope2.setTag('tag3', 'val3a'); - - startSpan({ name: 'outer' }, span => { - expect(getIsolationScope()).toBe(initialIsolationScope); - - spanId1 = span.spanContext().spanId; - traceId1 = span.spanContext().traceId; - - setTag('tag4', 'val4a'); - - captureException(error1); - }); - }); - }); - - withScope(scope1 => { - scope1.setTag('tag2', 'val2b'); - - expect(getIsolationScope()).toBe(initialIsolationScope); - - withScope(scope2 => { - scope2.setTag('tag3', 'val3b'); - - startSpan({ name: 'outer' }, span => { - expect(getIsolationScope()).toBe(initialIsolationScope); - - spanId2 = span.spanContext().spanId; - traceId2 = span.spanContext().traceId; - - setTag('tag4', 'val4b'); - - captureException(error2); - }); - }); - }); - - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(2); - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: spanId1 - ? { - span_id: spanId1, - trace_id: traceId1, - } - : expect.any(Object), - }), - tags: { - tag1: 'val1', - tag2: 'val2a', - tag3: 'val3a', - tag4: 'val4a', - }, - }), - { - event_id: expect.any(String), - originalException: error1, - syntheticException: expect.any(Error), - }, - ); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: spanId2 - ? { - span_id: spanId2, - trace_id: traceId2, - } - : expect.any(Object), - }), - tags: { - tag1: 'val1', - tag2: 'val2b', - tag3: 'val3b', - tag4: 'val4b', - }, - }), - { - event_id: expect.any(String), - originalException: error2, - syntheticException: expect.any(Error), - }, - ); - - if (tracingEnabled) { - expect(beforeSendTransaction).toHaveBeenCalledTimes(2); - } - }); - - it('isolates parallel isolation scopes', async () => { - const beforeSend = vi.fn(() => null); - const beforeSendTransaction = vi.fn(() => null); - - mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; - const rootScope = getCurrentScope(); - - const error1 = new Error('test error 1'); - const error2 = new Error('test error 2'); - let spanId1: string | undefined; - let spanId2: string | undefined; - let traceId1: string | undefined; - let traceId2: string | undefined; - - rootScope.setTag('tag1', 'val1'); - - const initialIsolationScope = getIsolationScope(); - initialIsolationScope.setTag('isolationTag1', 'val1'); - - withIsolationScope(scope1 => { - scope1.setTag('tag2', 'val2a'); - - expect(getIsolationScope()).not.toBe(initialIsolationScope); - getIsolationScope().setTag('isolationTag2', 'val2'); - - withScope(scope2 => { - scope2.setTag('tag3', 'val3a'); - - startSpan({ name: 'outer' }, span => { - expect(getIsolationScope()).not.toBe(initialIsolationScope); - - spanId1 = span.spanContext().spanId; - traceId1 = span.spanContext().traceId; - - setTag('tag4', 'val4a'); - - captureException(error1); - }); - }); - }); - - withIsolationScope(scope1 => { - scope1.setTag('tag2', 'val2b'); - - expect(getIsolationScope()).not.toBe(initialIsolationScope); - getIsolationScope().setTag('isolationTag2', 'val2b'); - - withScope(scope2 => { - scope2.setTag('tag3', 'val3b'); - - startSpan({ name: 'outer' }, span => { - expect(getIsolationScope()).not.toBe(initialIsolationScope); - - spanId2 = span.spanContext().spanId; - traceId2 = span.spanContext().traceId; - - setTag('tag4', 'val4b'); - - captureException(error2); - }); - }); - }); - - await client.flush(); - - expect(spanId1).toBeDefined(); - expect(spanId2).toBeDefined(); - expect(traceId1).toBeDefined(); - expect(traceId2).toBeDefined(); - - expect(beforeSend).toHaveBeenCalledTimes(2); - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: spanId1, - trace_id: traceId1, - }, - }), - tags: { - tag1: 'val1', - tag2: 'val2a', - tag3: 'val3a', - tag4: 'val4a', - isolationTag1: 'val1', - isolationTag2: 'val2', - }, - }), - { - event_id: expect.any(String), - originalException: error1, - syntheticException: expect.any(Error), - }, - ); - - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: spanId2, - trace_id: traceId2, - }, - }), - tags: { - tag1: 'val1', - tag2: 'val2b', - tag3: 'val3b', - tag4: 'val4b', - isolationTag1: 'val1', - isolationTag2: 'val2b', - }, - }), - { - event_id: expect.any(String), - originalException: error2, - syntheticException: expect.any(Error), - }, - ); - - if (tracingEnabled) { - expect(beforeSendTransaction).toHaveBeenCalledTimes(2); - } - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts deleted file mode 100644 index 4c9909e09d9f..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts +++ /dev/null @@ -1,724 +0,0 @@ -import type { SpanContext } from '@opentelemetry/api'; -import { context, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; -import { TraceState } from '@opentelemetry/core'; -import type { Event, TransactionEvent } from '@sentry/core'; -import { - addBreadcrumb, - debug, - getClient, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - setTag, - startSpanManual, - withIsolationScope, -} from '@sentry/core'; -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { SENTRY_TRACE_STATE_DSC } from '../../../../packages/opentelemetry/src/constants'; -import { startInactiveSpan, startSpan } from '../../../../packages/opentelemetry/src/trace'; -import { makeTraceState } from '../../../../packages/opentelemetry/src/utils/makeTraceState'; -import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; - -describe('Integration | Transactions', () => { - afterEach(async () => { - vi.restoreAllMocks(); - vi.useRealTimers(); - await cleanupOtel(); - }); - - it('correctly creates transaction & spans', async () => { - const transactions: TransactionEvent[] = []; - const beforeSendTransaction = vi.fn(event => { - transactions.push(event); - return null; - }); - - mockSdkInit({ - tracesSampleRate: 1, - beforeSendTransaction, - release: '8.0.0', - }); - - const client = getClient() as TestClientInterface; - - addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); - setTag('outer.tag', 'test value'); - - startSpan( - { - op: 'test op', - name: 'test name', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', - }, - }, - span => { - addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); - - span.setAttributes({ - 'test.outer': 'test value', - }); - - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - setTag('test.tag', 'test value'); - - startSpan({ name: 'inner span 2' }, innerSpan => { - addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); - - innerSpan.setAttributes({ - 'test.inner': 'test value', - }); - }); - }, - ); - - await client.flush(); - - expect(transactions).toHaveLength(1); - const transaction = transactions[0]!; - - expect(transaction.breadcrumbs).toEqual([ - { message: 'test breadcrumb 1', timestamp: 123456 }, - { message: 'test breadcrumb 2', timestamp: 123456 }, - { message: 'test breadcrumb 3', timestamp: 123456 }, - ]); - - expect(transaction.contexts?.otel).toEqual({ - resource: { - 'service.name': 'opentelemetry-test', - 'service.namespace': 'sentry', - 'service.version': expect.any(String), - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': expect.any(String), - }, - }); - - expect(transaction.contexts?.trace).toEqual({ - data: { - 'sentry.op': 'test op', - 'sentry.origin': 'auto.test', - 'sentry.source': 'task', - 'sentry.sample_rate': 1, - 'test.outer': 'test value', - }, - op: 'test op', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'auto.test', - }); - - expect(transaction.sdkProcessingMetadata?.sampleRate).toEqual(1); - expect(transaction.sdkProcessingMetadata?.dynamicSamplingContext).toEqual({ - environment: 'production', - public_key: expect.any(String), - sample_rate: '1', - sampled: 'true', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - transaction: 'test name', - release: '8.0.0', - sample_rand: expect.any(String), - }); - - expect(transaction.environment).toEqual('production'); - expect(transaction.event_id).toEqual(expect.any(String)); - expect(transaction.start_timestamp).toEqual(expect.any(Number)); - expect(transaction.timestamp).toEqual(expect.any(Number)); - expect(transaction.transaction).toEqual('test name'); - - expect(transaction.tags).toEqual({ - 'outer.tag': 'test value', - 'test.tag': 'test value', - }); - expect(transaction.transaction_info).toEqual({ source: 'task' }); - expect(transaction.type).toEqual('transaction'); - - expect(transaction.spans).toHaveLength(2); - const spans = transaction.spans || []; - - // note: Currently, spans do not have any context/span added to them - // This is the same behavior as for the "regular" SDKs - expect(spans).toEqual([ - { - data: { - 'sentry.origin': 'manual', - }, - description: 'inner span 1', - origin: 'manual', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - { - data: { - 'test.inner': 'test value', - 'sentry.origin': 'manual', - }, - description: 'inner span 2', - origin: 'manual', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - ]); - }); - - it('correctly creates concurrent transaction & spans', async () => { - const beforeSendTransaction = vi.fn(() => null); - - mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; - - addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); - - withIsolationScope(() => { - startSpan( - { - op: 'test op', - name: 'test name', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - }, - }, - span => { - addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); - - span.setAttributes({ - 'test.outer': 'test value', - }); - - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - setTag('test.tag', 'test value'); - - startSpan({ name: 'inner span 2' }, innerSpan => { - addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); - - innerSpan.setAttributes({ - 'test.inner': 'test value', - }); - }); - }, - ); - }); - - withIsolationScope(() => { - startSpan({ op: 'test op b', name: 'test name b' }, span => { - addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 }); - - span.setAttributes({ - 'test.outer': 'test value b', - }); - - const subSpan = startInactiveSpan({ name: 'inner span 1b' }); - subSpan.end(); - - setTag('test.tag', 'test value b'); - - startSpan({ name: 'inner span 2b' }, innerSpan => { - addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 }); - - innerSpan.setAttributes({ - 'test.inner': 'test value b', - }); - }); - }); - }); - - await client.flush(); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(2); - expect(beforeSendTransaction).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test breadcrumb 1', timestamp: 123456 }, - { message: 'test breadcrumb 2', timestamp: 123456 }, - { message: 'test breadcrumb 3', timestamp: 123456 }, - ], - contexts: expect.objectContaining({ - trace: { - data: { - 'sentry.op': 'test op', - 'sentry.origin': 'auto.test', - 'sentry.source': 'task', - 'test.outer': 'test value', - 'sentry.sample_rate': 1, - }, - op: 'test op', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'auto.test', - }, - }), - spans: [expect.any(Object), expect.any(Object)], - start_timestamp: expect.any(Number), - tags: { - 'test.tag': 'test value', - }, - timestamp: expect.any(Number), - transaction: 'test name', - transaction_info: { source: 'task' }, - type: 'transaction', - }), - { - event_id: expect.any(String), - }, - ); - - expect(beforeSendTransaction).toHaveBeenCalledWith( - expect.objectContaining({ - breadcrumbs: [ - { message: 'test breadcrumb 1', timestamp: 123456 }, - { message: 'test breadcrumb 2b', timestamp: 123456 }, - { message: 'test breadcrumb 3b', timestamp: 123456 }, - ], - contexts: expect.objectContaining({ - trace: { - data: { - 'sentry.op': 'test op b', - 'sentry.origin': 'manual', - 'sentry.source': 'custom', - 'test.outer': 'test value b', - 'sentry.sample_rate': 1, - }, - op: 'test op b', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'manual', - }, - }), - spans: [expect.any(Object), expect.any(Object)], - start_timestamp: expect.any(Number), - tags: { - 'test.tag': 'test value b', - }, - timestamp: expect.any(Number), - transaction: 'test name b', - transaction_info: { source: 'custom' }, - type: 'transaction', - }), - { - event_id: expect.any(String), - }, - ); - }); - - it('correctly creates transaction & spans with a trace header data', async () => { - const beforeSendTransaction = vi.fn(() => null); - - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - const parentSpanId = '6e0c63257de34c92'; - - const traceState = makeTraceState({ - dsc: undefined, - sampled: true, - }); - - const spanContext: SpanContext = { - traceId, - spanId: parentSpanId, - isRemote: true, - traceFlags: TraceFlags.SAMPLED, - traceState, - }; - - mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - - const client = getClient() as TestClientInterface; - - // We simulate the correct context we'd normally get from the SentryPropagator - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - startSpan( - { - op: 'test op', - name: 'test name', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - }, - }, - () => { - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - startSpan({ name: 'inner span 2' }, () => {}); - }, - ); - }); - - await client.flush(); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - expect(beforeSendTransaction).toHaveBeenLastCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - data: { - 'sentry.op': 'test op', - 'sentry.origin': 'auto.test', - 'sentry.source': 'task', - }, - op: 'test op', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - parent_span_id: parentSpanId, - status: 'ok', - trace_id: traceId, - origin: 'auto.test', - }, - }), - // spans are circular (they have a reference to the transaction), which leads to jest choking on this - // instead we compare them in detail below - spans: [expect.any(Object), expect.any(Object)], - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - transaction: 'test name', - transaction_info: { source: 'task' }, - type: 'transaction', - }), - { - event_id: expect.any(String), - }, - ); - - // Checking the spans here, as they are circular to the transaction... - const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown]; - const spans = runArgs[0].spans || []; - - // note: Currently, spans do not have any context/span added to them - // This is the same behavior as for the "regular" SDKs - expect(spans).toEqual([ - { - data: { - 'sentry.origin': 'manual', - }, - description: 'inner span 1', - origin: 'manual', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: traceId, - }, - { - data: { - 'sentry.origin': 'manual', - }, - description: 'inner span 2', - origin: 'manual', - parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), - span_id: expect.stringMatching(/[a-f0-9]{16}/), - start_timestamp: expect.any(Number), - status: 'ok', - timestamp: expect.any(Number), - trace_id: traceId, - }, - ]); - }); - - it('cleans up spans that are not flushed for over 5 mins', async () => { - const beforeSendTransaction = vi.fn(() => null); - - const now = Date.now(); - vi.useFakeTimers(); - vi.setSystemTime(now); - - const logs: unknown[] = []; - vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg)); - - mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - - const spanProcessor = getSpanProcessor(); - - const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; - - if (!exporter) { - throw new Error('No exporter found, aborting test...'); - } - - void startSpan({ name: 'test name' }, async () => { - startInactiveSpan({ name: 'inner span 1' }).end(); - startInactiveSpan({ name: 'inner span 2' }).end(); - - // Pretend this is pending for 10 minutes - await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000)); - }); - - // Child-spans have been added to the exporter, but they are pending since they are waiting for their parent - const finishedSpans1 = []; - exporter['_finishedSpanBuckets'].forEach(bucket => { - if (bucket) { - finishedSpans1.push(...bucket.spans); - } - }); - expect(finishedSpans1.length).toBe(2); - expect(beforeSendTransaction).toHaveBeenCalledTimes(0); - - // Now wait for 5 mins - vi.advanceTimersByTime(5 * 60 * 1_000 + 1); - - // Adding another span will trigger the cleanup - startSpan({ name: 'other span' }, () => {}); - - vi.advanceTimersByTime(1); - - // Old spans have been cleared away - const finishedSpans2 = []; - exporter['_finishedSpanBuckets'].forEach(bucket => { - if (bucket) { - finishedSpans2.push(...bucket.spans); - } - }); - expect(finishedSpans2.length).toBe(0); - - // Called once for the 'other span' - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - - expect(logs).toEqual( - expect.arrayContaining([ - 'SpanExporter dropped 2 spans because they were pending for more than 300 seconds.', - 'SpanExporter exported 1 spans, 0 spans are waiting for their parent spans to finish', - ]), - ); - }); - - it('includes child spans that are finished in the same tick but after their parent span', async () => { - const now = Date.now(); - vi.useFakeTimers(); - vi.setSystemTime(now); - - const logs: unknown[] = []; - vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg)); - - const transactions: Event[] = []; - - mockSdkInit({ - tracesSampleRate: 1, - beforeSendTransaction: event => { - transactions.push(event); - return null; - }, - }); - - const spanProcessor = getSpanProcessor(); - - const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; - - if (!exporter) { - throw new Error('No exporter found, aborting test...'); - } - - startSpanManual({ name: 'test name' }, async span => { - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - const subSpan2 = startInactiveSpan({ name: 'inner span 2' }); - - span.end(); - subSpan2.end(); - }); - - vi.advanceTimersByTime(1); - - expect(transactions).toHaveLength(1); - expect(transactions[0]?.spans).toHaveLength(2); - - // No spans are pending - const finishedSpans = []; - exporter['_finishedSpanBuckets'].forEach(bucket => { - if (bucket) { - finishedSpans.push(...bucket.spans); - } - }); - expect(finishedSpans.length).toBe(0); - }); - - it('collects child spans that are finished within 5 minutes their parent span has been sent', async () => { - const timeout = 5 * 60 * 1000; - const now = Date.now(); - vi.useFakeTimers(); - vi.setSystemTime(now); - - const logs: unknown[] = []; - vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg)); - - const transactions: Event[] = []; - - mockSdkInit({ - tracesSampleRate: 1, - beforeSendTransaction: event => { - transactions.push(event); - return null; - }, - }); - - const spanProcessor = getSpanProcessor(); - - const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; - - if (!exporter) { - throw new Error('No exporter found, aborting test...'); - } - - startSpanManual({ name: 'test name' }, async span => { - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - const subSpan2 = startInactiveSpan({ name: 'inner span 2' }); - - span.end(); - - setTimeout(() => { - subSpan2.end(); - }, timeout - 2); - }); - - vi.advanceTimersByTime(timeout - 1); - - expect(transactions).toHaveLength(2); - expect(transactions[0]?.spans).toHaveLength(1); - - const finishedSpans: any = exporter['_finishedSpanBuckets'].flatMap(bucket => - bucket ? Array.from(bucket.spans) : [], - ); - expect(finishedSpans.length).toBe(0); - }); - - it('discards child spans that are finished after 5 minutes their parent span has been sent', async () => { - const timeout = 5 * 60 * 1000; - const now = Date.now(); - vi.useFakeTimers(); - vi.setSystemTime(now); - - const logs: unknown[] = []; - vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg)); - - const transactions: Event[] = []; - - mockSdkInit({ - tracesSampleRate: 1, - beforeSendTransaction: event => { - transactions.push(event); - return null; - }, - }); - - const spanProcessor = getSpanProcessor(); - - const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; - - if (!exporter) { - throw new Error('No exporter found, aborting test...'); - } - - startSpanManual({ name: 'test name' }, async span => { - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - const subSpan2 = startInactiveSpan({ name: 'inner span 2' }); - - span.end(); - - setTimeout(() => { - subSpan2.end(); - }, timeout + 1); - }); - - vi.advanceTimersByTime(timeout + 2); - - expect(transactions).toHaveLength(1); - expect(transactions[0]?.spans).toHaveLength(1); - - // subSpan2 is pending (and will eventually be cleaned up) - const finishedSpans: any = []; - exporter['_finishedSpanBuckets'].forEach(bucket => { - if (bucket) { - finishedSpans.push(...bucket.spans); - } - }); - expect(finishedSpans.length).toBe(1); - expect(finishedSpans[0]?.name).toBe('inner span 2'); - }); - - it('uses & inherits DSC on span trace state', async () => { - const transactionEvents: Event[] = []; - const beforeSendTransaction = vi.fn(event => { - transactionEvents.push(event); - return null; - }); - - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - const parentSpanId = '6e0c63257de34c92'; - - const dscString = `sentry-transaction=other-transaction,sentry-environment=other,sentry-release=8.0.0,sentry-public_key=public,sentry-trace_id=${traceId},sentry-sampled=true`; - - const spanContext: SpanContext = { - traceId, - spanId: parentSpanId, - isRemote: true, - traceFlags: TraceFlags.SAMPLED, - traceState: new TraceState().set(SENTRY_TRACE_STATE_DSC, dscString), - }; - - mockSdkInit({ - tracesSampleRate: 1, - beforeSendTransaction, - release: '7.0.0', - }); - - const client = getClient() as TestClientInterface; - - // We simulate the correct context we'd normally get from the SentryPropagator - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - startSpan( - { - op: 'test op', - name: 'test name', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - }, - }, - span => { - expect(span.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - - expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - - subSpan.end(); - - startSpan({ name: 'inner span 2' }, subSpan => { - expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - }); - }, - ); - }); - - await client.flush(); - - expect(transactionEvents).toHaveLength(1); - expect(transactionEvents[0]?.sdkProcessingMetadata?.dynamicSamplingContext).toEqual({ - environment: 'other', - public_key: 'public', - release: '8.0.0', - sampled: 'true', - trace_id: traceId, - transaction: 'other-transaction', - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts b/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts deleted file mode 100644 index 8e3f85b38250..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts +++ /dev/null @@ -1,670 +0,0 @@ -import { - context, - defaultTextMapGetter, - defaultTextMapSetter, - propagation, - ROOT_CONTEXT, - trace, - TraceFlags, -} from '@opentelemetry/api'; -import { suppressTracing } from '@opentelemetry/core'; -import { getCurrentScope, withScope } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { - SENTRY_BAGGAGE_HEADER, - SENTRY_SCOPES_CONTEXT_KEY, - SENTRY_TRACE_HEADER, -} from '../../../packages/opentelemetry/src/constants'; -import { SentryPropagator } from '../../../packages/opentelemetry/src/propagator'; -import { getSamplingDecision } from '../../../packages/opentelemetry/src/utils/getSamplingDecision'; -import { makeTraceState } from '../../../packages/opentelemetry/src/utils/makeTraceState'; -import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; - -describe('SentryPropagator', () => { - const propagator = new SentryPropagator(); - let carrier: { [key: string]: unknown }; - - beforeEach(() => { - carrier = {}; - mockSdkInit({ - environment: 'production', - release: '1.0.0', - tracesSampleRate: 1, - dsn: 'https://abc@domain/123', - }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('returns fields set', () => { - expect(propagator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]); - }); - - describe('inject', () => { - describe('without active local span', () => { - it('uses scope propagation context without DSC if no span is found', () => { - withScope(scope => { - scope.setPropagationContext({ - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - parentSpanId: '6e0c63257de34c93', - sampled: true, - sampleRand: Math.random(), - }); - - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - ].sort(), - ); - expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/); - }); - }); - - it('uses scope propagation context with DSC if no span is found', () => { - withScope(scope => { - scope.setPropagationContext({ - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - parentSpanId: '6e0c63257de34c93', - sampled: true, - sampleRand: Math.random(), - dsc: { - transaction: 'sampled-transaction', - sampled: 'false', - trace_id: 'dsc_trace_id', - public_key: 'dsc_public_key', - environment: 'dsc_environment', - release: 'dsc_release', - sample_rate: '0.5', - replay_id: 'dsc_replay_id', - }, - }); - - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'sentry-environment=dsc_environment', - 'sentry-release=dsc_release', - 'sentry-public_key=dsc_public_key', - 'sentry-trace_id=dsc_trace_id', - 'sentry-transaction=sampled-transaction', - 'sentry-sampled=false', - 'sentry-sample_rate=0.5', - 'sentry-replay_id=dsc_replay_id', - ].sort(), - ); - expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/); - }); - }); - - it('uses propagation data from current scope if no scope & span is found', () => { - const scope = getCurrentScope(); - const traceId = scope.getPropagationContext().traceId; - - const ctx = trace.deleteSpan(ROOT_CONTEXT).deleteValue(SENTRY_SCOPES_CONTEXT_KEY); - propagator.inject(ctx, carrier, defaultTextMapSetter); - - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual([ - 'sentry-environment=production', - 'sentry-public_key=abc', - 'sentry-release=1.0.0', - `sentry-trace_id=${traceId}`, - ]); - expect(carrier[SENTRY_TRACE_HEADER]).toMatch(traceId); - }); - }); - - describe('with active span', () => { - it.each([ - [ - 'continues a remote trace without dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - isRemote: true, - }, - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-sampled=true', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=test', - expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/), - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', - true, - ], - [ - 'continues a remote trace with dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - isRemote: true, - traceState: makeTraceState({ - dsc: { - transaction: 'sampled-transaction', - sampled: 'true', - trace_id: 'dsc_trace_id', - public_key: 'dsc_public_key', - environment: 'dsc_environment', - release: 'dsc_release', - sample_rate: '0.5', - replay_id: 'dsc_replay_id', - }, - }), - }, - [ - 'sentry-environment=dsc_environment', - 'sentry-release=dsc_release', - 'sentry-public_key=dsc_public_key', - 'sentry-trace_id=dsc_trace_id', - 'sentry-transaction=sampled-transaction', - 'sentry-sampled=true', - 'sentry-sample_rate=0.5', - 'sentry-replay_id=dsc_replay_id', - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', - true, - ], - [ - 'continues an unsampled remote trace without dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - }, - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-sampled=true', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=test', - expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/), - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', - undefined, - ], - [ - 'continues an unsampled remote trace with sampled trace state & without dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - traceState: makeTraceState({ - sampled: false, - }), - }, - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-sampled=false', - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0', - false, - ], - [ - 'continues an unsampled remote trace with dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - traceState: makeTraceState({ - dsc: { - transaction: 'sampled-transaction', - sampled: 'false', - trace_id: 'dsc_trace_id', - public_key: 'dsc_public_key', - environment: 'dsc_environment', - release: 'dsc_release', - sample_rate: '0.5', - replay_id: 'dsc_replay_id', - }, - }), - }, - [ - 'sentry-environment=dsc_environment', - 'sentry-release=dsc_release', - 'sentry-public_key=dsc_public_key', - 'sentry-trace_id=dsc_trace_id', - 'sentry-transaction=sampled-transaction', - 'sentry-sampled=false', - 'sentry-sample_rate=0.5', - 'sentry-replay_id=dsc_replay_id', - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0', - false, - ], - [ - 'continues an unsampled remote trace with dsc & sampled trace state', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - traceState: makeTraceState({ - sampled: false, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'dsc_trace_id', - public_key: 'dsc_public_key', - environment: 'dsc_environment', - release: 'dsc_release', - sample_rate: '0.5', - replay_id: 'dsc_replay_id', - }, - }), - }, - [ - 'sentry-environment=dsc_environment', - 'sentry-release=dsc_release', - 'sentry-public_key=dsc_public_key', - 'sentry-trace_id=dsc_trace_id', - 'sentry-transaction=sampled-transaction', - 'sentry-sample_rate=0.5', - 'sentry-replay_id=dsc_replay_id', - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0', - false, - ], - [ - 'starts a new trace without existing dsc', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }, - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-sampled=true', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - ], - 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', - true, - ], - ])('%s', (_name, spanContext, baggage, sentryTrace, samplingDecision) => { - expect(getSamplingDecision(spanContext)).toBe(samplingDecision); - - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - trace.getTracer('test').startActiveSpan('test', span => { - propagator.inject(context.active(), carrier, defaultTextMapSetter); - baggage.forEach(baggageItem => { - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toContainEqual(baggageItem); - }); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace.replace('{{spanId}}', span.spanContext().spanId)); - }); - }); - }); - - it('uses local span over propagation context', () => { - context.with( - trace.setSpanContext(ROOT_CONTEXT, { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - isRemote: true, - }), - () => { - trace.getTracer('test').startActiveSpan('test', span => { - withScope(scope => { - scope.setPropagationContext({ - traceId: 'TRACE_ID', - parentSpanId: 'PARENT_SPAN_ID', - sampled: true, - sampleRand: Math.random(), - }); - - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-sampled=true', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=test', - expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/), - ].forEach(item => { - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toContainEqual(item); - }); - expect(carrier[SENTRY_TRACE_HEADER]).toBe( - `d4cda95b652f4a1592b449d5929fda1b-${span.spanContext().spanId}-1`, - ); - }); - }); - }, - ); - }); - - it('uses remote span with deferred sampling decision over propagation context', () => { - const carrier: Record = {}; - context.with( - trace.setSpanContext(ROOT_CONTEXT, { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - }), - () => { - withScope(scope => { - scope.setPropagationContext({ - traceId: 'TRACE_ID', - parentSpanId: 'PARENT_SPAN_ID', - sampled: true, - sampleRand: Math.random(), - }); - - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - ].sort(), - ); - // Used spanId is a random ID, not from the remote span - expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}/); - expect(carrier[SENTRY_TRACE_HEADER]).not.toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92'); - }); - }, - ); - }); - - it('uses remote span over propagation context', () => { - const carrier: Record = {}; - context.with( - trace.setSpanContext(ROOT_CONTEXT, { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - isRemote: true, - traceState: makeTraceState({ sampled: false }), - }), - () => { - withScope(scope => { - scope.setPropagationContext({ - traceId: 'TRACE_ID', - parentSpanId: 'PARENT_SPAN_ID', - sampled: true, - sampleRand: Math.random(), - }); - - propagator.inject(context.active(), carrier, defaultTextMapSetter); - - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-public_key=abc', - 'sentry-sampled=false', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - ].sort(), - ); - // Used spanId is a random ID, not from the remote span - expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-0/); - expect(carrier[SENTRY_TRACE_HEADER]).not.toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0'); - }); - }, - ); - }); - }); - - it('should include existing baggage', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'foo=bar', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-public_key=abc', - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-sampled=true', - ].sort(), - ); - }); - - it('should include existing baggage header', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - - const carrier = { - other: 'header', - baggage: 'foo=bar,other=yes', - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage(); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'foo=bar', - 'other=yes', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-public_key=abc', - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-sampled=true', - ].sort(), - ); - }); - - it('should include existing baggage array header', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - - const carrier = { - other: 'header', - baggage: ['foo=bar,other=yes', 'other2=no'], - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage(); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'foo=bar', - 'other=yes', - 'other2=no', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-public_key=abc', - 'sentry-environment=production', - 'sentry-release=1.0.0', - 'sentry-sampled=true', - ].sort(), - ); - }); - - it('should overwrite existing sentry baggage header', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - - const carrier = { - baggage: 'foo=bar,other=yes,sentry-release=9.9.9,sentry-other=yes', - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage(); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'foo=bar', - 'other=yes', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-public_key=abc', - 'sentry-environment=production', - 'sentry-other=yes', - 'sentry-release=1.0.0', - 'sentry-sampled=true', - ].sort(), - ); - }); - - it('should create baggage without propagation context', () => { - const scope = getCurrentScope(); - const traceId = scope.getPropagationContext().traceId; - - const context = ROOT_CONTEXT; - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe( - `foo=bar,sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=${traceId}`, - ); - }); - - it('should NOT set baggage and sentry-trace header if instrumentation is suppressed', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - - const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); - }); - }); - - describe('extract', () => { - it('sets data from sentry trace header on span context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - traceState: makeTraceState({}), - }); - expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true); - }); - - it('sets data from negative sampled sentry trace header on span context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - traceState: makeTraceState({ sampled: false }), - }); - expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(false); - }); - - it('sets data from not sampled sentry trace header on span context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - traceState: makeTraceState({}), - }); - expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(undefined); - }); - - it('handles undefined sentry trace header', () => { - const sentryTraceHeader = undefined; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual(undefined); - expect(getCurrentScope().getPropagationContext()).toEqual({ - traceId: expect.stringMatching(/[a-f0-9]{32}/), - sampleRand: expect.any(Number), - }); - }); - - it('sets data from baggage header on span context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - const baggage = - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction,sentry-sample_rand=0.123'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - traceState: makeTraceState({ - dsc: { - environment: 'production', - release: '1.0.0', - public_key: 'abc', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - transaction: 'dsc-transaction', - sample_rand: '0.123', - }, - }), - }); - expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true); - }); - - it('handles empty dsc baggage header', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - const baggage = ''; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - traceState: makeTraceState({}), - }); - expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true); - }); - - it('handles when sentry-trace is an empty array', () => { - carrier[SENTRY_TRACE_HEADER] = []; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual(undefined); - expect(getCurrentScope().getPropagationContext()).toEqual({ - traceId: expect.stringMatching(/[a-f0-9]{32}/), - sampleRand: expect.any(Number), - }); - }); - }); -}); - -function baggageToArray(baggage: unknown): string[] { - return typeof baggage === 'string' ? baggage.split(',').sort() : []; -} diff --git a/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts b/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts deleted file mode 100644 index 86cf7b135f97..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { context, SpanKind, trace } from '@opentelemetry/api'; -import { TraceState } from '@opentelemetry/core'; -import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; -import { ATTR_HTTP_REQUEST_METHOD } from '@opentelemetry/semantic-conventions'; -import { generateSpanId, generateTraceId } from '@sentry/core'; -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING } from '../../../packages/opentelemetry/src/constants'; -import { SentrySampler } from '../../../packages/opentelemetry/src/sampler'; -import { cleanupOtel } from './helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from './helpers/TestClient'; - -describe('SentrySampler', () => { - afterEach(async () => { - await cleanupOtel(); - }); - - it('works with tracesSampleRate=0', () => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); - const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent'); - const sampler = new SentrySampler(client); - - const ctx = context.active(); - const traceId = generateTraceId(); - const spanName = 'test'; - const spanKind = SpanKind.INTERNAL; - const spanAttributes = {}; - const links = undefined; - - const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual( - expect.objectContaining({ - decision: SamplingDecision.NOT_RECORD, - attributes: { 'sentry.sample_rate': 0 }, - }), - ); - expect(actual.traceState?.get('sentry.sampled_not_recording')).toBe('1'); - expect(actual.traceState?.get('sentry.sample_rand')).toEqual(expect.any(String)); - expect(spyOnDroppedEvent).toHaveBeenCalledTimes(1); - expect(spyOnDroppedEvent).toHaveBeenCalledWith('sample_rate', 'transaction'); - - spyOnDroppedEvent.mockReset(); - }); - - it('works with tracesSampleRate=0 & for a child span', () => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); - const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent'); - const sampler = new SentrySampler(client); - - const traceId = generateTraceId(); - const ctx = trace.setSpanContext(context.active(), { - spanId: generateSpanId(), - traceId, - traceFlags: 0, - traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'), - }); - const spanName = 'test'; - const spanKind = SpanKind.INTERNAL; - const spanAttributes = {}; - const links = undefined; - - const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual({ - decision: SamplingDecision.NOT_RECORD, - traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'), - }); - expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); - - spyOnDroppedEvent.mockReset(); - }); - - it('works with tracesSampleRate=1', () => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent'); - const sampler = new SentrySampler(client); - - const ctx = context.active(); - const traceId = generateTraceId(); - const spanName = 'test'; - const spanKind = SpanKind.INTERNAL; - const spanAttributes = {}; - const links = undefined; - - const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual( - expect.objectContaining({ - decision: SamplingDecision.RECORD_AND_SAMPLED, - attributes: { 'sentry.sample_rate': 1 }, - }), - ); - expect(actual.traceState?.constructor.name).toBe('TraceState'); - expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); - - spyOnDroppedEvent.mockReset(); - }); - - it('works with traceSampleRate=undefined', () => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: undefined })); - const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent'); - const sampler = new SentrySampler(client); - - const ctx = context.active(); - const traceId = generateTraceId(); - const spanName = 'test'; - const spanKind = SpanKind.INTERNAL; - const spanAttributes = {}; - const links = undefined; - - const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual({ - decision: SamplingDecision.NOT_RECORD, - traceState: new TraceState(), - }); - expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); - - spyOnDroppedEvent.mockReset(); - }); - - it('ignores local http client root spans', () => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); - const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent'); - const sampler = new SentrySampler(client); - - const ctx = context.active(); - const traceId = generateTraceId(); - const spanName = 'test'; - const spanKind = SpanKind.CLIENT; - const spanAttributes = { - [ATTR_HTTP_REQUEST_METHOD]: 'GET', - }; - const links = undefined; - - const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual({ - decision: SamplingDecision.NOT_RECORD, - traceState: new TraceState(), - }); - expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); - - spyOnDroppedEvent.mockReset(); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts b/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts deleted file mode 100644 index 5a1782c89e7b..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions'; -import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, startInactiveSpan, startSpanManual } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { createTransactionForOtelSpan } from '../../../packages/opentelemetry/src/spanExporter'; -import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; - -describe('createTransactionForOtelSpan', () => { - beforeEach(() => { - mockSdkInit({ - tracesSampleRate: 1, - }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('works with a basic span', () => { - const span = startInactiveSpan({ name: 'test', startTime: 1733821670000 }); - span.end(1733821672000); - - const event = createTransactionForOtelSpan(span as any); - // we do not care about this here - delete event.sdkProcessingMetadata; - - expect(event).toEqual({ - contexts: { - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: { - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - 'sentry.origin': 'manual', - }, - origin: 'manual', - status: 'ok', - }, - otel: { - resource: { - 'service.name': 'opentelemetry-test', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': expect.any(String), - 'service.namespace': 'sentry', - 'service.version': SDK_VERSION, - }, - }, - }, - spans: [], - start_timestamp: 1733821670, - timestamp: 1733821672, - transaction: 'test', - type: 'transaction', - transaction_info: { source: 'custom' }, - }); - }); - - it('works with a http.server span', () => { - const span = startInactiveSpan({ - name: 'test', - startTime: 1733821670000, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200, - }, - }); - span.end(1733821672000); - - const event = createTransactionForOtelSpan(span as any); - // we do not care about this here - delete event.sdkProcessingMetadata; - - expect(event).toEqual({ - contexts: { - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - data: { - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - 'sentry.origin': 'manual', - 'sentry.op': 'http.server', - 'http.response.status_code': 200, - }, - origin: 'manual', - status: 'ok', - op: 'http.server', - }, - otel: { - resource: { - 'service.name': 'opentelemetry-test', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': expect.any(String), - 'service.namespace': 'sentry', - 'service.version': SDK_VERSION, - }, - }, - response: { - status_code: 200, - }, - }, - spans: [], - start_timestamp: 1733821670, - timestamp: 1733821672, - transaction: 'test', - type: 'transaction', - transaction_info: { source: 'custom' }, - }); - }); - - it('adds span link to the trace context when adding with addLink()', () => { - const span = startInactiveSpan({ name: 'parent1' }); - span.end(); - - startSpanManual({ name: 'rootSpan' }, rootSpan => { - rootSpan.addLink({ context: span.spanContext(), attributes: { 'sentry.link.type': 'previous_trace' } }); - rootSpan.end(); - - const prevTraceId = span.spanContext().traceId; - const prevSpanId = span.spanContext().spanId; - const event = createTransactionForOtelSpan(rootSpan as any); - - expect(event.contexts?.trace).toEqual( - expect.objectContaining({ - links: [ - expect.objectContaining({ - attributes: { 'sentry.link.type': 'previous_trace' }, - sampled: true, - trace_id: expect.stringMatching(prevTraceId), - span_id: expect.stringMatching(prevSpanId), - }), - ], - }), - ); - }); - }); - - it('adds span link to the trace context when linked in span options', () => { - const span = startInactiveSpan({ name: 'parent1' }); - - const prevTraceId = span.spanContext().traceId; - const prevSpanId = span.spanContext().spanId; - - const linkedSpan = startInactiveSpan({ - name: 'parent2', - links: [{ context: span.spanContext(), attributes: { 'sentry.link.type': 'previous_trace' } }], - }); - - span.end(); - linkedSpan.end(); - - const event = createTransactionForOtelSpan(linkedSpan as any); - - expect(event.contexts?.trace).toEqual( - expect.objectContaining({ - links: [ - expect.objectContaining({ - attributes: { 'sentry.link.type': 'previous_trace' }, - sampled: true, - trace_id: expect.stringMatching(prevTraceId), - span_id: expect.stringMatching(prevSpanId), - }), - ], - }), - ); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/trace.test.ts b/dev-packages/opentelemetry-v2-tests/test/trace.test.ts deleted file mode 100644 index 52d5e67477d0..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/trace.test.ts +++ /dev/null @@ -1,1935 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import type { Span, TimeInput } from '@opentelemetry/api'; -import { context, ROOT_CONTEXT, SpanKind, trace, TraceFlags } from '@opentelemetry/api'; -import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; -import type { Event, Scope } from '@sentry/core'; -import { - getClient, - getCurrentScope, - getDynamicSamplingContextFromClient, - getDynamicSamplingContextFromSpan, - getRootSpan, - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - spanIsSampled, - spanToJSON, - suppressTracing, - withScope, -} from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { - continueTrace, - startInactiveSpan, - startSpan, - startSpanManual, -} from '../../../packages/opentelemetry/src/trace'; -import type { AbstractSpan } from '../../../packages/opentelemetry/src/types'; -import { getActiveSpan } from '../../../packages/opentelemetry/src/utils/getActiveSpan'; -import { getParentSpanId } from '../../../packages/opentelemetry/src/utils/getParentSpanId'; -import { getSamplingDecision } from '../../../packages/opentelemetry/src/utils/getSamplingDecision'; -import { getSpanKind } from '../../../packages/opentelemetry/src/utils/getSpanKind'; -import { makeTraceState } from '../../../packages/opentelemetry/src/utils/makeTraceState'; -import { spanHasAttributes, spanHasName } from '../../../packages/opentelemetry/src/utils/spanTypes'; -import { isSpan } from './helpers/isSpan'; -import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; - -describe('trace', () => { - beforeEach(() => { - mockSdkInit({ tracesSampleRate: 1 }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - describe('startSpan', () => { - it('works with a sync callback', () => { - const spans: Span[] = []; - - expect(getActiveSpan()).toEqual(undefined); - - const res = startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - spans.push(outerSpan); - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getActiveSpan()).toEqual(outerSpan); - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - spans.push(innerSpan); - - expect(getSpanName(innerSpan)).toEqual('inner'); - expect(getActiveSpan()).toEqual(innerSpan); - }); - - return 'test value'; - }); - - expect(res).toEqual('test value'); - - expect(getActiveSpan()).toEqual(undefined); - expect(spans).toHaveLength(2); - const [outerSpan, innerSpan] = spans as [Span, Span]; - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getSpanName(innerSpan)).toEqual('inner'); - - expect(getSpanEndTime(outerSpan)).not.toEqual([0, 0]); - expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]); - }); - - it('works with an async callback', async () => { - const spans: Span[] = []; - - expect(getActiveSpan()).toEqual(undefined); - - const res = await startSpan({ name: 'outer' }, async outerSpan => { - expect(outerSpan).toBeDefined(); - spans.push(outerSpan); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getActiveSpan()).toEqual(outerSpan); - - await startSpan({ name: 'inner' }, async innerSpan => { - expect(innerSpan).toBeDefined(); - spans.push(innerSpan); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(getSpanName(innerSpan)).toEqual('inner'); - expect(getActiveSpan()).toEqual(innerSpan); - }); - - return 'test value'; - }); - - expect(res).toEqual('test value'); - - expect(getActiveSpan()).toEqual(undefined); - expect(spans).toHaveLength(2); - const [outerSpan, innerSpan] = spans as [Span, Span]; - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getSpanName(innerSpan)).toEqual('inner'); - - expect(getSpanEndTime(outerSpan)).not.toEqual([0, 0]); - expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]); - }); - - it('works with multiple parallel calls', () => { - const spans1: Span[] = []; - const spans2: Span[] = []; - - expect(getActiveSpan()).toEqual(undefined); - - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - spans1.push(outerSpan); - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getActiveSpan()).toEqual(outerSpan); - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - spans1.push(innerSpan); - - expect(getSpanName(innerSpan)).toEqual('inner'); - expect(getActiveSpan()).toEqual(innerSpan); - }); - }); - - startSpan({ name: 'outer2' }, outerSpan => { - expect(outerSpan).toBeDefined(); - spans2.push(outerSpan); - - expect(getSpanName(outerSpan)).toEqual('outer2'); - expect(getActiveSpan()).toEqual(outerSpan); - - startSpan({ name: 'inner2' }, innerSpan => { - expect(innerSpan).toBeDefined(); - spans2.push(innerSpan); - - expect(getSpanName(innerSpan)).toEqual('inner2'); - expect(getActiveSpan()).toEqual(innerSpan); - }); - }); - - expect(getActiveSpan()).toEqual(undefined); - expect(spans1).toHaveLength(2); - expect(spans2).toHaveLength(2); - }); - - it('works with multiple parallel async calls', async () => { - const spans1: Span[] = []; - const spans2: Span[] = []; - - expect(getActiveSpan()).toEqual(undefined); - - const promise1 = startSpan({ name: 'outer' }, async outerSpan => { - expect(outerSpan).toBeDefined(); - spans1.push(outerSpan); - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getActiveSpan()).toEqual(outerSpan); - expect(getRootSpan(outerSpan)).toEqual(outerSpan); - - await new Promise(resolve => setTimeout(resolve, 10)); - - await startSpan({ name: 'inner' }, async innerSpan => { - expect(innerSpan).toBeDefined(); - spans1.push(innerSpan); - - expect(getSpanName(innerSpan)).toEqual('inner'); - expect(getActiveSpan()).toEqual(innerSpan); - expect(getRootSpan(innerSpan)).toEqual(outerSpan); - }); - }); - - const promise2 = startSpan({ name: 'outer2' }, async outerSpan => { - expect(outerSpan).toBeDefined(); - spans2.push(outerSpan); - - expect(getSpanName(outerSpan)).toEqual('outer2'); - expect(getActiveSpan()).toEqual(outerSpan); - expect(getRootSpan(outerSpan)).toEqual(outerSpan); - - await new Promise(resolve => setTimeout(resolve, 10)); - - await startSpan({ name: 'inner2' }, async innerSpan => { - expect(innerSpan).toBeDefined(); - spans2.push(innerSpan); - - expect(getSpanName(innerSpan)).toEqual('inner2'); - expect(getActiveSpan()).toEqual(innerSpan); - expect(getRootSpan(innerSpan)).toEqual(outerSpan); - }); - }); - - await Promise.all([promise1, promise2]); - - expect(getActiveSpan()).toEqual(undefined); - expect(spans1).toHaveLength(2); - expect(spans2).toHaveLength(2); - }); - - it('allows to pass context arguments', () => { - startSpan( - { - name: 'outer', - }, - span => { - expect(span).toBeDefined(); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - }); - }, - ); - - startSpan( - { - name: 'outer', - op: 'my-op', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin', - }, - }, - span => { - expect(span).toBeDefined(); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'my-op', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - }); - }, - ); - }); - - it('allows to pass base SpanOptions', () => { - const date = [5000, 0] as TimeInput; - - startSpan( - { - name: 'outer', - kind: SpanKind.CLIENT, - attributes: { - test1: 'test 1', - test2: 2, - }, - startTime: date, - }, - span => { - expect(span).toBeDefined(); - expect(getSpanName(span)).toEqual('outer'); - expect(getSpanStartTime(span)).toEqual(date); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - test1: 'test 1', - test2: 2, - }); - expect(getSpanKind(span)).toEqual(SpanKind.CLIENT); - }, - ); - }); - - it('allows to pass a startTime in seconds', () => { - const startTime = 1708504860.961; - const start = startSpan({ name: 'outer', startTime: startTime }, span => { - return getSpanStartTime(span); - }); - - expect(start).toEqual([1708504860, 961000000]); - }); - - it('allows to pass a scope', () => { - const initialScope = getCurrentScope(); - - let manualScope: Scope; - let parentSpan: Span; - - // "hack" to create a manual scope with a parent span - startSpanManual({ name: 'detached' }, span => { - parentSpan = span; - manualScope = getCurrentScope(); - manualScope.setTag('manual', 'tag'); - }); - - expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag' }); - expect(getCurrentScope()).not.toBe(manualScope!); - - getCurrentScope().setTag('outer', 'tag'); - - startSpan({ name: 'GET users/[id]', scope: manualScope! }, span => { - // the current scope in the callback is a fork of the manual scope - expect(getCurrentScope()).not.toBe(initialScope); - expect(getCurrentScope()).not.toBe(manualScope); - expect(getCurrentScope().getScopeData().tags).toEqual({ manual: 'tag' }); - - // getActiveSpan returns the correct span - expect(getActiveSpan()).toBe(span); - - // span hierarchy is correct - expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId); - - // scope data modifications are isolated between original and forked manual scope - getCurrentScope().setTag('inner', 'tag'); - manualScope!.setTag('manual-scope-inner', 'tag'); - - expect(getCurrentScope().getScopeData().tags).toEqual({ manual: 'tag', inner: 'tag' }); - expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag', 'manual-scope-inner': 'tag' }); - }); - - // manualScope modifications remain set outside the callback - expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag', 'manual-scope-inner': 'tag' }); - - // current scope is reset back to initial scope - expect(getCurrentScope()).toBe(initialScope); - expect(getCurrentScope().getScopeData().tags).toEqual({ outer: 'tag' }); - - // although the manual span is still running, it's no longer active due to being outside of the callback - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass a parentSpan', () => { - let parentSpan: Span; - - startSpanManual({ name: 'detached' }, span => { - parentSpan = span; - }); - - startSpan({ name: 'GET users/[id]', parentSpan: parentSpan! }, span => { - expect(getActiveSpan()).toBe(span); - expect(spanToJSON(span).parent_span_id).toBe(parentSpan.spanContext().spanId); - }); - - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass parentSpan=null', () => { - startSpan({ name: 'GET users/[id' }, () => { - startSpan({ name: 'child', parentSpan: null }, span => { - expect(spanToJSON(span).parent_span_id).toBe(undefined); - }); - }); - }); - - it('allows to add span links', () => { - const rawSpan1 = startInactiveSpan({ name: 'pageload_span' }); - - // @ts-expect-error links exists on span - expect(rawSpan1?.links).toEqual([]); - - const span1JSON = spanToJSON(rawSpan1); - - startSpan({ name: '/users/:id' }, rawSpan2 => { - rawSpan2.addLink({ - context: rawSpan1.spanContext(), - attributes: { - 'sentry.link.type': 'previous_trace', - }, - }); - - const span2LinkJSON = spanToJSON(rawSpan2).links?.[0]; - - expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace'); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id); - expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id); - expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id); - }); - }); - - it('allows to pass span links in span options', () => { - const rawSpan1 = startInactiveSpan({ name: 'pageload_span' }); - - // @ts-expect-error links exists on span - expect(rawSpan1?.links).toEqual([]); - - const span1JSON = spanToJSON(rawSpan1); - - startSpan( - { - name: '/users/:id', - links: [ - { - context: rawSpan1.spanContext(), - attributes: { 'sentry.link.type': 'previous_trace' }, - }, - ], - }, - rawSpan2 => { - const span2LinkJSON = spanToJSON(rawSpan2).links?.[0]; - - expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace'); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id); - expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id); - expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id); - }, - ); - }); - - it('allows to force a transaction with forceTransaction=true', async () => { - const client = getClient()!; - const transactionEvents: Event[] = []; - - client.getOptions().beforeSendTransaction = event => { - transactionEvents.push({ - ...event, - sdkProcessingMetadata: { - dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, - }, - }); - return event; - }; - - startSpan({ name: 'outer transaction' }, () => { - startSpan({ name: 'inner span' }, () => { - startSpan({ name: 'inner transaction', forceTransaction: true }, () => { - startSpan({ name: 'inner span 2' }, () => { - // all good - }); - }); - }); - }); - - await client.flush(); - - const normalizedTransactionEvents = transactionEvents.map(event => { - return { - ...event, - spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), - }; - }); - - expect(normalizedTransactionEvents).toHaveLength(2); - - const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); - const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); - - const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; - // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0]?.id; - const innerSpanId = innerTransaction?.contexts?.trace?.span_id; - - expect(outerTraceId).toBeDefined(); - expect(innerParentSpanId).toBeDefined(); - expect(innerSpanId).toBeDefined(); - // inner span ID should _not_ be the parent span ID, but the id of the new span - expect(innerSpanId).not.toEqual(innerParentSpanId); - - expect(outerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - 'sentry.origin': 'manual', - }, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'manual', - status: 'ok', - }); - expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.transaction).toEqual('outer transaction'); - expect(outerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - - expect(innerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.origin': 'manual', - }, - parent_span_id: innerParentSpanId, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: outerTraceId, - origin: 'manual', - status: 'ok', - }); - expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.transaction).toEqual('inner transaction'); - expect(innerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - }); - - // TODO: propagation scope is not picked up by spans... - - describe('onlyIfParent', () => { - it('does not create a span if there is no parent', () => { - const span = startSpan({ name: 'test span', onlyIfParent: true }, span => { - return span; - }); - - expect(isSpan(span)).toBe(false); - }); - - it('creates a span if there is a parent', () => { - const span = startSpan({ name: 'parent span' }, () => { - const span = startSpan({ name: 'test span', onlyIfParent: true }, span => { - return span; - }); - - return span; - }); - - expect(isSpan(span)).toBe(true); - }); - }); - }); - - describe('startInactiveSpan', () => { - it('works at the root', () => { - const span = startInactiveSpan({ name: 'test' }); - - expect(span).toBeDefined(); - expect(getSpanName(span)).toEqual('test'); - expect(getSpanEndTime(span)).toEqual([0, 0]); - expect(getActiveSpan()).toBeUndefined(); - - span.end(); - - expect(getSpanEndTime(span)).not.toEqual([0, 0]); - expect(getActiveSpan()).toBeUndefined(); - }); - - it('works as a child span', () => { - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(getActiveSpan()).toEqual(outerSpan); - - const innerSpan = startInactiveSpan({ name: 'test' }); - - expect(innerSpan).toBeDefined(); - expect(getSpanName(innerSpan)).toEqual('test'); - expect(getSpanEndTime(innerSpan)).toEqual([0, 0]); - expect(getActiveSpan()).toEqual(outerSpan); - - innerSpan.end(); - - expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]); - expect(getActiveSpan()).toEqual(outerSpan); - }); - }); - - it('allows to pass context arguments', () => { - const span = startInactiveSpan({ - name: 'outer', - }); - - expect(span).toBeDefined(); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - }); - - const span2 = startInactiveSpan({ - name: 'outer', - op: 'my-op', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin', - }, - }); - - expect(span2).toBeDefined(); - expect(getSpanAttributes(span2)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'my-op', - }); - }); - - it('allows to pass base SpanOptions', () => { - const date = [5000, 0] as TimeInput; - - const span = startInactiveSpan({ - name: 'outer', - kind: SpanKind.CLIENT, - attributes: { - test1: 'test 1', - test2: 2, - }, - startTime: date, - }); - - expect(span).toBeDefined(); - expect(getSpanName(span)).toEqual('outer'); - expect(getSpanStartTime(span)).toEqual(date); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - test1: 'test 1', - test2: 2, - }); - expect(getSpanKind(span)).toEqual(SpanKind.CLIENT); - }); - - it('allows to pass a startTime in seconds', () => { - const startTime = 1708504860.961; - const span = startInactiveSpan({ name: 'outer', startTime: startTime }); - - expect(getSpanStartTime(span)).toEqual([1708504860, 961000000]); - }); - - it('allows to pass a scope', () => { - const initialScope = getCurrentScope(); - - let manualScope: Scope; - - const parentSpan = startSpanManual({ name: 'detached' }, span => { - manualScope = getCurrentScope(); - manualScope.setTag('manual', 'tag'); - return span; - }); - - getCurrentScope().setTag('outer', 'tag'); - - const span = startInactiveSpan({ name: 'GET users/[id]', scope: manualScope! }); - expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId); - - expect(getCurrentScope()).toBe(initialScope); - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass a parentSpan', () => { - let parentSpan: Span; - - startSpanManual({ name: 'detached' }, span => { - parentSpan = span; - }); - - const span = startInactiveSpan({ name: 'GET users/[id]', parentSpan: parentSpan! }); - - expect(getActiveSpan()).toBe(undefined); - expect(spanToJSON(span).parent_span_id).toBe(parentSpan!.spanContext().spanId); - - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass parentSpan=null', () => { - startSpan({ name: 'outer' }, () => { - const span = startInactiveSpan({ name: 'test span', parentSpan: null }); - expect(spanToJSON(span).parent_span_id).toBe(undefined); - span.end(); - }); - }); - - it('allows to pass span links in span options', () => { - const rawSpan1 = startInactiveSpan({ name: 'pageload_span' }); - - // @ts-expect-error links exists on span - expect(rawSpan1?.links).toEqual([]); - - const rawSpan2 = startInactiveSpan({ - name: 'GET users/[id]', - links: [ - { - context: rawSpan1.spanContext(), - attributes: { 'sentry.link.type': 'previous_trace' }, - }, - ], - }); - - const span1JSON = spanToJSON(rawSpan1); - const span2JSON = spanToJSON(rawSpan2); - const span2LinkJSON = span2JSON.links?.[0]; - - expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace'); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id); - expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id); - expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id); - - // sampling decision is inherited - expect(span2LinkJSON?.sampled).toBe(Boolean(spanToJSON(rawSpan1).data['sentry.sample_rate'])); - }); - - it('allows to force a transaction with forceTransaction=true', async () => { - const client = getClient()!; - const transactionEvents: Event[] = []; - - client.getOptions().beforeSendTransaction = event => { - transactionEvents.push({ - ...event, - sdkProcessingMetadata: { - dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, - }, - }); - return event; - }; - - startSpan({ name: 'outer transaction' }, () => { - startSpan({ name: 'inner span' }, () => { - const innerTransaction = startInactiveSpan({ name: 'inner transaction', forceTransaction: true }); - innerTransaction.end(); - }); - }); - - await client.flush(); - - const normalizedTransactionEvents = transactionEvents.map(event => { - return { - ...event, - spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), - }; - }); - - expect(normalizedTransactionEvents).toHaveLength(2); - - const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); - const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); - - const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; - // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0]?.id; - const innerSpanId = innerTransaction?.contexts?.trace?.span_id; - - expect(outerTraceId).toBeDefined(); - expect(innerParentSpanId).toBeDefined(); - expect(innerSpanId).toBeDefined(); - // inner span ID should _not_ be the parent span ID, but the id of the new span - expect(innerSpanId).not.toEqual(innerParentSpanId); - - expect(outerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - 'sentry.origin': 'manual', - }, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'manual', - status: 'ok', - }); - expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.transaction).toEqual('outer transaction'); - expect(outerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - - expect(innerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.origin': 'manual', - }, - parent_span_id: innerParentSpanId, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: outerTraceId, - origin: 'manual', - status: 'ok', - }); - expect(innerTransaction?.spans).toEqual([]); - expect(innerTransaction?.transaction).toEqual('inner transaction'); - expect(innerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - }); - - describe('onlyIfParent', () => { - it('does not create a span if there is no parent', () => { - const span = startInactiveSpan({ name: 'test span', onlyIfParent: true }); - - expect(isSpan(span)).toBe(false); - }); - - it('creates a span if there is a parent', () => { - const span = startSpan({ name: 'parent span' }, () => { - const span = startInactiveSpan({ name: 'test span', onlyIfParent: true }); - - return span; - }); - - expect(isSpan(span)).toBe(true); - }); - }); - - it('includes the scope at the time the span was started when finished', async () => { - const beforeSendTransaction = vi.fn(event => event); - - const client = getClient()!; - - client.getOptions().beforeSendTransaction = beforeSendTransaction; - - let span: Span; - - const scope = getCurrentScope(); - scope.setTag('outer', 'foo'); - - withScope(scope => { - scope.setTag('scope', 1); - span = startInactiveSpan({ name: 'my-span' }); - scope.setTag('scope_after_span', 2); - }); - - withScope(scope => { - scope.setTag('scope', 2); - span.end(); - }); - - await client.flush(); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - expect(beforeSendTransaction).toHaveBeenCalledWith( - expect.objectContaining({ - tags: expect.objectContaining({ - outer: 'foo', - scope: 1, - scope_after_span: 2, - }), - }), - expect.anything(), - ); - }); - }); - - describe('startSpanManual', () => { - it('does not automatically finish the span', () => { - expect(getActiveSpan()).toEqual(undefined); - - let _outerSpan: Span | undefined; - let _innerSpan: Span | undefined; - - const res = startSpanManual({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - _outerSpan = outerSpan; - - expect(getSpanName(outerSpan)).toEqual('outer'); - expect(getActiveSpan()).toEqual(outerSpan); - - startSpanManual({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - _innerSpan = innerSpan; - - expect(getSpanName(innerSpan)).toEqual('inner'); - expect(getActiveSpan()).toEqual(innerSpan); - }); - - expect(getSpanEndTime(_innerSpan!)).toEqual([0, 0]); - - _innerSpan!.end(); - - expect(getSpanEndTime(_innerSpan!)).not.toEqual([0, 0]); - - return 'test value'; - }); - - expect(getSpanEndTime(_outerSpan!)).toEqual([0, 0]); - - _outerSpan!.end(); - - expect(getSpanEndTime(_outerSpan!)).not.toEqual([0, 0]); - - expect(res).toEqual('test value'); - - expect(getActiveSpan()).toEqual(undefined); - }); - - it('allows to pass base SpanOptions', () => { - const date = [5000, 0] as TimeInput; - - startSpanManual( - { - name: 'outer', - kind: SpanKind.CLIENT, - attributes: { - test1: 'test 1', - test2: 2, - }, - startTime: date, - }, - span => { - expect(span).toBeDefined(); - expect(getSpanName(span)).toEqual('outer'); - expect(getSpanStartTime(span)).toEqual(date); - expect(getSpanAttributes(span)).toEqual({ - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - test1: 'test 1', - test2: 2, - }); - expect(getSpanKind(span)).toEqual(SpanKind.CLIENT); - }, - ); - }); - - it('allows to pass a startTime in seconds', () => { - const startTime = 1708504860.961; - const start = startSpanManual({ name: 'outer', startTime: startTime }, span => { - const start = getSpanStartTime(span); - span.end(); - return start; - }); - - expect(start).toEqual([1708504860, 961000000]); - }); - - it('allows to pass a scope', () => { - const initialScope = getCurrentScope(); - - let manualScope: Scope; - let parentSpan: Span; - - startSpanManual({ name: 'detached' }, span => { - parentSpan = span; - manualScope = getCurrentScope(); - manualScope.setTag('manual', 'tag'); - }); - - getCurrentScope().setTag('outer', 'tag'); - - startSpanManual({ name: 'GET users/[id]', scope: manualScope! }, span => { - expect(getCurrentScope()).not.toBe(initialScope); - - expect(getCurrentScope()).toEqual(manualScope); - expect(getActiveSpan()).toBe(span); - - expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId); - - span.end(); - }); - - expect(getCurrentScope()).toBe(initialScope); - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass a parentSpan', () => { - let parentSpan: Span; - - startSpanManual({ name: 'detached' }, span => { - parentSpan = span; - }); - - startSpanManual({ name: 'GET users/[id]', parentSpan: parentSpan! }, span => { - expect(getActiveSpan()).toBe(span); - expect(spanToJSON(span).parent_span_id).toBe(parentSpan.spanContext().spanId); - - span.end(); - }); - - expect(getActiveSpan()).toBe(undefined); - }); - - it('allows to pass parentSpan=null', () => { - startSpan({ name: 'outer' }, () => { - startSpanManual({ name: 'GET users/[id]', parentSpan: null }, span => { - expect(spanToJSON(span).parent_span_id).toBe(undefined); - span.end(); - }); - }); - }); - - it('allows to add span links', () => { - const rawSpan1 = startInactiveSpan({ name: 'pageload_span' }); - - // @ts-expect-error links exists on span - expect(rawSpan1?.links).toEqual([]); - - const span1JSON = spanToJSON(rawSpan1); - - startSpanManual({ name: '/users/:id' }, rawSpan2 => { - rawSpan2.addLink({ - context: rawSpan1.spanContext(), - attributes: { - 'sentry.link.type': 'previous_trace', - }, - }); - - const span2LinkJSON = spanToJSON(rawSpan2).links?.[0]; - - expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace'); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id); - expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id); - expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id); - }); - }); - - it('allows to pass span links in span options', () => { - const rawSpan1 = startInactiveSpan({ name: 'pageload_span' }); - - // @ts-expect-error links exists on span - expect(rawSpan1?.links).toEqual([]); - - const span1JSON = spanToJSON(rawSpan1); - - startSpanManual( - { - name: '/users/:id', - links: [ - { - context: rawSpan1.spanContext(), - attributes: { 'sentry.link.type': 'previous_trace' }, - }, - ], - }, - rawSpan2 => { - const span2LinkJSON = spanToJSON(rawSpan2).links?.[0]; - - expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace'); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id); - expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id); - - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId); - // @ts-expect-error links and _spanContext exist on span - expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id); - expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id); - }, - ); - }); - - it('allows to force a transaction with forceTransaction=true', async () => { - const client = getClient()!; - const transactionEvents: Event[] = []; - - client.getOptions().beforeSendTransaction = event => { - transactionEvents.push({ - ...event, - sdkProcessingMetadata: { - dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, - }, - }); - return event; - }; - - startSpanManual({ name: 'outer transaction' }, span => { - startSpanManual({ name: 'inner span' }, span => { - startSpanManual({ name: 'inner transaction', forceTransaction: true }, span => { - startSpanManual({ name: 'inner span 2' }, span => { - // all good - span.end(); - }); - span.end(); - }); - span.end(); - }); - span.end(); - }); - - await client.flush(); - - const normalizedTransactionEvents = transactionEvents.map(event => { - return { - ...event, - spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), - }; - }); - - expect(normalizedTransactionEvents).toHaveLength(2); - - const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); - const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); - - const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; - // The inner transaction should be a child of the last span of the outer transaction - const innerParentSpanId = outerTransaction?.spans?.[0]?.id; - const innerSpanId = innerTransaction?.contexts?.trace?.span_id; - - expect(outerTraceId).toBeDefined(); - expect(innerParentSpanId).toBeDefined(); - expect(innerSpanId).toBeDefined(); - // inner span ID should _not_ be the parent span ID, but the id of the new span - expect(innerSpanId).not.toEqual(innerParentSpanId); - - expect(outerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.sample_rate': 1, - 'sentry.origin': 'manual', - }, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - origin: 'manual', - status: 'ok', - }); - expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.transaction).toEqual('outer transaction'); - expect(outerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - - expect(innerTransaction?.contexts?.trace).toEqual({ - data: { - 'sentry.source': 'custom', - 'sentry.origin': 'manual', - }, - parent_span_id: innerParentSpanId, - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: outerTraceId, - origin: 'manual', - status: 'ok', - }); - expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.transaction).toEqual('inner transaction'); - expect(innerTransaction?.sdkProcessingMetadata).toEqual({ - dynamicSamplingContext: { - environment: 'production', - public_key: 'username', - trace_id: outerTraceId, - sample_rate: '1', - transaction: 'outer transaction', - sampled: 'true', - sample_rand: expect.any(String), - }, - }); - }); - - describe('onlyIfParent', () => { - it('does not create a span if there is no parent', () => { - const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => { - return span; - }); - - expect(isSpan(span)).toBe(false); - }); - - it('creates a span if there is a parent', () => { - const span = startSpan({ name: 'parent span' }, () => { - const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => { - return span; - }); - - return span; - }); - - expect(isSpan(span)).toBe(true); - }); - }); - }); - - describe('propagation', () => { - it('starts new trace, if there is no parent', () => { - withScope(scope => { - const propagationContext = scope.getPropagationContext(); - const span = startInactiveSpan({ name: 'test span' }); - - expect(span).toBeDefined(); - const traceId = spanToJSON(span).trace_id; - expect(traceId).toMatch(/[a-f0-9]{32}/); - expect(spanToJSON(span).parent_span_id).toBe(undefined); - expect(spanToJSON(span).trace_id).not.toEqual(propagationContext.traceId); - - expect(getDynamicSamplingContextFromSpan(span)).toEqual({ - trace_id: traceId, - environment: 'production', - public_key: 'username', - sample_rate: '1', - sampled: 'true', - transaction: 'test span', - sample_rand: expect.any(String), - }); - }); - }); - - // Note: This _should_ never happen, when we have an incoming trace, we should always have a parent span - it('starts new trace, ignoring parentSpanId, if there is no parent', () => { - withScope(scope => { - const propagationContext = scope.getPropagationContext(); - propagationContext.parentSpanId = '1121201211212012'; - const span = startInactiveSpan({ name: 'test span' }); - - expect(span).toBeDefined(); - const traceId = spanToJSON(span).trace_id; - expect(traceId).toMatch(/[a-f0-9]{32}/); - expect(spanToJSON(span).parent_span_id).toBe(undefined); - expect(spanToJSON(span).trace_id).not.toEqual(propagationContext.traceId); - - expect(getDynamicSamplingContextFromSpan(span)).toEqual({ - environment: 'production', - public_key: 'username', - trace_id: traceId, - sample_rate: '1', - sampled: 'true', - transaction: 'test span', - sample_rand: expect.any(String), - }); - }); - }); - - it('picks up the trace context from the parent without DSC', () => { - withScope(scope => { - const propagationContext = scope.getPropagationContext(); - - startSpan({ name: 'parent span' }, parentSpan => { - const span = startInactiveSpan({ name: 'test span' }); - - expect(span).toBeDefined(); - expect(spanToJSON(span).trace_id).toEqual(parentSpan.spanContext().traceId); - expect(spanToJSON(span).parent_span_id).toEqual(parentSpan.spanContext().spanId); - expect(getDynamicSamplingContextFromSpan(span)).toEqual({ - ...getDynamicSamplingContextFromClient(propagationContext.traceId, getClient()!), - trace_id: parentSpan.spanContext().traceId, - transaction: 'parent span', - sampled: 'true', - sample_rate: '1', - sample_rand: expect.any(String), - }); - }); - }); - }); - - it('picks up the trace context from the parent with DSC', () => { - withScope(() => { - const ctx = trace.setSpanContext(ROOT_CONTEXT, { - traceId: '12312012123120121231201212312012', - spanId: '1121201211212012', - isRemote: false, - traceFlags: TraceFlags.SAMPLED, - traceState: makeTraceState({ - dsc: { - release: '1.0', - environment: 'production', - }, - }), - }); - - context.with(ctx, () => { - const span = startInactiveSpan({ name: 'test span' }); - - expect(span).toBeDefined(); - expect(spanToJSON(span).trace_id).toEqual('12312012123120121231201212312012'); - expect(spanToJSON(span).parent_span_id).toEqual('1121201211212012'); - expect(getDynamicSamplingContextFromSpan(span)).toEqual({ - release: '1.0', - environment: 'production', - }); - }); - }); - }); - - it('picks up the trace context from a remote parent', () => { - withScope(() => { - const ctx = trace.setSpanContext(ROOT_CONTEXT, { - traceId: '12312012123120121231201212312012', - spanId: '1121201211212012', - isRemote: true, - traceFlags: TraceFlags.SAMPLED, - traceState: makeTraceState({ - dsc: { - release: '1.0', - environment: 'production', - }, - }), - }); - - context.with(ctx, () => { - const span = startInactiveSpan({ name: 'test span' }); - - expect(span).toBeDefined(); - expect(spanToJSON(span).trace_id).toEqual('12312012123120121231201212312012'); - expect(spanToJSON(span).parent_span_id).toEqual('1121201211212012'); - expect(getDynamicSamplingContextFromSpan(span)).toEqual({ - release: '1.0', - environment: 'production', - }); - }); - }); - }); - }); -}); - -describe('trace (tracing disabled)', () => { - beforeEach(() => { - mockSdkInit({ tracesSampleRate: 0 }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('startSpan calls callback without span', () => { - const val = startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(false); - - return 'test value'; - }); - - expect(val).toEqual('test value'); - }); - - it('startInactiveSpan returns a NonRecordinSpan', () => { - const span = startInactiveSpan({ name: 'test' }); - - expect(span).toBeDefined(); - expect(span.isRecording()).toBe(false); - }); -}); - -describe('trace (sampling)', () => { - afterEach(async () => { - await cleanupOtel(); - vi.clearAllMocks(); - }); - - it('samples with a tracesSampleRate, when Math.random() > tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - mockSdkInit({ tracesSampleRate: 0.5 }); - - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(false); - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - expect(innerSpan.isRecording()).toBe(false); - }); - }); - }); - - it('samples with a tracesSampleRate, when Math.random() < tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.4); - - mockSdkInit({ tracesSampleRate: 0.5 }); - - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(true); - // All fields are empty for NonRecordingSpan - expect(getSpanName(outerSpan)).toBe('outer'); - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - expect(innerSpan.isRecording()).toBe(true); - expect(getSpanName(innerSpan)).toBe('inner'); - }); - }); - }); - - it('positive parent sampling takes precedence over tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - mockSdkInit({ tracesSampleRate: 1 }); - - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(true); - expect(getSpanName(outerSpan)).toBe('outer'); - - // Now let's mutate the tracesSampleRate so that the next entry _should_ not be sampled - // but it will because of parent sampling - const client = getClient(); - client!.getOptions().tracesSampleRate = 0.5; - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - expect(innerSpan.isRecording()).toBe(true); - expect(getSpanName(innerSpan)).toBe('inner'); - }); - }); - }); - - it('negative parent sampling takes precedence over tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - mockSdkInit({ tracesSampleRate: 0.5 }); - - // This will def. be unsampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(false); - - // Now let's mutate the tracesSampleRate so that the next entry _should_ be sampled - // but it will remain unsampled because of parent sampling - const client = getClient(); - client!.getOptions().tracesSampleRate = 1; - - startSpan({ name: 'inner' }, innerSpan => { - expect(innerSpan).toBeDefined(); - expect(innerSpan.isRecording()).toBe(false); - }); - }); - }); - - it('positive remote parent sampling takes precedence over tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - mockSdkInit({ tracesSampleRate: 0.5 }); - - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - const parentSpanId = '6e0c63257de34c92'; - - const spanContext = { - traceId, - spanId: parentSpanId, - sampled: true, - isRemote: true, - traceFlags: TraceFlags.SAMPLED, - }; - - // We simulate the correct context we'd normally get from the SentryPropagator - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(true); - expect(getSpanName(outerSpan)).toBe('outer'); - }); - }); - }); - - it('negative remote parent sampling takes precedence over tracesSampleRate', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - mockSdkInit({ tracesSampleRate: 0.5 }); - - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - const parentSpanId = '6e0c63257de34c92'; - - const spanContext = { - traceId, - spanId: parentSpanId, - sampled: false, - isRemote: true, - traceFlags: TraceFlags.NONE, - }; - - // We simulate the correct context we'd normally get from the SentryPropagator - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(false); - }); - }); - }); - - it('samples with a tracesSampler returning a boolean', () => { - let tracesSamplerResponse: boolean = true; - - const tracesSampler = vi.fn(() => { - return tracesSamplerResponse; - }); - - mockSdkInit({ tracesSampler }); - - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - }); - - expect(tracesSampler).toBeCalledTimes(1); - expect(tracesSampler).toHaveBeenLastCalledWith({ - parentSampled: undefined, - name: 'outer', - attributes: {}, - inheritOrSampleWith: expect.any(Function), - }); - - // Now return `false`, it should not sample - tracesSamplerResponse = false; - - startSpan({ name: 'outer2' }, outerSpan => { - expect(outerSpan.isRecording()).toBe(false); - - startSpan({ name: 'inner2' }, innerSpan => { - expect(innerSpan.isRecording()).toBe(false); - }); - }); - - expect(tracesSampler).toHaveBeenCalledTimes(2); - expect(tracesSampler).toHaveBeenCalledWith( - expect.objectContaining({ - parentSampled: undefined, - name: 'outer', - attributes: {}, - }), - ); - expect(tracesSampler).toHaveBeenCalledWith( - expect.objectContaining({ - parentSampled: undefined, - name: 'outer2', - attributes: {}, - }), - ); - - // Only root spans should go through the sampler - expect(tracesSampler).not.toHaveBeenLastCalledWith({ - name: 'inner2', - }); - }); - - it('samples with a tracesSampler returning a number', () => { - vi.spyOn(Math, 'random').mockImplementation(() => 0.6); - - let tracesSamplerResponse: number = 1; - - const tracesSampler = vi.fn(() => { - return tracesSamplerResponse; - }); - - mockSdkInit({ tracesSampler }); - - startSpan( - { - name: 'outer', - op: 'test.op', - attributes: { attr1: 'yes', attr2: 1 }, - }, - outerSpan => { - expect(outerSpan).toBeDefined(); - }, - ); - - expect(tracesSampler).toHaveBeenCalledTimes(1); - expect(tracesSampler).toHaveBeenLastCalledWith({ - parentSampled: undefined, - name: 'outer', - attributes: { - attr1: 'yes', - attr2: 1, - 'sentry.op': 'test.op', - }, - inheritOrSampleWith: expect.any(Function), - }); - - // Now return `0`, it should not sample - tracesSamplerResponse = 0; - - startSpan({ name: 'outer2' }, outerSpan => { - expect(outerSpan.isRecording()).toBe(false); - - startSpan({ name: 'inner2' }, innerSpan => { - expect(innerSpan.isRecording()).toBe(false); - }); - }); - - expect(tracesSampler).toHaveBeenCalledTimes(2); - expect(tracesSampler).toHaveBeenCalledWith( - expect.objectContaining({ - parentSampled: undefined, - name: 'outer2', - attributes: {}, - }), - ); - - // Only root spans should be passed to tracesSampler - expect(tracesSampler).not.toHaveBeenLastCalledWith( - expect.objectContaining({ - name: 'inner2', - }), - ); - - // Now return `0.4`, it should not sample - tracesSamplerResponse = 0.4; - - startSpan({ name: 'outer3' }, outerSpan => { - expect(outerSpan.isRecording()).toBe(false); - }); - - expect(tracesSampler).toHaveBeenCalledTimes(3); - expect(tracesSampler).toHaveBeenLastCalledWith({ - parentSampled: undefined, - name: 'outer3', - attributes: {}, - inheritOrSampleWith: expect.any(Function), - }); - }); - - it('samples with a tracesSampler even if parent is remotely sampled', () => { - const tracesSampler = vi.fn(() => { - return false; - }); - - mockSdkInit({ tracesSampler }); - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - const parentSpanId = '6e0c63257de34c92'; - - const spanContext = { - traceId, - spanId: parentSpanId, - sampled: true, - isRemote: true, - traceFlags: TraceFlags.SAMPLED, - }; - - // We simulate the correct context we'd normally get from the SentryPropagator - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan.isRecording()).toBe(false); - }); - }); - - expect(tracesSampler).toBeCalledTimes(1); - expect(tracesSampler).toHaveBeenLastCalledWith({ - parentSampled: true, - name: 'outer', - attributes: {}, - inheritOrSampleWith: expect.any(Function), - }); - }); - - it('ignores parent span context if it is invalid', () => { - mockSdkInit({ tracesSampleRate: 1 }); - const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; - - const spanContext = { - traceId, - spanId: 'INVALID', - traceFlags: TraceFlags.SAMPLED, - }; - - context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { - startSpan({ name: 'outer' }, span => { - expect(span.isRecording()).toBe(true); - expect(span.spanContext().spanId).not.toBe('INVALID'); - expect(span.spanContext().spanId).toMatch(/[a-f0-9]{16}/); - expect(span.spanContext().traceId).not.toBe(traceId); - expect(span.spanContext().traceId).toMatch(/[a-f0-9]{32}/); - }); - }); - }); -}); - -describe('HTTP methods (sampling)', () => { - beforeEach(() => { - mockSdkInit({ tracesSampleRate: 1 }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('does sample when HTTP method is other than OPTIONS or HEAD', () => { - const spanGET = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'GET' } }, span => { - return span; - }); - expect(spanIsSampled(spanGET)).toBe(true); - expect(getSamplingDecision(spanGET.spanContext())).toBe(true); - - const spanPOST = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'POST' } }, span => { - return span; - }); - expect(spanIsSampled(spanPOST)).toBe(true); - expect(getSamplingDecision(spanPOST.spanContext())).toBe(true); - - const spanPUT = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'PUT' } }, span => { - return span; - }); - expect(spanIsSampled(spanPUT)).toBe(true); - expect(getSamplingDecision(spanPUT.spanContext())).toBe(true); - - const spanDELETE = startSpanManual( - { name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'DELETE' } }, - span => { - return span; - }, - ); - expect(spanIsSampled(spanDELETE)).toBe(true); - expect(getSamplingDecision(spanDELETE.spanContext())).toBe(true); - }); - - it('does not sample when HTTP method is OPTIONS', () => { - const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'OPTIONS' } }, span => { - return span; - }); - expect(spanIsSampled(span)).toBe(false); - expect(getSamplingDecision(span.spanContext())).toBe(false); - }); - - it('does not sample when HTTP method is HEAD', () => { - const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'HEAD' } }, span => { - return span; - }); - expect(spanIsSampled(span)).toBe(false); - expect(getSamplingDecision(span.spanContext())).toBe(false); - }); -}); - -describe('continueTrace', () => { - beforeEach(() => { - mockSdkInit({ tracesSampleRate: 1 }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('works without trace & baggage data', () => { - const scope = continueTrace({ sentryTrace: undefined, baggage: undefined }, () => { - const span = getActiveSpan()!; - expect(span).toBeUndefined(); - return getCurrentScope(); - }); - - expect(scope.getPropagationContext()).toEqual({ - traceId: expect.any(String), - sampleRand: expect.any(Number), - }); - - expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); - }); - - it('works with trace data', () => { - continueTrace( - { - sentryTrace: '12312012123120121231201212312012-1121201211212012-0', - baggage: undefined, - }, - () => { - const span = getActiveSpan()!; - expect(span).toBeDefined(); - expect(spanToJSON(span)).toEqual({ - span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - data: {}, - start_timestamp: 0, - }); - expect(getSamplingDecision(span.spanContext())).toBe(false); - expect(spanIsSampled(span)).toBe(false); - }, - ); - }); - - it('works with trace & baggage data', () => { - continueTrace( - { - sentryTrace: '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-version=1.0,sentry-environment=production', - }, - () => { - const span = getActiveSpan()!; - expect(span).toBeDefined(); - expect(spanToJSON(span)).toEqual({ - span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - data: {}, - start_timestamp: 0, - }); - expect(getSamplingDecision(span.spanContext())).toBe(true); - expect(spanIsSampled(span)).toBe(true); - }, - ); - }); - - it('works with trace & 3rd party baggage data', () => { - continueTrace( - { - sentryTrace: '12312012123120121231201212312012-1121201211212012-1', - baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring', - }, - () => { - const span = getActiveSpan()!; - expect(span).toBeDefined(); - expect(spanToJSON(span)).toEqual({ - span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - data: {}, - start_timestamp: 0, - }); - expect(getSamplingDecision(span.spanContext())).toBe(true); - expect(spanIsSampled(span)).toBe(true); - }, - ); - }); - - it('returns response of callback', () => { - const result = continueTrace( - { - sentryTrace: '12312012123120121231201212312012-1121201211212012-0', - baggage: undefined, - }, - () => { - return 'aha'; - }, - ); - - expect(result).toEqual('aha'); - }); -}); - -describe('suppressTracing', () => { - beforeEach(() => { - mockSdkInit({ tracesSampleRate: 1 }); - }); - - afterEach(async () => { - await cleanupOtel(); - }); - - it('works for a root span', () => { - const span = suppressTracing(() => { - return startInactiveSpan({ name: 'span' }); - }); - - expect(span.isRecording()).toBe(false); - expect(spanIsSampled(span)).toBe(false); - }); - - it('works for a child span', () => { - startSpan({ name: 'outer' }, span => { - expect(span.isRecording()).toBe(true); - expect(spanIsSampled(span)).toBe(true); - - const child1 = startInactiveSpan({ name: 'inner1' }); - - expect(child1.isRecording()).toBe(true); - expect(spanIsSampled(child1)).toBe(true); - - const child2 = suppressTracing(() => { - return startInactiveSpan({ name: 'span' }); - }); - - expect(child2.isRecording()).toBe(false); - expect(spanIsSampled(child2)).toBe(false); - }); - }); - - it('works for a child span with forceTransaction=true', () => { - startSpan({ name: 'outer' }, span => { - expect(span.isRecording()).toBe(true); - expect(spanIsSampled(span)).toBe(true); - - const child = suppressTracing(() => { - return startInactiveSpan({ name: 'span', forceTransaction: true }); - }); - - expect(child.isRecording()).toBe(false); - expect(spanIsSampled(child)).toBe(false); - }); - }); -}); - -function getSpanName(span: AbstractSpan): string | undefined { - return spanHasName(span) ? span.name : undefined; -} - -function getSpanEndTime(span: AbstractSpan): [number, number] | undefined { - return (span as ReadableSpan).endTime; -} - -function getSpanStartTime(span: AbstractSpan): [number, number] | undefined { - return (span as ReadableSpan).startTime; -} - -function getSpanAttributes(span: AbstractSpan): Record | undefined { - return spanHasAttributes(span) ? span.attributes : undefined; -} - -function getSpanParentSpanId(span: AbstractSpan): string | undefined { - return getParentSpanId(span as ReadableSpan); -} diff --git a/dev-packages/opentelemetry-v2-tests/test/tsconfig.json b/dev-packages/opentelemetry-v2-tests/test/tsconfig.json deleted file mode 100644 index 38ca0b13bcdd..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../tsconfig.test.json" -} diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts deleted file mode 100644 index c91e49ea5f84..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { trace } from '@opentelemetry/api'; -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { getRootSpan } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getActiveSpan } from '../../../../packages/opentelemetry/src/utils/getActiveSpan'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('getActiveSpan', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions()); - [provider] = setupOtel(client); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - it('returns undefined if no span is active', () => { - const span = getActiveSpan(); - expect(span).toBeUndefined(); - }); - - it('returns undefined if no provider is active', async () => { - await provider?.forceFlush(); - await provider?.shutdown(); - provider = undefined; - - const span = getActiveSpan(); - expect(span).toBeUndefined(); - }); - - it('returns currently active span', () => { - const tracer = trace.getTracer('test'); - - expect(getActiveSpan()).toBeUndefined(); - - tracer.startActiveSpan('test', span => { - expect(getActiveSpan()).toBe(span); - - const inner1 = tracer.startSpan('inner1'); - - expect(getActiveSpan()).toBe(span); - - inner1.end(); - - tracer.startActiveSpan('inner2', inner2 => { - expect(getActiveSpan()).toBe(inner2); - - inner2.end(); - }); - - expect(getActiveSpan()).toBe(span); - - span.end(); - }); - - expect(getActiveSpan()).toBeUndefined(); - }); - - it('returns currently active span in concurrent spans', () => { - const tracer = trace.getTracer('test'); - - expect(getActiveSpan()).toBeUndefined(); - - tracer.startActiveSpan('test1', span => { - expect(getActiveSpan()).toBe(span); - - tracer.startActiveSpan('inner1', inner1 => { - expect(getActiveSpan()).toBe(inner1); - inner1.end(); - }); - - span.end(); - }); - - tracer.startActiveSpan('test2', span => { - expect(getActiveSpan()).toBe(span); - - tracer.startActiveSpan('inner2', inner => { - expect(getActiveSpan()).toBe(inner); - inner.end(); - }); - - span.end(); - }); - - expect(getActiveSpan()).toBeUndefined(); - }); -}); - -describe('getRootSpan', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - [provider] = setupOtel(client); - }); - - afterEach(async () => { - await provider?.forceFlush(); - await provider?.shutdown(); - }); - - it('returns currently active root span', () => { - const tracer = trace.getTracer('test'); - - tracer.startActiveSpan('test', span => { - expect(getRootSpan(span)).toBe(span); - - const inner1 = tracer.startSpan('inner1'); - - expect(getRootSpan(inner1)).toBe(span); - - inner1.end(); - - tracer.startActiveSpan('inner2', inner2 => { - expect(getRootSpan(inner2)).toBe(span); - - inner2.end(); - }); - - span.end(); - }); - }); - - it('returns currently active root span in concurrent spans', () => { - const tracer = trace.getTracer('test'); - - tracer.startActiveSpan('test1', span => { - expect(getRootSpan(span)).toBe(span); - - tracer.startActiveSpan('inner1', inner1 => { - expect(getRootSpan(inner1)).toBe(span); - inner1.end(); - }); - - span.end(); - }); - - tracer.startActiveSpan('test2', span => { - expect(getRootSpan(span)).toBe(span); - - tracer.startActiveSpan('inner2', inner => { - expect(getRootSpan(inner)).toBe(span); - inner.end(); - }); - - span.end(); - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts deleted file mode 100644 index 3f0914b6afb7..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import type { Span } from '@opentelemetry/api'; -import { trace } from '@opentelemetry/api'; -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getRequestSpanData } from '../../../../packages/opentelemetry/src/utils/getRequestSpanData'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('getRequestSpanData', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - [provider] = setupOtel(client); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - function createSpan(name: string): Span { - return trace.getTracer('test').startSpan(name); - } - - it('works with basic span', () => { - const span = createSpan('test-span'); - const data = getRequestSpanData(span); - - expect(data).toEqual({}); - }); - - it('works with http span', () => { - const span = createSpan('test-span'); - span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'http://example.com?foo=bar#baz', - [SEMATTRS_HTTP_METHOD]: 'GET', - }); - - const data = getRequestSpanData(span); - - expect(data).toEqual({ - url: 'http://example.com', - 'http.method': 'GET', - 'http.query': '?foo=bar', - 'http.fragment': '#baz', - }); - }); - - it('works without method', () => { - const span = createSpan('test-span'); - span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'http://example.com', - }); - - const data = getRequestSpanData(span); - - expect(data).toEqual({ - url: 'http://example.com', - 'http.method': 'GET', - }); - }); - - it('works with incorrect URL', () => { - const span = createSpan('test-span'); - span.setAttributes({ - [SEMATTRS_HTTP_URL]: 'malformed-url-here', - [SEMATTRS_HTTP_METHOD]: 'GET', - }); - - const data = getRequestSpanData(span); - - expect(data).toEqual({ - url: 'malformed-url-here', - 'http.method': 'GET', - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts deleted file mode 100644 index 16dacdafe8ee..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Span } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import { describe, expect, it } from 'vitest'; -import { getSpanKind } from '../../../../packages/opentelemetry/src/utils/getSpanKind'; - -describe('getSpanKind', () => { - it('works', () => { - expect(getSpanKind({} as Span)).toBe(SpanKind.INTERNAL); - expect(getSpanKind({ kind: SpanKind.CLIENT } as unknown as Span)).toBe(SpanKind.CLIENT); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts deleted file mode 100644 index 136b6251523d..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { context, trace } from '@opentelemetry/api'; -import { getCurrentScope, setAsyncContextStrategy } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { getTraceData } from '../../../../packages/opentelemetry/src/utils/getTraceData'; -import { makeTraceState } from '../../../../packages/opentelemetry/src/utils/makeTraceState'; -import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; - -describe('getTraceData', () => { - beforeEach(() => { - setAsyncContextStrategy(undefined); - mockSdkInit(); - }); - - afterEach(async () => { - await cleanupOtel(); - vi.clearAllMocks(); - }); - - it('returns the tracing data from the span, if a span is available', () => { - const ctx = trace.setSpanContext(context.active(), { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: 1, - }); - - context.with(ctx, () => { - const data = getTraceData(); - - expect(data).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: - 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', - }); - }); - }); - - it('allows to pass a span directly', () => { - const ctx = trace.setSpanContext(context.active(), { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: 1, - }); - - const span = trace.getSpan(ctx)!; - - const data = getTraceData({ span }); - - expect(data).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: - 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true', - }); - }); - - it('returns propagationContext DSC data if no span is available', () => { - getCurrentScope().setPropagationContext({ - traceId: '12345678901234567890123456789012', - sampleRand: Math.random(), - sampled: true, - dsc: { - environment: 'staging', - public_key: 'key', - trace_id: '12345678901234567890123456789012', - }, - }); - - const traceData = getTraceData(); - - expect(traceData['sentry-trace']).toMatch(/^12345678901234567890123456789012-[a-f0-9]{16}-1$/); - expect(traceData.baggage).toEqual( - 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012', - ); - }); - - it('works with an span with frozen DSC in traceState', () => { - const ctx = trace.setSpanContext(context.active(), { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: 1, - traceState: makeTraceState({ - dsc: { environment: 'test-dev', public_key: '456', trace_id: '12345678901234567890123456789088' }, - }), - }); - - context.with(ctx, () => { - const data = getTraceData(); - - expect(data).toEqual({ - 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', - baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088', - }); - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts deleted file mode 100644 index 87d7daa4a43a..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { trace } from '@opentelemetry/api'; -import type { BasicTracerProvider, ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import type { Span } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { withActiveSpan } from '../../../../packages/opentelemetry/src/trace'; -import { groupSpansWithParents } from '../../../../packages/opentelemetry/src/utils/groupSpansWithParents'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('groupSpansWithParents', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - [provider] = setupOtel(client); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - it('works with no spans', () => { - const actual = groupSpansWithParents([]); - expect(actual).toEqual([]); - }); - - it('works with a single root span & in-order spans', () => { - const tracer = trace.getTracer('test'); - const rootSpan = tracer.startSpan('root') as unknown as ReadableSpan; - const parentSpan1 = withActiveSpan( - rootSpan as unknown as Span, - () => tracer.startSpan('parent1') as unknown as ReadableSpan, - ); - const parentSpan2 = withActiveSpan( - rootSpan as unknown as Span, - () => tracer.startSpan('parent2') as unknown as ReadableSpan, - ); - const child1 = withActiveSpan( - parentSpan1 as unknown as Span, - () => tracer.startSpan('child1') as unknown as ReadableSpan, - ); - - const actual = groupSpansWithParents([rootSpan, parentSpan1, parentSpan2, child1]); - expect(actual).toHaveLength(4); - - // Ensure parent & span is correctly set - const rootRef = actual.find(ref => ref.span === rootSpan); - const parent1Ref = actual.find(ref => ref.span === parentSpan1); - const parent2Ref = actual.find(ref => ref.span === parentSpan2); - const child1Ref = actual.find(ref => ref.span === child1); - - expect(rootRef).toBeDefined(); - expect(parent1Ref).toBeDefined(); - expect(parent2Ref).toBeDefined(); - expect(child1Ref).toBeDefined(); - - expect(rootRef?.parentNode).toBeUndefined(); - expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]); - - expect(parent1Ref?.span).toBe(parentSpan1); - expect(parent2Ref?.span).toBe(parentSpan2); - - expect(parent1Ref?.parentNode).toBe(rootRef); - expect(parent2Ref?.parentNode).toBe(rootRef); - - expect(parent1Ref?.children).toEqual([child1Ref]); - expect(parent2Ref?.children).toEqual([]); - - expect(child1Ref?.parentNode).toBe(parent1Ref); - expect(child1Ref?.children).toEqual([]); - }); - - it('works with a spans with missing root span', () => { - const tracer = trace.getTracer('test'); - - // We create this root span here, but we do not pass it to `groupSpansWithParents` below - const rootSpan = tracer.startSpan('root') as unknown as ReadableSpan; - const parentSpan1 = withActiveSpan( - rootSpan as unknown as Span, - () => tracer.startSpan('parent1') as unknown as ReadableSpan, - ); - const parentSpan2 = withActiveSpan( - rootSpan as unknown as Span, - () => tracer.startSpan('parent2') as unknown as ReadableSpan, - ); - const child1 = withActiveSpan( - parentSpan1 as unknown as Span, - () => tracer.startSpan('child1') as unknown as ReadableSpan, - ); - - const actual = groupSpansWithParents([parentSpan1, parentSpan2, child1]); - expect(actual).toHaveLength(4); - - // Ensure parent & span is correctly set - const rootRef = actual.find(ref => ref.id === rootSpan.spanContext().spanId); - const parent1Ref = actual.find(ref => ref.span === parentSpan1); - const parent2Ref = actual.find(ref => ref.span === parentSpan2); - const child1Ref = actual.find(ref => ref.span === child1); - - expect(rootRef).toBeDefined(); - expect(parent1Ref).toBeDefined(); - expect(parent2Ref).toBeDefined(); - expect(child1Ref).toBeDefined(); - - expect(rootRef?.parentNode).toBeUndefined(); - expect(rootRef?.span).toBeUndefined(); - expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]); - - expect(parent1Ref?.span).toBe(parentSpan1); - expect(parent2Ref?.span).toBe(parentSpan2); - - expect(parent1Ref?.parentNode).toBe(rootRef); - expect(parent2Ref?.parentNode).toBe(rootRef); - - expect(parent1Ref?.children).toEqual([child1Ref]); - expect(parent2Ref?.children).toEqual([]); - - expect(child1Ref?.parentNode).toBe(parent1Ref); - expect(child1Ref?.children).toEqual([]); - }); - - it('works with multiple root spans & out-of-order spans', () => { - const tracer = trace.getTracer('test'); - const rootSpan1 = tracer.startSpan('root1') as unknown as ReadableSpan; - const rootSpan2 = tracer.startSpan('root2') as unknown as ReadableSpan; - const parentSpan1 = withActiveSpan( - rootSpan1 as unknown as Span, - () => tracer.startSpan('parent1') as unknown as ReadableSpan, - ); - const parentSpan2 = withActiveSpan( - rootSpan2 as unknown as Span, - () => tracer.startSpan('parent2') as unknown as ReadableSpan, - ); - const childSpan1 = withActiveSpan( - parentSpan1 as unknown as Span, - () => tracer.startSpan('child1') as unknown as ReadableSpan, - ); - - const actual = groupSpansWithParents([childSpan1, parentSpan1, parentSpan2, rootSpan2, rootSpan1]); - expect(actual).toHaveLength(5); - - // Ensure parent & span is correctly set - const root1Ref = actual.find(ref => ref.span === rootSpan1); - const root2Ref = actual.find(ref => ref.span === rootSpan2); - const parent1Ref = actual.find(ref => ref.span === parentSpan1); - const parent2Ref = actual.find(ref => ref.span === parentSpan2); - const child1Ref = actual.find(ref => ref.span === childSpan1); - - expect(root1Ref).toBeDefined(); - expect(root2Ref).toBeDefined(); - expect(parent1Ref).toBeDefined(); - expect(parent2Ref).toBeDefined(); - expect(child1Ref).toBeDefined(); - - expect(root1Ref?.parentNode).toBeUndefined(); - expect(root1Ref?.children).toEqual([parent1Ref]); - - expect(root2Ref?.parentNode).toBeUndefined(); - expect(root2Ref?.children).toEqual([parent2Ref]); - - expect(parent1Ref?.span).toBe(parentSpan1); - expect(parent2Ref?.span).toBe(parentSpan2); - - expect(parent1Ref?.parentNode).toBe(root1Ref); - expect(parent2Ref?.parentNode).toBe(root2Ref); - - expect(parent1Ref?.children).toEqual([child1Ref]); - expect(parent2Ref?.children).toEqual([]); - - expect(child1Ref?.parentNode).toBe(parent1Ref); - expect(child1Ref?.children).toEqual([]); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts deleted file mode 100644 index b479da0d61ad..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import type { Span } from '@opentelemetry/api'; -import { trace } from '@opentelemetry/api'; -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_RPC_GRPC_STATUS_CODE } from '@opentelemetry/semantic-conventions'; -import type { SpanStatus } from '@sentry/core'; -import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { mapStatus } from '../../../../packages/opentelemetry/src/utils/mapStatus'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('mapStatus', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - [provider] = setupOtel(client); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - function createSpan(name: string): Span { - return trace.getTracer('test').startSpan(name); - } - - const statusTestTable: [undefined | number | string, undefined | string, SpanStatus][] = [ - // http codes - [400, undefined, { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }], - [401, undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], - [403, undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], - [404, undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }], - [409, undefined, { code: SPAN_STATUS_ERROR, message: 'already_exists' }], - [429, undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], - [499, undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }], - [500, undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }], - [501, undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], - [503, undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }], - [504, undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], - [999, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], - - // grpc codes - [undefined, '1', { code: SPAN_STATUS_ERROR, message: 'cancelled' }], - [undefined, '2', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], - [undefined, '3', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }], - [undefined, '4', { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], - [undefined, '5', { code: SPAN_STATUS_ERROR, message: 'not_found' }], - [undefined, '6', { code: SPAN_STATUS_ERROR, message: 'already_exists' }], - [undefined, '7', { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], - [undefined, '8', { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], - [undefined, '9', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], - [undefined, '10', { code: SPAN_STATUS_ERROR, message: 'aborted' }], - [undefined, '11', { code: SPAN_STATUS_ERROR, message: 'out_of_range' }], - [undefined, '12', { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], - [undefined, '13', { code: SPAN_STATUS_ERROR, message: 'internal_error' }], - [undefined, '14', { code: SPAN_STATUS_ERROR, message: 'unavailable' }], - [undefined, '15', { code: SPAN_STATUS_ERROR, message: 'data_loss' }], - [undefined, '16', { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], - [undefined, '999', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], - - // http takes precedence over grpc - [400, '2', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }], - ]; - - it.each(statusTestTable)('works with httpCode=%s, grpcCode=%s', (httpCode, grpcCode, expected) => { - const span = createSpan('test-span'); - span.setStatus({ code: 0 }); // UNSET - - if (httpCode) { - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, httpCode); - } - - if (grpcCode) { - span.setAttribute(SEMATTRS_RPC_GRPC_STATUS_CODE, grpcCode); - } - - const actual = mapStatus(span); - expect(actual).toEqual(expected); - }); - - it('works with string SEMATTRS_HTTP_STATUS_CODE', () => { - const span = createSpan('test-span'); - - span.setStatus({ code: 0 }); // UNSET - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, '400'); - - const actual = mapStatus(span); - expect(actual).toEqual({ code: SPAN_STATUS_ERROR, message: 'invalid_argument' }); - }); - - it('returns ok span status when is UNSET present on span', () => { - const span = createSpan('test-span'); - span.setStatus({ code: 0 }); // UNSET - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_OK }); - }); - - it('returns ok span status when already present on span', () => { - const span = createSpan('test-span'); - span.setStatus({ code: 1 }); // OK - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_OK }); - }); - - it('returns error status when span already has error status', () => { - const span = createSpan('test-span'); - span.setStatus({ code: 2, message: 'invalid_argument' }); // ERROR - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'invalid_argument' }); - }); - - it('returns error status when span already has error status without message', () => { - const span = createSpan('test-span'); - span.setStatus({ code: 2 }); // ERROR - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'unknown_error' }); - }); - - it('infers error status form attributes when span already has error status without message', () => { - const span = createSpan('test-span'); - span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, 500); - span.setStatus({ code: 2 }); // ERROR - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - }); - - it('returns unknown error status when code is unknown', () => { - const span = createSpan('test-span'); - span.setStatus({ code: -1 as 0 }); - expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'unknown_error' }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts deleted file mode 100644 index 56d50a3b2fbc..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts +++ /dev/null @@ -1,690 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import type { Span } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import { - ATTR_HTTP_ROUTE, - SEMATTRS_DB_STATEMENT, - SEMATTRS_DB_SYSTEM, - SEMATTRS_FAAS_TRIGGER, - SEMATTRS_HTTP_HOST, - SEMATTRS_HTTP_METHOD, - SEMATTRS_HTTP_STATUS_CODE, - SEMATTRS_HTTP_TARGET, - SEMATTRS_HTTP_URL, - SEMATTRS_MESSAGING_SYSTEM, - SEMATTRS_RPC_SERVICE, -} from '@opentelemetry/semantic-conventions'; -import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { describe, expect, it } from 'vitest'; -import { - descriptionForHttpMethod, - getSanitizedUrl, - getUserUpdatedNameAndSource, - parseSpanDescription, -} from '../../../../packages/opentelemetry/src/utils/parseSpanDescription'; - -describe('parseSpanDescription', () => { - it.each([ - [ - 'works without attributes & name', - undefined, - undefined, - undefined, - { - description: '', - op: undefined, - source: 'custom', - }, - ], - [ - 'works with empty attributes', - {}, - 'test name', - SpanKind.CLIENT, - { - description: 'test name', - op: undefined, - source: 'custom', - }, - ], - [ - 'works with deprecated http method', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'test name', - op: 'http.client', - source: 'custom', - }, - ], - [ - 'works with http method', - { - 'http.request.method': 'GET', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'test name', - op: 'http.client', - source: 'custom', - }, - ], - [ - 'works with db system', - { - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'SELECT * from users', - op: 'db', - source: 'task', - }, - ], - [ - 'works with db system and custom source', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'test name', - op: 'db', - source: 'custom', - }, - ], - [ - 'works with db system and custom source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'custom name', - op: 'db', - source: 'custom', - }, - ], - [ - 'works with db system and component source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'custom name', - op: 'db', - source: 'component', - }, - ], - [ - 'works with db system without statement', - { - [SEMATTRS_DB_SYSTEM]: 'mysql', - }, - 'test name', - SpanKind.CLIENT, - { - description: 'test name', - op: 'db', - source: 'task', - }, - ], - [ - 'works with rpc service', - { - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'rpc', - source: 'route', - }, - ], - [ - 'works with rpc service and custom source', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'rpc', - source: 'custom', - }, - ], - [ - 'works with rpc service and custom source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'rpc', - source: 'custom', - }, - ], - [ - 'works with rpc service and component source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_RPC_SERVICE]: 'rpc-test-service', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'rpc', - source: 'component', - }, - ], - [ - 'works with messaging system', - { - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'message', - source: 'route', - }, - ], - [ - 'works with messaging system and custom source', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'message', - source: 'custom', - }, - ], - [ - 'works with messaging system and custom source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'message', - source: 'custom', - }, - ], - [ - 'works with messaging system and component source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'message', - source: 'component', - }, - ], - [ - 'works with faas trigger', - { - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'test-faas-trigger', - source: 'route', - }, - ], - [ - 'works with faas trigger and custom source', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', - }, - 'test name', - undefined, - { - description: 'test name', - op: 'test-faas-trigger', - source: 'custom', - }, - ], - [ - 'works with faas trigger and custom source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'test-faas-trigger', - source: 'custom', - }, - ], - [ - 'works with faas trigger and component source and custom name', - { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - undefined, - { - description: 'custom name', - op: 'test-faas-trigger', - source: 'component', - }, - ], - ])('%s', (_, attributes, name, kind, expected) => { - const actual = parseSpanDescription({ attributes, kind, name } as unknown as Span); - expect(actual).toEqual(expected); - }); -}); - -describe('descriptionForHttpMethod', () => { - it.each([ - [ - 'works without attributes', - 'GET', - {}, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'test name', - source: 'custom', - }, - ], - [ - 'works with basic client GET', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'GET https://www.example.com/my-path', - data: { - url: 'https://www.example.com/my-path', - }, - source: 'url', - }, - ], - [ - 'works with prefetch request', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', - 'sentry.http.prefetch': true, - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client.prefetch', - description: 'GET https://www.example.com/my-path', - data: { - url: 'https://www.example.com/my-path', - }, - source: 'url', - }, - ], - [ - 'works with basic server POST', - 'POST', - { - [SEMATTRS_HTTP_METHOD]: 'POST', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', - }, - 'test name', - SpanKind.SERVER, - { - op: 'http.server', - description: 'POST /my-path', - data: { - url: 'https://www.example.com/my-path', - }, - source: 'url', - }, - ], - [ - 'works with client GET with route', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'GET /my-path/:id', - data: { - url: 'https://www.example.com/my-path/123', - }, - source: 'route', - }, - ], - [ - 'works with basic client GET with SpanKind.INTERNAL', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path', - [SEMATTRS_HTTP_TARGET]: '/my-path', - }, - 'test name', - SpanKind.INTERNAL, - { - op: 'http', - description: 'test name', - data: { - url: 'https://www.example.com/my-path', - }, - source: 'custom', - }, - ], - [ - "doesn't overwrite span name with source custom", - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'test name', - data: { - url: 'https://www.example.com/my-path/123', - }, - source: 'custom', - }, - ], - [ - 'takes user-passed span name (with source custom)', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'custom name', - data: { - url: 'https://www.example.com/my-path/123', - }, - source: 'custom', - }, - ], - [ - 'takes user-passed span name (with source component)', - 'GET', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', - [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [ATTR_HTTP_ROUTE]: '/my-path/:id', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - }, - 'test name', - SpanKind.CLIENT, - { - op: 'http.client', - description: 'custom name', - data: { - url: 'https://www.example.com/my-path/123', - }, - source: 'component', - }, - ], - ])('%s', (_, httpMethod, attributes, name, kind, expected) => { - const actual = descriptionForHttpMethod({ attributes, kind, name }, httpMethod); - expect(actual).toEqual(expected); - }); -}); - -describe('getSanitizedUrl', () => { - it.each([ - [ - 'works without attributes', - {}, - SpanKind.CLIENT, - { - urlPath: undefined, - url: undefined, - fragment: undefined, - query: undefined, - hasRoute: false, - }, - ], - [ - 'uses url without query for client request', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - hasRoute: false, - }, - ], - [ - 'uses url without hash for client request', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/sub#hash', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/sub#hash', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/sub', - url: 'http://example.com/sub', - fragment: '#hash', - query: undefined, - hasRoute: false, - }, - ], - [ - 'uses route if available for client request', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [ATTR_HTTP_ROUTE]: '/my-route', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - hasRoute: true, - }, - ], - [ - 'falls back to target for client request if url not available', - { - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/', - url: undefined, - fragment: undefined, - query: undefined, - hasRoute: false, - }, - ], - [ - 'uses target without query for server request', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - hasRoute: false, - }, - ], - [ - 'uses target without hash for server request', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/sub#hash', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/sub', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - hasRoute: false, - }, - ], - [ - 'uses route for server request if available', - { - [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_HTTP_TARGET]: '/?what=true', - [ATTR_HTTP_ROUTE]: '/my-route', - [SEMATTRS_HTTP_HOST]: 'example.com:80', - [SEMATTRS_HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - hasRoute: true, - }, - ], - ])('%s', (_, attributes, kind, expected) => { - const actual = getSanitizedUrl(attributes, kind); - - expect(actual).toEqual(expected); - }); -}); - -describe('getUserUpdatedNameAndSource', () => { - it('returns param name if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not set', () => { - expect(getUserUpdatedNameAndSource('base name', {})).toEqual({ description: 'base name', source: 'custom' }); - }); - - it('returns param name with custom fallback source if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not set', () => { - expect(getUserUpdatedNameAndSource('base name', {}, 'route')).toEqual({ - description: 'base name', - source: 'route', - }); - }); - - it('returns param name if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not a string', () => { - expect(getUserUpdatedNameAndSource('base name', { [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 123 })).toEqual({ - description: 'base name', - source: 'custom', - }); - }); - - it.each(['custom', 'task', 'url', 'route'])( - 'returns `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute if is a string and source is %s', - source => { - expect( - getUserUpdatedNameAndSource('base name', { - [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - }), - ).toEqual({ - description: 'custom name', - source, - }); - }, - ); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts deleted file mode 100644 index 8f453bb9792c..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { SentrySampler } from '../../../../packages/opentelemetry/src/sampler'; -import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor'; -import { openTelemetrySetupCheck } from '../../../../packages/opentelemetry/src/utils/setupCheck'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('openTelemetrySetupCheck', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - cleanupOtel(provider); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - it('returns empty array by default', () => { - const setup = openTelemetrySetupCheck(); - expect(setup).toEqual([]); - }); - - it('returns all setup parts', () => { - const client = new TestClient(getDefaultTestClientOptions()); - [provider] = setupOtel(client); - - const setup = openTelemetrySetupCheck(); - expect(setup).toEqual(['SentrySpanProcessor', 'SentrySampler', 'SentryPropagator', 'SentryContextManager']); - }); - - it('returns partial setup parts', () => { - const client = new TestClient(getDefaultTestClientOptions()); - provider = new BasicTracerProvider({ - sampler: new SentrySampler(client), - spanProcessors: [new SentrySpanProcessor()], - }); - - const setup = openTelemetrySetupCheck(); - expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts deleted file mode 100644 index fbf6e1b69991..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { captureException, setCurrentClient } from '@sentry/core'; -import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { setupEventContextTrace } from '../../../../packages/opentelemetry/src/setupEventContextTrace'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import type { TestClientInterface } from '../helpers/TestClient'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -const PUBLIC_DSN = 'https://username@domain/123'; - -describe('setupEventContextTrace', () => { - const beforeSend = vi.fn(() => null); - let client: TestClientInterface; - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - client = new TestClient( - getDefaultTestClientOptions({ - sampleRate: 1, - tracesSampleRate: 1, - beforeSend, - debug: true, - dsn: PUBLIC_DSN, - }), - ); - - setCurrentClient(client); - client.init(); - - setupEventContextTrace(client); - [provider] = setupOtel(client); - }); - - afterEach(() => { - beforeSend.mockReset(); - cleanupOtel(provider); - }); - - afterAll(() => { - vi.clearAllMocks(); - }); - - it('works with no active span', async () => { - const error = new Error('test'); - captureException(error); - await client.flush(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: expect.stringMatching(/[a-f0-9]{16}/), - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }), - }), - expect.objectContaining({ - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }), - ); - }); - - it('works with active span', async () => { - const error = new Error('test'); - - let outerId: string | undefined; - let innerId: string | undefined; - let traceId: string | undefined; - - client.tracer.startActiveSpan('outer', outerSpan => { - outerId = outerSpan.spanContext().spanId; - traceId = outerSpan.spanContext().traceId; - - client.tracer.startActiveSpan('inner', innerSpan => { - innerId = innerSpan.spanContext().spanId; - captureException(error); - }); - }); - - await client.flush(); - - expect(outerId).toBeDefined(); - expect(innerId).toBeDefined(); - expect(traceId).toBeDefined(); - - expect(beforeSend).toHaveBeenCalledTimes(1); - expect(beforeSend).toHaveBeenCalledWith( - expect.objectContaining({ - contexts: expect.objectContaining({ - trace: { - span_id: innerId, - parent_span_id: outerId, - trace_id: traceId, - }, - }), - }), - expect.objectContaining({ - event_id: expect.any(String), - originalException: error, - syntheticException: expect.any(Error), - }), - ); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts deleted file mode 100644 index c1f9fe2a18c7..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { Span, SpanOptions } from '@opentelemetry/api'; -import { trace } from '@opentelemetry/api'; -import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, - spanToJSON, -} from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { setupOtel } from '../helpers/initOtel'; -import { cleanupOtel } from '../helpers/mockSdkInit'; -import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient'; - -describe('spanToJSON', () => { - describe('OpenTelemetry Span', () => { - let provider: BasicTracerProvider | undefined; - - beforeEach(() => { - const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - [provider] = setupOtel(client); - }); - - afterEach(() => { - cleanupOtel(provider); - }); - - function createSpan(name: string, params?: SpanOptions): Span { - return trace.getTracer('test').startSpan(name, params); - } - - it('works with a simple span', () => { - const span = createSpan('test span', { startTime: [123, 0] }); - - expect(spanToJSON(span)).toEqual({ - span_id: span.spanContext().spanId, - trace_id: span.spanContext().traceId, - start_timestamp: 123, - description: 'test span', - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - }, - }); - }); - - it('works with a full span', () => { - const span = createSpan('test span', { startTime: [123, 0] }); - - span.setAttributes({ - attr1: 'value1', - attr2: 2, - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'test op', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto', - }); - - span.setStatus({ code: 2, message: 'unknown_error' }); - span.end([456, 0]); - - expect(spanToJSON(span)).toEqual({ - span_id: span.spanContext().spanId, - trace_id: span.spanContext().traceId, - start_timestamp: 123, - timestamp: 456, - description: 'test span', - op: 'test op', - origin: 'auto', - data: { - attr1: 'value1', - attr2: 2, - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'test op', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - }, - status: 'unknown_error', - }); - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts deleted file mode 100644 index 00c9eccdf98e..000000000000 --- a/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Span } from '@opentelemetry/api'; -import { describe, expect, it } from 'vitest'; -import { - spanHasAttributes, - spanHasEvents, - spanHasKind, - spanHasParentId, -} from '../../../../packages/opentelemetry/src/utils/spanTypes'; - -describe('spanTypes', () => { - describe('spanHasAttributes', () => { - it.each([ - [{}, false], - [{ attributes: null }, false], - [{ attributes: {} }, true], - ])('works with %p', (span, expected) => { - const castSpan = span as unknown as Span; - const actual = spanHasAttributes(castSpan); - - expect(actual).toBe(expected); - - if (actual) { - expect(castSpan.attributes).toBeDefined(); - } - }); - }); - - describe('spanHasKind', () => { - it.each([ - [{}, false], - [{ kind: null }, false], - [{ kind: 0 }, true], - [{ kind: 5 }, true], - [{ kind: 'TEST_KIND' }, false], - ])('works with %p', (span, expected) => { - const castSpan = span as unknown as Span; - const actual = spanHasKind(castSpan); - - expect(actual).toBe(expected); - - if (actual) { - expect(castSpan.kind).toBeDefined(); - } - }); - }); - - describe('spanHasParentId', () => { - it.each([ - [{}, false], - [{ parentSpanId: null }, false], - [{ parentSpanId: 'TEST_PARENT_ID' }, true], - ])('works with %p', (span, expected) => { - const castSpan = span as unknown as Span; - const actual = spanHasParentId(castSpan); - - expect(actual).toBe(expected); - - if (actual) { - expect(castSpan.parentSpanId).toBeDefined(); - } - }); - }); - - describe('spanHasEvents', () => { - it.each([ - [{}, false], - [{ events: null }, false], - [{ events: [] }, true], - ])('works with %p', (span, expected) => { - const castSpan = span as unknown as Span; - const actual = spanHasEvents(castSpan); - - expect(actual).toBe(expected); - - if (actual) { - expect(castSpan.events).toBeDefined(); - } - }); - }); -}); diff --git a/dev-packages/opentelemetry-v2-tests/tsconfig.json b/dev-packages/opentelemetry-v2-tests/tsconfig.json deleted file mode 100644 index b9f9b425c7df..000000000000 --- a/dev-packages/opentelemetry-v2-tests/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./build", - "types": ["node", "vitest/globals"] - }, - "include": ["test/**/*", "vite.config.ts"] -} diff --git a/dev-packages/opentelemetry-v2-tests/tsconfig.test.json b/dev-packages/opentelemetry-v2-tests/tsconfig.test.json deleted file mode 100644 index ca7dbeb3be94..000000000000 --- a/dev-packages/opentelemetry-v2-tests/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "include": ["test/**/*", "vite.config.ts"], - - "compilerOptions": { - // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node"] - - // other package-specific, test-specific options - } -} diff --git a/dev-packages/opentelemetry-v2-tests/vite.config.ts b/dev-packages/opentelemetry-v2-tests/vite.config.ts deleted file mode 100644 index d7ea407dfac7..000000000000 --- a/dev-packages/opentelemetry-v2-tests/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import baseConfig from '../../vite/vite.config'; - -export default { - ...baseConfig, - test: { - ...baseConfig.test, - coverage: { - enabled: false, - }, - }, -}; diff --git a/package.json b/package.json index f2cbefa5faa4..af9e6d764565 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,7 @@ "dev-packages/size-limit-gh-action", "dev-packages/clear-cache-gh-action", "dev-packages/external-contributor-gh-action", - "dev-packages/rollup-utils", - "dev-packages/opentelemetry-v2-tests" + "dev-packages/rollup-utils" ], "devDependencies": { "@rollup/plugin-commonjs": "^25.0.7", diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 4e04a83226ee..0bca7574c42c 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -65,9 +65,9 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.57.2", - "@opentelemetry/instrumentation-aws-lambda": "0.50.3", - "@opentelemetry/instrumentation-aws-sdk": "0.49.1", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-aws-lambda": "0.54.0", + "@opentelemetry/instrumentation-aws-sdk": "0.56.0", "@sentry/core": "9.40.0", "@sentry/node": "9.40.0", "@types/aws-lambda": "^8.10.62" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 4a2d3f32c3fe..a2502a70cd0c 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,9 +45,9 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "0.57.2", - "@opentelemetry/instrumentation-nestjs-core": "0.44.1", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-nestjs-core": "0.49.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@sentry/core": "9.40.0", "@sentry/node": "9.40.0" diff --git a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts index 968c24a469e4..85eaae360b1c 100644 --- a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts +++ b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts @@ -1,9 +1,9 @@ -import { isWrapped } from '@opentelemetry/core'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, + isWrapped, } from '@opentelemetry/instrumentation'; import { captureException, SDK_VERSION, startSpan } from '@sentry/core'; import { getEventSpanOptions } from './helpers'; diff --git a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts index fff9f92616f3..04b20f5d4d6a 100644 --- a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts +++ b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts @@ -1,9 +1,9 @@ -import { isWrapped } from '@opentelemetry/core'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, + isWrapped, } from '@opentelemetry/instrumentation'; import type { Span } from '@sentry/core'; import { diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 1962ff71925a..752af8fc0042 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -72,11 +72,11 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "^0.57.2", - "@opentelemetry/resources": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@types/node": "^18.19.1" }, diff --git a/packages/node-core/test/helpers/mockSdkInit.ts b/packages/node-core/test/helpers/mockSdkInit.ts index ce82f92de3d8..0ea8a93cb064 100644 --- a/packages/node-core/test/helpers/mockSdkInit.ts +++ b/packages/node-core/test/helpers/mockSdkInit.ts @@ -1,6 +1,6 @@ import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; +import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'; +import { type SpanProcessor, BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, @@ -64,12 +64,14 @@ export function setupOtel(client: NodeClient): BasicTracerProvider | undefined { // Create and configure TracerProvider with same config as Node SDK const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), - resource: new Resource({ - [ATTR_SERVICE_NAME]: 'node', - // eslint-disable-next-line deprecation/deprecation - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_VERSION]: SDK_VERSION, - }), + resource: defaultResource().merge( + resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'node', + // eslint-disable-next-line deprecation/deprecation + [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', + [ATTR_SERVICE_VERSION]: SDK_VERSION, + }), + ), forceFlushTimeoutMillis: 500, spanProcessors: [ new SentrySpanProcessor({ @@ -128,6 +130,30 @@ export function cleanupOtel(_provider?: BasicTracerProvider): void { resetGlobals(); } +export function getSpanProcessor(): SentrySpanProcessor | undefined { + const client = getClient(); + if (!client?.traceProvider) { + return undefined; + } + + const provider = getProvider(client.traceProvider); + if (!provider) { + return undefined; + } + + // Access the span processors from the provider via _activeSpanProcessor + // Casted as any because _activeSpanProcessor is marked as readonly + const multiSpanProcessor = (provider as any)._activeSpanProcessor as + | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) + | undefined; + + const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( + (spanProcessor: SpanProcessor) => spanProcessor instanceof SentrySpanProcessor, + ) as SentrySpanProcessor | undefined; + + return spanProcessor; +} + export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined { let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider(); diff --git a/packages/node-core/test/integration/transactions.test.ts b/packages/node-core/test/integration/transactions.test.ts index 0ce3f7c99984..7b13a400dedb 100644 --- a/packages/node-core/test/integration/transactions.test.ts +++ b/packages/node-core/test/integration/transactions.test.ts @@ -1,11 +1,9 @@ import { context, trace, TraceFlags } from '@opentelemetry/api'; -import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; import type { TransactionEvent } from '@sentry/core'; import { debug, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { SentrySpanProcessor } from '@sentry/opentelemetry'; import { afterEach, describe, expect, it, vi } from 'vitest'; import * as Sentry from '../../src'; -import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; +import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit'; describe('Integration | Transactions', () => { afterEach(() => { @@ -562,13 +560,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; diff --git a/packages/node/package.json b/packages/node/package.json index 9e26d5756c65..4d4e9b4225d0 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -66,33 +66,33 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "^0.57.2", - "@opentelemetry/instrumentation-amqplib": "^0.46.1", - "@opentelemetry/instrumentation-connect": "0.43.1", - "@opentelemetry/instrumentation-dataloader": "0.16.1", - "@opentelemetry/instrumentation-express": "0.47.1", - "@opentelemetry/instrumentation-fs": "0.19.1", - "@opentelemetry/instrumentation-generic-pool": "0.43.1", - "@opentelemetry/instrumentation-graphql": "0.47.1", - "@opentelemetry/instrumentation-hapi": "0.45.2", - "@opentelemetry/instrumentation-http": "0.57.2", - "@opentelemetry/instrumentation-ioredis": "0.47.1", - "@opentelemetry/instrumentation-kafkajs": "0.7.1", - "@opentelemetry/instrumentation-knex": "0.44.1", - "@opentelemetry/instrumentation-koa": "0.47.1", - "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", - "@opentelemetry/instrumentation-mongodb": "0.52.0", - "@opentelemetry/instrumentation-mongoose": "0.46.1", - "@opentelemetry/instrumentation-mysql": "0.45.1", - "@opentelemetry/instrumentation-mysql2": "0.45.2", - "@opentelemetry/instrumentation-pg": "0.51.1", - "@opentelemetry/instrumentation-redis-4": "0.46.1", - "@opentelemetry/instrumentation-tedious": "0.18.1", - "@opentelemetry/instrumentation-undici": "0.10.1", - "@opentelemetry/resources": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0", + "@opentelemetry/instrumentation-amqplib": "0.50.0", + "@opentelemetry/instrumentation-connect": "0.47.0", + "@opentelemetry/instrumentation-dataloader": "0.21.0", + "@opentelemetry/instrumentation-express": "0.52.0", + "@opentelemetry/instrumentation-fs": "0.23.0", + "@opentelemetry/instrumentation-generic-pool": "0.47.0", + "@opentelemetry/instrumentation-graphql": "0.51.0", + "@opentelemetry/instrumentation-hapi": "0.50.0", + "@opentelemetry/instrumentation-http": "0.203.0", + "@opentelemetry/instrumentation-ioredis": "0.51.0", + "@opentelemetry/instrumentation-kafkajs": "0.12.0", + "@opentelemetry/instrumentation-knex": "0.48.0", + "@opentelemetry/instrumentation-koa": "0.51.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.48.0", + "@opentelemetry/instrumentation-mongodb": "0.56.0", + "@opentelemetry/instrumentation-mongoose": "0.50.0", + "@opentelemetry/instrumentation-mysql": "0.49.0", + "@opentelemetry/instrumentation-mysql2": "0.49.0", + "@opentelemetry/instrumentation-pg": "0.55.0", + "@opentelemetry/instrumentation-redis": "0.51.0", + "@opentelemetry/instrumentation-tedious": "0.22.0", + "@opentelemetry/instrumentation-undici": "0.14.0", + "@opentelemetry/resources": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@prisma/instrumentation": "6.11.1", "@sentry/core": "9.40.0", diff --git a/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts b/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts deleted file mode 100644 index f6a83e31b073..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum AttributeNames { - EXPRESS_TYPE = 'express.type', - EXPRESS_NAME = 'express.name', -} diff --git a/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts b/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts deleted file mode 100644 index 5cfc47c555d9..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum ExpressLayerType { - ROUTER = 'router', - MIDDLEWARE = 'middleware', - REQUEST_HANDLER = 'request_handler', -} diff --git a/packages/node/src/integrations/tracing/express-v5/instrumentation.ts b/packages/node/src/integrations/tracing/express-v5/instrumentation.ts deleted file mode 100644 index bc810341db35..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/instrumentation.ts +++ /dev/null @@ -1,326 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/member-ordering */ -/* eslint-disable guard-for-in */ -/* eslint-disable @typescript-eslint/ban-types */ -/* eslint-disable prefer-rest-params */ -/* eslint-disable @typescript-eslint/no-this-alias */ -/* eslint-disable jsdoc/require-jsdoc */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/explicit-member-accessibility */ - -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Attributes } from '@opentelemetry/api'; -import { context, diag, SpanStatusCode, trace } from '@opentelemetry/api'; -import { getRPCMetadata, RPCType } from '@opentelemetry/core'; -import { - InstrumentationBase, - InstrumentationNodeModuleDefinition, - isWrapped, - safeExecuteInTheMiddle, -} from '@opentelemetry/instrumentation'; -import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; -import type * as express from 'express'; -import { AttributeNames } from './enums/AttributeNames'; -import { ExpressLayerType } from './enums/ExpressLayerType'; -import type { ExpressLayer, ExpressRouter, PatchedRequest } from './internal-types'; -import { _LAYERS_STORE_PROPERTY, kLayerPatched } from './internal-types'; -import type { ExpressInstrumentationConfig, ExpressRequestInfo } from './types'; -import { asErrorAndMessage, getLayerMetadata, getLayerPath, isLayerIgnored, storeLayerPath } from './utils'; - -export const PACKAGE_VERSION = '0.1.0'; -export const PACKAGE_NAME = '@sentry/instrumentation-express-v5'; - -/** Express instrumentation for OpenTelemetry */ -export class ExpressInstrumentationV5 extends InstrumentationBase { - constructor(config: ExpressInstrumentationConfig = {}) { - super(PACKAGE_NAME, PACKAGE_VERSION, config); - } - - init() { - return [ - new InstrumentationNodeModuleDefinition( - 'express', - ['>=5.0.0'], - moduleExports => this._setup(moduleExports), - moduleExports => this._tearDown(moduleExports), - ), - ]; - } - - private _setup(moduleExports: any) { - const routerProto = moduleExports.Router.prototype; - // patch express.Router.route - if (isWrapped(routerProto.route)) { - this._unwrap(routerProto, 'route'); - } - this._wrap(routerProto, 'route', this._getRoutePatch()); - // patch express.Router.use - if (isWrapped(routerProto.use)) { - this._unwrap(routerProto, 'use'); - } - this._wrap(routerProto, 'use', this._getRouterUsePatch() as any); - // patch express.Application.use - if (isWrapped(moduleExports.application.use)) { - this._unwrap(moduleExports.application, 'use'); - } - this._wrap(moduleExports.application, 'use', this._getAppUsePatch() as any); - return moduleExports; - } - - private _tearDown(moduleExports: any) { - if (moduleExports === undefined) return; - const routerProto = moduleExports.Router.prototype; - this._unwrap(routerProto, 'route'); - this._unwrap(routerProto, 'use'); - this._unwrap(moduleExports.application, 'use'); - } - - /** - * Get the patch for Router.route function - */ - private _getRoutePatch() { - const instrumentation = this; - return function (original: express.Router['route']) { - return function route_trace(this: ExpressRouter, ...args: Parameters) { - const route = original.apply(this, args); - const layer = this.stack[this.stack.length - 1] as ExpressLayer; - instrumentation._applyPatch(layer, getLayerPath(args)); - return route; - }; - }; - } - - /** - * Get the patch for Router.use function - */ - private _getRouterUsePatch() { - const instrumentation = this; - return function (original: express.Router['use']) { - return function use(this: express.Application, ...args: Parameters) { - const route = original.apply(this, args); - const layer = this.stack[this.stack.length - 1] as ExpressLayer; - instrumentation._applyPatch(layer, getLayerPath(args)); - return route; - }; - }; - } - - /** - * Get the patch for Application.use function - */ - private _getAppUsePatch() { - const instrumentation = this; - return function (original: express.Application['use']) { - return function use( - // In express 5.x the router is stored in `router` whereas in 4.x it's stored in `_router` - this: { _router?: ExpressRouter; router?: ExpressRouter }, - ...args: Parameters - ) { - // if we access app.router in express 4.x we trigger an assertion error - // This property existed in v3, was removed in v4 and then re-added in v5 - const router = this.router; - const route = original.apply(this, args); - if (router) { - const layer = router.stack[router.stack.length - 1] as ExpressLayer; - instrumentation._applyPatch(layer, getLayerPath(args)); - } - return route; - }; - }; - } - - /** Patch each express layer to create span and propagate context */ - private _applyPatch(this: ExpressInstrumentationV5, layer: ExpressLayer, layerPath?: string) { - const instrumentation = this; - // avoid patching multiple times the same layer - if (layer[kLayerPatched] === true) return; - layer[kLayerPatched] = true; - - this._wrap(layer, 'handle', original => { - // TODO: instrument error handlers - if (original.length === 4) return original; - - const patched = function (this: ExpressLayer, req: PatchedRequest, res: express.Response) { - storeLayerPath(req, layerPath); - const route = (req[_LAYERS_STORE_PROPERTY] as string[]) - .filter(path => path !== '/' && path !== '/*') - .join('') - // remove duplicate slashes to normalize route - .replace(/\/{2,}/g, '/'); - - const actualRoute = route.length > 0 ? route : undefined; - - const attributes: Attributes = { - // eslint-disable-next-line deprecation/deprecation - [SEMATTRS_HTTP_ROUTE]: actualRoute, - }; - const metadata = getLayerMetadata(route, layer, layerPath); - const type = metadata.attributes[AttributeNames.EXPRESS_TYPE] as ExpressLayerType; - - const rpcMetadata = getRPCMetadata(context.active()); - if (rpcMetadata?.type === RPCType.HTTP) { - rpcMetadata.route = actualRoute; - } - - // verify against the config if the layer should be ignored - if (isLayerIgnored(metadata.name, type, instrumentation.getConfig())) { - if (type === ExpressLayerType.MIDDLEWARE) { - (req[_LAYERS_STORE_PROPERTY] as string[]).pop(); - } - return original.apply(this, arguments); - } - - if (trace.getSpan(context.active()) === undefined) { - return original.apply(this, arguments); - } - - const spanName = instrumentation._getSpanName( - { - request: req, - layerType: type, - route, - }, - metadata.name, - ); - const span = instrumentation.tracer.startSpan(spanName, { - attributes: Object.assign(attributes, metadata.attributes), - }); - - const { requestHook } = instrumentation.getConfig(); - if (requestHook) { - safeExecuteInTheMiddle( - () => - requestHook(span, { - request: req, - layerType: type, - route, - }), - e => { - if (e) { - diag.error('express instrumentation: request hook failed', e); - } - }, - true, - ); - } - - let spanHasEnded = false; - if (metadata.attributes[AttributeNames.EXPRESS_TYPE] !== ExpressLayerType.MIDDLEWARE) { - span.end(); - spanHasEnded = true; - } - // listener for response.on('finish') - const onResponseFinish = () => { - if (spanHasEnded === false) { - spanHasEnded = true; - span.end(); - } - }; - - // verify we have a callback - const args = Array.from(arguments); - const callbackIdx = args.findIndex(arg => typeof arg === 'function'); - if (callbackIdx >= 0) { - arguments[callbackIdx] = function () { - // express considers anything but an empty value, "route" or "router" - // passed to its callback to be an error - const maybeError = arguments[0]; - const isError = ![undefined, null, 'route', 'router'].includes(maybeError); - if (!spanHasEnded && isError) { - const [error, message] = asErrorAndMessage(maybeError); - span.recordException(error); - span.setStatus({ - code: SpanStatusCode.ERROR, - message, - }); - } - - if (spanHasEnded === false) { - spanHasEnded = true; - req.res?.removeListener('finish', onResponseFinish); - span.end(); - } - if (!(req.route && isError)) { - (req[_LAYERS_STORE_PROPERTY] as string[]).pop(); - } - const callback = args[callbackIdx] as Function; - return callback.apply(this, arguments); - }; - } - - try { - return original.apply(this, arguments); - } catch (anyError) { - const [error, message] = asErrorAndMessage(anyError); - span.recordException(error); - span.setStatus({ - code: SpanStatusCode.ERROR, - message, - }); - throw anyError; - } finally { - /** - * At this point if the callback wasn't called, that means either the - * layer is asynchronous (so it will call the callback later on) or that - * the layer directly end the http response, so we'll hook into the "finish" - * event to handle the later case. - */ - if (!spanHasEnded) { - res.once('finish', onResponseFinish); - } - } - }; - - // `handle` isn't just a regular function in some cases. It also contains - // some properties holding metadata and state so we need to proxy them - // through through patched function - // ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1950 - // Also some apps/libs do their own patching before OTEL and have these properties - // in the proptotype. So we use a `for...in` loop to get own properties and also - // any enumerable prop in the prototype chain - // ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2271 - for (const key in original) { - Object.defineProperty(patched, key, { - get() { - return original[key]; - }, - set(value) { - original[key] = value; - }, - }); - } - return patched; - }); - } - - _getSpanName(info: ExpressRequestInfo, defaultName: string) { - const { spanNameHook } = this.getConfig(); - - if (!(spanNameHook instanceof Function)) { - return defaultName; - } - - try { - return spanNameHook(info, defaultName) ?? defaultName; - } catch (err) { - diag.error('express instrumentation: error calling span name rewrite hook', err); - return defaultName; - } - } -} diff --git a/packages/node/src/integrations/tracing/express-v5/internal-types.ts b/packages/node/src/integrations/tracing/express-v5/internal-types.ts deleted file mode 100644 index 482dc0b6b4ea..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/internal-types.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/ban-types */ - -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Request } from 'express'; - -/** - * This symbol is used to mark express layer as being already instrumented - * since its possible to use a given layer multiple times (ex: middlewares) - */ -export const kLayerPatched: unique symbol = Symbol('express-layer-patched'); - -/** - * This const define where on the `request` object the Instrumentation will mount the - * current stack of express layer. - * - * It is necessary because express doesn't store the different layers - * (ie: middleware, router etc) that it called to get to the current layer. - * Given that, the only way to know the route of a given layer is to - * store the path of where each previous layer has been mounted. - * - * ex: bodyParser > auth middleware > /users router > get /:id - * in this case the stack would be: ["/users", "/:id"] - * - * ex2: bodyParser > /api router > /v1 router > /users router > get /:id - * stack: ["/api", "/v1", "/users", ":id"] - * - */ -export const _LAYERS_STORE_PROPERTY = '__ot_middlewares'; - -export type PatchedRequest = { - [_LAYERS_STORE_PROPERTY]?: string[]; -} & Request; -export type PathParams = string | RegExp | Array; - -// https://github.com/expressjs/express/blob/main/lib/router/index.js#L53 -export type ExpressRouter = { - stack: ExpressLayer[]; -}; - -// https://github.com/expressjs/express/blob/main/lib/router/layer.js#L33 -export type ExpressLayer = { - handle: Function & Record; - [kLayerPatched]?: boolean; - name: string; - path: string; - route?: ExpressLayer; -}; diff --git a/packages/node/src/integrations/tracing/express-v5/types.ts b/packages/node/src/integrations/tracing/express-v5/types.ts deleted file mode 100644 index 0623cac1cbc5..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/types.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Span } from '@opentelemetry/api'; -import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; -import type { ExpressLayerType } from './enums/ExpressLayerType'; - -export type LayerPathSegment = string | RegExp | number; - -export type IgnoreMatcher = string | RegExp | ((name: string) => boolean); - -export type ExpressRequestInfo = { - /** An express request object */ - request: T; - route: string; - layerType: ExpressLayerType; -}; - -export type SpanNameHook = ( - info: ExpressRequestInfo, - /** - * If no decision is taken based on RequestInfo, the default name - * supplied by the instrumentation can be used instead. - */ - defaultName: string, -) => string; - -/** - * Function that can be used to add custom attributes to the current span or the root span on - * a Express request - * @param span - The Express middleware layer span. - * @param info - An instance of ExpressRequestInfo that contains info about the request such as the route, and the layer type. - */ -export interface ExpressRequestCustomAttributeFunction { - (span: Span, info: ExpressRequestInfo): void; -} - -/** - * Options available for the Express Instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-express#express-instrumentation-options)) - */ -export interface ExpressInstrumentationConfig extends InstrumentationConfig { - /** Ignore specific based on their name */ - ignoreLayers?: IgnoreMatcher[]; - /** Ignore specific layers based on their type */ - ignoreLayersType?: ExpressLayerType[]; - spanNameHook?: SpanNameHook; - - /** Function for adding custom attributes on Express request */ - requestHook?: ExpressRequestCustomAttributeFunction; -} diff --git a/packages/node/src/integrations/tracing/express-v5/utils.ts b/packages/node/src/integrations/tracing/express-v5/utils.ts deleted file mode 100644 index 85bf42958bdd..000000000000 --- a/packages/node/src/integrations/tracing/express-v5/utils.ts +++ /dev/null @@ -1,191 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Attributes } from '@opentelemetry/api'; -import { AttributeNames } from './enums/AttributeNames'; -import { ExpressLayerType } from './enums/ExpressLayerType'; -import type { ExpressLayer, PatchedRequest } from './internal-types'; -import { _LAYERS_STORE_PROPERTY } from './internal-types'; -import type { ExpressInstrumentationConfig, IgnoreMatcher, LayerPathSegment } from './types'; - -/** - * Store layers path in the request to be able to construct route later - * @param request The request where - * @param [value] the value to push into the array - */ -export const storeLayerPath = (request: PatchedRequest, value?: string): void => { - if (Array.isArray(request[_LAYERS_STORE_PROPERTY]) === false) { - Object.defineProperty(request, _LAYERS_STORE_PROPERTY, { - enumerable: false, - value: [], - }); - } - if (value === undefined) return; - (request[_LAYERS_STORE_PROPERTY] as string[]).push(value); -}; - -/** - * Recursively search the router path from layer stack - * @param path The path to reconstruct - * @param layer The layer to reconstruct from - * @returns The reconstructed path - */ -export const getRouterPath = (path: string, layer: ExpressLayer): string => { - const stackLayer = layer.handle?.stack?.[0]; - - if (stackLayer?.route?.path) { - return `${path}${stackLayer.route.path}`; - } - - if (stackLayer?.handle?.stack) { - return getRouterPath(path, stackLayer); - } - - return path; -}; - -/** - * Parse express layer context to retrieve a name and attributes. - * @param route The route of the layer - * @param layer Express layer - * @param [layerPath] if present, the path on which the layer has been mounted - */ -export const getLayerMetadata = ( - route: string, - layer: ExpressLayer, - layerPath?: string, -): { - attributes: Attributes; - name: string; -} => { - if (layer.name === 'router') { - const maybeRouterPath = getRouterPath('', layer); - const extractedRouterPath = maybeRouterPath ? maybeRouterPath : layerPath || route || '/'; - - return { - attributes: { - [AttributeNames.EXPRESS_NAME]: extractedRouterPath, - [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.ROUTER, - }, - name: `router - ${extractedRouterPath}`, - }; - } else if (layer.name === 'bound dispatch' || layer.name === 'handle') { - return { - attributes: { - [AttributeNames.EXPRESS_NAME]: (route || layerPath) ?? 'request handler', - [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.REQUEST_HANDLER, - }, - name: `request handler${layer.path ? ` - ${route || layerPath}` : ''}`, - }; - } else { - return { - attributes: { - [AttributeNames.EXPRESS_NAME]: layer.name, - [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.MIDDLEWARE, - }, - name: `middleware - ${layer.name}`, - }; - } -}; - -/** - * Check whether the given obj match pattern - * @param constant e.g URL of request - * @param obj obj to inspect - * @param pattern Match pattern - */ -const satisfiesPattern = (constant: string, pattern: IgnoreMatcher): boolean => { - if (typeof pattern === 'string') { - return pattern === constant; - } else if (pattern instanceof RegExp) { - return pattern.test(constant); - } else if (typeof pattern === 'function') { - return pattern(constant); - } else { - throw new TypeError('Pattern is in unsupported datatype'); - } -}; - -/** - * Check whether the given request is ignored by configuration - * It will not re-throw exceptions from `list` provided by the client - * @param constant e.g URL of request - * @param [list] List of ignore patterns - * @param [onException] callback for doing something when an exception has - * occurred - */ -export const isLayerIgnored = ( - name: string, - type: ExpressLayerType, - config?: ExpressInstrumentationConfig, -): boolean => { - if (Array.isArray(config?.ignoreLayersType) && config?.ignoreLayersType?.includes(type)) { - return true; - } - if (Array.isArray(config?.ignoreLayers) === false) return false; - try { - for (const pattern of config!.ignoreLayers!) { - if (satisfiesPattern(name, pattern)) { - return true; - } - } - } catch { - /* catch block */ - } - - return false; -}; - -/** - * Converts a user-provided error value into an error and error message pair - * - * @param error - User-provided error value - * @returns Both an Error or string representation of the value and an error message - */ -export const asErrorAndMessage = (error: unknown): [error: string | Error, message: string] => - error instanceof Error ? [error, error.message] : [String(error), String(error)]; - -/** - * Extracts the layer path from the route arguments - * - * @param args - Arguments of the route - * @returns The layer path - */ -export const getLayerPath = (args: [LayerPathSegment | LayerPathSegment[], ...unknown[]]): string | undefined => { - const firstArg = args[0]; - - if (Array.isArray(firstArg)) { - return firstArg.map(arg => extractLayerPathSegment(arg) || '').join(','); - } - - return extractLayerPathSegment(firstArg); -}; - -const extractLayerPathSegment = (arg: LayerPathSegment) => { - if (typeof arg === 'string') { - return arg; - } - - if (arg instanceof RegExp || typeof arg === 'number') { - return arg.toString(); - } - - return; -}; diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts index 24155e2f5452..fed0a65cfd0b 100644 --- a/packages/node/src/integrations/tracing/express.ts +++ b/packages/node/src/integrations/tracing/express.ts @@ -15,10 +15,8 @@ import { } from '@sentry/core'; import { addOriginToSpan, ensureIsWrapped, generateInstrumentOnce } from '@sentry/node-core'; import { DEBUG_BUILD } from '../../debug-build'; -import { ExpressInstrumentationV5 } from './express-v5/instrumentation'; const INTEGRATION_NAME = 'Express'; -const INTEGRATION_NAME_V5 = 'Express-V5'; function requestHook(span: Span): void { addOriginToSpan(span, 'auto.http.otel.express'); @@ -61,21 +59,11 @@ export const instrumentExpress = generateInstrumentOnce( }), ); -export const instrumentExpressV5 = generateInstrumentOnce( - INTEGRATION_NAME_V5, - () => - new ExpressInstrumentationV5({ - requestHook: span => requestHook(span), - spanNameHook: (info, defaultName) => spanNameHook(info, defaultName), - }), -); - const _expressIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { instrumentExpress(); - instrumentExpressV5(); }, }; }) satisfies IntegrationFn; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 54fb4c72be2d..f27b3cf615e8 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -2,7 +2,7 @@ import type { Integration } from '@sentry/core'; import { instrumentOtelHttp } from '../http'; import { amqplibIntegration, instrumentAmqplib } from './amqplib'; import { connectIntegration, instrumentConnect } from './connect'; -import { expressIntegration, instrumentExpress, instrumentExpressV5 } from './express'; +import { expressIntegration, instrumentExpress } from './express'; import { fastifyIntegration, instrumentFastify, instrumentFastifyV3 } from './fastify'; import { genericPoolIntegration, instrumentGenericPool } from './genericPool'; import { graphqlIntegration, instrumentGraphql } from './graphql'; @@ -59,7 +59,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => return [ instrumentOtelHttp, instrumentExpress, - instrumentExpressV5, instrumentConnect, instrumentFastify, instrumentFastifyV3, diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 66c95705e457..308c8be29abe 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -1,7 +1,7 @@ import type { Span } from '@opentelemetry/api'; import type { RedisResponseCustomAttributeFunction } from '@opentelemetry/instrumentation-ioredis'; import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'; -import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4'; +import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis'; import type { IntegrationFn } from '@sentry/core'; import { defineIntegration, @@ -81,7 +81,7 @@ const instrumentIORedis = generateInstrumentOnce('IORedis', () => { }); }); -const instrumentRedis4 = generateInstrumentOnce('Redis-4', () => { +const instrumentRedisModule = generateInstrumentOnce('Redis', () => { return new RedisInstrumentation({ responseHook: cacheResponseHook, }); @@ -91,7 +91,7 @@ const instrumentRedis4 = generateInstrumentOnce('Redis-4', () => { export const instrumentRedis = Object.assign( (): void => { instrumentIORedis(); - instrumentRedis4(); + instrumentRedisModule(); // todo: implement them gradually // new LegacyRedisInstrumentation({}), diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 4e58414f347a..fc6b02c3830d 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -1,5 +1,5 @@ import { context, propagation, trace } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; +import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { @@ -109,12 +109,14 @@ export function setupOtel(client: NodeClient, options: AdditionalOpenTelemetryOp // Create and configure NodeTracerProvider const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), - resource: new Resource({ - [ATTR_SERVICE_NAME]: 'node', - // eslint-disable-next-line deprecation/deprecation - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_VERSION]: SDK_VERSION, - }), + resource: defaultResource().merge( + resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'node', + // eslint-disable-next-line deprecation/deprecation + [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', + [ATTR_SERVICE_VERSION]: SDK_VERSION, + }), + ), forceFlushTimeoutMillis: 500, spanProcessors: [ new SentrySpanProcessor({ diff --git a/packages/node/test/helpers/mockSdkInit.ts b/packages/node/test/helpers/mockSdkInit.ts index 29e19f50e0f8..dc4c3586d978 100644 --- a/packages/node/test/helpers/mockSdkInit.ts +++ b/packages/node/test/helpers/mockSdkInit.ts @@ -1,6 +1,7 @@ import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api'; -import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; +import { type SpanProcessor, BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; +import { SentrySpanProcessor } from '@sentry/opentelemetry'; import type { NodeClient } from '../../src'; import { init } from '../../src/sdk'; import type { NodeClientOptions } from '../../src/types'; @@ -42,6 +43,29 @@ export function cleanupOtel(_provider?: BasicTracerProvider): void { propagation.disable(); } +export function getSpanProcessor(): SentrySpanProcessor | undefined { + const client = getClient(); + if (!client?.traceProvider) { + return undefined; + } + + const provider = getProvider(client.traceProvider); + if (!provider) { + return undefined; + } + + // Access the span processors from the provider via _activeSpanProcessor + const multiSpanProcessor = provider?.['_activeSpanProcessor'] as + | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) + | undefined; + + const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( + (spanProcessor: SpanProcessor) => spanProcessor instanceof SentrySpanProcessor, + ) as SentrySpanProcessor | undefined; + + return spanProcessor; +} + export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined { let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider(); diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts index 0ce3f7c99984..7b13a400dedb 100644 --- a/packages/node/test/integration/transactions.test.ts +++ b/packages/node/test/integration/transactions.test.ts @@ -1,11 +1,9 @@ import { context, trace, TraceFlags } from '@opentelemetry/api'; -import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; import type { TransactionEvent } from '@sentry/core'; import { debug, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { SentrySpanProcessor } from '@sentry/opentelemetry'; import { afterEach, describe, expect, it, vi } from 'vitest'; import * as Sentry from '../../src'; -import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; +import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit'; describe('Integration | Transactions', () => { afterEach(() => { @@ -562,13 +560,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 6ff4e1de048e..6734b3183b22 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -50,9 +50,9 @@ }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1", + "@opentelemetry/context-async-hooks": "^2.0.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0" }, "scripts": { diff --git a/packages/opentelemetry/test/asyncContextStrategy.test.ts b/packages/opentelemetry/test/asyncContextStrategy.test.ts index 6541e8b9220c..89a3ab856075 100644 --- a/packages/opentelemetry/test/asyncContextStrategy.test.ts +++ b/packages/opentelemetry/test/asyncContextStrategy.test.ts @@ -23,7 +23,7 @@ describe('asyncContextStrategy', () => { const options = getDefaultTestClientOptions(); const client = new TestClient(options); - provider = setupOtel(client); + [provider] = setupOtel(client); setOpenTelemetryContextAsyncContextStrategy(); }); diff --git a/packages/opentelemetry/test/helpers/initOtel.ts b/packages/opentelemetry/test/helpers/initOtel.ts index fd7a33884b5c..bf281f716657 100644 --- a/packages/opentelemetry/test/helpers/initOtel.ts +++ b/packages/opentelemetry/test/helpers/initOtel.ts @@ -1,6 +1,6 @@ import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; -import { Resource } from '@opentelemetry/resources'; +import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { ATTR_SERVICE_NAME, @@ -49,23 +49,27 @@ export function initOtel(): void { setupEventContextTrace(client); enhanceDscWithOpenTelemetryRootSpanName(client); - const provider = setupOtel(client); + const [provider, spanProcessor] = setupOtel(client); client.traceProvider = provider; + client.spanProcessor = spanProcessor; } /** Just exported for tests. */ -export function setupOtel(client: TestClientInterface): BasicTracerProvider { +export function setupOtel(client: TestClientInterface): [BasicTracerProvider, SentrySpanProcessor] { + const spanProcessor = new SentrySpanProcessor(); // Create and configure NodeTracerProvider const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), - resource: new Resource({ - [ATTR_SERVICE_NAME]: 'opentelemetry-test', - // eslint-disable-next-line deprecation/deprecation - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_VERSION]: SDK_VERSION, - }), + resource: defaultResource().merge( + resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'opentelemetry-test', + // eslint-disable-next-line deprecation/deprecation + [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', + [ATTR_SERVICE_VERSION]: SDK_VERSION, + }), + ), forceFlushTimeoutMillis: 500, - spanProcessors: [new SentrySpanProcessor()], + spanProcessors: [spanProcessor], }); // We use a custom context manager to keep context in sync with sentry scope @@ -75,5 +79,5 @@ export function setupOtel(client: TestClientInterface): BasicTracerProvider { propagation.setGlobalPropagator(new SentryPropagator()); context.setGlobalContextManager(new SentryContextManager()); - return provider; + return [provider, spanProcessor]; } diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/isSpan.ts b/packages/opentelemetry/test/helpers/isSpan.ts similarity index 100% rename from dev-packages/opentelemetry-v2-tests/test/helpers/isSpan.ts rename to packages/opentelemetry/test/helpers/isSpan.ts diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index 486397e32cef..91b1369eb928 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -3,6 +3,7 @@ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { ClientOptions, Options } from '@sentry/core'; import { flush, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy'; +import { SentrySpanProcessor } from '../../src/spanProcessor'; import type { OpenTelemetryClient } from '../../src/types'; import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck'; import { initOtel } from './initOtel'; @@ -51,6 +52,20 @@ export async function cleanupOtel(_provider?: BasicTracerProvider): Promise(); + if (!client) { + return undefined; + } + + const spanProcessor = client.spanProcessor; + if (spanProcessor instanceof SentrySpanProcessor) { + return spanProcessor; + } + + return undefined; +} + export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined { let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider(); diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index b476d7536e5e..570df4a86aa8 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -1,7 +1,6 @@ import type { SpanContext } from '@opentelemetry/api'; import { context, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api'; import { TraceState } from '@opentelemetry/core'; -import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; import type { Event, TransactionEvent } from '@sentry/core'; import { addBreadcrumb, @@ -15,10 +14,9 @@ import { } from '@sentry/core'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { SENTRY_TRACE_STATE_DSC } from '../../src/constants'; -import { SentrySpanProcessor } from '../../src/spanProcessor'; import { startInactiveSpan, startSpan } from '../../src/trace'; import { makeTraceState } from '../../src/utils/makeTraceState'; -import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; +import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit'; import type { TestClientInterface } from '../helpers/TestClient'; describe('Integration | Transactions', () => { @@ -444,13 +442,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; @@ -522,13 +514,7 @@ describe('Integration | Transactions', () => { }, }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; @@ -580,13 +566,7 @@ describe('Integration | Transactions', () => { }, }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; @@ -651,13 +631,7 @@ describe('Integration | Transactions', () => { }, }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; @@ -713,13 +687,7 @@ describe('Integration | Transactions', () => { }, }); - const provider = getProvider(); - const multiSpanProcessor = provider?.activeSpanProcessor as - | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) - | undefined; - const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find( - spanProcessor => spanProcessor instanceof SentrySpanProcessor, - ) as SentrySpanProcessor | undefined; + const spanProcessor = getSpanProcessor(); const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined; diff --git a/packages/opentelemetry/test/sampler.test.ts b/packages/opentelemetry/test/sampler.test.ts index 6ae5c3d48308..b7ffd9522837 100644 --- a/packages/opentelemetry/test/sampler.test.ts +++ b/packages/opentelemetry/test/sampler.test.ts @@ -81,11 +81,13 @@ describe('SentrySampler', () => { const links = undefined; const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); - expect(actual).toEqual({ - decision: SamplingDecision.RECORD_AND_SAMPLED, - attributes: { 'sentry.sample_rate': 1 }, - traceState: expect.any(TraceState), - }); + expect(actual).toEqual( + expect.objectContaining({ + decision: SamplingDecision.RECORD_AND_SAMPLED, + attributes: { 'sentry.sample_rate': 1 }, + }), + ); + expect(actual.traceState?.constructor.name).toBe('TraceState'); expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); spyOnDroppedEvent.mockReset(); diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index d8432172a601..173bd6359a5f 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -2,7 +2,6 @@ import type { Span, TimeInput } from '@opentelemetry/api'; import { context, ROOT_CONTEXT, SpanKind, trace, TraceFlags } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { Span as SpanClass } from '@opentelemetry/sdk-trace-base'; import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; import type { Event, Scope } from '@sentry/core'; import { @@ -21,6 +20,7 @@ import { withScope, } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { getParentSpanId } from '../../../packages/opentelemetry/src/utils/getParentSpanId'; import { continueTrace, startInactiveSpan, startSpan, startSpanManual } from '../src/trace'; import type { AbstractSpan } from '../src/types'; import { getActiveSpan } from '../src/utils/getActiveSpan'; @@ -28,6 +28,7 @@ import { getSamplingDecision } from '../src/utils/getSamplingDecision'; import { getSpanKind } from '../src/utils/getSpanKind'; import { makeTraceState } from '../src/utils/makeTraceState'; import { spanHasAttributes, spanHasName } from '../src/utils/spanTypes'; +import { isSpan } from './helpers/isSpan'; import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; describe('trace', () => { @@ -534,7 +535,7 @@ describe('trace', () => { return span; }); - expect(span).not.toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(false); }); it('creates a span if there is a parent', () => { @@ -546,7 +547,7 @@ describe('trace', () => { return span; }); - expect(span).toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(true); }); }); }); @@ -826,7 +827,7 @@ describe('trace', () => { it('does not create a span if there is no parent', () => { const span = startInactiveSpan({ name: 'test span', onlyIfParent: true }); - expect(span).not.toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(false); }); it('creates a span if there is a parent', () => { @@ -836,7 +837,7 @@ describe('trace', () => { return span; }); - expect(span).toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(true); }); }); @@ -1196,7 +1197,7 @@ describe('trace', () => { return span; }); - expect(span).not.toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(false); }); it('creates a span if there is a parent', () => { @@ -1208,7 +1209,7 @@ describe('trace', () => { return span; }); - expect(span).toBeInstanceOf(SpanClass); + expect(isSpan(span)).toBe(true); }); }); }); @@ -1972,5 +1973,5 @@ function getSpanAttributes(span: AbstractSpan): Record | undefi } function getSpanParentSpanId(span: AbstractSpan): string | undefined { - return (span as ReadableSpan).parentSpanId; + return getParentSpanId(span as ReadableSpan); } diff --git a/packages/opentelemetry/test/utils/getActiveSpan.test.ts b/packages/opentelemetry/test/utils/getActiveSpan.test.ts index 383f91d5d6af..7a3eefaa6b3d 100644 --- a/packages/opentelemetry/test/utils/getActiveSpan.test.ts +++ b/packages/opentelemetry/test/utils/getActiveSpan.test.ts @@ -12,7 +12,7 @@ describe('getActiveSpan', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions()); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { @@ -97,7 +97,7 @@ describe('getRootSpan', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(async () => { diff --git a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts index 2f5484d916b9..ad40ec83d480 100644 --- a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts +++ b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts @@ -14,7 +14,7 @@ describe('getRequestSpanData', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { diff --git a/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts b/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts index d8ccec93f3e2..c71569c322d5 100644 --- a/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts +++ b/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts @@ -13,7 +13,7 @@ describe('groupSpansWithParents', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { diff --git a/packages/opentelemetry/test/utils/mapStatus.test.ts b/packages/opentelemetry/test/utils/mapStatus.test.ts index 4147eeca2251..1831ec01fc95 100644 --- a/packages/opentelemetry/test/utils/mapStatus.test.ts +++ b/packages/opentelemetry/test/utils/mapStatus.test.ts @@ -16,7 +16,7 @@ describe('mapStatus', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { diff --git a/packages/opentelemetry/test/utils/setupCheck.test.ts b/packages/opentelemetry/test/utils/setupCheck.test.ts index a2b204d063b6..526945108ba7 100644 --- a/packages/opentelemetry/test/utils/setupCheck.test.ts +++ b/packages/opentelemetry/test/utils/setupCheck.test.ts @@ -25,20 +25,18 @@ describe('openTelemetrySetupCheck', () => { it('returns all setup parts', () => { const client = new TestClient(getDefaultTestClientOptions()); - provider = setupOtel(client); + [provider] = setupOtel(client); const setup = openTelemetrySetupCheck(); - expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor', 'SentryPropagator', 'SentryContextManager']); + expect(setup).toEqual(['SentrySpanProcessor', 'SentrySampler', 'SentryPropagator', 'SentryContextManager']); }); it('returns partial setup parts', () => { const client = new TestClient(getDefaultTestClientOptions()); provider = new BasicTracerProvider({ sampler: new SentrySampler(client), + spanProcessors: [new SentrySpanProcessor()], }); - // We want to test this deprecated case also works - // eslint-disable-next-line deprecation/deprecation - provider.addSpanProcessor(new SentrySpanProcessor()); const setup = openTelemetrySetupCheck(); expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']); diff --git a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts index a705b546e610..19c8e178c160 100644 --- a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts +++ b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts @@ -29,7 +29,7 @@ describe('setupEventContextTrace', () => { client.init(); setupEventContextTrace(client); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { diff --git a/packages/opentelemetry/test/utils/spanToJSON.test.ts b/packages/opentelemetry/test/utils/spanToJSON.test.ts index 19115a18e0f7..c1f9fe2a18c7 100644 --- a/packages/opentelemetry/test/utils/spanToJSON.test.ts +++ b/packages/opentelemetry/test/utils/spanToJSON.test.ts @@ -18,7 +18,7 @@ describe('spanToJSON', () => { beforeEach(() => { const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); - provider = setupOtel(client); + [provider] = setupOtel(client); }); afterEach(() => { diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 965392e80c10..0b796d62b893 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -35,8 +35,8 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/instrumentation": "0.57.2", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@sentry/browser": "9.40.0", "@sentry/cli": "^2.46.0", diff --git a/packages/remix/package.json b/packages/remix/package.json index ba0dccdb3786..8e3eb52a6504 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.57.2", + "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.46.0", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 63209f104add..a5926948be3a 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -40,15 +40,15 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/resources": "^1.30.1", - "@opentelemetry/semantic-conventions": "^1.34.0", - "@sentry/core": "9.40.0", - "@sentry/opentelemetry": "9.40.0" + "@opentelemetry/resources": "^2.0.0", + "@sentry/core": "9.40.0" }, "devDependencies": { "@edge-runtime/types": "3.0.1", - "@opentelemetry/core": "^1.30.1", - "@opentelemetry/sdk-trace-base": "^1.30.1" + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@sentry/opentelemetry": "9.40.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/vercel-edge/rollup.npm.config.mjs b/packages/vercel-edge/rollup.npm.config.mjs index 3cfd779d57f6..ae01f43703d0 100644 --- a/packages/vercel-edge/rollup.npm.config.mjs +++ b/packages/vercel-edge/rollup.npm.config.mjs @@ -4,7 +4,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants, plugins } from '@sentry-inter export default makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: ['src/index.ts'], - bundledBuiltins: ['perf_hooks'], + bundledBuiltins: ['perf_hooks', 'util'], packageSpecificConfig: { context: 'globalThis', output: { @@ -21,9 +21,10 @@ export default makeNPMConfigVariants( }), { // This plugin is needed because otel imports `performance` from `perf_hooks` and also uses it via the `performance` global. + // It also imports `inspect` and `promisify` from node's `util` which are not available in the edge runtime so we need to define a polyfill. // Both of these APIs are not available in the edge runtime so we need to define a polyfill. // Vercel does something similar in the `@vercel/otel` package: https://github.com/vercel/otel/blob/087601ae585cb116bb2b46c211d014520de76c71/packages/otel/build.ts#L62 - name: 'perf-hooks-performance-polyfill', + name: 'edge-runtime-polyfills', banner: ` { if (globalThis.performance === undefined) { @@ -37,6 +38,8 @@ export default makeNPMConfigVariants( resolveId: source => { if (source === 'perf_hooks') { return '\0perf_hooks_sentry_shim'; + } else if (source === 'util') { + return '\0util_sentry_shim'; } else { return null; } @@ -49,6 +52,22 @@ export default makeNPMConfigVariants( now: () => Date.now() } `; + } else if (id === '\0util_sentry_shim') { + return ` + export const inspect = (object) => + JSON.stringify(object, null, 2); + + export const promisify = (fn) => { + return (...args) => { + return new Promise((resolve, reject) => { + fn(...args, (err, result) => { + if (err) reject(err); + else resolve(result); + }); + }); + }; + }; + `; } else { return null; } diff --git a/packages/vercel-edge/src/client.ts b/packages/vercel-edge/src/client.ts index bdf8512be933..a34d1b36f09c 100644 --- a/packages/vercel-edge/src/client.ts +++ b/packages/vercel-edge/src/client.ts @@ -39,11 +39,8 @@ export class VercelEdgeClient extends ServerRuntimeClient { const provider = this.traceProvider; - const spanProcessor = provider?.activeSpanProcessor; - if (spanProcessor) { - await spanProcessor.forceFlush(); - } + await provider?.forceFlush(); if (this.getOptions().sendClientReports) { this._flushOutcomes(); diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 12485a6c6579..ba83d7752ed3 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -1,5 +1,5 @@ import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; +import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { ATTR_SERVICE_NAME, @@ -158,12 +158,14 @@ export function setupOtel(client: VercelEdgeClient): void { // Create and configure NodeTracerProvider const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), - resource: new Resource({ - [ATTR_SERVICE_NAME]: 'edge', - // eslint-disable-next-line deprecation/deprecation - [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [ATTR_SERVICE_VERSION]: SDK_VERSION, - }), + resource: defaultResource().merge( + resourceFromAttributes({ + [ATTR_SERVICE_NAME]: 'edge', + // eslint-disable-next-line deprecation/deprecation + [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', + [ATTR_SERVICE_VERSION]: SDK_VERSION, + }), + ), forceFlushTimeoutMillis: 500, spanProcessors: [ new SentrySpanProcessor({ diff --git a/yarn.lock b/yarn.lock index 16bf8fe6d899..10c669dc1da4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5843,249 +5843,252 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/context-async-hooks@^1.30.1": - version "1.30.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz#4f76280691a742597fd0bf682982126857622948" - integrity sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA== - "@opentelemetry/context-async-hooks@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.0.tgz#c98a727238ca199cda943780acf6124af8d8cd80" integrity sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA== -"@opentelemetry/core@1.30.1", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.26.0", "@opentelemetry/core@^1.30.1", "@opentelemetry/core@^1.8.0": - version "1.30.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.30.1.tgz#a0b468bb396358df801881709ea38299fc30ab27" - integrity sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ== - dependencies: - "@opentelemetry/semantic-conventions" "1.28.0" - -"@opentelemetry/core@2.0.0", "@opentelemetry/core@^2.0.0": +"@opentelemetry/core@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.0.0.tgz#37e9f0e9ddec4479b267aca6f32d88757c941b3a" integrity sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ== dependencies: "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/instrumentation-amqplib@^0.46.1": - version "0.46.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz#7101678488d0e942162ca85c9ac6e93e1f3e0008" - integrity sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ== +"@opentelemetry/core@2.0.1", "@opentelemetry/core@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.0.1.tgz#44e1149d5666a4743cde943ef89841db3ce0f8bc" + integrity sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/instrumentation-aws-lambda@0.50.3": - version "0.50.3" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.50.3.tgz#bf76bd137780004aecfbb5c8335482afe5739878" - integrity sha512-kotm/mRvSWUauudxcylc5YCDei+G/r+jnOH6q5S99aPLQ/Ms8D2yonMIxEJUILIPlthEmwLYxkw3ualWzMjm/A== +"@opentelemetry/instrumentation-amqplib@0.50.0": + version "0.50.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.50.0.tgz#91899a7e2821db956daeaa803d3bd8f5af8b8050" + integrity sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" - "@types/aws-lambda" "8.10.147" -"@opentelemetry/instrumentation-aws-sdk@0.49.1": - version "0.49.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.49.1.tgz#e5de7235af82a5b77eca2132da62d41d64dbbba9" - integrity sha512-Vbj4BYeV/1K4Pbbfk+gQ8gwYL0w+tBeUwG88cOxnF7CLPO1XnskGV8Q3Gzut2Ah/6Dg17dBtlzEqL3UiFP2Z6A== +"@opentelemetry/instrumentation-aws-lambda@0.54.0": + version "0.54.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.54.0.tgz#835263593aa988ec460e840d3d47110392aaf92e" + integrity sha512-uiYI+kcMUJ/H9cxAwB8c9CaG8behLRgcYSOEA8M/tMQ54Y1ZmzAuEE3QKOi21/s30x5Q+by9g7BwiVfDtqzeMA== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/propagation-utils" "^0.30.16" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/aws-lambda" "8.10.150" + +"@opentelemetry/instrumentation-aws-sdk@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.56.0.tgz#a65cd88351b7bd8566413798764679295166754a" + integrity sha512-Jl2B/FYEb6tBCk9G31CMomKPikGU2g+CEhrGddDI0o1YeNpg3kAO9dExF+w489/IJUGZX6/wudyNvV7z4k9NjQ== + dependencies: + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/propagation-utils" "^0.31.3" + "@opentelemetry/semantic-conventions" "^1.34.0" -"@opentelemetry/instrumentation-connect@0.43.1": - version "0.43.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz#8ce88b94ce211c7bbdc9bd984b7a37876061bde3" - integrity sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw== +"@opentelemetry/instrumentation-connect@0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.47.0.tgz#47271b8454fa88d97aa78e175c3d0cb7e10bd9e2" + integrity sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@types/connect" "3.4.38" -"@opentelemetry/instrumentation-dataloader@0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz#5d1d2c79f067c3102df7101f1753060ed93a1566" - integrity sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ== +"@opentelemetry/instrumentation-dataloader@0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.0.tgz#19202a85000cae9612f74bc689005ed3164e30a4" + integrity sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" -"@opentelemetry/instrumentation-express@0.47.1": - version "0.47.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz#7cf74f35e43cc3c8186edd1249fdb225849c48b2" - integrity sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw== +"@opentelemetry/instrumentation-express@0.52.0": + version "0.52.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.52.0.tgz#d87d2130fe779dd757db28edb78262af83510d5b" + integrity sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-fs@0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz#ebfe40781949574a66a82b8511d9bcd414dbfe98" - integrity sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A== +"@opentelemetry/instrumentation-fs@0.23.0": + version "0.23.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.23.0.tgz#e3cd3a53fa975c69de33e207b35561f3f90106f0" + integrity sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" -"@opentelemetry/instrumentation-generic-pool@0.43.1": - version "0.43.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz#6d1e181b32debc9510bdbbd63fe4ce5bc310d577" - integrity sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww== +"@opentelemetry/instrumentation-generic-pool@0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.47.0.tgz#f5fa9d42236eb7d57fa544954f316faee937b0b4" + integrity sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" -"@opentelemetry/instrumentation-graphql@0.47.1": - version "0.47.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz#1037bb546c82060d6d5d6f5dbd8765e31ccf6c26" - integrity sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ== +"@opentelemetry/instrumentation-graphql@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.51.0.tgz#1b29aa6330d196d523460e593167dca7dbcd42bb" + integrity sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" -"@opentelemetry/instrumentation-hapi@0.45.2": - version "0.45.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz#14d670e0bbbdf864187a9f80265a9219ed2d01cf" - integrity sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ== +"@opentelemetry/instrumentation-hapi@0.50.0": + version "0.50.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.50.0.tgz#c755e9c21bfeb82046221bfd51303f816ae649e8" + integrity sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-http@0.57.2": - version "0.57.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz#f425eda67b6241c3abe08e4ea972169b85ef3064" - integrity sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg== +"@opentelemetry/instrumentation-http@0.203.0": + version "0.203.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.203.0.tgz#21f198547b5c72fc64e83ed25cdc991aef7b8fee" + integrity sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g== dependencies: - "@opentelemetry/core" "1.30.1" - "@opentelemetry/instrumentation" "0.57.2" - "@opentelemetry/semantic-conventions" "1.28.0" + "@opentelemetry/core" "2.0.1" + "@opentelemetry/instrumentation" "0.203.0" + "@opentelemetry/semantic-conventions" "^1.29.0" forwarded-parse "2.1.2" - semver "^7.5.2" -"@opentelemetry/instrumentation-ioredis@0.47.1": - version "0.47.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz#5cedd0ebe8cfd3569513a9b44945827bf844b331" - integrity sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA== +"@opentelemetry/instrumentation-ioredis@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.51.0.tgz#47360999ad2b035aa2ac604c410272da671142d3" + integrity sha512-9IUws0XWCb80NovS+17eONXsw1ZJbHwYYMXiwsfR9TSurkLV5UNbRSKb9URHO+K+pIJILy9wCxvyiOneMr91Ig== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/redis-common" "^0.38.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-kafkajs@0.7.1": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz#cc7a31a5fe2c14171611da8e46827f762f332625" - integrity sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ== +"@opentelemetry/instrumentation-kafkajs@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.12.0.tgz#231e6cc8a2a70d06162ed7e4ebe2ab5baa3a6670" + integrity sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/semantic-conventions" "^1.30.0" -"@opentelemetry/instrumentation-knex@0.44.1": - version "0.44.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz#72f4efd798695c077ab218045d4c682231fbb36a" - integrity sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ== +"@opentelemetry/instrumentation-knex@0.48.0": + version "0.48.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.48.0.tgz#ed24a81dfe6099cfe56136a3fed90565e1259f58" + integrity sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/semantic-conventions" "^1.33.1" -"@opentelemetry/instrumentation-koa@0.47.1": - version "0.47.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz#ba57eccd44a75ec59e3129757fda4e8c8dd7ce2c" - integrity sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A== +"@opentelemetry/instrumentation-koa@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.51.0.tgz#1ff57866b7882033639477d3d2d9bada19a2129f" + integrity sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-lru-memoizer@0.44.1": - version "0.44.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz#1f0ec28130f8c379d310dc531a8b25780be8e445" - integrity sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg== +"@opentelemetry/instrumentation-lru-memoizer@0.48.0": + version "0.48.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.48.0.tgz#b9fbbd45b7a742a6795bf7166f65684251f184b1" + integrity sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" -"@opentelemetry/instrumentation-mongodb@0.52.0": - version "0.52.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz#a5ed123f3fac5d7d08347353cd37d9cf00893746" - integrity sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g== +"@opentelemetry/instrumentation-mongodb@0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.56.0.tgz#81281d2d151c3bfb26864c50b938a82ba2831b2d" + integrity sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-mongoose@0.46.1": - version "0.46.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz#23f22b7d4d5a548ac8add2a52ec2fec4e61c7de1" - integrity sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg== +"@opentelemetry/instrumentation-mongoose@0.50.0": + version "0.50.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.50.0.tgz#1fae5d2769ca7e67d15291fb91b61403839ad91d" + integrity sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-mysql2@0.45.2": - version "0.45.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz#590ed22f274a6999e57c3283433a119274cb572b" - integrity sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ== +"@opentelemetry/instrumentation-mysql2@0.49.0": + version "0.49.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.49.0.tgz#ad518f9420cf8d2035bd4f80519406b66b66bb1a" + integrity sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" - "@opentelemetry/sql-common" "^0.40.1" + "@opentelemetry/sql-common" "^0.41.0" -"@opentelemetry/instrumentation-mysql@0.45.1": - version "0.45.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz#6fb3fdf7b5afa62bfa4ce73fae213539bb660841" - integrity sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg== +"@opentelemetry/instrumentation-mysql@0.49.0": + version "0.49.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.49.0.tgz#24fa7473134867236ed4068ee645e51922bcb654" + integrity sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" - "@types/mysql" "2.15.26" + "@types/mysql" "2.15.27" -"@opentelemetry/instrumentation-nestjs-core@0.44.1": - version "0.44.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.44.1.tgz#54ee5877080055732093c59f8a9bc2aba4fae5f0" - integrity sha512-4TXaqJK27QXoMqrt4+hcQ6rKFd8B6V4JfrTJKnqBmWR1cbaqd/uwyl9yxhNH1JEkyo8GaBfdpBC4ZE4FuUhPmg== +"@opentelemetry/instrumentation-nestjs-core@0.49.0": + version "0.49.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.49.0.tgz#a79428e72c14250c44913a3a57f39c7297aab013" + integrity sha512-1R/JFwdmZIk3T/cPOCkVvFQeKYzbbUvDxVH3ShXamUwBlGkdEu5QJitlRMyVNZaHkKZKWgYrBarGQsqcboYgaw== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/semantic-conventions" "^1.30.0" -"@opentelemetry/instrumentation-pg@0.51.1": - version "0.51.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz#a999a13fa56dc67da49a1ccf8f5e56a9ed409477" - integrity sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q== +"@opentelemetry/instrumentation-pg@0.55.0": + version "0.55.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.55.0.tgz#f411d1e48c50b1c1f0f185d9fe94cfbb8812d8f6" + integrity sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw== dependencies: - "@opentelemetry/core" "^1.26.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" - "@opentelemetry/sql-common" "^0.40.1" - "@types/pg" "8.6.1" + "@opentelemetry/sql-common" "^0.41.0" + "@types/pg" "8.15.4" "@types/pg-pool" "2.0.6" -"@opentelemetry/instrumentation-redis-4@0.46.1": - version "0.46.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz#325697dfccda3e70662769c6db230a37812697c6" - integrity sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ== +"@opentelemetry/instrumentation-redis@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.51.0.tgz#70504ba6c3856fcb25e436b4915e85efaa7d38a6" + integrity sha512-uL/GtBA0u72YPPehwOvthAe+Wf8k3T+XQPBssJmTYl6fzuZjNq8zTfxVFhl9nRFjFVEe+CtiYNT0Q3AyqW1Z0A== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" - "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/instrumentation" "^0.203.0" + "@opentelemetry/redis-common" "^0.38.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-tedious@0.18.1": - version "0.18.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz#d87dba9d0ddfc77f9fcbcceabcc31cb5a5f7bb11" - integrity sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg== +"@opentelemetry/instrumentation-tedious@0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.22.0.tgz#f71374c52cb9c57a6b879bea3256a1465c02efbb" + integrity sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA== dependencies: - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@types/tedious" "^4.0.14" -"@opentelemetry/instrumentation-undici@0.10.1": - version "0.10.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz#228b7fc267e55533708be16c43e70bbb51a691de" - integrity sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ== +"@opentelemetry/instrumentation-undici@0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz#7a9cd276f7664773b5daf5ae53365b3593e6e7a9" + integrity sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg== + dependencies: + "@opentelemetry/core" "^2.0.0" + "@opentelemetry/instrumentation" "^0.203.0" + +"@opentelemetry/instrumentation@0.203.0", "@opentelemetry/instrumentation@^0.203.0": + version "0.203.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz#5c74a41cd6868f7ba47b346ff5a58ea7b18cf381" + integrity sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ== dependencies: - "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.57.1" + "@opentelemetry/api-logs" "0.203.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" -"@opentelemetry/instrumentation@0.57.2", "@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0", "@opentelemetry/instrumentation@^0.57.1", "@opentelemetry/instrumentation@^0.57.2": +"@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0": version "0.57.2" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz#8924549d7941ba1b5c6f04d5529cf48330456d1d" integrity sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg== @@ -6097,32 +6100,15 @@ semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/instrumentation@^0.203.0": - version "0.203.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz#5c74a41cd6868f7ba47b346ff5a58ea7b18cf381" - integrity sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ== - dependencies: - "@opentelemetry/api-logs" "0.203.0" - import-in-the-middle "^1.8.1" - require-in-the-middle "^7.1.1" - -"@opentelemetry/propagation-utils@^0.30.16": - version "0.30.16" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.16.tgz#6715d0225b618ea66cf34cc3800fa3452a8475fa" - integrity sha512-ZVQ3Z/PQ+2GQlrBfbMMMT0U7MzvYZLCPP800+ooyaBqm4hMvuQHfP028gB9/db0mwkmyEAMad9houukUVxhwcw== - -"@opentelemetry/redis-common@^0.36.2": - version "0.36.2" - resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47" - integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== +"@opentelemetry/propagation-utils@^0.31.3": + version "0.31.3" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.31.3.tgz#42aab61a1a3cec64ce221cbcec5f3f6fc84e9701" + integrity sha512-ZI6LKjyo+QYYZY5SO8vfoCQ9A69r1/g+pyjvtu5RSK38npINN1evEmwqbqhbg2CdcIK3a4PN6pDAJz/yC5/gAA== -"@opentelemetry/resources@1.30.1", "@opentelemetry/resources@^1.30.1": - version "1.30.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.30.1.tgz#a4eae17ebd96947fdc7a64f931ca4b71e18ce964" - integrity sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA== - dependencies: - "@opentelemetry/core" "1.30.1" - "@opentelemetry/semantic-conventions" "1.28.0" +"@opentelemetry/redis-common@^0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.0.tgz#87d2a792dcbcf466a41bb7dfb8a7cd094d643d0b" + integrity sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ== "@opentelemetry/resources@2.0.0": version "2.0.0" @@ -6132,14 +6118,13 @@ "@opentelemetry/core" "2.0.0" "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/sdk-trace-base@^1.30.1": - version "1.30.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz#41a42234096dc98e8f454d24551fc80b816feb34" - integrity sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg== +"@opentelemetry/resources@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.0.1.tgz#0365d134291c0ed18d96444a1e21d0e6a481c840" + integrity sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw== dependencies: - "@opentelemetry/core" "1.30.1" - "@opentelemetry/resources" "1.30.1" - "@opentelemetry/semantic-conventions" "1.28.0" + "@opentelemetry/core" "2.0.1" + "@opentelemetry/semantic-conventions" "^1.29.0" "@opentelemetry/sdk-trace-base@^2.0.0": version "2.0.0" @@ -6150,22 +6135,17 @@ "@opentelemetry/resources" "2.0.0" "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/semantic-conventions@1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6" - integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA== - -"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.34.0": +"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.33.1", "@opentelemetry/semantic-conventions@^1.34.0": version "1.34.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz#8b6a46681b38a4d5947214033ac48128328c1738" integrity sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA== -"@opentelemetry/sql-common@^0.40.1": - version "0.40.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz#93fbc48d8017449f5b3c3274f2268a08af2b83b6" - integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg== +"@opentelemetry/sql-common@^0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.41.0.tgz#7ddef1ea7fb6338dcca8a9d2485c7dfd53c076b4" + integrity sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA== dependencies: - "@opentelemetry/core" "^1.1.0" + "@opentelemetry/core" "^2.0.0" "@parcel/watcher-android-arm64@2.5.1": version "2.5.1" @@ -8003,10 +7983,10 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== -"@types/aws-lambda@8.10.147", "@types/aws-lambda@^8.10.62": - version "8.10.147" - resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.147.tgz#dc5c89aa32f47a9b35e52c32630545c83afa6f2f" - integrity sha512-nD0Z9fNIZcxYX5Mai2CTmFD7wX7UldCkW2ezCF8D1T5hdiLsnTWDGRpfRYntU6VjTdLQjOvyszru7I1c1oCQew== +"@types/aws-lambda@8.10.150", "@types/aws-lambda@^8.10.62": + version "8.10.150" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.150.tgz#4998b238750ec389a326a7cdb625808834036bd3" + integrity sha512-AX+AbjH/rH5ezX1fbK8onC/a+HyQHo7QGmvoxAE42n22OsciAxvZoZNEr22tbXs8WfP1nIsBjKDpgPm3HjOZbA== "@types/babel__core@^7.20.1", "@types/babel__core@^7.20.4": version "7.20.5" @@ -8584,10 +8564,10 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.32.tgz#f6cd08939ae3ad886fcc92ef7f0109dacddf61ab" integrity sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g== -"@types/mysql@2.15.26", "@types/mysql@^2.15.21": - version "2.15.26" - resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.26.tgz#f0de1484b9e2354d587e7d2bd17a873cc8300836" - integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ== +"@types/mysql@2.15.27", "@types/mysql@^2.15.21": + version "2.15.27" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.27.tgz#fb13b0e8614d39d42f40f381217ec3215915f1e9" + integrity sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA== dependencies: "@types/node" "*" @@ -8671,19 +8651,10 @@ dependencies: "@types/pg" "*" -"@types/pg@*", "@types/pg@^8.6.5": - version "8.10.2" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.2.tgz#7814d1ca02c8071f4d0864c1b17c589b061dba43" - integrity sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw== - dependencies: - "@types/node" "*" - pg-protocol "*" - pg-types "^4.0.1" - -"@types/pg@8.6.1": - version "8.6.1" - resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" - integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== +"@types/pg@*", "@types/pg@8.15.4", "@types/pg@^8.6.5": + version "8.15.4" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.15.4.tgz#419f791c6fac8e0bed66dd8f514b60f8ba8db46d" + integrity sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg== dependencies: "@types/node" "*" pg-protocol "*" @@ -23687,7 +23658,7 @@ object.values@^1.1.1, object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -obuf@^1.0.0, obuf@^1.1.2, obuf@~1.1.2: +obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== @@ -24533,11 +24504,6 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-numeric@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" - integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== - pg-pool@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.0.tgz#134b0213755c5e7135152976488aa7cd7ee1268d" @@ -24559,19 +24525,6 @@ pg-types@2.2.0, pg-types@^2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg-types@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542" - integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g== - dependencies: - pg-int8 "1.0.1" - pg-numeric "1.0.2" - postgres-array "~3.0.1" - postgres-bytea "~3.0.0" - postgres-date "~2.0.1" - postgres-interval "^3.0.0" - postgres-range "^1.1.1" - pg@8.16.0: version "8.16.0" resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.0.tgz#40b08eedb5eb1834252cf3e3629503e32e6c6c04" @@ -25378,33 +25331,16 @@ postgres-array@~2.0.0: resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== -postgres-array@~3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" - integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== - postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= -postgres-bytea@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" - integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== - dependencies: - obuf "~1.1.2" - postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== -postgres-date@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457" - integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw== - postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -25412,16 +25348,6 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -postgres-interval@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" - integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== - -postgres-range@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" - integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== - postgres@^3.4.7: version "3.4.7" resolved "https://registry.yarnpkg.com/postgres/-/postgres-3.4.7.tgz#122f460a808fe300cae53f592108b9906e625345"