From 473daadac3f207b5d620bfca66ef55caacc36262 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 11 Feb 2025 10:52:52 +0100 Subject: [PATCH 1/4] fix(core): Ensure `http.client` span descriptions don't contain query params or fragments .. more tests --- .../fetch-strip-query-and-fragment/init.js | 11 ++ .../fetch-strip-query-and-fragment/subject.js | 19 ++ .../template.html | 12 ++ .../fetch-strip-query-and-fragment/test.ts | 176 ++++++++++++++++++ .../xhr-strip-query-and-fragment/init.js | 11 ++ .../xhr-strip-query-and-fragment/subject.js | 29 +++ .../template.html | 12 ++ .../xhr-strip-query-and-fragment/test.ts | 172 +++++++++++++++++ .../node-integration-tests/package.json | 1 + .../http-client-spans/fetch-basic/scenario.ts | 15 ++ .../http-client-spans/fetch-basic/test.ts | 48 +++++ .../fetch-strip-query/scenario.ts | 14 ++ .../fetch-strip-query/test.ts | 53 ++++++ .../http-basic}/scenario.ts | 0 .../http-basic}/test.ts | 6 +- .../http-strip-query/scenario.ts | 31 +++ .../http-strip-query/test.ts | 53 ++++++ packages/browser/src/tracing/request.ts | 24 ++- .../bun/test/integrations/bunserver.test.ts | 19 +- packages/core/src/fetch.ts | 18 +- packages/core/test/utils-hoist/url.test.ts | 123 ++++++++++++ 21 files changed, 824 insertions(+), 23 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts rename dev-packages/node-integration-tests/suites/tracing/{spans => http-client-spans/http-basic}/scenario.ts (100%) rename dev-packages/node-integration-tests/suites/tracing/{spans => http-client-spans/http-basic}/test.ts (87%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/init.js new file mode 100644 index 000000000000..de6b87574482 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false })], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, + autoSessionTracking: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/subject.js new file mode 100644 index 000000000000..37441bf4463a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/subject.js @@ -0,0 +1,19 @@ +function withRootSpan(cb) { + return Sentry.startSpan({ name: 'rootSpan' }, cb); +} + +document.getElementById('btnQuery').addEventListener('click', async () => { + await withRootSpan(() => fetch('http://sentry-test-site.example/0?id=123;page=5')); +}); + +document.getElementById('btnFragment').addEventListener('click', async () => { + await withRootSpan(() => fetch('http://sentry-test-site.example/1#fragment')); +}); + +document.getElementById('btnQueryFragment').addEventListener('click', async () => { + await withRootSpan(() => fetch('http://sentry-test-site.example/2?id=1#fragment')); +}); + +document.getElementById('btnQueryFragmentSameOrigin').addEventListener('click', async () => { + await withRootSpan(() => fetch('/api/users?id=1#fragment')); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/template.html b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/template.html new file mode 100644 index 000000000000..d02fa0868f56 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/test.ts new file mode 100644 index 000000000000..a0fea6e6af29 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-strip-query-and-fragment/test.ts @@ -0,0 +1,176 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('strips query params in fetch request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQuery').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/0', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/0?id=123;page=5', + 'http.query': '?id=123;page=5', + 'http.response.status_code': 200, + 'http.response_content_length': 2, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'fetch', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/0?id=123;page=5', + }), + }); + + expect(requestSpan?.data).not.toHaveProperty('http.fragment'); +}); + +sentryTest('strips hash fragment in fetch request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnFragment').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/1', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/1#fragment', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'http.response_content_length': 2, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'fetch', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/1#fragment', + }), + }); + + expect(requestSpan?.data).not.toHaveProperty('http.query'); +}); + +sentryTest('strips hash fragment and query params in fetch request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQueryFragment').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/2', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/2?id=1#fragment', + 'http.query': '?id=1', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'http.response_content_length': 2, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'fetch', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/2?id=1#fragment', + }), + }); +}); + +sentryTest( + 'strips hash fragment and query params in same-origin fetch request spans', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('**/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQueryFragmentSameOrigin').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET /api/users', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test.io/api/users?id=1#fragment', + 'http.query': '?id=1', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'http.response_content_length': 2, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'fetch', + 'server.address': 'sentry-test.io', + url: '/api/users?id=1#fragment', + }), + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/init.js new file mode 100644 index 000000000000..de6b87574482 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false })], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, + autoSessionTracking: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/subject.js new file mode 100644 index 000000000000..e27c6d3cf013 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/subject.js @@ -0,0 +1,29 @@ +function withRootSpan(cb) { + return Sentry.startSpan({ name: 'rootSpan' }, cb); +} + +function makeXHRRequest(url) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.onload = () => resolve(xhr.responseText); + xhr.onerror = () => reject(xhr.statusText); + xhr.send(); + }); +} + +document.getElementById('btnQuery').addEventListener('click', async () => { + await withRootSpan(() => makeXHRRequest('http://sentry-test-site.example/0?id=123;page=5')); +}); + +document.getElementById('btnFragment').addEventListener('click', async () => { + await withRootSpan(() => makeXHRRequest('http://sentry-test-site.example/1#fragment')); +}); + +document.getElementById('btnQueryFragment').addEventListener('click', async () => { + await withRootSpan(() => makeXHRRequest('http://sentry-test-site.example/2?id=1#fragment')); +}); + +document.getElementById('btnQueryFragmentSameOrigin').addEventListener('click', async () => { + await withRootSpan(() => makeXHRRequest('/api/users?id=1#fragment')); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/template.html b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/template.html new file mode 100644 index 000000000000..533636f821c3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts new file mode 100644 index 000000000000..40228b9e55ac --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts @@ -0,0 +1,172 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('strips query params in XHR request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQuery').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/0', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/0?id=123;page=5', + 'http.query': '?id=123;page=5', + 'http.response.status_code': 200, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'xhr', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/0?id=123;page=5', + }), + }); + + expect(requestSpan?.data).not.toHaveProperty('http.fragment'); +}); + +sentryTest('strips hash fragment in XHR request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnFragment').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/1', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/1#fragment', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'xhr', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/1#fragment', + }), + }); + + expect(requestSpan?.data).not.toHaveProperty('http.query'); +}); + +sentryTest('strips hash fragment and query params in XHR request spans', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQueryFragment').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test-site.example/2', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/2?id=1#fragment', + 'http.query': '?id=1', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'xhr', + 'server.address': 'sentry-test-site.example', + url: 'http://sentry-test-site.example/2?id=1#fragment', + }), + }); +}); + +sentryTest( + 'strips hash fragment and query params in same-origin XHR request spans', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('**/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + const txnPromise = waitForTransactionRequest(page); + await page.locator('#btnQueryFragmentSameOrigin').click(); + const transactionEvent = envelopeRequestParser(await txnPromise); + + expect(transactionEvent.transaction).toEqual('rootSpan'); + + const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); + + expect(requestSpan).toMatchObject({ + description: 'GET http://sentry-test.io/api/users', + parent_span_id: transactionEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: transactionEvent.contexts?.trace?.trace_id, + data: expect.objectContaining({ + 'http.method': 'GET', + 'http.url': 'http://sentry-test.io/api/users?id=1#fragment', + 'http.query': '?id=1', + 'http.fragment': '#fragment', + 'http.response.status_code': 200, + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.browser', + type: 'xhr', + 'server.address': 'sentry-test.io', + url: '/api/users?id=1#fragment', + }), + }); + }, +); diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index e123e852f4b9..9d89dea67940 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -20,6 +20,7 @@ "fix": "eslint . --format stylish --fix", "type-check": "tsc", "test": "jest --config ./jest.config.js", + "test:no-prisma": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" }, "dependencies": { diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/scenario.ts new file mode 100644 index 000000000000..44ea548bab8f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/scenario.ts @@ -0,0 +1,15 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +Sentry.startSpan({ name: 'test_transaction' }, async () => { + await fetch(`${process.env.SERVER_URL}/api/v0`); + await fetch(`${process.env.SERVER_URL}/api/v1`); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/test.ts new file mode 100644 index 000000000000..006190864fe6 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic/test.ts @@ -0,0 +1,48 @@ +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; + +test('captures spans for outgoing fetch requests', done => { + expect.assertions(3); + + createTestServer(done) + .get('/api/v0', () => { + // Just ensure we're called + expect(true).toBe(true); + }) + .get( + '/api/v1', + () => { + // Just ensure we're called + expect(true).toBe(true); + }, + 404, + ) + .start() + .then(([SERVER_URL, closeTestServer]) => { + createRunner(__dirname, 'scenario.ts') + .withEnv({ SERVER_URL }) + .expect({ + transaction: { + transaction: 'test_transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: expect.stringMatching(/GET .*\/api\/v0/), + op: 'http.client', + origin: 'auto.http.otel.node_fetch', + status: 'ok', + }), + expect.objectContaining({ + description: expect.stringMatching(/GET .*\/api\/v1/), + op: 'http.client', + origin: 'auto.http.otel.node_fetch', + status: 'not_found', + data: expect.objectContaining({ + 'http.response.status_code': 404, + }), + }), + ]), + }, + }) + .start(closeTestServer); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/scenario.ts new file mode 100644 index 000000000000..0c72d545c39b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/scenario.ts @@ -0,0 +1,14 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +Sentry.startSpan({ name: 'test_transaction' }, async () => { + await fetch(`${process.env.SERVER_URL}/api/v0/users?id=1#fragment`); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts new file mode 100644 index 000000000000..16d16f4588e4 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts @@ -0,0 +1,53 @@ +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; + +test('strips and handles query params in spans of outgoing fetch requests', done => { + expect.assertions(4); + + createTestServer(done) + .get('/api/v0/users', () => { + // Just ensure we're called + expect(true).toBe(true); + }) + .start() + .then(([SERVER_URL, closeTestServer]) => { + createRunner(__dirname, 'scenario.ts') + .withEnv({ SERVER_URL }) + .expect({ + transaction: txn => { + expect(txn.transaction).toEqual('test_transaction'); + expect(txn.spans).toHaveLength(1); + expect(txn.spans?.at(0)).toMatchObject({ + data: { + url: `${SERVER_URL}/api/v0/users`, + 'url.full': `${SERVER_URL}/api/v0/users?id=1`, + 'url.path': '/api/v0/users', + 'url.query': '?id=1', + 'url.scheme': 'http', + 'http.query': 'id=1', + 'http.request.method': 'GET', + 'http.request.method_original': 'GET', + 'http.response.header.content-length': 0, + 'http.response.status_code': 200, + 'network.peer.address': '::1', + 'network.peer.port': expect.any(Number), + 'otel.kind': 'CLIENT', + 'server.port': expect.any(Number), + 'user_agent.original': 'node', + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.node_fetch', + 'server.address': 'localhost', + }, + description: `GET ${SERVER_URL}/api/v0/users`, + op: 'http.client', + origin: 'auto.http.otel.node_fetch', + status: 'ok', + parent_span_id: txn.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: txn.contexts?.trace?.trace_id, + }); + }, + }) + .start(closeTestServer); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-basic/scenario.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-basic/scenario.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/spans/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-basic/test.ts similarity index 87% rename from dev-packages/node-integration-tests/suites/tracing/spans/test.ts rename to dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-basic/test.ts index e349622d39f8..bb642baf0e1c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/spans/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-basic/test.ts @@ -1,7 +1,7 @@ -import { createRunner } from '../../../utils/runner'; -import { createTestServer } from '../../../utils/server'; +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; -test('should capture spans for outgoing http requests', done => { +test('captures spans for outgoing http requests', done => { expect.assertions(3); createTestServer(done) diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/scenario.ts new file mode 100644 index 000000000000..074c9778aa75 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/scenario.ts @@ -0,0 +1,31 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +import * as http from 'http'; + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +Sentry.startSpan({ name: 'test_transaction' }, async () => { + await makeHttpRequest(`${process.env.SERVER_URL}/api/v0/users?id=1#fragment`); +}); + +function makeHttpRequest(url: string): Promise { + return new Promise(resolve => { + http + .request(url, httpRes => { + httpRes.on('data', () => { + // we don't care about data + }); + httpRes.on('end', () => { + resolve(); + }); + }) + .end(); + }); +} diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts new file mode 100644 index 000000000000..0a4c421dc82b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts @@ -0,0 +1,53 @@ +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; + +test('strips and handles query params in spans of outgoing http requests', done => { + expect.assertions(4); + + createTestServer(done) + .get('/api/v0/users', () => { + // Just ensure we're called + expect(true).toBe(true); + }) + .start() + .then(([SERVER_URL, closeTestServer]) => { + createRunner(__dirname, 'scenario.ts') + .withEnv({ SERVER_URL }) + .expect({ + transaction: txn => { + expect(txn.transaction).toEqual('test_transaction'); + expect(txn.spans).toHaveLength(1); + expect(txn.spans?.at(0)).toMatchObject({ + data: { + url: `${SERVER_URL}/api/v0/users`, + 'http.url': `${SERVER_URL}/api/v0/users?id=1`, + 'http.target': '/api/v0/users?id=1', + 'http.flavor': '1.1', + 'http.host': expect.stringMatching(/localhost:\d+$/), + 'http.method': 'GET', + 'http.query': 'id=1', + 'http.response.status_code': 200, + 'http.response_content_length_uncompressed': 0, + 'http.status_code': 200, + 'http.status_text': 'OK', + 'net.peer.ip': '::1', + 'net.peer.name': 'localhost', + 'net.peer.port': expect.any(Number), + 'net.transport': 'ip_tcp', + 'otel.kind': 'CLIENT', + 'sentry.op': 'http.client', + 'sentry.origin': 'auto.http.otel.http', + }, + description: `GET ${SERVER_URL}/api/v0/users`, + op: 'http.client', + origin: 'auto.http.otel.http', + status: 'ok', + parent_span_id: txn.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: txn.contexts?.trace?.trace_id, + }); + }, + }) + .start(closeTestServer); + }); +}); diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 144aec73c977..483666e9e9ca 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -16,6 +16,7 @@ import { getActiveSpan, getClient, getLocationHref, + getSanitizedUrlString, getTraceData, hasSpansEnabled, instrumentFetchRequest, @@ -24,6 +25,7 @@ import { spanToJSON, startInactiveSpan, stringMatchesSomePattern, + stripUrlQueryAndFragment, } from '@sentry/core'; import { WINDOW } from '../helpers'; @@ -324,7 +326,9 @@ export function xhrCallback( return undefined; } - const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(sentryXhrData.url); + const { url, method } = sentryXhrData; + + const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url); // check first if the request has finished and is tracked by an existing span which should now end if (handlerData.endTimestamp && shouldCreateSpanResult) { @@ -342,23 +346,27 @@ export function xhrCallback( return undefined; } - const fullUrl = getFullURL(sentryXhrData.url); - const host = fullUrl ? parseUrl(fullUrl).host : undefined; + const fullUrl = getFullURL(url); + const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url); + + const urlForSpanName = parsedUrl ? getSanitizedUrlString(parsedUrl) : stripUrlQueryAndFragment(url); const hasParent = !!getActiveSpan(); const span = shouldCreateSpanResult && hasParent ? startInactiveSpan({ - name: `${sentryXhrData.method} ${sentryXhrData.url}`, + name: `${method} ${urlForSpanName}`, attributes: { + url, type: 'xhr', - 'http.method': sentryXhrData.method, + 'http.method': method, 'http.url': fullUrl, - url: sentryXhrData.url, - 'server.address': host, + 'server.address': parsedUrl?.host, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', + ...(parsedUrl?.search && { 'http.query': parsedUrl?.search }), + ...(parsedUrl?.hash && { 'http.fragment': parsedUrl?.hash }), }, }) : new SentryNonRecordingSpan(); @@ -366,7 +374,7 @@ export function xhrCallback( xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; spans[xhr.__sentry_xhr_span_id__] = span; - if (shouldAttachHeaders(sentryXhrData.url)) { + if (shouldAttachHeaders(url)) { addTracingHeadersToXhrRequest( xhr, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index dd1f738a334b..e448402c0479 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -41,17 +41,26 @@ describe('Bun Serve Integration', () => { }, port, }); - await fetch(`http://localhost:${port}/`); + await fetch(`http://localhost:${port}/users?id=123`); server.stop(); if (!generatedSpan) { throw 'No span was generated in the test'; } - expect(spanToJSON(generatedSpan).status).toBe('ok'); - expect(spanToJSON(generatedSpan).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(generatedSpan).op).toEqual('http.server'); - expect(spanToJSON(generatedSpan).description).toEqual('GET /'); + const spanJson = spanToJSON(generatedSpan); + expect(spanJson.status).toBe('ok'); + expect(spanJson.op).toEqual('http.server'); + expect(spanJson.description).toEqual('GET /users'); + expect(spanJson.data).toEqual({ + 'http.query': '?id=123', + 'http.request.method': 'GET', + 'http.response.status_code': 200, + 'sentry.op': 'http.server', + 'sentry.origin': 'auto.http.bun.serve', + 'sentry.sample_rate': 1, + 'sentry.source': 'url', + }); }); test('generates a post transaction', async () => { diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 3c43584b3951..58dccf7cee79 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -5,7 +5,7 @@ import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import type { FetchBreadcrumbHint, HandlerDataFetch, Span, SpanOrigin } from './types-hoist'; import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; import { isInstanceOf } from './utils-hoist/is'; -import { parseUrl } from './utils-hoist/url'; +import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from './utils-hoist/url'; import { hasSpansEnabled } from './utils/hasSpansEnabled'; import { getActiveSpan } from './utils/spanUtils'; import { getTraceData } from './utils/traceData'; @@ -35,7 +35,9 @@ export function instrumentFetchRequest( return undefined; } - const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(handlerData.fetchData.url); + const { method, url } = handlerData.fetchData; + + const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url); if (handlerData.endTimestamp && shouldCreateSpanResult) { const spanId = handlerData.fetchData.__span; @@ -51,25 +53,27 @@ export function instrumentFetchRequest( return undefined; } - const { method, url } = handlerData.fetchData; - const fullUrl = getFullURL(url); - const host = fullUrl ? parseUrl(fullUrl).host : undefined; + const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url); + + const urlForSpanName = parsedUrl ? getSanitizedUrlString(parsedUrl) : stripUrlQueryAndFragment(url); const hasParent = !!getActiveSpan(); const span = shouldCreateSpanResult && hasParent ? startInactiveSpan({ - name: `${method} ${url}`, + name: `${method} ${urlForSpanName}`, attributes: { url, type: 'fetch', 'http.method': method, 'http.url': fullUrl, - 'server.address': host, + 'server.address': parsedUrl?.host, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', + ...(parsedUrl?.search && { 'http.query': parsedUrl?.search }), + ...(parsedUrl?.hash && { 'http.fragment': parsedUrl?.hash }), }, }) : new SentryNonRecordingSpan(); diff --git a/packages/core/test/utils-hoist/url.test.ts b/packages/core/test/utils-hoist/url.test.ts index cd066201945d..a16c72dc1cd2 100644 --- a/packages/core/test/utils-hoist/url.test.ts +++ b/packages/core/test/utils-hoist/url.test.ts @@ -72,3 +72,126 @@ describe('getSanitizedUrlString', () => { expect(getSanitizedUrlString(urlObject)).toEqual(sanitizedURL); }); }); + +describe('parseUrl', () => { + it.each([ + [ + 'https://somedomain.com', + { host: 'somedomain.com', path: '', search: '', hash: '', protocol: 'https', relative: '' }, + ], + [ + 'https://somedomain.com/path/to/happiness', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '', + hash: '', + protocol: 'https', + relative: '/path/to/happiness', + }, + ], + [ + 'https://somedomain.com/path/to/happiness?auhtToken=abc123¶m2=bar', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '?auhtToken=abc123¶m2=bar', + hash: '', + protocol: 'https', + relative: '/path/to/happiness?auhtToken=abc123¶m2=bar', + }, + ], + [ + 'https://somedomain.com/path/to/happiness?auhtToken=abc123¶m2=bar#wildfragment', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '?auhtToken=abc123¶m2=bar', + hash: '#wildfragment', + protocol: 'https', + relative: '/path/to/happiness?auhtToken=abc123¶m2=bar#wildfragment', + }, + ], + [ + 'https://somedomain.com/path/to/happiness#somewildfragment123', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '', + hash: '#somewildfragment123', + protocol: 'https', + relative: '/path/to/happiness#somewildfragment123', + }, + ], + [ + 'https://somedomain.com/path/to/happiness#somewildfragment123?auhtToken=abc123¶m2=bar', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '', + hash: '#somewildfragment123?auhtToken=abc123¶m2=bar', + protocol: 'https', + relative: '/path/to/happiness#somewildfragment123?auhtToken=abc123¶m2=bar', + }, + ], + [ + // yup, this is a valid URL (protocol-agnostic URL) + '//somedomain.com/path/to/happiness?auhtToken=abc123¶m2=bar#wildfragment', + { + host: 'somedomain.com', + path: '/path/to/happiness', + search: '?auhtToken=abc123¶m2=bar', + hash: '#wildfragment', + protocol: undefined, + relative: '/path/to/happiness?auhtToken=abc123¶m2=bar#wildfragment', + }, + ], + ['', {}], + [ + '\n', + { + hash: '', + host: undefined, + path: '\n', + protocol: undefined, + relative: '\n', + search: '', + }, + ], + [ + 'somerandomString', + { + hash: '', + host: undefined, + path: 'somerandomString', + protocol: undefined, + relative: 'somerandomString', + search: '', + }, + ], + [ + 'somedomain.com', + { + host: undefined, + path: 'somedomain.com', + search: '', + hash: '', + protocol: undefined, + relative: 'somedomain.com', + }, + ], + [ + 'somedomain.com/path/?q=1#fragment', + { + host: undefined, + path: 'somedomain.com/path/', + search: '?q=1', + hash: '#fragment', + protocol: undefined, + relative: 'somedomain.com/path/?q=1#fragment', + }, + ], + ])('returns parsed partial URL object for %s', (url: string, expected: any) => { + expect(parseUrl(url)).toEqual(expected); + }); +}); From a3abf0aa22d08b4684c3d1dc37d4c13d5927f19e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 13 Feb 2025 17:23:30 +0100 Subject: [PATCH 2/4] use correct url for description --- .../tracing/request/xhr-strip-query-and-fragment/test.ts | 2 +- packages/browser/src/tracing/request.ts | 2 +- packages/core/src/fetch.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts index 40228b9e55ac..d4ed06fcdd4e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-strip-query-and-fragment/test.ts @@ -149,7 +149,7 @@ sentryTest( const requestSpan = transactionEvent.spans?.find(({ op }) => op === 'http.client'); expect(requestSpan).toMatchObject({ - description: 'GET http://sentry-test.io/api/users', + description: 'GET /api/users', parent_span_id: transactionEvent.contexts?.trace?.span_id, span_id: expect.stringMatching(/[a-f0-9]{16}/), start_timestamp: expect.any(Number), diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 483666e9e9ca..b8f68f690ff6 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -349,7 +349,7 @@ export function xhrCallback( const fullUrl = getFullURL(url); const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url); - const urlForSpanName = parsedUrl ? getSanitizedUrlString(parsedUrl) : stripUrlQueryAndFragment(url); + const urlForSpanName = stripUrlQueryAndFragment(url); const hasParent = !!getActiveSpan(); diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 58dccf7cee79..4a2beb0edbd1 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -56,14 +56,12 @@ export function instrumentFetchRequest( const fullUrl = getFullURL(url); const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url); - const urlForSpanName = parsedUrl ? getSanitizedUrlString(parsedUrl) : stripUrlQueryAndFragment(url); - const hasParent = !!getActiveSpan(); const span = shouldCreateSpanResult && hasParent ? startInactiveSpan({ - name: `${method} ${urlForSpanName}`, + name: `${method} ${stripUrlQueryAndFragment(url)}`, attributes: { url, type: 'fetch', From bcbb212cad9684d35c50f7a099edc6827d2154f9 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 13 Feb 2025 17:41:19 +0100 Subject: [PATCH 3/4] i want to delete biome and everything around it from this repository --- packages/browser/src/tracing/request.ts | 1 - packages/core/src/fetch.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index b8f68f690ff6..17dd71f0abba 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -16,7 +16,6 @@ import { getActiveSpan, getClient, getLocationHref, - getSanitizedUrlString, getTraceData, hasSpansEnabled, instrumentFetchRequest, diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 4a2beb0edbd1..a96d421d0023 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -5,7 +5,7 @@ import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import type { FetchBreadcrumbHint, HandlerDataFetch, Span, SpanOrigin } from './types-hoist'; import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; import { isInstanceOf } from './utils-hoist/is'; -import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from './utils-hoist/url'; +import { parseUrl, stripUrlQueryAndFragment } from './utils-hoist/url'; import { hasSpansEnabled } from './utils/hasSpansEnabled'; import { getActiveSpan } from './utils/spanUtils'; import { getTraceData } from './utils/traceData'; From c31d665ce75b2bd3f7a3271297dc640960290254 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 14 Feb 2025 11:37:13 +0100 Subject: [PATCH 4/4] use opt chaining instead of .at --- .../suites/tracing/http-client-spans/fetch-strip-query/test.ts | 2 +- .../suites/tracing/http-client-spans/http-strip-query/test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts index 16d16f4588e4..12bb11727228 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-strip-query/test.ts @@ -17,7 +17,7 @@ test('strips and handles query params in spans of outgoing fetch requests', done transaction: txn => { expect(txn.transaction).toEqual('test_transaction'); expect(txn.spans).toHaveLength(1); - expect(txn.spans?.at(0)).toMatchObject({ + expect(txn.spans?.[0]).toMatchObject({ data: { url: `${SERVER_URL}/api/v0/users`, 'url.full': `${SERVER_URL}/api/v0/users?id=1`, diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts index 0a4c421dc82b..37b638635eb9 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/http-strip-query/test.ts @@ -17,7 +17,7 @@ test('strips and handles query params in spans of outgoing http requests', done transaction: txn => { expect(txn.transaction).toEqual('test_transaction'); expect(txn.spans).toHaveLength(1); - expect(txn.spans?.at(0)).toMatchObject({ + expect(txn.spans?.[0]).toMatchObject({ data: { url: `${SERVER_URL}/api/v0/users`, 'http.url': `${SERVER_URL}/api/v0/users?id=1`,