From 6bf2374782825432d73bf62b2daabd833567fcd6 Mon Sep 17 00:00:00 2001
From: tjhiggins
Date: Tue, 21 Jan 2025 03:30:51 -0500
Subject: [PATCH 01/59] fix(node/nestjs): Use method on current fastify request
(#15066)
The previous code was using fastify `request.routeOptions.method` which
is all methods the current route supports - not the current request
method.
Ex. `@All()` nestjs decorator `request.routeOptions.method = ['GET',
'POST', 'HEAD', ...]`
- Added a test for `@All()`
- Updated instances of fastify request to use `request.method` which
matches express
---------
Co-authored-by: Charly Gomez
---
.../test-applications/nestjs-fastify/src/app.controller.ts | 7 ++++++-
.../nestjs-fastify/tests/transactions.test.ts | 5 +++++
packages/nestjs/src/integrations/types.ts | 2 +-
packages/nestjs/src/setup.ts | 6 ++----
packages/node/src/integrations/tracing/fastify.ts | 4 ++--
5 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts
index 33a6b1957d99..ca29bb9fb9ae 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/src/app.controller.ts
@@ -1,4 +1,4 @@
-import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
+import { All, Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
import { flush } from '@sentry/nestjs';
import { AppService } from './app.service';
import { AsyncInterceptor } from './async-example.interceptor';
@@ -121,4 +121,9 @@ export class AppController {
testFunctionName() {
return this.appService.getFunctionName();
}
+
+ @All('test-all')
+ testAll() {
+ return {};
+ }
}
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
index 609e01709650..96b60e5d976f 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
@@ -808,3 +808,8 @@ test('Calling canActivate method on service with Injectable decorator returns 20
const response = await fetch(`${baseURL}/test-service-canActivate`);
expect(response.status).toBe(200);
});
+
+test('Calling @All method on service with Injectable decorator returns 200', async ({ baseURL }) => {
+ const response = await fetch(`${baseURL}/test-all`);
+ expect(response.status).toBe(200);
+});
diff --git a/packages/nestjs/src/integrations/types.ts b/packages/nestjs/src/integrations/types.ts
index a983832ac8c6..8283e652edfb 100644
--- a/packages/nestjs/src/integrations/types.ts
+++ b/packages/nestjs/src/integrations/types.ts
@@ -4,9 +4,9 @@
// https://github.com/fastify/fastify/blob/87f9f20687c938828f1138f91682d568d2a31e53/types/request.d.ts#L41
interface FastifyRequest {
routeOptions?: {
- method?: string;
url?: string;
};
+ method?: string;
}
// Partial extract of ExpressRequest interface
diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts
index a839147c25a7..045c196a0b8c 100644
--- a/packages/nestjs/src/setup.ts
+++ b/packages/nestjs/src/setup.ts
@@ -16,9 +16,9 @@ import { isExpectedError } from './helpers';
// https://github.com/fastify/fastify/blob/87f9f20687c938828f1138f91682d568d2a31e53/types/request.d.ts#L41
interface FastifyRequest {
routeOptions?: {
- method?: string;
url?: string;
};
+ method?: string;
}
// Partial extract of ExpressRequest interface
@@ -57,9 +57,7 @@ class SentryTracingInterceptor implements NestInterceptor {
const req = context.switchToHttp().getRequest() as FastifyRequest | ExpressRequest;
if ('routeOptions' in req && req.routeOptions?.url) {
// fastify case
- getIsolationScope().setTransactionName(
- `${(req.routeOptions.method || 'GET').toUpperCase()} ${req.routeOptions.url}`,
- );
+ getIsolationScope().setTransactionName(`${(req.method || 'GET').toUpperCase()} ${req.routeOptions.url}`);
} else if ('route' in req && req.route?.path) {
// express case
getIsolationScope().setTransactionName(`${(req.method || 'GET').toUpperCase()} ${req.route.path}`);
diff --git a/packages/node/src/integrations/tracing/fastify.ts b/packages/node/src/integrations/tracing/fastify.ts
index 1da3f06cc20a..3a509f8b97b6 100644
--- a/packages/node/src/integrations/tracing/fastify.ts
+++ b/packages/node/src/integrations/tracing/fastify.ts
@@ -25,10 +25,10 @@ interface Fastify {
* Works for Fastify 3, 4 and presumably 5.
*/
interface FastifyRequestRouteInfo {
+ method?: string;
// since fastify@4.10.0
routeOptions?: {
url?: string;
- method?: string;
};
routerPath?: string;
}
@@ -107,7 +107,7 @@ export function setupFastifyErrorHandler(fastify: Fastify): void {
// Taken from Otel Fastify instrumentation:
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts#L94-L96
const routeName = reqWithRouteInfo.routeOptions?.url || reqWithRouteInfo.routerPath;
- const method = reqWithRouteInfo.routeOptions?.method || 'GET';
+ const method = reqWithRouteInfo.method || 'GET';
getIsolationScope().setTransactionName(`${method} ${routeName}`);
});
From 82620ecc7e00681c722e969decaae3795e2ddaf3 Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Tue, 21 Jan 2025 10:16:16 +0100
Subject: [PATCH 02/59] chore: Add external contributor to CHANGELOG.md
(#15103)
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #15066
---------
Co-authored-by: chargome <20254395+chargome@users.noreply.github.com>
Co-authored-by: Francesco Gringl-Novy
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67faf6798092..fb3cdbfd1fea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
-Work in this release was contributed by @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions!
+Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions!
- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))**
From a017048c5fb4c50f2af8109e4ca66f8ef41e4443 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Tue, 21 Jan 2025 04:45:29 -0500
Subject: [PATCH 03/59] chore: Fix stale yarn.lock (#15096)
---
yarn.lock | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 210d37713813..b00af64e5f03 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5596,13 +5596,6 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.12.0.tgz#4906ae27359d3311e3dea1b63770a16f60848550"
integrity sha512-UXwSsXo3F3yZ1dIBOG9ID8v2r9e+bqLWoizCtTb8rXtwF+N5TM7hzzvQz72o3nBU+zrI/D5e+OqAYK8ZgDd3DA==
-"@opentelemetry/core@1.30.0":
- version "1.30.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.30.0.tgz#ef959e11e137d72466e566e375ecc5a82e922b86"
- integrity sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA==
- dependencies:
- "@opentelemetry/semantic-conventions" "1.28.0"
-
"@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"
@@ -5892,14 +5885,6 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47"
integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==
-"@opentelemetry/resources@1.30.0":
- version "1.30.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.30.0.tgz#87604359e6195c017075b7d294a949ad018e692d"
- integrity sha512-5mGMjL0Uld/99t7/pcd7CuVtJbkARckLVuiOX84nO8RtLtIz0/J6EOHM2TGvPZ6F4K+XjUq13gMx14w80SVCQg==
- dependencies:
- "@opentelemetry/core" "1.30.0"
- "@opentelemetry/semantic-conventions" "1.28.0"
-
"@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"
From 03e0d02bb2281418c793933df87a41090071b9e2 Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Tue, 21 Jan 2025 14:29:25 +0100
Subject: [PATCH 04/59] feat(nextjs): Directly forward `sourcemaps.disable` to
webpack plugin (#15109)
---
packages/nextjs/src/config/types.ts | 2 +-
packages/nextjs/src/config/webpackPluginOptions.ts | 7 ++-----
.../test/config/webpack/webpackPluginOptions.test.ts | 4 ++--
3 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts
index bbba80e476c8..0a188341ce58 100644
--- a/packages/nextjs/src/config/types.ts
+++ b/packages/nextjs/src/config/types.ts
@@ -122,7 +122,7 @@ export type SentryBuildOptions = {
*/
sourcemaps?: {
/**
- * Disable any functionality related to source maps upload.
+ * Disable any functionality related to source maps.
*/
disable?: boolean;
diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts
index 43aea096bdaa..530659b2a703 100644
--- a/packages/nextjs/src/config/webpackPluginOptions.ts
+++ b/packages/nextjs/src/config/webpackPluginOptions.ts
@@ -23,7 +23,7 @@ export function getWebpackPluginOptions(
const distDir = (userNextConfig as NextConfigObject).distDir?.replace(/\\/g, '/') ?? '.next';
const distDirAbsPath = path.posix.join(projectDir, distDir);
- let sourcemapUploadAssets: string[] = [];
+ const sourcemapUploadAssets: string[] = [];
const sourcemapUploadIgnore: string[] = [];
if (isServer) {
@@ -51,10 +51,6 @@ export function getWebpackPluginOptions(
);
}
- if (sentryBuildOptions.sourcemaps?.disable) {
- sourcemapUploadAssets = [];
- }
-
return {
authToken: sentryBuildOptions.authToken,
headers: sentryBuildOptions.headers,
@@ -69,6 +65,7 @@ export function getWebpackPluginOptions(
silent: sentryBuildOptions.silent,
url: sentryBuildOptions.sentryUrl,
sourcemaps: {
+ disable: sentryBuildOptions.sourcemaps?.disable,
rewriteSources(source) {
if (source.startsWith('webpack://_N_E/')) {
return source.replace('webpack://_N_E/', '');
diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
index d6af815d13cb..c987cda267e4 100644
--- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
+++ b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
@@ -173,11 +173,11 @@ describe('getWebpackPluginOptions()', () => {
});
});
- it('sets `sourcemaps.assets` to an empty array when `sourcemaps.disable` is true', () => {
+ it('sets `sourcemaps.disable` plugin options to true when `sourcemaps.disable` is true', () => {
const buildContext = generateBuildContext({ isServer: false });
const generatedPluginOptions = getWebpackPluginOptions(buildContext, { sourcemaps: { disable: true } }, undefined);
expect(generatedPluginOptions.sourcemaps).toMatchObject({
- assets: [],
+ disable: true,
});
});
From 254a501cc5fc89bb6de6afb489a82c8deaecf926 Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Tue, 21 Jan 2025 16:47:22 +0100
Subject: [PATCH 05/59] feat!: Only collect ip addresses with `sendDefaultPii:
true` (#15084)
Ref:
https://github.com/getsentry/sentry-javascript/issues/5347#issuecomment-2602606373
---
.../suites/feedback/attachTo/test.ts | 3 --
.../suites/feedback/captureFeedback/test.ts | 3 --
.../hasSampling/test.ts | 3 --
.../feedback/captureFeedbackCsp/test.ts | 3 --
.../manual-client/browser-context/test.ts | 3 --
.../sendDefaultPii/errors/subject.js | 1 +
.../public-api/sendDefaultPii/errors/test.ts | 11 +++++
.../suites/public-api/sendDefaultPii/init.js | 9 ++++
.../sendDefaultPii/performance/subject.js | 1 +
.../sendDefaultPii/performance/test.ts | 21 ++++++++
.../public-api/sendDefaultPii/replay/init.js | 20 ++++++++
.../public-api/sendDefaultPii/replay/test.ts | 24 ++++++++++
.../sendDefaultPii/sessions/init.js | 9 ++++
.../sendDefaultPii/sessions/test.ts | 13 +++++
.../suites/public-api/setUser/init.js | 1 +
.../public-api/setUser/unset_user/test.ts | 4 ++
.../standalone-mixed-transaction/test.ts | 3 --
.../suites/public-api/withScope/init.js | 1 +
.../withScope/nested_scopes/test.ts | 24 ++++++++--
.../suites/replay/captureReplay/test.ts | 6 ---
.../captureReplayFromReplayPackage/test.ts | 6 ---
.../web-vitals-cls-standalone-spans/test.ts | 4 --
.../metrics/web-vitals-inp-late/test.ts | 1 -
.../web-vitals-inp-parametrized-late/test.ts | 1 -
.../web-vitals-inp-parametrized/test.ts | 1 -
.../tracing/metrics/web-vitals-inp/test.ts | 1 -
.../utils/replayEventTemplates.ts | 3 --
.../test-applications/react-17/src/index.tsx | 1 +
.../react-router-6/src/index.tsx | 1 +
.../react-router-7-spa/src/main.tsx | 1 +
docs/migration/v8-to-v9.md | 1 +
packages/browser-utils/src/metrics/utils.ts | 4 +-
packages/browser/src/client.ts | 48 +++++++++----------
packages/core/src/integrations/requestdata.ts | 9 +++-
34 files changed, 169 insertions(+), 76 deletions(-)
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/subject.js
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/subject.js
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts
diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts
index 177d8cc2994f..a9888ad6edf6 100644
--- a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts
+++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts
@@ -57,9 +57,6 @@ sentryTest('should capture feedback with custom button', async ({ getLocalTestUr
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
tags: {},
- user: {
- ip_address: '{{auto}}',
- },
sdk: {
integrations: expect.arrayContaining(['Feedback']),
version: expect.any(String),
diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts
index 68c45e2c2f2f..138e7225a7c8 100644
--- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts
+++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts
@@ -69,9 +69,6 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => {
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
});
diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts
index c91be3511d0a..51374ac81947 100644
--- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts
+++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts
@@ -103,9 +103,6 @@ sentryTest('should capture feedback', async ({ forceFlushReplay, getLocalTestUrl
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
});
diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts
index 56ba6606c724..77eadb71173a 100644
--- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts
+++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts
@@ -69,9 +69,6 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => {
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
const cspViolation = await page.evaluate('window.__CSPVIOLATION__');
diff --git a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts
index a220ba2923d6..642006f4c2fc 100644
--- a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts
+++ b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/test.ts
@@ -34,9 +34,6 @@ sentryTest('allows to setup a client manually & capture exceptions', async ({ ge
'User-Agent': expect.any(String),
}),
},
- user: {
- ip_address: '{{auto}}',
- },
timestamp: expect.any(Number),
environment: 'local',
release: '0.0.1',
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/subject.js b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/subject.js
new file mode 100644
index 000000000000..1b632e0a9289
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/subject.js
@@ -0,0 +1 @@
+Sentry.captureException(new Error('woot'));
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts
new file mode 100644
index 000000000000..310085607b09
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/errors/test.ts
@@ -0,0 +1,11 @@
+import { expect } from '@playwright/test';
+import type { Event } from '@sentry/core';
+
+import { sentryTest } from '../../../../utils/fixtures';
+import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
+
+sentryTest('should default user to {{auto}} on errors when sendDefaultPii: true', async ({ getLocalTestUrl, page }) => {
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ const eventData = await getFirstSentryEnvelopeRequest(page, url);
+ expect(eventData.user?.ip_address).toBe('{{auto}}');
+});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/init.js b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/init.js
new file mode 100644
index 000000000000..b876cb8a3288
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/init.js
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ tracesSampleRate: 1,
+ sendDefaultPii: true,
+});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/subject.js b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/subject.js
new file mode 100644
index 000000000000..8a509ca1d99d
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/subject.js
@@ -0,0 +1 @@
+Sentry.startSpan({ name: 'woot' }, () => undefined);
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts
new file mode 100644
index 000000000000..6e1f20826548
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/performance/test.ts
@@ -0,0 +1,21 @@
+import { expect } from '@playwright/test';
+import { sentryTest } from '../../../../utils/fixtures';
+import {
+ envelopeRequestParser,
+ shouldSkipTracingTest,
+ waitForTransactionRequestOnUrl,
+} from '../../../../utils/helpers';
+
+sentryTest(
+ 'should default user to {{auto}} on transactions when sendDefaultPii: true',
+ async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ const req = await waitForTransactionRequestOnUrl(page, url);
+ const transaction = envelopeRequestParser(req);
+ expect(transaction.user?.ip_address).toBe('{{auto}}');
+ },
+);
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/init.js b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/init.js
new file mode 100644
index 000000000000..b40eb0542e6b
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/init.js
@@ -0,0 +1,20 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+window.Replay = Sentry.replayIntegration({
+ flushMinDelay: 200,
+ flushMaxDelay: 200,
+ minReplayDuration: 0,
+ useCompression: false,
+ blockAllMedia: false,
+ unmask: ['.sentry-unmask, [data-sentry-unmask]'],
+});
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ replaysSessionSampleRate: 1.0,
+ replaysOnErrorSampleRate: 0.0,
+ integrations: [window.Replay],
+ sendDefaultPii: true,
+});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts
new file mode 100644
index 000000000000..4f4ad6e3003a
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/replay/test.ts
@@ -0,0 +1,24 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../../utils/fixtures';
+import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';
+
+sentryTest(
+ 'replay recording should contain default performance spans',
+ async ({ getLocalTestUrl, page, browserName }) => {
+ // We only test this against the NPM package and replay bundles
+ // and only on chromium as most performance entries are only available in chromium
+ if (shouldSkipReplayTest() || browserName !== 'chromium') {
+ sentryTest.skip();
+ }
+
+ const reqPromise0 = waitForReplayRequest(page, 0);
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+
+ await page.goto(url);
+ const replayEvent = getReplayEvent(await reqPromise0);
+
+ expect(replayEvent.user?.ip_address).toBe('{{auto}}');
+ },
+);
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/init.js b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/init.js
new file mode 100644
index 000000000000..2003a9cf82eb
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/init.js
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ sendDefaultPii: true,
+ release: '1.0',
+});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts
new file mode 100644
index 000000000000..898e1cd9dbec
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/public-api/sendDefaultPii/sessions/test.ts
@@ -0,0 +1,13 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../../utils/fixtures';
+import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
+
+sentryTest(
+ 'should default user to {{auto}} on sessions when sendDefaultPii: true',
+ async ({ getLocalTestUrl, page }) => {
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ const session = await getFirstSentryEnvelopeRequest(page, url);
+ expect((session as any).attrs.ip_address).toBe('{{auto}}');
+ },
+);
diff --git a/dev-packages/browser-integration-tests/suites/public-api/setUser/init.js b/dev-packages/browser-integration-tests/suites/public-api/setUser/init.js
index d8c94f36fdd0..1f66b52852eb 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/setUser/init.js
+++ b/dev-packages/browser-integration-tests/suites/public-api/setUser/init.js
@@ -4,4 +4,5 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ sendDefaultPii: true,
});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts b/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts
index 43bc03bfb4d1..6fbdf6491d69 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/setUser/unset_user/test.ts
@@ -10,6 +10,8 @@ sentryTest('should unset user', async ({ getLocalTestUrl, page }) => {
const eventData = await getMultipleSentryEnvelopeRequests(page, 3, { url });
expect(eventData[0].message).toBe('no_user');
+
+ // because sendDefaultPii: true
expect(eventData[0].user).toEqual({ ip_address: '{{auto}}' });
expect(eventData[1].message).toBe('user');
@@ -20,6 +22,8 @@ sentryTest('should unset user', async ({ getLocalTestUrl, page }) => {
});
expect(eventData[2].message).toBe('unset_user');
+
+ // because sendDefaultPii: true
expect(eventData[2].user).toEqual({
ip_address: '{{auto}}',
});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-mixed-transaction/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-mixed-transaction/test.ts
index 9c4c83bf6764..0663d16b6995 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-mixed-transaction/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-mixed-transaction/test.ts
@@ -103,9 +103,6 @@ sentryTest(
headers: expect.any(Object),
url: expect.any(String),
},
- user: {
- ip_address: '{{auto}}',
- },
sdk: expect.any(Object),
spans: [
{
diff --git a/dev-packages/browser-integration-tests/suites/public-api/withScope/init.js b/dev-packages/browser-integration-tests/suites/public-api/withScope/init.js
index d8c94f36fdd0..1f66b52852eb 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/withScope/init.js
+++ b/dev-packages/browser-integration-tests/suites/public-api/withScope/init.js
@@ -4,4 +4,5 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ sendDefaultPii: true,
});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts
index d75b0248bd4f..bf10eed2b504 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/withScope/nested_scopes/test.ts
@@ -10,22 +10,36 @@ sentryTest('should allow nested scoping', async ({ getLocalTestUrl, page }) => {
const eventData = await getMultipleSentryEnvelopeRequests(page, 5, { url });
expect(eventData[0].message).toBe('root_before');
- expect(eventData[0].user).toEqual({ id: 'qux', ip_address: '{{auto}}' });
+ expect(eventData[0].user).toEqual({
+ id: 'qux',
+ ip_address: '{{auto}}', // because sendDefaultPii: true
+ });
expect(eventData[0].tags).toBeUndefined();
expect(eventData[1].message).toBe('outer_before');
- expect(eventData[1].user).toEqual({ id: 'qux', ip_address: '{{auto}}' });
+ expect(eventData[1].user).toEqual({
+ id: 'qux',
+ ip_address: '{{auto}}', // because sendDefaultPii: true
+ });
expect(eventData[1].tags).toMatchObject({ foo: false });
expect(eventData[2].message).toBe('inner');
- expect(eventData[2].user).toEqual({ ip_address: '{{auto}}' });
+ expect(eventData[2].user).toEqual({
+ ip_address: '{{auto}}', // because sendDefaultPii: true
+ });
expect(eventData[2].tags).toMatchObject({ foo: false, bar: 10 });
expect(eventData[3].message).toBe('outer_after');
- expect(eventData[3].user).toEqual({ id: 'baz', ip_address: '{{auto}}' });
+ expect(eventData[3].user).toEqual({
+ id: 'baz',
+ ip_address: '{{auto}}', // because sendDefaultPii: true
+ });
expect(eventData[3].tags).toMatchObject({ foo: false });
expect(eventData[4].message).toBe('root_after');
- expect(eventData[4].user).toEqual({ id: 'qux', ip_address: '{{auto}}' });
+ expect(eventData[4].user).toEqual({
+ id: 'qux',
+ ip_address: '{{auto}}', // because sendDefaultPii: true
+ });
expect(eventData[4].tags).toBeUndefined();
});
diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts
index 9737a3908247..e581a8eacd57 100644
--- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts
@@ -55,9 +55,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
@@ -96,9 +93,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
});
diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts
index 1c7fe09e3c33..3a10ea72e18c 100644
--- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts
@@ -55,9 +55,6 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
@@ -96,9 +93,6 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g
'User-Agent': expect.stringContaining(''),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
});
});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts
index 9018252f37cb..02431dae3b79 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts
@@ -69,7 +69,6 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a
transaction: expect.stringContaining('index.html'),
'user_agent.original': expect.stringContaining('Chrome'),
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
- 'client.address': '{{auto}}',
},
description: expect.stringContaining('body > div#content > p'),
exclusive_time: 0,
@@ -138,7 +137,6 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as
transaction: expect.stringContaining('index.html'),
'user_agent.original': expect.stringContaining('Chrome'),
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
- 'client.address': '{{auto}}',
},
description: expect.stringContaining('body > div#content > p'),
exclusive_time: 0,
@@ -205,7 +203,6 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.',
transaction: expect.stringContaining('index.html'),
'user_agent.original': expect.stringContaining('Chrome'),
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
- 'client.address': '{{auto}}',
},
description: expect.stringContaining('body > div#content > p'),
exclusive_time: 0,
@@ -273,7 +270,6 @@ sentryTest(
transaction: expect.stringContaining('index.html'),
'user_agent.original': expect.stringContaining('Chrome'),
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
- 'client.address': '{{auto}}',
},
description: 'Layout shift',
exclusive_time: 0,
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
index d151772439a1..fffa85b89ae2 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
@@ -71,7 +71,6 @@ sentryTest('should capture an INP click event span after pageload', async ({ bro
'sentry.source': 'custom',
transaction: 'test-url',
'user_agent.original': expect.stringContaining('Chrome'),
- 'client.address': '{{auto}}',
},
measurements: {
inp: {
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
index 961f98518049..65852c734c98 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
@@ -74,7 +74,6 @@ sentryTest(
'sentry.source': 'custom',
transaction: 'test-route',
'user_agent.original': expect.stringContaining('Chrome'),
- 'client.address': '{{auto}}',
},
measurements: {
inp: {
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts
index 911929471ea4..5705fe6863b5 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts
@@ -70,7 +70,6 @@ sentryTest(
'sentry.origin': 'auto.http.browser.inp',
transaction: 'test-route',
'user_agent.original': expect.stringContaining('Chrome'),
- 'client.address': '{{auto}}',
},
measurements: {
inp: {
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts
index 1be97e2446dc..b3435a49b002 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts
@@ -69,7 +69,6 @@ sentryTest('should capture an INP click event span during pageload', async ({ br
'sentry.origin': 'auto.http.browser.inp',
transaction: 'test-url',
'user_agent.original': expect.stringContaining('Chrome'),
- 'client.address': '{{auto}}',
},
measurements: {
inp: {
diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts
index 8455bcc34600..52dbbca1c086 100644
--- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts
+++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts
@@ -37,9 +37,6 @@ const DEFAULT_REPLAY_EVENT = {
'User-Agent': expect.any(String),
},
},
- user: {
- ip_address: '{{auto}}',
- },
platform: 'javascript',
};
diff --git a/dev-packages/e2e-tests/test-applications/react-17/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-17/src/index.tsx
index 49609a988202..aab492c7388b 100644
--- a/dev-packages/e2e-tests/test-applications/react-17/src/index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-17/src/index.tsx
@@ -38,6 +38,7 @@ Sentry.init({
replaysOnErrorSampleRate: 0.0,
tunnel: 'http://localhost:3031',
+ sendDefaultPii: true,
});
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx
index 8c219563e5a4..76884645c4c0 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-router-6/src/index.tsx
@@ -40,6 +40,7 @@ Sentry.init({
replaysOnErrorSampleRate: 0.0,
tunnel: 'http://localhost:3031',
+ sendDefaultPii: true,
});
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa/src/main.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-spa/src/main.tsx
index a49c2c35de9d..baf12f7ff574 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-spa/src/main.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa/src/main.tsx
@@ -39,6 +39,7 @@ Sentry.init({
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 0.0,
tunnel: 'http://localhost:3031',
+ sendDefaultPii: true,
});
const SentryRoutes = Sentry.withSentryReactRouterV7Routing(Routes);
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index c44ecd587bc8..3b090a364386 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -108,6 +108,7 @@ Older Typescript versions _may_ still work, but we will not test them anymore an
### `@sentry/browser`
+- The SDK no longer instructs the Sentry backend to automatically infer IP addresses by default. This means that places where you previously saw IP addresses in Sentry may now be grouped to anonymous users. Set the `sendDefaultPii` option in `Sentry.init()` to true to instruct the Sentry backend to infer IP addresses.
- The `captureUserFeedback` method has been removed. Use the `captureFeedback` method instead and update the `comments` field to `message`.
### `@sentry/nextjs`
diff --git a/packages/browser-utils/src/metrics/utils.ts b/packages/browser-utils/src/metrics/utils.ts
index 2bc97d588395..91aefa8a8918 100644
--- a/packages/browser-utils/src/metrics/utils.ts
+++ b/packages/browser-utils/src/metrics/utils.ts
@@ -74,7 +74,7 @@ export function startStandaloneWebVitalSpan(options: StandaloneWebVitalSpanOptio
const { name, transaction, attributes: passedAttributes, startTime } = options;
- const { release, environment } = client.getOptions();
+ const { release, environment, sendDefaultPii } = client.getOptions();
// We need to get the replay, user, and activeTransaction from the current scope
// so that we can associate replay id, profile id, and a user display to the span
const replay = client.getIntegrationByName string }>('Replay');
@@ -109,7 +109,7 @@ export function startStandaloneWebVitalSpan(options: StandaloneWebVitalSpanOptio
'user_agent.original': WINDOW.navigator?.userAgent,
// This tells Sentry to infer the IP address from the request
- 'client.address': '{{auto}}',
+ 'client.address': sendDefaultPii ? '{{auto}}' : undefined,
...passedAttributes,
};
diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts
index 801a1bffbd2e..20b43ca6ddac 100644
--- a/packages/browser/src/client.ts
+++ b/packages/browser/src/client.ts
@@ -8,7 +8,6 @@ import type {
ParameterizedString,
Scope,
SeverityLevel,
- User,
} from '@sentry/core';
import { Client, applySdkMetadata, getSDKSource } from '@sentry/core';
import { eventFromException, eventFromMessage } from './eventbuilder';
@@ -84,22 +83,31 @@ export class BrowserClient extends Client {
});
}
- this.on('postprocessEvent', event => {
- addAutoIpAddressToUser(event);
- });
-
- this.on('beforeSendSession', session => {
- if ('aggregates' in session) {
- if (session.attrs?.['ip_address'] === undefined) {
- session.attrs = {
- ...session.attrs,
+ if (this._options.sendDefaultPii) {
+ this.on('postprocessEvent', event => {
+ if (event.user?.ip_address === undefined) {
+ event.user = {
+ ...event.user,
ip_address: '{{auto}}',
};
}
- } else {
- addAutoIpAddressToUser(session);
- }
- });
+ });
+
+ this.on('beforeSendSession', session => {
+ if ('aggregates' in session) {
+ if (session.attrs?.['ip_address'] === undefined) {
+ session.attrs = {
+ ...session.attrs,
+ ip_address: '{{auto}}',
+ };
+ }
+ } else {
+ if (session.ipAddress === undefined) {
+ session.ipAddress = '{{auto}}';
+ }
+ }
+ });
+ }
}
/**
@@ -134,15 +142,3 @@ export class BrowserClient extends Client {
return super._prepareEvent(event, hint, currentScope, isolationScope);
}
}
-
-// By default, we want to infer the IP address, unless this is explicitly set to `null`
-// We do this after all other processing is done
-// If `ip_address` is explicitly set to `null` or a value, we leave it as is
-function addAutoIpAddressToUser(objWithMaybeUser: { user?: User | null }): void {
- if (objWithMaybeUser.user?.ip_address === undefined) {
- objWithMaybeUser.user = {
- ...objWithMaybeUser.user,
- ip_address: '{{auto}}',
- };
- }
-}
diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts
index 72bd02c199fb..eea701be09f3 100644
--- a/packages/core/src/integrations/requestdata.ts
+++ b/packages/core/src/integrations/requestdata.ts
@@ -38,12 +38,17 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) =
return {
name: INTEGRATION_NAME,
- processEvent(event) {
+ processEvent(event, _hint, client) {
const { sdkProcessingMetadata = {} } = event;
const { normalizedRequest, ipAddress } = sdkProcessingMetadata;
+ const includeWithDefaultPiiApplied: RequestDataIncludeOptions = {
+ ...include,
+ ip: include.ip || client.getOptions().sendDefaultPii,
+ };
+
if (normalizedRequest) {
- addNormalizedRequestDataToEvent(event, normalizedRequest, { ipAddress }, include);
+ addNormalizedRequestDataToEvent(event, normalizedRequest, { ipAddress }, includeWithDefaultPiiApplied);
}
return event;
From c307597d94cec8a22d7c3e4c84f2b468cac6c2c4 Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Tue, 21 Jan 2025 17:56:43 +0100
Subject: [PATCH 06/59] feat(remix)!: Remove `autoInstrumentRemix` option
(#15074)
---
.../.eslintrc.cjs | 79 ---
.../.gitignore | 6 -
.../create-remix-app-express-legacy/.npmrc | 2 -
.../app/entry.client.tsx | 31 --
.../app/entry.server.tsx | 141 -----
.../app/root.tsx | 80 ---
.../app/routes/_index.tsx | 28 -
.../app/routes/action-formdata.tsx | 17 -
.../app/routes/client-error.tsx | 24 -
.../app/routes/loader-error.tsx | 16 -
.../app/routes/navigate.tsx | 20 -
.../app/routes/user.$id.tsx | 3 -
.../create-remix-app-express-legacy/env.d.ts | 2 -
.../globals.d.ts | 7 -
.../instrument.cjs | 14 -
.../package.json | 56 --
.../playwright.config.mjs | 7 -
.../server.mjs | 52 --
.../start-event-proxy.mjs | 6 -
.../tests/client-errors.test.ts | 29 -
.../tests/client-transactions.test.ts | 57 --
.../tests/server-errors.test.ts | 14 -
.../tests/server-transactions.test.ts | 133 -----
.../tsconfig.json | 22 -
.../vite.config.ts | 18 -
.../instrument.mjs | 1 -
.../create-remix-app-express/instrument.mjs | 1 -
.../create-remix-app-v2-legacy/.eslintrc.js | 4 -
.../create-remix-app-v2-legacy/.gitignore | 6 -
.../create-remix-app-v2-legacy/.npmrc | 2 -
.../create-remix-app-v2-legacy/README.md | 61 ---
.../app/entry.client.tsx | 38 --
.../app/entry.server.tsx | 137 -----
.../create-remix-app-v2-legacy/app/root.tsx | 80 ---
.../app/routes/_index.tsx | 26 -
.../app/routes/client-error.tsx | 13 -
.../app/routes/navigate.tsx | 20 -
.../app/routes/user.$id.tsx | 3 -
.../create-remix-app-v2-legacy/globals.d.ts | 7 -
.../create-remix-app-v2-legacy/package.json | 37 --
.../playwright.config.mjs | 7 -
.../remix.config.js | 9 -
.../create-remix-app-v2-legacy/remix.env.d.ts | 2 -
.../start-event-proxy.mjs | 6 -
.../tests/client-errors.test.ts | 29 -
.../tests/client-transactions.test.ts | 57 --
.../tests/server-transactions.test.ts | 57 --
.../create-remix-app-v2-legacy/tsconfig.json | 22 -
.../create-remix-app-v2/instrument.server.cjs | 1 -
packages/remix/package.json | 10 +-
packages/remix/playwright.config.ts | 2 +-
packages/remix/src/index.server.ts | 5 +-
packages/remix/src/utils/instrumentServer.ts | 192 ++-----
packages/remix/src/utils/remixOptions.ts | 20 +-
.../test/integration/app/entry.server.tsx | 9 -
.../test/integration/instrument.server.mjs | 1 -
packages/remix/test/integration/package.json | 3 +-
.../instrumentation-legacy/action.test.ts | 497 ------------------
.../instrumentation-legacy/loader.test.ts | 270 ----------
.../server/instrumentation-legacy/ssr.test.ts | 46 --
.../action.test.ts | 0
.../loader.test.ts | 0
.../ssr.test.ts | 0
packages/remix/vitest.config.ts | 6 +-
64 files changed, 48 insertions(+), 2503 deletions(-)
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.eslintrc.cjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.gitignore
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.npmrc
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.client.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.server.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/root.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/_index.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/action-formdata.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/client-error.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/loader-error.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/navigate.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/user.$id.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/env.d.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/globals.d.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/instrument.cjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/package.json
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/playwright.config.mjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/server.mjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/start-event-proxy.mjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-errors.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-transactions.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-errors.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-transactions.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tsconfig.json
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/vite.config.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.eslintrc.js
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.gitignore
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.npmrc
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/README.md
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.client.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.server.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/root.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/_index.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/client-error.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/navigate.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/user.$id.tsx
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/globals.d.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/package.json
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/playwright.config.mjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.config.js
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.env.d.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/start-event-proxy.mjs
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-errors.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-transactions.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/server-transactions.test.ts
delete mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tsconfig.json
delete mode 100644 packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts
delete mode 100644 packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts
delete mode 100644 packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts
rename packages/remix/test/integration/test/server/{instrumentation-otel => instrumentation}/action.test.ts (100%)
rename packages/remix/test/integration/test/server/{instrumentation-otel => instrumentation}/loader.test.ts (100%)
rename packages/remix/test/integration/test/server/{instrumentation-otel => instrumentation}/ssr.test.ts (100%)
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.eslintrc.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.eslintrc.cjs
deleted file mode 100644
index 7adbd6f482f6..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.eslintrc.cjs
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * This is intended to be a basic starting point for linting in your app.
- * It relies on recommended configs out of the box for simplicity, but you can
- * and should modify this configuration to best suit your team's needs.
- */
-
-/** @type {import('eslint').Linter.Config} */
-module.exports = {
- root: true,
- parserOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module',
- ecmaFeatures: {
- jsx: true,
- },
- },
- env: {
- browser: true,
- commonjs: true,
- es6: true,
- },
-
- // Base config
- extends: ['eslint:recommended'],
-
- overrides: [
- // React
- {
- files: ['**/*.{js,jsx,ts,tsx}'],
- plugins: ['react', 'jsx-a11y'],
- extends: [
- 'plugin:react/recommended',
- 'plugin:react/jsx-runtime',
- 'plugin:react-hooks/recommended',
- 'plugin:jsx-a11y/recommended',
- ],
- settings: {
- react: {
- version: 'detect',
- },
- formComponents: ['Form'],
- linkComponents: [
- { name: 'Link', linkAttribute: 'to' },
- { name: 'NavLink', linkAttribute: 'to' },
- ],
- 'import/resolver': {
- typescript: {},
- },
- },
- },
-
- // Typescript
- {
- files: ['**/*.{ts,tsx}'],
- plugins: ['@typescript-eslint', 'import'],
- parser: '@typescript-eslint/parser',
- settings: {
- 'import/internal-regex': '^~/',
- 'import/resolver': {
- node: {
- extensions: ['.ts', '.tsx'],
- },
- typescript: {
- alwaysTryTypes: true,
- },
- },
- },
- extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript'],
- },
-
- // Node
- {
- files: ['.eslintrc.cjs', 'server.js'],
- env: {
- node: true,
- },
- },
- ],
-};
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.gitignore b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.gitignore
deleted file mode 100644
index 3f7bf98da3e1..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-node_modules
-
-/.cache
-/build
-/public/build
-.env
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.npmrc b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.npmrc
deleted file mode 100644
index 070f80f05092..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/.npmrc
+++ /dev/null
@@ -1,2 +0,0 @@
-@sentry:registry=http://127.0.0.1:4873
-@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.client.tsx
deleted file mode 100644
index 46a0d015cdc0..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.client.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-import { StrictMode, startTransition, useEffect } from 'react';
-import { hydrateRoot } from 'react-dom/client';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: window.ENV.SENTRY_DSN,
- integrations: [
- Sentry.browserTracingIntegration({
- useEffect,
- useLocation,
- useMatches,
- }),
- Sentry.replayIntegration(),
- ],
- // Performance Monitoring
- tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
- replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
- replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
- tunnel: 'http://localhost:3031/', // proxy server
-});
-
-startTransition(() => {
- hydrateRoot(
- document,
-
-
- ,
- );
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.server.tsx
deleted file mode 100644
index a387f32ee7a6..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/entry.server.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import * as Sentry from '@sentry/remix';
-
-import { PassThrough } from 'node:stream';
-import * as isbotModule from 'isbot';
-
-import type { AppLoadContext, EntryContext } from '@remix-run/node';
-import { createReadableStreamFromReadable } from '@remix-run/node';
-import { installGlobals } from '@remix-run/node';
-import { RemixServer } from '@remix-run/react';
-import { renderToPipeableStream } from 'react-dom/server';
-
-installGlobals();
-
-const ABORT_DELAY = 5_000;
-
-export const handleError = Sentry.sentryHandleError;
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
- loadContext: AppLoadContext,
-) {
- return isBotRequest(request.headers.get('user-agent'))
- ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
- : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
-}
-
-// We have some Remix apps in the wild already running with isbot@3 so we need
-// to maintain backwards compatibility even though we want new apps to use
-// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev.
-function isBotRequest(userAgent: string | null) {
- if (!userAgent) {
- return false;
- }
-
- // isbot >= 3.8.0, >4
- if ('isbot' in isbotModule && typeof isbotModule.isbot === 'function') {
- return isbotModule.isbot(userAgent);
- }
-
- // isbot < 3.8.0
- if ('default' in isbotModule && typeof isbotModule.default === 'function') {
- return isbotModule.default(userAgent);
- }
-
- return false;
-}
-
-function handleBotRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- ,
- {
- onAllReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set('Content-Type', 'text/html');
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- setTimeout(abort, ABORT_DELAY);
- });
-}
-
-function handleBrowserRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- ,
- {
- onShellReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set('Content-Type', 'text/html');
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- setTimeout(abort, ABORT_DELAY);
- });
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/root.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/root.tsx
deleted file mode 100644
index 517a37a9d76b..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/root.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { cssBundleHref } from '@remix-run/css-bundle';
-import { LinksFunction, MetaFunction, json } from '@remix-run/node';
-import {
- Links,
- LiveReload,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
- useLoaderData,
- useRouteError,
-} from '@remix-run/react';
-import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';
-import type { SentryMetaArgs } from '@sentry/remix';
-
-export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])];
-
-export const loader = () => {
- return json({
- ENV: {
- SENTRY_DSN: process.env.E2E_TEST_DSN,
- },
- });
-};
-
-export const meta = ({ data }: SentryMetaArgs>) => {
- return [
- {
- env: data.ENV,
- },
- {
- name: 'sentry-trace',
- content: data.sentryTrace,
- },
- {
- name: 'baggage',
- content: data.sentryBaggage,
- },
- ];
-};
-
-export function ErrorBoundary() {
- const error = useRouteError();
- const eventId = captureRemixErrorBoundaryError(error);
-
- return (
-
- ErrorBoundary Error
- {eventId}
-
- );
-}
-
-function App() {
- const { ENV } = useLoaderData();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default withSentry(App);
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/_index.tsx
deleted file mode 100644
index 69f39f7bc801..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/_index.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Link, useSearchParams } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-
-export default function Index() {
- const [searchParams] = useSearchParams();
-
- if (searchParams.get('tag')) {
- Sentry.setTags({
- sentry_test: searchParams.get('tag'),
- });
- }
-
- return (
-
- {
- throw new Error('I am an error!');
- }}
- />
-
- navigate
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/action-formdata.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/action-formdata.tsx
deleted file mode 100644
index 884beb2f51e9..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/action-formdata.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { json } from '@remix-run/node';
-import { Form } from '@remix-run/react';
-
-export async function action() {
- return json({ message: 'success' });
-}
-
-export default function ActionFormData() {
- return (
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/client-error.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/client-error.tsx
deleted file mode 100644
index f4d6ec9c4f0a..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/client-error.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { useSearchParams } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-
-import { useState } from 'react';
-
-export default function ErrorBoundaryCapture() {
- const [searchParams] = useSearchParams();
-
- if (searchParams.get('tag')) {
- Sentry.setTags({
- sentry_test: searchParams.get('tag'),
- });
- }
-
- const [count, setCount] = useState(0);
-
- if (count > 0) {
- throw new Error('Sentry React Component Error');
- } else {
- setTimeout(() => setCount(count + 1), 0);
- }
-
- return {count}
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/loader-error.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/loader-error.tsx
deleted file mode 100644
index 75d454571fa5..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/loader-error.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { LoaderFunction } from '@remix-run/node';
-import { useLoaderData } from '@remix-run/react';
-
-export default function LoaderError() {
- useLoaderData();
-
- return (
-
-
Loader Error
-
- );
-}
-
-export const loader: LoaderFunction = () => {
- throw new Error('Loader Error');
-};
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/navigate.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/navigate.tsx
deleted file mode 100644
index a84df11e7bb7..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/navigate.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { LoaderFunction } from '@remix-run/node';
-import { useLoaderData } from '@remix-run/react';
-
-export const loader: LoaderFunction = async ({ params: { id } }) => {
- if (id === '-1') {
- throw new Error('Unexpected Server Error');
- }
-
- return null;
-};
-
-export default function LoaderError() {
- const data = useLoaderData();
-
- return (
-
-
{data?.test ? data.test : 'Not Found'}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/user.$id.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/user.$id.tsx
deleted file mode 100644
index 13b2e0a34d1e..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/app/routes/user.$id.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function User() {
- return I am a blank page
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/env.d.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/env.d.ts
deleted file mode 100644
index 78ed2345c6e4..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-///
-///
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/globals.d.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/globals.d.ts
deleted file mode 100644
index 4130ac6a8a09..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/globals.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-interface Window {
- recordedTransactions?: string[];
- capturedExceptionId?: string;
- ENV: {
- SENTRY_DSN: string;
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/instrument.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/instrument.cjs
deleted file mode 100644
index feea6fb5388e..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/instrument.cjs
+++ /dev/null
@@ -1,14 +0,0 @@
-const Sentry = require('@sentry/remix');
-const process = require('process');
-
-Sentry.init({
- tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.E2E_TEST_DSN,
- tunnel: 'http://localhost:3031/', // proxy server
- sendDefaultPii: true, // Testing the FormData
- captureActionFormDataKeys: {
- file: true,
- text: true,
- },
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/package.json
deleted file mode 100644
index 5c362ffb97a1..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/package.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "private": true,
- "sideEffects": false,
- "type": "module",
- "scripts": {
- "build": "remix vite:build",
- "dev": "node ./server.mjs",
- "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
- "start": "cross-env NODE_ENV=production node ./server.mjs",
- "typecheck": "tsc",
- "clean": "npx rimraf node_modules pnpm-lock.yaml",
- "test:build": "pnpm install && pnpm build",
- "test:assert": "pnpm playwright test"
- },
- "dependencies": {
- "@remix-run/css-bundle": "^2.7.2",
- "@remix-run/express": "^2.7.2",
- "@remix-run/node": "^2.7.2",
- "@remix-run/react": "^2.7.2",
- "@sentry/remix": "latest || *",
- "compression": "^1.7.4",
- "cross-env": "^7.0.3",
- "express": "^4.18.2",
- "isbot": "^4.1.0",
- "morgan": "^1.10.0",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "source-map-support": "^0.5.21"
- },
- "devDependencies": {
- "@playwright/test": "^1.44.1",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "@remix-run/dev": "^2.7.2",
- "@sentry/core": "latest || *",
- "@types/compression": "^1.7.2",
- "@types/express": "^4.17.17",
- "@types/morgan": "^1.9.4",
- "@types/react": "^18.2.20",
- "@types/react-dom": "^18.2.7",
- "@types/source-map-support": "^0.5.6",
- "@typescript-eslint/eslint-plugin": "^6.7.4",
- "chokidar": "^3.5.3",
- "eslint": "^8.38.0",
- "eslint-import-resolver-typescript": "^3.6.1",
- "eslint-plugin-import": "^2.28.1",
- "eslint-plugin-jsx-a11y": "^6.7.1",
- "eslint-plugin-react": "^7.33.2",
- "eslint-plugin-react-hooks": "^4.6.0",
- "tsx": "4.7.2",
- "typescript": "^5.1.6",
- "vite-tsconfig-paths": "^4.2.1"
- },
- "volta": {
- "extends": "../../package.json"
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/playwright.config.mjs
deleted file mode 100644
index 31f2b913b58b..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/playwright.config.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-import { getPlaywrightConfig } from '@sentry-internal/test-utils';
-
-const config = getPlaywrightConfig({
- startCommand: `pnpm start`,
-});
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/server.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/server.mjs
deleted file mode 100644
index bde5876f4b29..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/server.mjs
+++ /dev/null
@@ -1,52 +0,0 @@
-import './instrument.cjs';
-
-import { createRequestHandler } from '@remix-run/express';
-import { installGlobals } from '@remix-run/node';
-import compression from 'compression';
-import express from 'express';
-import morgan from 'morgan';
-
-installGlobals();
-
-const viteDevServer =
- process.env.NODE_ENV === 'production'
- ? undefined
- : await import('vite').then(vite =>
- vite.createServer({
- server: { middlewareMode: true },
- }),
- );
-
-const app = express();
-
-app.use(compression());
-
-// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
-app.disable('x-powered-by');
-
-// handle asset requests
-if (viteDevServer) {
- app.use(viteDevServer.middlewares);
-} else {
- // Vite fingerprints its assets so we can cache forever.
- app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' }));
-}
-
-// Everything else (like favicon.ico) is cached for an hour. You may want to be
-// more aggressive with this caching.
-app.use(express.static('build/client', { maxAge: '1h' }));
-
-app.use(morgan('tiny'));
-
-// handle SSR requests
-app.all(
- '*',
- createRequestHandler({
- build: viteDevServer
- ? () => viteDevServer.ssrLoadModule('virtual:remix/server-build')
- : await import('./build/server/index.js'),
- }),
-);
-
-const port = process.env.PORT || 3030;
-app.listen(port, () => console.log(`Express server listening at http://localhost:${port}`));
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/start-event-proxy.mjs
deleted file mode 100644
index ccbe23f1804d..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/start-event-proxy.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'create-remix-app-express-legacy',
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-errors.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-errors.test.ts
deleted file mode 100644
index 2dc323ec4c47..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-errors.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-
-test('Sends a client-side exception to Sentry', async ({ page }) => {
- const errorPromise = waitForError('create-remix-app-express-legacy', errorEvent => {
- return errorEvent.exception?.values?.[0].value === 'I am an error!';
- });
-
- await page.goto('/');
-
- const exceptionButton = page.locator('id=exception-button');
- await exceptionButton.click();
-
- const errorEvent = await errorPromise;
-
- expect(errorEvent).toBeDefined();
-});
-
-test('Sends a client-side ErrorBoundary exception to Sentry', async ({ page }) => {
- const errorPromise = waitForError('create-remix-app-express-legacy', errorEvent => {
- return errorEvent.exception?.values?.[0].value === 'Sentry React Component Error';
- });
-
- await page.goto('/client-error');
-
- const errorEvent = await errorPromise;
-
- expect(errorEvent).toBeDefined();
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-transactions.test.ts
deleted file mode 100644
index 1dfec26a0a4d..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/client-transactions.test.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-
-test('Sends a pageload transaction to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === 'routes/_index';
- });
-
- await page.goto('/');
-
- const transactionEvent = await transactionPromise;
-
- expect(transactionEvent).toBeDefined();
-});
-
-test('Sends a navigation transaction to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === 'routes/user.$id';
- });
-
- await page.goto('/');
-
- const linkElement = page.locator('id=navigation');
- await linkElement.click();
-
- const transactionEvent = await transactionPromise;
-
- expect(transactionEvent).toBeDefined();
-});
-
-test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => {
- await page.goto('/');
-
- const sentryTraceMetaTag = await page.waitForSelector('meta[name="sentry-trace"]', {
- state: 'attached',
- });
- const baggageMetaTag = await page.waitForSelector('meta[name="baggage"]', {
- state: 'attached',
- });
-
- expect(sentryTraceMetaTag).toBeTruthy();
- expect(baggageMetaTag).toBeTruthy();
-});
-
-test('Renders `sentry-trace` and `baggage` meta tags for a sub-route', async ({ page }) => {
- await page.goto('/user/123');
-
- const sentryTraceMetaTag = await page.waitForSelector('meta[name="sentry-trace"]', {
- state: 'attached',
- });
- const baggageMetaTag = await page.waitForSelector('meta[name="baggage"]', {
- state: 'attached',
- });
-
- expect(sentryTraceMetaTag).toBeTruthy();
- expect(baggageMetaTag).toBeTruthy();
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-errors.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-errors.test.ts
deleted file mode 100644
index 8d76e15db32c..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-errors.test.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-
-test('Sends a loader error to Sentry', async ({ page }) => {
- const loaderErrorPromise = waitForError('create-remix-app-express-legacy', errorEvent => {
- return errorEvent.exception.values[0].value === 'Loader Error';
- });
-
- await page.goto('/loader-error');
-
- const loaderError = await loaderErrorPromise;
-
- expect(loaderError).toBeDefined();
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-transactions.test.ts
deleted file mode 100644
index 24b6024273d3..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tests/server-transactions.test.ts
+++ /dev/null
@@ -1,133 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { uuid4 } from '@sentry/core';
-
-import { waitForTransaction } from '@sentry-internal/test-utils';
-
-test.describe.configure({ mode: 'serial' });
-
-test('Sends parameterized transaction name to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'http.server';
- });
-
- await page.goto('/user/123');
-
- const transaction = await transactionPromise;
-
- expect(transaction).toBeDefined();
- expect(transaction.transaction).toBe('routes/user.$id');
-});
-
-test('Sends form data with action span to Sentry', async ({ page }) => {
- const formdataActionTransaction = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent?.spans?.some(span => span.op === 'function.remix.action');
- });
-
- await page.goto('/action-formdata');
-
- await page.fill('input[name=text]', 'test');
- await page.setInputFiles('input[type=file]', {
- name: 'file.txt',
- mimeType: 'text/plain',
- buffer: Buffer.from('this is test'),
- });
-
- await page.locator('button[type=submit]').click();
-
- const actionSpan = (await formdataActionTransaction).spans.find(span => span.op === 'function.remix.action');
-
- expect(actionSpan).toBeDefined();
- expect(actionSpan.op).toBe('function.remix.action');
- expect(actionSpan.data).toMatchObject({
- 'remix.action_form_data.text': 'test',
- 'remix.action_form_data.file': 'file.txt',
- });
-});
-
-test('Sends a loader span to Sentry', async ({ page }) => {
- const loaderTransactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent?.spans?.some(span => span.op === 'function.remix.loader');
- });
-
- await page.goto('/');
-
- const loaderSpan = (await loaderTransactionPromise).spans.find(span => span.op === 'function.remix.loader');
-
- expect(loaderSpan).toBeDefined();
- expect(loaderSpan.op).toBe('function.remix.loader');
-});
-test('Propagates trace when ErrorBoundary is triggered', async ({ page }) => {
- // We use this to identify the transactions
- const testTag = uuid4();
-
- const httpServerTransactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- const pageLoadTransactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- page.goto(`/client-error?tag=${testTag}`);
-
- const pageloadTransaction = await pageLoadTransactionPromise;
- const httpServerTransaction = await httpServerTransactionPromise;
-
- expect(pageloadTransaction).toBeDefined();
- expect(httpServerTransaction).toBeDefined();
-
- const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
- const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
-
- const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
- const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
- const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
-
- expect(httpServerTransaction.transaction).toBe('routes/client-error');
- expect(pageloadTransaction.transaction).toBe('routes/client-error');
-
- expect(httpServerTraceId).toBeDefined();
- expect(httpServerSpanId).toBeDefined();
-
- expect(pageLoadTraceId).toEqual(httpServerTraceId);
- expect(pageLoadParentSpanId).toEqual(httpServerSpanId);
- expect(pageLoadSpanId).not.toEqual(httpServerSpanId);
-});
-
-test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => {
- // We use this to identify the transactions
- const testTag = uuid4();
-
- const httpServerTransactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- const pageLoadTransactionPromise = waitForTransaction('create-remix-app-express-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- page.goto(`/?tag=${testTag}`);
-
- const pageloadTransaction = await pageLoadTransactionPromise;
- const httpServerTransaction = await httpServerTransactionPromise;
-
- expect(pageloadTransaction).toBeDefined();
- expect(httpServerTransaction).toBeDefined();
-
- const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
- const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
-
- const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
- const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
- const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
-
- expect(httpServerTransaction.transaction).toBe('routes/_index');
- expect(pageloadTransaction.transaction).toBe('routes/_index');
-
- expect(httpServerTraceId).toBeDefined();
- expect(httpServerSpanId).toBeDefined();
-
- expect(pageLoadTraceId).toEqual(httpServerTraceId);
- expect(pageLoadParentSpanId).toEqual(httpServerSpanId);
- expect(pageLoadSpanId).not.toEqual(httpServerSpanId);
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tsconfig.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tsconfig.json
deleted file mode 100644
index b58e7d722f35..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
- "compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
- "isolatedModules": true,
- "esModuleInterop": true,
- "jsx": "react-jsx",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "target": "ES2022",
- "strict": true,
- "allowJs": true,
- "skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
- "baseUrl": ".",
- "paths": {
- "~/*": ["./app/*"]
- },
- // Remix takes care of building everything in `remix build`.
- "noEmit": true,
- },
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/vite.config.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/vite.config.ts
deleted file mode 100644
index 13de9243b22a..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-legacy/vite.config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { vitePlugin as remix } from '@remix-run/dev';
-import { defineConfig } from 'vite';
-import tsconfigPaths from 'vite-tsconfig-paths';
-
-import { installGlobals } from '@remix-run/node';
-
-installGlobals();
-
-export default defineConfig({
- plugins: [
- remix(),
- tsconfigPaths({
- // The dev server config errors are not relevant to this test app
- // https://github.com/aleclarson/vite-tsconfig-paths?tab=readme-ov-file#options
- ignoreConfigErrors: true,
- }),
- ],
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs
index 5fb6bd039fdb..b52053445456 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/instrument.mjs
@@ -6,6 +6,5 @@ Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: 'http://localhost:3031/', // proxy server
- autoInstrumentRemix: true, // auto instrument Remix
integrations: [Sentry.nativeNodeFetchIntegration()],
});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs
index f2e7d35fab80..0cbfe8ec4e5b 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/instrument.mjs
@@ -7,7 +7,6 @@ Sentry.init({
dsn: process.env.E2E_TEST_DSN,
tunnel: 'http://localhost:3031/', // proxy server
sendDefaultPii: true, // Testing the FormData
- autoInstrumentRemix: true, // auto instrument Remix
captureActionFormDataKeys: {
file: true,
text: true,
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.eslintrc.js b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.eslintrc.js
deleted file mode 100644
index f2faf1470fd8..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.eslintrc.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('eslint').Linter.Config} */
-module.exports = {
- extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'],
-};
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.gitignore b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.gitignore
deleted file mode 100644
index 3f7bf98da3e1..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-node_modules
-
-/.cache
-/build
-/public/build
-.env
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.npmrc b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.npmrc
deleted file mode 100644
index 070f80f05092..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/.npmrc
+++ /dev/null
@@ -1,2 +0,0 @@
-@sentry:registry=http://127.0.0.1:4873
-@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/README.md b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/README.md
deleted file mode 100644
index 54336d746713..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Welcome to Remix!
-
-- [Remix Docs](https://remix.run/docs)
-
-## Development
-
-From your terminal:
-
-```sh
-npm run dev
-```
-
-This starts your app in development mode, rebuilding assets on file changes.
-
-## Deployment
-
-First, build your app for production:
-
-```sh
-npm run build
-```
-
-Then run the app in production mode:
-
-```sh
-npm start
-```
-
-Now you'll need to pick a host to deploy it to.
-
-### DIY
-
-If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
-
-Make sure to deploy the output of `remix build`
-
-- `build/`
-- `public/build/`
-
-### Using a Template
-
-When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new
-project, then copy over relevant code/assets from your current app to the new project that's pre-configured for your
-target server.
-
-Most importantly, this means everything in the `app/` directory, but if you've further customized your current
-application outside of there it may also include:
-
-- Any assets you've added/updated in `public/`
-- Any updated versions of root files such as `.eslintrc.js`, etc.
-
-```sh
-cd ..
-# create a new project, and pick a pre-configured host
-npx create-remix@latest
-cd my-new-remix-app
-# remove the new project's app (not the old one!)
-rm -rf app
-# copy your app over
-cp -R ../my-old-remix-app/app app
-```
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.client.tsx
deleted file mode 100644
index 2109aad0a421..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.client.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * By default, Remix will handle hydrating your app on the client for you.
- * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
- * For more information, see https://remix.run/file-conventions/entry.client
- */
-
-import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-import { StrictMode, startTransition, useEffect } from 'react';
-import { hydrateRoot } from 'react-dom/client';
-
-Sentry.init({
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: window.ENV.SENTRY_DSN,
- integrations: [
- Sentry.browserTracingIntegration({
- useEffect,
- useLocation,
- useMatches,
- }),
- Sentry.replayIntegration(),
- ],
- // Performance Monitoring
- tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
- // Session Replay
- replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
- replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
- tunnel: 'http://localhost:3031/', // proxy server
-});
-
-startTransition(() => {
- hydrateRoot(
- document,
-
-
- ,
- );
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.server.tsx
deleted file mode 100644
index c34e49664197..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/entry.server.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import * as Sentry from '@sentry/remix';
-
-Sentry.init({
- tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
- environment: 'qa', // dynamic sampling bias to keep transactions
- dsn: process.env.E2E_TEST_DSN,
- tunnel: 'http://localhost:3031/', // proxy server
-});
-
-/**
- * By default, Remix will handle generating the HTTP Response for you.
- * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
- * For more information, see https://remix.run/file-conventions/entry.server
- */
-
-import { PassThrough } from 'node:stream';
-
-import type { AppLoadContext, EntryContext } from '@remix-run/node';
-import { createReadableStreamFromReadable } from '@remix-run/node';
-import { installGlobals } from '@remix-run/node';
-import { RemixServer } from '@remix-run/react';
-import isbot from 'isbot';
-import { renderToPipeableStream } from 'react-dom/server';
-
-installGlobals();
-
-const ABORT_DELAY = 5_000;
-
-const handleErrorImpl = () => {
- Sentry.setTag('remix-test-tag', 'remix-test-value');
-};
-
-export const handleError = Sentry.wrapHandleErrorWithSentry(handleErrorImpl);
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
- loadContext: AppLoadContext,
-) {
- return isbot(request.headers.get('user-agent'))
- ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
- : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
-}
-
-function handleBotRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- ,
- {
- onAllReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set('Content-Type', 'text/html');
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- setTimeout(abort, ABORT_DELAY);
- });
-}
-
-function handleBrowserRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
-) {
- return new Promise((resolve, reject) => {
- let shellRendered = false;
- const { pipe, abort } = renderToPipeableStream(
- ,
- {
- onShellReady() {
- shellRendered = true;
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set('Content-Type', 'text/html');
-
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- }),
- );
-
- pipe(body);
- },
- onShellError(error: unknown) {
- reject(error);
- },
- onError(error: unknown) {
- responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell. Don't log
- // errors encountered during initial shell rendering since they'll
- // reject and get logged in handleDocumentRequest.
- if (shellRendered) {
- console.error(error);
- }
- },
- },
- );
-
- setTimeout(abort, ABORT_DELAY);
- });
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/root.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/root.tsx
deleted file mode 100644
index 517a37a9d76b..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/root.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { cssBundleHref } from '@remix-run/css-bundle';
-import { LinksFunction, MetaFunction, json } from '@remix-run/node';
-import {
- Links,
- LiveReload,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
- useLoaderData,
- useRouteError,
-} from '@remix-run/react';
-import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';
-import type { SentryMetaArgs } from '@sentry/remix';
-
-export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])];
-
-export const loader = () => {
- return json({
- ENV: {
- SENTRY_DSN: process.env.E2E_TEST_DSN,
- },
- });
-};
-
-export const meta = ({ data }: SentryMetaArgs>) => {
- return [
- {
- env: data.ENV,
- },
- {
- name: 'sentry-trace',
- content: data.sentryTrace,
- },
- {
- name: 'baggage',
- content: data.sentryBaggage,
- },
- ];
-};
-
-export function ErrorBoundary() {
- const error = useRouteError();
- const eventId = captureRemixErrorBoundaryError(error);
-
- return (
-
- ErrorBoundary Error
- {eventId}
-
- );
-}
-
-function App() {
- const { ENV } = useLoaderData();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default withSentry(App);
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/_index.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/_index.tsx
deleted file mode 100644
index 40de0390d6ac..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/_index.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Link, useSearchParams } from '@remix-run/react';
-import * as Sentry from '@sentry/remix';
-
-export default function Index() {
- const [searchParams] = useSearchParams();
-
- if (searchParams.get('tag')) {
- Sentry.setTag('sentry_test', searchParams.get('tag'));
- }
-
- return (
-
- {
- throw new Error('I am an error!');
- }}
- />
-
- navigate
-
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/client-error.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/client-error.tsx
deleted file mode 100644
index 4e5330621191..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/client-error.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useState } from 'react';
-
-export default function ErrorBoundaryCapture() {
- const [count, setCount] = useState(0);
-
- if (count > 0) {
- throw new Error('Sentry React Component Error');
- } else {
- setTimeout(() => setCount(count + 1), 0);
- }
-
- return {count}
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/navigate.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/navigate.tsx
deleted file mode 100644
index a84df11e7bb7..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/navigate.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { LoaderFunction } from '@remix-run/node';
-import { useLoaderData } from '@remix-run/react';
-
-export const loader: LoaderFunction = async ({ params: { id } }) => {
- if (id === '-1') {
- throw new Error('Unexpected Server Error');
- }
-
- return null;
-};
-
-export default function LoaderError() {
- const data = useLoaderData();
-
- return (
-
-
{data?.test ? data.test : 'Not Found'}
-
- );
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/user.$id.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/user.$id.tsx
deleted file mode 100644
index 13b2e0a34d1e..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/app/routes/user.$id.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function User() {
- return I am a blank page
;
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/globals.d.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/globals.d.ts
deleted file mode 100644
index 4130ac6a8a09..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/globals.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-interface Window {
- recordedTransactions?: string[];
- capturedExceptionId?: string;
- ENV: {
- SENTRY_DSN: string;
- };
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/package.json
deleted file mode 100644
index 77058ff26783..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/package.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "private": true,
- "sideEffects": false,
- "scripts": {
- "build": "remix build",
- "dev": "remix dev",
- "start": "remix-serve build/index.js",
- "typecheck": "tsc",
- "clean": "npx rimraf node_modules pnpm-lock.yaml",
- "test:build": "pnpm install && pnpm build",
- "test:assert": "pnpm playwright test"
- },
- "dependencies": {
- "@sentry/remix": "latest || *",
- "@remix-run/css-bundle": "2.7.2",
- "@remix-run/node": "2.7.2",
- "@remix-run/react": "2.7.2",
- "@remix-run/serve": "2.7.2",
- "isbot": "^3.6.8",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@playwright/test": "^1.44.1",
- "@sentry-internal/test-utils": "link:../../../test-utils",
- "@remix-run/dev": "2.7.2",
- "@remix-run/eslint-config": "2.7.2",
- "@sentry/core": "latest || *",
- "@types/react": "^18.0.35",
- "@types/react-dom": "^18.0.11",
- "eslint": "^8.38.0",
- "typescript": "^5.0.4"
- },
- "volta": {
- "extends": "../../package.json"
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/playwright.config.mjs
deleted file mode 100644
index 31f2b913b58b..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/playwright.config.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-import { getPlaywrightConfig } from '@sentry-internal/test-utils';
-
-const config = getPlaywrightConfig({
- startCommand: `pnpm start`,
-});
-
-export default config;
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.config.js b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.config.js
deleted file mode 100644
index cb3c8c7a9fb7..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- ignoredRouteFiles: ['**/.*'],
- // appDirectory: 'app',
- // assetsBuildDirectory: 'public/build',
- // serverBuildPath: 'build/index.js',
- // publicPath: '/build/',
- serverModuleFormat: 'cjs',
-};
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.env.d.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.env.d.ts
deleted file mode 100644
index dcf8c45e1d4c..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/remix.env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-///
-///
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/start-event-proxy.mjs
deleted file mode 100644
index 1719aa397840..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/start-event-proxy.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-import { startEventProxyServer } from '@sentry-internal/test-utils';
-
-startEventProxyServer({
- port: 3031,
- proxyServerName: 'create-remix-app-v2-legacy',
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-errors.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-errors.test.ts
deleted file mode 100644
index 1e7aa050756e..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-errors.test.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForError } from '@sentry-internal/test-utils';
-
-test('Sends a client-side exception to Sentry', async ({ page }) => {
- const errorPromise = waitForError('create-remix-app-v2-legacy', errorEvent => {
- return errorEvent.exception?.values?.[0].value === 'I am an error!';
- });
-
- await page.goto('/');
-
- const exceptionButton = page.locator('id=exception-button');
- await exceptionButton.click();
-
- const errorEvent = await errorPromise;
-
- expect(errorEvent).toBeDefined();
-});
-
-test('Sends a client-side ErrorBoundary exception to Sentry', async ({ page }) => {
- const errorPromise = waitForError('create-remix-app-v2-legacy', errorEvent => {
- return errorEvent.exception?.values?.[0].value === 'Sentry React Component Error';
- });
-
- await page.goto('/client-error');
-
- const errorEvent = await errorPromise;
-
- expect(errorEvent).toBeDefined();
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-transactions.test.ts
deleted file mode 100644
index 6c6a23405b4c..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/client-transactions.test.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { waitForTransaction } from '@sentry-internal/test-utils';
-
-test('Sends a pageload transaction to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-v2-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === 'routes/_index';
- });
-
- await page.goto('/');
-
- const transactionEvent = await transactionPromise;
-
- expect(transactionEvent).toBeDefined();
-});
-
-test('Sends a navigation transaction to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-v2-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === 'routes/user.$id';
- });
-
- await page.goto('/');
-
- const linkElement = page.locator('id=navigation');
- await linkElement.click();
-
- const transactionEvent = await transactionPromise;
-
- expect(transactionEvent).toBeDefined();
-});
-
-test('Renders `sentry-trace` and `baggage` meta tags for the root route', async ({ page }) => {
- await page.goto('/');
-
- const sentryTraceMetaTag = await page.waitForSelector('meta[name="sentry-trace"]', {
- state: 'attached',
- });
- const baggageMetaTag = await page.waitForSelector('meta[name="baggage"]', {
- state: 'attached',
- });
-
- expect(sentryTraceMetaTag).toBeTruthy();
- expect(baggageMetaTag).toBeTruthy();
-});
-
-test('Renders `sentry-trace` and `baggage` meta tags for a sub-route', async ({ page }) => {
- await page.goto('/user/123');
-
- const sentryTraceMetaTag = await page.waitForSelector('meta[name="sentry-trace"]', {
- state: 'attached',
- });
- const baggageMetaTag = await page.waitForSelector('meta[name="baggage"]', {
- state: 'attached',
- });
-
- expect(sentryTraceMetaTag).toBeTruthy();
- expect(baggageMetaTag).toBeTruthy();
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/server-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/server-transactions.test.ts
deleted file mode 100644
index e19a7056e158..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tests/server-transactions.test.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { expect, test } from '@playwright/test';
-import { uuid4 } from '@sentry/core';
-
-import { waitForTransaction } from '@sentry-internal/test-utils';
-
-test.describe.configure({ mode: 'serial' });
-
-test('Sends parameterized transaction name to Sentry', async ({ page }) => {
- const transactionPromise = waitForTransaction('create-remix-app-v2-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'http.server';
- });
-
- await page.goto('/user/123');
-
- const transaction = await transactionPromise;
-
- expect(transaction).toBeDefined();
- expect(transaction.transaction).toBe('routes/user.$id');
-});
-
-test('Sends two linked transactions (server & client) to Sentry', async ({ page }) => {
- // We use this to identify the transactions
- const testTag = uuid4();
-
- const httpServerTransactionPromise = waitForTransaction('create-remix-app-v2-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- const pageLoadTransactionPromise = waitForTransaction('create-remix-app-v2-legacy', transactionEvent => {
- return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.tags?.['sentry_test'] === testTag;
- });
-
- page.goto(`/?tag=${testTag}`);
-
- const pageloadTransaction = await pageLoadTransactionPromise;
- const httpServerTransaction = await httpServerTransactionPromise;
-
- expect(pageloadTransaction).toBeDefined();
- expect(httpServerTransaction).toBeDefined();
-
- const httpServerTraceId = httpServerTransaction.contexts?.trace?.trace_id;
- const httpServerSpanId = httpServerTransaction.contexts?.trace?.span_id;
-
- const pageLoadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
- const pageLoadSpanId = pageloadTransaction.contexts?.trace?.span_id;
- const pageLoadParentSpanId = pageloadTransaction.contexts?.trace?.parent_span_id;
-
- expect(httpServerTransaction.transaction).toBe('routes/_index');
- expect(pageloadTransaction.transaction).toBe('routes/_index');
-
- expect(httpServerTraceId).toBeDefined();
- expect(httpServerSpanId).toBeDefined();
-
- expect(pageLoadTraceId).toEqual(httpServerTraceId);
- expect(pageLoadParentSpanId).toEqual(httpServerSpanId);
- expect(pageLoadSpanId).not.toEqual(httpServerSpanId);
-});
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tsconfig.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tsconfig.json
deleted file mode 100644
index 20f8a386a6c4..000000000000
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-legacy/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
- "compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2019"],
- "isolatedModules": true,
- "esModuleInterop": true,
- "jsx": "react-jsx",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "target": "ES2019",
- "strict": true,
- "allowJs": true,
- "forceConsistentCasingInFileNames": true,
- "baseUrl": ".",
- "paths": {
- "~/*": ["./app/*"]
- },
-
- // Remix takes care of building everything in `remix build`.
- "noEmit": true
- }
-}
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs
index 5b80ca7b8695..6d211cac4592 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/instrument.server.cjs
@@ -5,5 +5,4 @@ Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: process.env.E2E_TEST_DSN,
tunnel: 'http://localhost:3031/', // proxy server
- autoInstrumentRemix: true, // auto instrument Remix
});
diff --git a/packages/remix/package.json b/packages/remix/package.json
index 838a46a34e5e..afca2a11055a 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -91,14 +91,8 @@
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
"test": "yarn test:unit",
- "test:integration": "run-s test:integration:otel test:integration:legacy",
- "test:integration:otel": "export USE_OTEL=1 && yarn test:integration:v2",
- "test:integration:legacy": "export USE_OTEL=0 && yarn test:integration:v2",
- "test:integration:v2": "run-s test:integration:clean test:integration:prepare test:integration:client test:integration:server",
- "test:integration:ci": "run-s test:integration:ci:otel test:integration:ci:legacy",
- "test:integration:ci:otel": "USE_OTEL=1 yarn test:integration:ci:common",
- "test:integration:ci:legacy": "USE_OTEL=0 yarn test:integration:ci:common",
- "test:integration:ci:common": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server",
+ "test:integration": "run-s test:integration:clean test:integration:prepare test:integration:client test:integration:server",
+ "test:integration:ci": "run-s test:integration:clean test:integration:prepare test:integration:client:ci test:integration:server",
"test:integration:prepare": "(cd test/integration && yarn install)",
"test:integration:clean": "(cd test/integration && rimraf .cache node_modules build)",
"test:integration:client": "yarn playwright install-deps && yarn playwright test test/integration/test/client/ --project='chromium'",
diff --git a/packages/remix/playwright.config.ts b/packages/remix/playwright.config.ts
index 6e9fde329fd3..cacbe1f561ec 100644
--- a/packages/remix/playwright.config.ts
+++ b/packages/remix/playwright.config.ts
@@ -13,7 +13,7 @@ const config: PlaywrightTestConfig = {
// Note that 3 is a random number selected to work well with our CI setup
workers: process.env.CI ? 3 : undefined,
webServer: {
- command: `(cd test/integration/ && yarn build && yarn ${process.env.USE_OTEL === '1' ? 'start:otel' : 'start'})`,
+ command: `(cd test/integration/ && yarn build && yarn start)`,
port: 3000,
},
projects: [
diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts
index bd2a270edf5c..640282b57bed 100644
--- a/packages/remix/src/index.server.ts
+++ b/packages/remix/src/index.server.ts
@@ -151,8 +151,7 @@ export function getRemixDefaultIntegrations(options: RemixOptions): Integration[
return [
...getDefaultNodeIntegrations(options as NodeOptions).filter(integration => integration.name !== 'Http'),
httpIntegration(),
- // eslint-disable-next-line deprecation/deprecation
- options.autoInstrumentRemix ? remixIntegration() : undefined,
+ remixIntegration(),
].filter(int => int) as Integration[];
}
@@ -170,7 +169,7 @@ export function init(options: RemixOptions): NodeClient | undefined {
const client = nodeInit(options as NodeOptions);
- instrumentServer(options);
+ instrumentServer();
return client;
}
diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts
index 90ce8217f4db..a0dd11874416 100644
--- a/packages/remix/src/utils/instrumentServer.ts
+++ b/packages/remix/src/utils/instrumentServer.ts
@@ -1,29 +1,18 @@
-/* eslint-disable max-lines */
-import type { RequestEventData, Span, TransactionSource, WrappedFunction } from '@sentry/core';
+import type { RequestEventData, WrappedFunction } from '@sentry/core';
import {
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
continueTrace,
fill,
- getActiveSpan,
getClient,
- getRootSpan,
getTraceData,
hasTracingEnabled,
isNodeEnv,
loadModule,
logger,
- setHttpStatus,
- spanToJSON,
- startSpan,
winterCGRequestToRequestData,
withIsolationScope,
} from '@sentry/core';
import { DEBUG_BUILD } from './debug-build';
import { captureRemixServerException, errorHandleDataFunction, errorHandleDocumentRequestFunction } from './errors';
-import type { RemixOptions } from './remixOptions';
-import { createRoutes, getTransactionName } from './utils';
import { extractData, isDeferredData, isResponse, isRouteErrorResponse, json } from './vendor/response';
import type {
AppData,
@@ -36,7 +25,6 @@ import type {
RemixRequest,
RequestHandler,
ServerBuild,
- ServerRoute,
ServerRouteManifest,
} from './vendor/types';
@@ -89,7 +77,23 @@ export function wrapHandleErrorWithSentry(
};
}
-function makeWrappedDocumentRequestFunction(autoInstrumentRemix?: boolean) {
+function getTraceAndBaggage(): {
+ sentryTrace?: string;
+ sentryBaggage?: string;
+} {
+ if (isNodeEnv()) {
+ const traceData = getTraceData();
+
+ return {
+ sentryTrace: traceData['sentry-trace'],
+ sentryBaggage: traceData.baggage,
+ };
+ }
+
+ return {};
+}
+
+function makeWrappedDocumentRequestFunction() {
return function (origDocumentRequestFunction: HandleDocumentRequestFunction): HandleDocumentRequestFunction {
return async function (
this: unknown,
@@ -99,99 +103,35 @@ function makeWrappedDocumentRequestFunction(autoInstrumentRemix?: boolean) {
context: EntryContext,
loadContext?: Record,
): Promise {
- const documentRequestContext = {
+ return errorHandleDocumentRequestFunction.call(this, origDocumentRequestFunction, {
request,
responseStatusCode,
responseHeaders,
context,
loadContext,
- };
-
- if (!autoInstrumentRemix) {
- const activeSpan = getActiveSpan();
- const rootSpan = activeSpan && getRootSpan(activeSpan);
-
- const name = rootSpan ? spanToJSON(rootSpan).description : undefined;
-
- return startSpan(
- {
- // If we don't have a root span, `onlyIfParent` will lead to the span not being created anyhow
- // So we don't need to care too much about the fallback name, it's just for typing purposes....
- name: name || '',
- onlyIfParent: true,
- attributes: {
- method: request.method,
- url: request.url,
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.remix',
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.remix.document_request',
- },
- },
- () => {
- return errorHandleDocumentRequestFunction.call(this, origDocumentRequestFunction, documentRequestContext);
- },
- );
- } else {
- return errorHandleDocumentRequestFunction.call(this, origDocumentRequestFunction, documentRequestContext);
- }
+ });
};
};
}
-function makeWrappedDataFunction(
- origFn: DataFunction,
- id: string,
- name: 'action' | 'loader',
- autoInstrumentRemix?: boolean,
-): DataFunction {
+function makeWrappedDataFunction(origFn: DataFunction, id: string, name: 'action' | 'loader'): DataFunction {
return async function (this: unknown, args: DataFunctionArgs): Promise {
- if (!autoInstrumentRemix) {
- return startSpan(
- {
- op: `function.remix.${name}`,
- name: id,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.remix',
- name,
- },
- },
- (span: Span) => {
- return errorHandleDataFunction.call(this, origFn, name, args, span);
- },
- );
- } else {
- return errorHandleDataFunction.call(this, origFn, name, args);
- }
+ return errorHandleDataFunction.call(this, origFn, name, args);
};
}
const makeWrappedAction =
- (id: string, autoInstrumentRemix?: boolean) =>
+ (id: string) =>
(origAction: DataFunction): DataFunction => {
- return makeWrappedDataFunction(origAction, id, 'action', autoInstrumentRemix);
+ return makeWrappedDataFunction(origAction, id, 'action');
};
const makeWrappedLoader =
- (id: string, autoInstrumentRemix?: boolean) =>
+ (id: string) =>
(origLoader: DataFunction): DataFunction => {
- return makeWrappedDataFunction(origLoader, id, 'loader', autoInstrumentRemix);
+ return makeWrappedDataFunction(origLoader, id, 'loader');
};
-function getTraceAndBaggage(): {
- sentryTrace?: string;
- sentryBaggage?: string;
-} {
- if (isNodeEnv()) {
- const traceData = getTraceData();
-
- return {
- sentryTrace: traceData['sentry-trace'],
- sentryBaggage: traceData.baggage,
- };
- }
-
- return {};
-}
-
function makeWrappedRootLoader() {
return function (origLoader: DataFunction): DataFunction {
return async function (this: unknown, args: DataFunctionArgs): Promise {
@@ -236,16 +176,7 @@ function makeWrappedRootLoader() {
};
}
-function wrapRequestHandler(
- origRequestHandler: RequestHandler,
- build: ServerBuild | (() => ServerBuild | Promise),
- autoInstrumentRemix: boolean,
-): RequestHandler {
- let resolvedBuild: ServerBuild;
- let routes: ServerRoute[];
- let name: string;
- let source: TransactionSource;
-
+function wrapRequestHandler(origRequestHandler: RequestHandler): RequestHandler {
return async function (this: unknown, request: RemixRequest, loadContext?: AppLoadContext): Promise {
const upperCaseMethod = request.method.toUpperCase();
// We don't want to wrap OPTIONS and HEAD requests
@@ -253,16 +184,6 @@ function wrapRequestHandler(
return origRequestHandler.call(this, request, loadContext);
}
- if (!autoInstrumentRemix) {
- if (typeof build === 'function') {
- resolvedBuild = await build();
- } else {
- resolvedBuild = build;
- }
-
- routes = createRoutes(resolvedBuild.routes);
- }
-
return withIsolationScope(async isolationScope => {
const options = getClient()?.getOptions();
@@ -274,13 +195,6 @@ function wrapRequestHandler(
DEBUG_BUILD && logger.warn('Failed to normalize Remix request');
}
- if (!autoInstrumentRemix) {
- const url = new URL(request.url);
- [name, source] = getTransactionName(routes, url);
-
- isolationScope.setTransactionName(name);
- }
-
isolationScope.setSDKProcessingMetadata({ normalizedRequest });
if (!options || !hasTracingEnabled(options)) {
@@ -293,29 +207,6 @@ function wrapRequestHandler(
baggage: request.headers.get('baggage') || '',
},
async () => {
- if (!autoInstrumentRemix) {
- return startSpan(
- {
- name,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.remix',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
- method: request.method,
- },
- },
- async span => {
- const res = (await origRequestHandler.call(this, request, loadContext)) as Response;
-
- if (isResponse(res)) {
- setHttpStatus(span, res.status);
- }
-
- return res;
- },
- );
- }
-
return (await origRequestHandler.call(this, request, loadContext)) as Response;
},
);
@@ -323,7 +214,7 @@ function wrapRequestHandler(
};
}
-function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolean): ServerBuild {
+function instrumentBuildCallback(build: ServerBuild): ServerBuild {
const routes: ServerRouteManifest = {};
const wrappedEntry = { ...build.entry, module: { ...build.entry.module } };
@@ -333,7 +224,7 @@ function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolea
// We should be able to wrap them, as they may not be wrapped before.
const defaultExport = wrappedEntry.module.default as undefined | WrappedFunction;
if (defaultExport && !defaultExport.__sentry_original__) {
- fill(wrappedEntry.module, 'default', makeWrappedDocumentRequestFunction(autoInstrumentRemix));
+ fill(wrappedEntry.module, 'default', makeWrappedDocumentRequestFunction());
}
for (const [id, route] of Object.entries(build.routes)) {
@@ -341,12 +232,12 @@ function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolea
const routeAction = wrappedRoute.module.action as undefined | WrappedFunction;
if (routeAction && !routeAction.__sentry_original__) {
- fill(wrappedRoute.module, 'action', makeWrappedAction(id, autoInstrumentRemix));
+ fill(wrappedRoute.module, 'action', makeWrappedAction(id));
}
const routeLoader = wrappedRoute.module.loader as undefined | WrappedFunction;
if (routeLoader && !routeLoader.__sentry_original__) {
- fill(wrappedRoute.module, 'loader', makeWrappedLoader(id, autoInstrumentRemix));
+ fill(wrappedRoute.module, 'loader', makeWrappedLoader(id));
}
// Entry module should have a loader function to provide `sentry-trace` and `baggage`
@@ -371,40 +262,35 @@ function instrumentBuildCallback(build: ServerBuild, autoInstrumentRemix: boolea
*/
export function instrumentBuild(
build: ServerBuild | (() => ServerBuild | Promise),
- options: RemixOptions,
): ServerBuild | (() => ServerBuild | Promise) {
- // eslint-disable-next-line deprecation/deprecation
- const autoInstrumentRemix = options?.autoInstrumentRemix || false;
-
if (typeof build === 'function') {
return function () {
const resolvedBuild = build();
if (resolvedBuild instanceof Promise) {
return resolvedBuild.then(build => {
- return instrumentBuildCallback(build, autoInstrumentRemix);
+ return instrumentBuildCallback(build);
});
} else {
- return instrumentBuildCallback(resolvedBuild, autoInstrumentRemix);
+ return instrumentBuildCallback(resolvedBuild);
}
};
} else {
- return instrumentBuildCallback(build, autoInstrumentRemix);
+ return instrumentBuildCallback(build);
}
}
-const makeWrappedCreateRequestHandler = (options: RemixOptions) =>
+const makeWrappedCreateRequestHandler = () =>
function (origCreateRequestHandler: CreateRequestHandlerFunction): CreateRequestHandlerFunction {
return function (
this: unknown,
build: ServerBuild | (() => Promise),
...args: unknown[]
): RequestHandler {
- const newBuild = instrumentBuild(build, options);
+ const newBuild = instrumentBuild(build);
const requestHandler = origCreateRequestHandler.call(this, newBuild, ...args);
- // eslint-disable-next-line deprecation/deprecation
- return wrapRequestHandler(requestHandler, newBuild, options.autoInstrumentRemix || false);
+ return wrapRequestHandler(requestHandler);
};
};
@@ -412,7 +298,7 @@ const makeWrappedCreateRequestHandler = (options: RemixOptions) =>
* Monkey-patch Remix's `createRequestHandler` from `@remix-run/server-runtime`
* which Remix Adapters (https://remix.run/docs/en/v1/api/remix) use underneath.
*/
-export function instrumentServer(options: RemixOptions): void {
+export function instrumentServer(): void {
const pkg = loadModule<{
createRequestHandler: CreateRequestHandlerFunction;
}>('@remix-run/server-runtime');
@@ -423,5 +309,5 @@ export function instrumentServer(options: RemixOptions): void {
return;
}
- fill(pkg, 'createRequestHandler', makeWrappedCreateRequestHandler(options));
+ fill(pkg, 'createRequestHandler', makeWrappedCreateRequestHandler());
}
diff --git a/packages/remix/src/utils/remixOptions.ts b/packages/remix/src/utils/remixOptions.ts
index 58e8ae74b1d2..e65305c7696f 100644
--- a/packages/remix/src/utils/remixOptions.ts
+++ b/packages/remix/src/utils/remixOptions.ts
@@ -4,22 +4,4 @@ import type { BrowserOptions } from '@sentry/react';
export type RemixOptions = (Options | BrowserOptions | NodeOptions) & {
captureActionFormDataKeys?: Record;
-} & (
- | {
- /**
- * Enables OpenTelemetry Remix instrumentation.
- *
- * Note: This option will be the default behavior and will be removed in the next major version.
- */
- autoInstrumentRemix?: true;
- }
- | {
- /**
- * Enables OpenTelemetry Remix instrumentation
- *
- * @deprecated Setting this option to `false` is deprecated as the next major version will default to behaving as if this option were `true` and the option itself will be removed.
- * It is recommended to set this option to `true`.
- */
- autoInstrumentRemix?: false;
- }
- );
+};
diff --git a/packages/remix/test/integration/app/entry.server.tsx b/packages/remix/test/integration/app/entry.server.tsx
index f7ca9d82345a..86b0312eb92f 100644
--- a/packages/remix/test/integration/app/entry.server.tsx
+++ b/packages/remix/test/integration/app/entry.server.tsx
@@ -1,14 +1,5 @@
import * as Sentry from '@sentry/remix';
-if (process.env.USE_OTEL !== '1') {
- Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- tracesSampleRate: 1,
- tracePropagationTargets: ['example.org'],
- autoInstrumentRemix: false,
- });
-}
-
import type { EntryContext } from '@remix-run/node';
import { RemixServer } from '@remix-run/react';
import { renderToString } from 'react-dom/server';
diff --git a/packages/remix/test/integration/instrument.server.mjs b/packages/remix/test/integration/instrument.server.mjs
index 3efde8ef19c4..5598d81af581 100644
--- a/packages/remix/test/integration/instrument.server.mjs
+++ b/packages/remix/test/integration/instrument.server.mjs
@@ -4,5 +4,4 @@ Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1,
tracePropagationTargets: ['example.org'],
- autoInstrumentRemix: true,
});
diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json
index 518cee89ec18..28bbfe12396e 100644
--- a/packages/remix/test/integration/package.json
+++ b/packages/remix/test/integration/package.json
@@ -5,8 +5,7 @@
"scripts": {
"build": "remix build",
"dev": "remix dev",
- "start": "remix-serve build/index.js",
- "start:otel": "NODE_OPTIONS='--import=./instrument.server.mjs' remix-serve build/index.js"
+ "start": "NODE_OPTIONS='--import=./instrument.server.mjs' remix-serve build/index.js"
},
"dependencies": {
"@remix-run/express": "2.15.2",
diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts
deleted file mode 100644
index c502b801ed81..000000000000
--- a/packages/remix/test/integration/test/server/instrumentation-legacy/action.test.ts
+++ /dev/null
@@ -1,497 +0,0 @@
-import { describe, it } from 'vitest';
-import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers';
-
-describe('Remix API Actions', () => {
- it('correctly instruments a parameterized Remix API action', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/123123`;
- const envelope = await env.getEnvelopeRequest({
- url,
- method: 'post',
- envelopeType: 'transaction',
- });
- const transaction = envelope[2]!;
-
- assertSentryTransaction(transaction, {
- transaction: `routes/action-json-response.$id`,
- spans: [
- {
- description: `routes/action-json-response.$id`,
- op: 'function.remix.action',
- },
- {
- description: 'root',
- op: 'function.remix.loader',
- },
- {
- description: `routes/action-json-response.$id`,
- op: 'function.remix.loader',
- },
- {
- description: `routes/action-json-response.$id`,
- op: 'function.remix.document_request',
- },
- ],
- request: {
- method: 'POST',
- url,
- cookies: expect.any(Object),
- headers: {
- 'user-agent': expect.any(String),
- host: expect.stringContaining('localhost:'),
- },
- },
- });
- });
-
- it('reports an error thrown from the action', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-1`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- status: 'internal_error',
- data: {
- 'http.response.status_code': 500,
- },
- },
- },
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Unexpected Server Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('includes request data in transaction and error events', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-1`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- transaction: `routes/action-json-response.$id`,
- request: {
- method: 'POST',
- url,
- cookies: expect.any(Object),
- headers: {
- 'user-agent': expect.any(String),
- host: expect.stringContaining('localhost:'),
- },
- },
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Unexpected Server Error',
- },
- ],
- },
- request: {
- method: 'POST',
- url,
- cookies: expect.any(Object),
- headers: {
- 'user-agent': expect.any(String),
- host: expect.stringContaining('localhost:'),
- },
- },
- });
- });
-
- it('handles an error-throwing redirection target', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-2`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 3,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
- assertSentryTransaction(transaction_1![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'ok',
- data: {
- method: 'POST',
- 'http.response.status_code': 302,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryTransaction(transaction_2![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'GET',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Unexpected Server Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles a thrown `json()` error response with `statusText`', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-3`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Sentry Test Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'action',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles a thrown `json()` error response without `statusText`', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-4`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Object captured as exception with keys: data',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'action',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles a thrown `json()` error response with string body', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-5`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Sentry Test Error [string body]',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'action',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles a thrown `json()` error response with an empty object', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/action-json-response/-6`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/action-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/action-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Object captured as exception with keys: [object has no keys]',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'action',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles thrown string (primitive) from an action', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/server-side-unexpected-errors/-1`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['event', 'transaction'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/server-side-unexpected-errors.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Thrown String Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('handles thrown object from an action', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/server-side-unexpected-errors/-2`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 2,
- method: 'post',
- envelopeType: ['event', 'transaction'],
- });
-
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'POST',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/server-side-unexpected-errors.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/server-side-unexpected-errors(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Thrown Object Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-});
diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts
deleted file mode 100644
index 79fb7bb270d3..000000000000
--- a/packages/remix/test/integration/test/server/instrumentation-legacy/loader.test.ts
+++ /dev/null
@@ -1,270 +0,0 @@
-import { Event } from '@sentry/core';
-import { describe, expect, it } from 'vitest';
-import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers';
-
-describe('Remix API Loaders', () => {
- it('reports an error thrown from the loader', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-json-response/-2`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] });
-
- const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2];
- const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2];
-
- assertSentryTransaction(transaction!, {
- contexts: {
- trace: {
- status: 'internal_error',
- data: {
- 'http.response.status_code': 500,
- },
- },
- },
- });
-
- assertSentryEvent(event!, {
- transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Unexpected Server Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('reports a thrown error response the loader', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-throw-response/-1`;
-
- // We also wait for the transaction, even though we don't care about it for this test
- // but otherwise this may leak into another test
- const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['event', 'transaction'] });
-
- const event = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[1]?.[2] : envelopes[0]?.[2];
- const transaction = envelopes[0]?.[2]?.type === 'transaction' ? envelopes[0]?.[2] : envelopes[1]?.[2];
-
- assertSentryTransaction(transaction!, {
- contexts: {
- trace: {
- status: 'internal_error',
- data: {
- 'http.response.status_code': 500,
- },
- },
- },
- });
-
- assertSentryEvent(event!, {
- transaction: expect.stringMatching(/routes\/loader-throw-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Not found',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'loader',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('correctly instruments a parameterized Remix API loader', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-json-response/123123`;
- const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' });
- const transaction = envelope[2]!;
-
- assertSentryTransaction(transaction, {
- transaction: 'routes/loader-json-response.$id',
- transaction_info: {
- source: 'route',
- },
- spans: [
- {
- description: 'root',
- op: 'function.remix.loader',
- },
- {
- description: 'routes/loader-json-response.$id',
- op: 'function.remix.loader',
- },
- {
- description: 'routes/loader-json-response.$id',
- op: 'function.remix.document_request',
- },
- ],
- });
- });
-
- it('handles an error-throwing redirection target', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-json-response/-1`;
-
- const envelopes = await env.getMultipleEnvelopeRequest({
- url,
- count: 3,
- envelopeType: ['transaction', 'event'],
- });
-
- const [transaction_1, transaction_2] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
-
- assertSentryTransaction(transaction_1![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'ok',
- data: {
- method: 'GET',
- 'http.response.status_code': 302,
- },
- },
- },
- transaction: 'routes/loader-json-response.$id',
- });
-
- assertSentryTransaction(transaction_2![2]!, {
- contexts: {
- trace: {
- op: 'http.server',
- status: 'internal_error',
- data: {
- method: 'GET',
- 'http.response.status_code': 500,
- },
- },
- },
- transaction: 'routes/loader-json-response.$id',
- });
-
- assertSentryEvent(event![2]!, {
- transaction: expect.stringMatching(/routes\/loader-json-response(\/|\.)\$id/),
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Unexpected Server Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-
- it('makes sure scope does not bleed between requests', async () => {
- const env = await RemixTestEnv.init();
-
- const envelopes = await Promise.all([
- env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/1`, endServer: false, envelopeType: 'transaction' }),
- env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/2`, endServer: false, envelopeType: 'transaction' }),
- env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/3`, endServer: false, envelopeType: 'transaction' }),
- env.getEnvelopeRequest({ url: `${env.url}/scope-bleed/4`, endServer: false, envelopeType: 'transaction' }),
- ]);
-
- await new Promise(resolve => env.server.close(resolve));
-
- envelopes.forEach(envelope => {
- const tags = envelope[2]?.tags as NonNullable;
- const customTagArr = Object.keys(tags).filter(t => t.startsWith('tag'));
- expect(customTagArr).toHaveLength(1);
-
- const key = customTagArr[0]!;
- const val = key[key.length - 1];
- expect(tags[key]).toEqual(val);
- });
- });
-
- it('continues transaction from sentry-trace header and baggage', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-json-response/3`;
-
- // send sentry-trace and baggage headers to loader
- env.setAxiosConfig({
- headers: {
- 'sentry-trace': '12312012123120121231201212312012-1121201211212012-1',
- baggage: 'sentry-version=1.0,sentry-environment=production,sentry-trace_id=12312012123120121231201212312012',
- },
- });
- const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' });
-
- expect(envelope[0]?.trace).toMatchObject({
- trace_id: '12312012123120121231201212312012',
- });
-
- assertSentryTransaction(envelope![2]!, {
- contexts: {
- trace: {
- trace_id: '12312012123120121231201212312012',
- parent_span_id: '1121201211212012',
- },
- },
- });
- });
-
- it('correctly instruments a deferred loader', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/loader-defer-response/123123`;
- const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' });
- const transaction = envelope[2]!;
-
- assertSentryTransaction(transaction, {
- transaction: 'routes/loader-defer-response.$id',
- transaction_info: {
- source: 'route',
- },
- spans: [
- {
- description: 'root',
- op: 'function.remix.loader',
- },
- {
- description: 'routes/loader-defer-response.$id',
- op: 'function.remix.loader',
- },
- {
- description: 'routes/loader-defer-response.$id',
- op: 'function.remix.document_request',
- },
- ],
- });
- });
-
- it('does not capture thrown redirect responses', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/throw-redirect`;
-
- const envelopesCount = await env.countEnvelopes({
- url,
- envelopeType: 'event',
- timeout: 3000,
- });
-
- expect(envelopesCount).toBe(0);
- });
-});
diff --git a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts b/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts
deleted file mode 100644
index c4ea2503e67f..000000000000
--- a/packages/remix/test/integration/test/server/instrumentation-legacy/ssr.test.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { RemixTestEnv, assertSentryEvent, assertSentryTransaction } from '../utils/helpers';
-
-describe('Server Side Rendering', () => {
- it('correctly reports a server side rendering error', async () => {
- const env = await RemixTestEnv.init();
- const url = `${env.url}/ssr-error`;
- const envelopes = await env.getMultipleEnvelopeRequest({ url, count: 2, envelopeType: ['transaction', 'event'] });
- const [transaction] = envelopes.filter(envelope => envelope[1]?.type === 'transaction');
- const [event] = envelopes.filter(envelope => envelope[1]?.type === 'event');
- assertSentryTransaction(transaction![2]!, {
- contexts: {
- trace: {
- status: 'internal_error',
- data: {
- 'http.response.status_code': 500,
- },
- },
- },
- tags: {
- // Testing that the wrapped `handleError` correctly adds tags
- 'remix-test-tag': 'remix-test-value',
- },
- });
-
- assertSentryEvent(event![2]!, {
- transaction: 'routes/ssr-error',
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Sentry SSR Test Error',
- stacktrace: expect.any(Object),
- mechanism: {
- data: {
- function: 'remix.server.handleError',
- },
- handled: false,
- type: 'instrument',
- },
- },
- ],
- },
- });
- });
-});
diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts b/packages/remix/test/integration/test/server/instrumentation/action.test.ts
similarity index 100%
rename from packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts
rename to packages/remix/test/integration/test/server/instrumentation/action.test.ts
diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation/loader.test.ts
similarity index 100%
rename from packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts
rename to packages/remix/test/integration/test/server/instrumentation/loader.test.ts
diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/ssr.test.ts b/packages/remix/test/integration/test/server/instrumentation/ssr.test.ts
similarity index 100%
rename from packages/remix/test/integration/test/server/instrumentation-otel/ssr.test.ts
rename to packages/remix/test/integration/test/server/instrumentation/ssr.test.ts
diff --git a/packages/remix/vitest.config.ts b/packages/remix/vitest.config.ts
index 774227165ee3..d2194a288aa1 100644
--- a/packages/remix/vitest.config.ts
+++ b/packages/remix/vitest.config.ts
@@ -1,13 +1,11 @@
import { defineConfig } from 'vitest/config';
-const useOtel = process.env.USE_OTEL === '1';
-
export default defineConfig({
test: {
globals: true,
disableConsoleIntercept: true,
silent: false,
- setupFiles: useOtel ? './test/integration/instrument.server.mjs' : undefined,
- include: useOtel ? ['**/instrumentation-otel/*.test.ts'] : ['**/instrumentation-legacy/*.test.ts'],
+ setupFiles: './test/integration/instrument.server.mjs',
+ include: ['**/integration/test/server/**/*.test.ts'],
},
});
From a3cf458bb14cebf73276ce79051f29c229d167b6 Mon Sep 17 00:00:00 2001
From: Tim Fish
Date: Tue, 21 Jan 2025 18:54:49 +0100
Subject: [PATCH 07/59] feat(node): Capture exceptions from `worker_threads`
(#15105)
Updates the `childProcessIntegration` to capture `worker_threads` errors
(including their stack traces) rather than capturing a breadcrumb.
This features is enabled by default and can be disabled by setting the
`captureWorkerErrors` option to `false`:
```ts
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.childProcessIntegration({ captureWorkerErrors: false })],
});
```
When `captureWorkerErrors: false`, a breadcrumb will be captured
instead.
This PR also adds more integration tests for the
`childProcessIntegration`.
---
.../suites/breadcrumbs/process-thread/app.mjs | 1 +
.../suites/child-process/child.js | 3 +
.../suites/child-process/child.mjs | 3 +
.../suites/child-process/fork.js | 18 +++++
.../suites/child-process/fork.mjs | 19 ++++++
.../suites/child-process/test.ts | 65 +++++++++++++++++++
.../suites/child-process/worker.js | 18 +++++
.../suites/child-process/worker.mjs | 19 ++++++
.../node/src/integrations/childProcess.ts | 37 +++++++----
9 files changed, 171 insertions(+), 12 deletions(-)
create mode 100644 dev-packages/node-integration-tests/suites/child-process/child.js
create mode 100644 dev-packages/node-integration-tests/suites/child-process/child.mjs
create mode 100644 dev-packages/node-integration-tests/suites/child-process/fork.js
create mode 100644 dev-packages/node-integration-tests/suites/child-process/fork.mjs
create mode 100644 dev-packages/node-integration-tests/suites/child-process/test.ts
create mode 100644 dev-packages/node-integration-tests/suites/child-process/worker.js
create mode 100644 dev-packages/node-integration-tests/suites/child-process/worker.mjs
diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs
index 903470806ad9..298952d58ced 100644
--- a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs
+++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs
@@ -9,6 +9,7 @@ const __dirname = new URL('.', import.meta.url).pathname;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
+ integrations: [Sentry.childProcessIntegration({ captureWorkerErrors: false })],
transport: loggingTransport,
});
diff --git a/dev-packages/node-integration-tests/suites/child-process/child.js b/dev-packages/node-integration-tests/suites/child-process/child.js
new file mode 100644
index 000000000000..cb1937007297
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/child.js
@@ -0,0 +1,3 @@
+setTimeout(() => {
+ throw new Error('Test error');
+}, 1000);
diff --git a/dev-packages/node-integration-tests/suites/child-process/child.mjs b/dev-packages/node-integration-tests/suites/child-process/child.mjs
new file mode 100644
index 000000000000..cb1937007297
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/child.mjs
@@ -0,0 +1,3 @@
+setTimeout(() => {
+ throw new Error('Test error');
+}, 1000);
diff --git a/dev-packages/node-integration-tests/suites/child-process/fork.js b/dev-packages/node-integration-tests/suites/child-process/fork.js
new file mode 100644
index 000000000000..c6e5cd3f0b7f
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/fork.js
@@ -0,0 +1,18 @@
+const Sentry = require('@sentry/node');
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const path = require('path');
+const { fork } = require('child_process');
+
+Sentry.init({
+ debug: true,
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ transport: loggingTransport,
+});
+
+// eslint-disable-next-line no-unused-vars
+const _child = fork(path.join(__dirname, 'child.mjs'));
+
+setTimeout(() => {
+ throw new Error('Exiting main process');
+}, 3000);
diff --git a/dev-packages/node-integration-tests/suites/child-process/fork.mjs b/dev-packages/node-integration-tests/suites/child-process/fork.mjs
new file mode 100644
index 000000000000..88503fa887a9
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/fork.mjs
@@ -0,0 +1,19 @@
+import { fork } from 'child_process';
+import * as path from 'path';
+import { loggingTransport } from '@sentry-internal/node-integration-tests';
+import * as Sentry from '@sentry/node';
+
+const __dirname = new URL('.', import.meta.url).pathname;
+
+Sentry.init({
+ debug: true,
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ transport: loggingTransport,
+});
+
+const _child = fork(path.join(__dirname, 'child.mjs'));
+
+setTimeout(() => {
+ throw new Error('Exiting main process');
+}, 3000);
diff --git a/dev-packages/node-integration-tests/suites/child-process/test.ts b/dev-packages/node-integration-tests/suites/child-process/test.ts
new file mode 100644
index 000000000000..9b9064dacf3e
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/test.ts
@@ -0,0 +1,65 @@
+import type { Event } from '@sentry/core';
+import { conditionalTest } from '../../utils';
+import { cleanupChildProcesses, createRunner } from '../../utils/runner';
+
+const WORKER_EVENT: Event = {
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Test error',
+ mechanism: {
+ type: 'instrument',
+ handled: false,
+ data: {
+ threadId: expect.any(String),
+ },
+ },
+ },
+ ],
+ },
+};
+
+const CHILD_EVENT: Event = {
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Exiting main process',
+ },
+ ],
+ },
+ breadcrumbs: [
+ {
+ category: 'child_process',
+ message: "Child process exited with code '1'",
+ level: 'warning',
+ },
+ ],
+};
+
+describe('should capture child process events', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ conditionalTest({ min: 20 })('worker', () => {
+ test('ESM', done => {
+ createRunner(__dirname, 'worker.mjs').expect({ event: WORKER_EVENT }).start(done);
+ });
+
+ test('CJS', done => {
+ createRunner(__dirname, 'worker.js').expect({ event: WORKER_EVENT }).start(done);
+ });
+ });
+
+ conditionalTest({ min: 20 })('fork', () => {
+ test('ESM', done => {
+ createRunner(__dirname, 'fork.mjs').expect({ event: CHILD_EVENT }).start(done);
+ });
+
+ test('CJS', done => {
+ createRunner(__dirname, 'fork.js').expect({ event: CHILD_EVENT }).start(done);
+ });
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/child-process/worker.js b/dev-packages/node-integration-tests/suites/child-process/worker.js
new file mode 100644
index 000000000000..99b645d9001c
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/worker.js
@@ -0,0 +1,18 @@
+const Sentry = require('@sentry/node');
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const path = require('path');
+const { Worker } = require('worker_threads');
+
+Sentry.init({
+ debug: true,
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ transport: loggingTransport,
+});
+
+// eslint-disable-next-line no-unused-vars
+const _worker = new Worker(path.join(__dirname, 'child.js'));
+
+setTimeout(() => {
+ process.exit();
+}, 3000);
diff --git a/dev-packages/node-integration-tests/suites/child-process/worker.mjs b/dev-packages/node-integration-tests/suites/child-process/worker.mjs
new file mode 100644
index 000000000000..dcca0bcc4105
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/child-process/worker.mjs
@@ -0,0 +1,19 @@
+import * as path from 'path';
+import { loggingTransport } from '@sentry-internal/node-integration-tests';
+import * as Sentry from '@sentry/node';
+import { Worker } from 'worker_threads';
+
+const __dirname = new URL('.', import.meta.url).pathname;
+
+Sentry.init({
+ debug: true,
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ transport: loggingTransport,
+});
+
+const _worker = new Worker(path.join(__dirname, 'child.mjs'));
+
+setTimeout(() => {
+ process.exit();
+}, 3000);
diff --git a/packages/node/src/integrations/childProcess.ts b/packages/node/src/integrations/childProcess.ts
index 934bac99dc42..6fc6046c7e2f 100644
--- a/packages/node/src/integrations/childProcess.ts
+++ b/packages/node/src/integrations/childProcess.ts
@@ -1,7 +1,7 @@
import type { ChildProcess } from 'node:child_process';
import * as diagnosticsChannel from 'node:diagnostics_channel';
import type { Worker } from 'node:worker_threads';
-import { addBreadcrumb, defineIntegration } from '@sentry/core';
+import { addBreadcrumb, captureException, defineIntegration } from '@sentry/core';
interface Options {
/**
@@ -10,17 +10,24 @@ interface Options {
* @default false
*/
includeChildProcessArgs?: boolean;
+
+ /**
+ * Whether to capture errors from worker threads.
+ *
+ * @default true
+ */
+ captureWorkerErrors?: boolean;
}
const INTEGRATION_NAME = 'ChildProcess';
/**
- * Capture breadcrumbs for child processes and worker threads.
+ * Capture breadcrumbs and events for child processes and worker threads.
*/
export const childProcessIntegration = defineIntegration((options: Options = {}) => {
return {
name: INTEGRATION_NAME,
- setup(_client) {
+ setup() {
diagnosticsChannel.channel('child_process').subscribe((event: unknown) => {
if (event && typeof event === 'object' && 'process' in event) {
captureChildProcessEvents(event.process as ChildProcess, options);
@@ -29,7 +36,7 @@ export const childProcessIntegration = defineIntegration((options: Options = {})
diagnosticsChannel.channel('worker_threads').subscribe((event: unknown) => {
if (event && typeof event === 'object' && 'worker' in event) {
- captureWorkerThreadEvents(event.worker as Worker);
+ captureWorkerThreadEvents(event.worker as Worker, options);
}
});
},
@@ -62,7 +69,7 @@ function captureChildProcessEvents(child: ChildProcess, options: Options): void
addBreadcrumb({
category: 'child_process',
message: `Child process exited with code '${code}'`,
- level: 'warning',
+ level: code === 0 ? 'info' : 'warning',
data,
});
}
@@ -82,7 +89,7 @@ function captureChildProcessEvents(child: ChildProcess, options: Options): void
});
}
-function captureWorkerThreadEvents(worker: Worker): void {
+function captureWorkerThreadEvents(worker: Worker, options: Options): void {
let threadId: number | undefined;
worker
@@ -90,11 +97,17 @@ function captureWorkerThreadEvents(worker: Worker): void {
threadId = worker.threadId;
})
.on('error', error => {
- addBreadcrumb({
- category: 'worker_thread',
- message: `Worker thread errored with '${error.message}'`,
- level: 'error',
- data: { threadId },
- });
+ if (options.captureWorkerErrors !== false) {
+ captureException(error, {
+ mechanism: { type: 'instrument', handled: false, data: { threadId: String(threadId) } },
+ });
+ } else {
+ addBreadcrumb({
+ category: 'worker_thread',
+ message: `Worker thread errored with '${error.message}'`,
+ level: 'error',
+ data: { threadId },
+ });
+ }
});
}
From 3c7450dbba3807ec3d662069715b38a7abafc44e Mon Sep 17 00:00:00 2001
From: Andrew Liu <159852527+aliu39@users.noreply.github.com>
Date: Tue, 21 Jan 2025 10:46:43 -0800
Subject: [PATCH 08/59] feat(flags): Add Unleash browser integration (#15036)
Ref https://github.com/getsentry/team-replay/issues/514. Adds an
integration for tracking Unleash flag evaluations, by patching the
[isEnabled](https://docs.getunleash.io/reference/sdks/javascript-browser#step-4-check-feature-toggle-states)
method prototype.
Ref https://develop.sentry.dev/sdk/expected-features/#feature-flags
PR pointing to v8:
https://github.com/getsentry/sentry-javascript/pull/14948
---
.../featureFlags/unleash/badSignature/init.js | 17 +++++
.../featureFlags/unleash/badSignature/test.ts | 59 +++++++++++++++
.../featureFlags/unleash/basic/test.ts | 58 +++++++++++++++
.../integrations/featureFlags/unleash/init.js | 50 +++++++++++++
.../featureFlags/unleash/subject.js | 3 +
.../featureFlags/unleash/template.html | 9 +++
.../featureFlags/unleash/withScope/test.ts | 65 +++++++++++++++++
packages/browser/src/index.ts | 1 +
.../featureFlags/unleash/index.ts | 1 +
.../featureFlags/unleash/integration.ts | 73 +++++++++++++++++++
.../featureFlags/unleash/types.ts | 23 ++++++
11 files changed, 359 insertions(+)
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/subject.js
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/template.html
create mode 100644 dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/withScope/test.ts
create mode 100644 packages/browser/src/integrations/featureFlags/unleash/index.ts
create mode 100644 packages/browser/src/integrations/featureFlags/unleash/integration.ts
create mode 100644 packages/browser/src/integrations/featureFlags/unleash/types.ts
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js
new file mode 100644
index 000000000000..dc92fbc296a4
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js
@@ -0,0 +1,17 @@
+import * as Sentry from '@sentry/browser';
+
+window.UnleashClient = class {
+ isEnabled(x) {
+ return x;
+ }
+};
+
+window.Sentry = Sentry;
+window.sentryUnleashIntegration = Sentry.unleashIntegration({ unleashClientClass: window.UnleashClient });
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ sampleRate: 1.0,
+ integrations: [window.sentryUnleashIntegration],
+ debug: true, // Required to test logging.
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/test.ts
new file mode 100644
index 000000000000..9b95d4d51c81
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/test.ts
@@ -0,0 +1,59 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../../../utils/fixtures';
+
+import { shouldSkipFeatureFlagsTest } from '../../../../../utils/helpers';
+
+sentryTest('Logs and returns if isEnabled does not match expected signature', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipFeatureFlagsTest()) {
+ sentryTest.skip();
+ }
+ const bundleKey = process.env.PW_BUNDLE || '';
+ const hasDebug = !bundleKey.includes('_min');
+
+ await page.route('https://dsn.ingest.sentry.io/**/*', route => {
+ return route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ id: 'test-id' }),
+ });
+ });
+
+ const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
+ await page.goto(url);
+
+ const errorLogs: string[] = [];
+ page.on('console', msg => {
+ if (msg.type() == 'error') {
+ errorLogs.push(msg.text());
+ }
+ });
+
+ const results = await page.evaluate(() => {
+ const unleash = new (window as any).UnleashClient();
+ const res1 = unleash.isEnabled('my-feature');
+ const res2 = unleash.isEnabled(999);
+ const res3 = unleash.isEnabled({});
+ return [res1, res2, res3];
+ });
+
+ // Test that the expected results are still returned. Note isEnabled is identity function for this test.
+ expect(results).toEqual(['my-feature', 999, {}]);
+
+ // Expected error logs.
+ if (hasDebug) {
+ expect(errorLogs).toEqual(
+ expect.arrayContaining([
+ expect.stringContaining(
+ '[Feature Flags] UnleashClient.isEnabled does not match expected signature. arg0: my-feature (string), result: my-feature (string)',
+ ),
+ expect.stringContaining(
+ '[Feature Flags] UnleashClient.isEnabled does not match expected signature. arg0: 999 (number), result: 999 (number)',
+ ),
+ expect.stringContaining(
+ '[Feature Flags] UnleashClient.isEnabled does not match expected signature. arg0: [object Object] (object), result: [object Object] (object)',
+ ),
+ ]),
+ );
+ }
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts
new file mode 100644
index 000000000000..5bb72caddd24
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/basic/test.ts
@@ -0,0 +1,58 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../../../utils/fixtures';
+
+import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers';
+
+const FLAG_BUFFER_SIZE = 100; // Corresponds to constant in featureFlags.ts, in browser utils.
+
+sentryTest('Basic test with eviction, update, and no async tasks', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipFeatureFlagsTest()) {
+ sentryTest.skip();
+ }
+
+ await page.route('https://dsn.ingest.sentry.io/**/*', route => {
+ return route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ id: 'test-id' }),
+ });
+ });
+
+ const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
+ await page.goto(url);
+
+ await page.evaluate(bufferSize => {
+ const client = new (window as any).UnleashClient();
+
+ client.isEnabled('feat1');
+ client.isEnabled('strFeat');
+ client.isEnabled('noPayloadFeat');
+ client.isEnabled('jsonFeat');
+ client.isEnabled('noVariantFeat');
+ client.isEnabled('disabledFeat');
+
+ for (let i = 7; i <= bufferSize; i++) {
+ client.isEnabled(`feat${i}`);
+ }
+ client.isEnabled(`feat${bufferSize + 1}`); // eviction
+ client.isEnabled('noPayloadFeat'); // update (move to tail)
+ }, FLAG_BUFFER_SIZE);
+
+ const reqPromise = waitForErrorRequest(page);
+ await page.locator('#error').click();
+ const req = await reqPromise;
+ const event = envelopeRequestParser(req);
+
+ const expectedFlags = [{ flag: 'strFeat', result: true }];
+ expectedFlags.push({ flag: 'jsonFeat', result: true });
+ expectedFlags.push({ flag: 'noVariantFeat', result: true });
+ expectedFlags.push({ flag: 'disabledFeat', result: false });
+ for (let i = 7; i <= FLAG_BUFFER_SIZE; i++) {
+ expectedFlags.push({ flag: `feat${i}`, result: false });
+ }
+ expectedFlags.push({ flag: `feat${FLAG_BUFFER_SIZE + 1}`, result: false });
+ expectedFlags.push({ flag: 'noPayloadFeat', result: true });
+
+ expect(event.contexts?.flags?.values).toEqual(expectedFlags);
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js
new file mode 100644
index 000000000000..9f1f28730cf7
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js
@@ -0,0 +1,50 @@
+import * as Sentry from '@sentry/browser';
+
+window.UnleashClient = class {
+ constructor() {
+ this._featureToVariant = {
+ strFeat: { name: 'variant1', enabled: true, feature_enabled: true, payload: { type: 'string', value: 'test' } },
+ noPayloadFeat: { name: 'eu-west', enabled: true, feature_enabled: true },
+ jsonFeat: {
+ name: 'paid-orgs',
+ enabled: true,
+ feature_enabled: true,
+ payload: {
+ type: 'json',
+ value: '{"foo": {"bar": "baz"}, "hello": [1, 2, 3]}',
+ },
+ },
+
+ // Enabled feature with no configured variants.
+ noVariantFeat: { name: 'disabled', enabled: false, feature_enabled: true },
+
+ // Disabled feature.
+ disabledFeat: { name: 'disabled', enabled: false, feature_enabled: false },
+ };
+
+ // Variant returned for features that don't exist.
+ // `feature_enabled` may be defined in prod, but we want to test the undefined case.
+ this._fallbackVariant = {
+ name: 'disabled',
+ enabled: false,
+ };
+ }
+
+ isEnabled(toggleName) {
+ const variant = this._featureToVariant[toggleName] || this._fallbackVariant;
+ return variant.feature_enabled || false;
+ }
+
+ getVariant(toggleName) {
+ return this._featureToVariant[toggleName] || this._fallbackVariant;
+ }
+};
+
+window.Sentry = Sentry;
+window.sentryUnleashIntegration = Sentry.unleashIntegration({ unleashClientClass: window.UnleashClient });
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ sampleRate: 1.0,
+ integrations: [window.sentryUnleashIntegration],
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/subject.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/subject.js
new file mode 100644
index 000000000000..e6697408128c
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/subject.js
@@ -0,0 +1,3 @@
+document.getElementById('error').addEventListener('click', () => {
+ throw new Error('Button triggered error');
+});
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/template.html b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/template.html
new file mode 100644
index 000000000000..9330c6c679f4
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/template.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/withScope/test.ts b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/withScope/test.ts
new file mode 100644
index 000000000000..2d821bf6c81d
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/withScope/test.ts
@@ -0,0 +1,65 @@
+import { expect } from '@playwright/test';
+
+import { sentryTest } from '../../../../../utils/fixtures';
+
+import { envelopeRequestParser, shouldSkipFeatureFlagsTest, waitForErrorRequest } from '../../../../../utils/helpers';
+
+import type { Scope } from '@sentry/browser';
+
+sentryTest('Flag evaluations in forked scopes are stored separately.', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipFeatureFlagsTest()) {
+ sentryTest.skip();
+ }
+
+ await page.route('https://dsn.ingest.sentry.io/**/*', route => {
+ return route.fulfill({
+ status: 200,
+ contentType: 'application/json',
+ body: JSON.stringify({ id: 'test-id' }),
+ });
+ });
+
+ const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
+ await page.goto(url);
+
+ const forkedReqPromise = waitForErrorRequest(page, event => !!event.tags && event.tags.isForked === true);
+ const mainReqPromise = waitForErrorRequest(page, event => !!event.tags && event.tags.isForked === false);
+
+ await page.evaluate(() => {
+ const Sentry = (window as any).Sentry;
+ const errorButton = document.querySelector('#error') as HTMLButtonElement;
+ const unleash = new (window as any).UnleashClient();
+
+ unleash.isEnabled('strFeat');
+
+ Sentry.withScope((scope: Scope) => {
+ unleash.isEnabled('disabledFeat');
+ unleash.isEnabled('strFeat');
+ scope.setTag('isForked', true);
+ if (errorButton) {
+ errorButton.click();
+ }
+ });
+
+ unleash.isEnabled('noPayloadFeat');
+ Sentry.getCurrentScope().setTag('isForked', false);
+ errorButton.click();
+ return true;
+ });
+
+ const forkedReq = await forkedReqPromise;
+ const forkedEvent = envelopeRequestParser(forkedReq);
+
+ const mainReq = await mainReqPromise;
+ const mainEvent = envelopeRequestParser(mainReq);
+
+ expect(forkedEvent.contexts?.flags?.values).toEqual([
+ { flag: 'disabledFeat', result: false },
+ { flag: 'strFeat', result: true },
+ ]);
+
+ expect(mainEvent.contexts?.flags?.values).toEqual([
+ { flag: 'strFeat', result: true },
+ { flag: 'noPayloadFeat', result: true },
+ ]);
+});
diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts
index 40d7926efdd1..42c388d73547 100644
--- a/packages/browser/src/index.ts
+++ b/packages/browser/src/index.ts
@@ -67,3 +67,4 @@ export {
} from './integrations/featureFlags';
export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly';
export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature';
+export { unleashIntegration } from './integrations/featureFlags/unleash';
diff --git a/packages/browser/src/integrations/featureFlags/unleash/index.ts b/packages/browser/src/integrations/featureFlags/unleash/index.ts
new file mode 100644
index 000000000000..934ff196ee95
--- /dev/null
+++ b/packages/browser/src/integrations/featureFlags/unleash/index.ts
@@ -0,0 +1 @@
+export { unleashIntegration } from './integration';
diff --git a/packages/browser/src/integrations/featureFlags/unleash/integration.ts b/packages/browser/src/integrations/featureFlags/unleash/integration.ts
new file mode 100644
index 000000000000..c451afb831ba
--- /dev/null
+++ b/packages/browser/src/integrations/featureFlags/unleash/integration.ts
@@ -0,0 +1,73 @@
+import type { Client, Event, EventHint, IntegrationFn } from '@sentry/core';
+
+import { defineIntegration, fill, logger } from '@sentry/core';
+import { DEBUG_BUILD } from '../../../debug-build';
+import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags';
+import type { UnleashClient, UnleashClientClass } from './types';
+
+/**
+ * Sentry integration for capturing feature flag evaluations from the Unleash SDK.
+ *
+ * See the [feature flag documentation](https://develop.sentry.dev/sdk/expected-features/#feature-flags) for more information.
+ *
+ * @example
+ * ```
+ * import { UnleashClient } from 'unleash-proxy-client';
+ * import * as Sentry from '@sentry/browser';
+ *
+ * Sentry.init({
+ * dsn: '___PUBLIC_DSN___',
+ * integrations: [Sentry.unleashIntegration({unleashClientClass: UnleashClient})],
+ * });
+ *
+ * const unleash = new UnleashClient(...);
+ * unleash.start();
+ *
+ * unleash.isEnabled('my-feature');
+ * unleash.getVariant('other-feature');
+ * Sentry.captureException(new Error('something went wrong'));
+ * ```
+ */
+export const unleashIntegration = defineIntegration(
+ ({ unleashClientClass }: { unleashClientClass: UnleashClientClass }) => {
+ return {
+ name: 'Unleash',
+
+ processEvent(event: Event, _hint: EventHint, _client: Client): Event {
+ return copyFlagsFromScopeToEvent(event);
+ },
+
+ setupOnce() {
+ const unleashClientPrototype = unleashClientClass.prototype as UnleashClient;
+ fill(unleashClientPrototype, 'isEnabled', _wrappedIsEnabled);
+ },
+ };
+ },
+) satisfies IntegrationFn;
+
+/**
+ * Wraps the UnleashClient.isEnabled method to capture feature flag evaluations. Its only side effect is writing to Sentry scope.
+ *
+ * This wrapper is safe for all isEnabled signatures. If the signature does not match (this: UnleashClient, toggleName: string, ...args: unknown[]) => boolean,
+ * we log an error and return the original result.
+ *
+ * @param original - The original method.
+ * @returns Wrapped method. Results should match the original.
+ */
+function _wrappedIsEnabled(
+ original: (this: UnleashClient, ...args: unknown[]) => unknown,
+): (this: UnleashClient, ...args: unknown[]) => unknown {
+ return function (this: UnleashClient, ...args: unknown[]): unknown {
+ const toggleName = args[0];
+ const result = original.apply(this, args);
+
+ if (typeof toggleName === 'string' && typeof result === 'boolean') {
+ insertFlagToScope(toggleName, result);
+ } else if (DEBUG_BUILD) {
+ logger.error(
+ `[Feature Flags] UnleashClient.isEnabled does not match expected signature. arg0: ${toggleName} (${typeof toggleName}), result: ${result} (${typeof result})`,
+ );
+ }
+ return result;
+ };
+}
diff --git a/packages/browser/src/integrations/featureFlags/unleash/types.ts b/packages/browser/src/integrations/featureFlags/unleash/types.ts
new file mode 100644
index 000000000000..c87798859911
--- /dev/null
+++ b/packages/browser/src/integrations/featureFlags/unleash/types.ts
@@ -0,0 +1,23 @@
+export interface IVariant {
+ name: string;
+ enabled: boolean;
+ feature_enabled?: boolean;
+ payload?: {
+ type: string;
+ value: string;
+ };
+}
+
+export interface UnleashClient {
+ isEnabled(this: UnleashClient, featureName: string): boolean;
+ getVariant(this: UnleashClient, featureName: string): IVariant;
+}
+
+export interface IConfig {
+ [key: string]: unknown;
+ appName: string;
+ clientKey: string;
+ url: URL | string;
+}
+
+export type UnleashClientClass = new (config: IConfig) => UnleashClient;
From 63235496e8594dd89443012d4e11f61db98ce693 Mon Sep 17 00:00:00 2001
From: Catherine Lee <55311782+c298lee@users.noreply.github.com>
Date: Tue, 21 Jan 2025 13:58:51 -0500
Subject: [PATCH 09/59] feat(user feedback): Adds draw tool for UF screenshot
annotations (#15062)
- Updates some cropping variable names to make it more clear that it's
only meant for cropping
- The pen tool button is improved in
https://github.com/getsentry/sentry-javascript/pull/15102, which needs
to be merged first
- The drawing tool button needs to be "on" to annotate, but cropping can
happen at any time
- Once the button is "on", drawing happens on mouse down and mouse move,
and on mouse up, the drawing gets "squashed" onto the image. The
"squashing" can be moved to happen at a different time in a future PR if
we want to incorporate undo, selection, or erasing
- The experimental flag must be on to use annotations: `_experiments:
{annotations: true}`
https://github.com/user-attachments/assets/2fac5e56-5caf-454b-b8b3-afabbd2c31b9
Closes https://github.com/getsentry/sentry-javascript/issues/15064
---------
Co-authored-by: Ryan Albrecht
---
.../core/src/types-hoist/feedback/config.ts | 9 +
packages/feedback/src/core/integration.ts | 3 +
.../src/screenshot/components/PenIcon.tsx | 31 ++++
.../components/ScreenshotEditor.tsx | 159 +++++++++++++++---
.../components/ScreenshotInput.css.ts | 22 ++-
5 files changed, 198 insertions(+), 26 deletions(-)
create mode 100644 packages/feedback/src/screenshot/components/PenIcon.tsx
diff --git a/packages/core/src/types-hoist/feedback/config.ts b/packages/core/src/types-hoist/feedback/config.ts
index 4ec846c7d98d..d7b3d78995bb 100644
--- a/packages/core/src/types-hoist/feedback/config.ts
+++ b/packages/core/src/types-hoist/feedback/config.ts
@@ -57,6 +57,15 @@ export interface FeedbackGeneralConfiguration {
name: string;
};
+ /**
+ * _experiments allows users to enable experimental or internal features.
+ * We don't consider such features as part of the public API and hence we don't guarantee semver for them.
+ * Experimental features can be added, changed or removed at any time.
+ *
+ * Default: undefined
+ */
+ _experiments: Partial<{ annotations: boolean }>;
+
/**
* Set an object that will be merged sent as tags data with the event.
*/
diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts
index e5f1092856f1..8b312b902258 100644
--- a/packages/feedback/src/core/integration.ts
+++ b/packages/feedback/src/core/integration.ts
@@ -84,6 +84,7 @@ export const buildFeedbackIntegration = ({
email: 'email',
name: 'username',
},
+ _experiments = {},
tags,
styleNonce,
scriptNonce,
@@ -158,6 +159,8 @@ export const buildFeedbackIntegration = ({
onSubmitError,
onSubmitSuccess,
onFormSubmitted,
+
+ _experiments,
};
let _shadow: ShadowRoot | null = null;
diff --git a/packages/feedback/src/screenshot/components/PenIcon.tsx b/packages/feedback/src/screenshot/components/PenIcon.tsx
new file mode 100644
index 000000000000..ec50862c1dd4
--- /dev/null
+++ b/packages/feedback/src/screenshot/components/PenIcon.tsx
@@ -0,0 +1,31 @@
+import type { VNode, h as hType } from 'preact';
+
+interface FactoryParams {
+ h: typeof hType;
+}
+
+export default function PenIconFactory({
+ h, // eslint-disable-line @typescript-eslint/no-unused-vars
+}: FactoryParams) {
+ return function PenIcon(): VNode {
+ return (
+
+ );
+ };
+}
diff --git a/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx b/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx
index ef33e1b611b0..e242415c0903 100644
--- a/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx
+++ b/packages/feedback/src/screenshot/components/ScreenshotEditor.tsx
@@ -6,6 +6,7 @@ import { h } from 'preact'; // eslint-disable-line @typescript-eslint/no-unused-
import type * as Hooks from 'preact/hooks';
import { DOCUMENT, WINDOW } from '../../constants';
import CropCornerFactory from './CropCorner';
+import PenIconFactory from './PenIcon';
import { createScreenshotInputStyles } from './ScreenshotInput.css';
import { useTakeScreenshotFactory } from './useTakeScreenshot';
@@ -72,40 +73,55 @@ export function ScreenshotEditorFactory({
options,
}: FactoryParams): ComponentType {
const useTakeScreenshot = useTakeScreenshotFactory({ hooks });
+ const CropCorner = CropCornerFactory({ h });
+ const PenIcon = PenIconFactory({ h });
return function ScreenshotEditor({ onError }: Props): VNode {
const styles = hooks.useMemo(() => ({ __html: createScreenshotInputStyles(options.styleNonce).innerText }), []);
- const CropCorner = CropCornerFactory({ h });
const canvasContainerRef = hooks.useRef(null);
const cropContainerRef = hooks.useRef(null);
const croppingRef = hooks.useRef(null);
+ const annotatingRef = hooks.useRef(null);
const [croppingRect, setCroppingRect] = hooks.useState({ startX: 0, startY: 0, endX: 0, endY: 0 });
const [confirmCrop, setConfirmCrop] = hooks.useState(false);
const [isResizing, setIsResizing] = hooks.useState(false);
+ const [isAnnotating, setIsAnnotating] = hooks.useState(false);
hooks.useEffect(() => {
- WINDOW.addEventListener('resize', resizeCropper, false);
+ WINDOW.addEventListener('resize', resize);
+
+ return () => {
+ WINDOW.removeEventListener('resize', resize);
+ };
}, []);
- function resizeCropper(): void {
- const cropper = croppingRef.current;
- const imageDimensions = constructRect(getContainedSize(imageBuffer));
- if (cropper) {
- cropper.width = imageDimensions.width * DPI;
- cropper.height = imageDimensions.height * DPI;
- cropper.style.width = `${imageDimensions.width}px`;
- cropper.style.height = `${imageDimensions.height}px`;
- const ctx = cropper.getContext('2d');
- if (ctx) {
- ctx.scale(DPI, DPI);
- }
+ function resizeCanvas(canvasRef: Hooks.Ref, imageDimensions: Rect): void {
+ const canvas = canvasRef.current;
+ if (!canvas) {
+ return;
}
- const cropButton = cropContainerRef.current;
- if (cropButton) {
- cropButton.style.width = `${imageDimensions.width}px`;
- cropButton.style.height = `${imageDimensions.height}px`;
+ canvas.width = imageDimensions.width * DPI;
+ canvas.height = imageDimensions.height * DPI;
+ canvas.style.width = `${imageDimensions.width}px`;
+ canvas.style.height = `${imageDimensions.height}px`;
+ const ctx = canvas.getContext('2d');
+ if (ctx) {
+ ctx.scale(DPI, DPI);
+ }
+ }
+
+ function resize(): void {
+ const imageDimensions = constructRect(getContainedSize(imageBuffer));
+
+ resizeCanvas(croppingRef, imageDimensions);
+ resizeCanvas(annotatingRef, imageDimensions);
+
+ const cropContainer = cropContainerRef.current;
+ if (cropContainer) {
+ cropContainer.style.width = `${imageDimensions.width}px`;
+ cropContainer.style.height = `${imageDimensions.height}px`;
}
setCroppingRect({ startX: 0, startY: 0, endX: imageDimensions.width, endY: imageDimensions.height });
@@ -141,6 +157,7 @@ export function ScreenshotEditorFactory({
}, [croppingRect]);
function onGrabButton(e: Event, corner: string): void {
+ setIsAnnotating(false);
setConfirmCrop(false);
setIsResizing(true);
const handleMouseMove = makeHandleMouseMove(corner);
@@ -247,7 +264,49 @@ export function ScreenshotEditorFactory({
DOCUMENT.addEventListener('mouseup', handleMouseUp);
}
- function submit(): void {
+ function onAnnotateStart(): void {
+ if (!isAnnotating) {
+ return;
+ }
+
+ const handleMouseMove = (moveEvent: MouseEvent): void => {
+ const annotateCanvas = annotatingRef.current;
+ if (annotateCanvas) {
+ const rect = annotateCanvas.getBoundingClientRect();
+
+ const x = moveEvent.clientX - rect.x;
+ const y = moveEvent.clientY - rect.y;
+
+ const ctx = annotateCanvas.getContext('2d');
+ if (ctx) {
+ ctx.lineTo(x, y);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ }
+ }
+ };
+
+ const handleMouseUp = (): void => {
+ const ctx = annotatingRef.current?.getContext('2d');
+ // starts a new path so on next mouse down, the lines won't connect
+ if (ctx) {
+ ctx.beginPath();
+ }
+
+ // draws the annotation onto the image buffer
+ // TODO: move this to a better place
+ applyAnnotation();
+
+ DOCUMENT.removeEventListener('mousemove', handleMouseMove);
+ DOCUMENT.removeEventListener('mouseup', handleMouseUp);
+ };
+
+ DOCUMENT.addEventListener('mousemove', handleMouseMove);
+ DOCUMENT.addEventListener('mouseup', handleMouseUp);
+ }
+
+ function applyCrop(): void {
const cutoutCanvas = DOCUMENT.createElement('canvas');
const imageBox = constructRect(getContainedSize(imageBuffer));
const croppingBox = constructRect(croppingRect);
@@ -277,7 +336,32 @@ export function ScreenshotEditorFactory({
imageBuffer.style.width = `${croppingBox.width}px`;
imageBuffer.style.height = `${croppingBox.height}px`;
ctx.drawImage(cutoutCanvas, 0, 0);
- resizeCropper();
+ resize();
+ }
+ }
+
+ function applyAnnotation(): void {
+ // draw the annotations onto the image (ie "squash" the canvases)
+ const imageCtx = imageBuffer.getContext('2d');
+ const annotateCanvas = annotatingRef.current;
+ if (imageCtx && annotateCanvas) {
+ imageCtx.drawImage(
+ annotateCanvas,
+ 0,
+ 0,
+ annotateCanvas.width,
+ annotateCanvas.height,
+ 0,
+ 0,
+ imageBuffer.width,
+ imageBuffer.height,
+ );
+
+ // clear the annotation canvas
+ const annotateCtx = annotateCanvas.getContext('2d');
+ if (annotateCtx) {
+ annotateCtx.clearRect(0, 0, annotateCanvas.width, annotateCanvas.height);
+ }
}
}
@@ -303,7 +387,7 @@ export function ScreenshotEditorFactory({
(dialog.el as HTMLElement).style.display = 'block';
const container = canvasContainerRef.current;
container?.appendChild(imageBuffer);
- resizeCropper();
+ resize();
}, []),
onError: hooks.useCallback(error => {
(dialog.el as HTMLElement).style.display = 'block';
@@ -314,11 +398,32 @@ export function ScreenshotEditorFactory({
return (
+ {options._experiments.annotations && (
+
+ )}
-
+
{
e.preventDefault();
- submit();
+ applyCrop();
setConfirmCrop(false);
}}
class="btn btn--primary"
@@ -382,6 +487,12 @@ export function ScreenshotEditorFactory({
+
);
diff --git a/packages/feedback/src/screenshot/components/ScreenshotInput.css.ts b/packages/feedback/src/screenshot/components/ScreenshotInput.css.ts
index ae7eaefba86b..5b439390d068 100644
--- a/packages/feedback/src/screenshot/components/ScreenshotInput.css.ts
+++ b/packages/feedback/src/screenshot/components/ScreenshotInput.css.ts
@@ -15,6 +15,7 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme
padding-top: 65px;
padding-bottom: 65px;
flex-grow: 1;
+ position: relative;
background-color: ${surface200};
background-image: repeating-linear-gradient(
@@ -44,14 +45,18 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme
.editor__canvas-container canvas {
object-fit: contain;
- position: relative;
+ position: absolute;
+}
+
+.editor__crop-container {
+ position: absolute;
}
.editor__crop-btn-group {
padding: 8px;
gap: 8px;
border-radius: var(--menu-border-radius, 6px);
- background: var(--button-primary-background, var(--background));
+ background: var(--button-background, var(--background));
width: 175px;
position: absolute;
}
@@ -84,6 +89,19 @@ export function createScreenshotInputStyles(styleNonce?: string): HTMLStyleEleme
border-left: none;
border-top: none;
}
+.editor__tool-container {
+ position: absolute;
+ padding: 10px 0px;
+ top: 0;
+}
+.editor__pen-tool {
+ height: 30px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: var(--button-border, var(--border));
+ border-radius: var(--button-border-radius, 6px);
+}
`;
if (styleNonce) {
From bde83181c8dfafa779798a81a2b401ca3d7a14fc Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 09:19:57 +0100
Subject: [PATCH 10/59] meta: Postpone non-breaking TODOs (#15080)
---
packages/aws-serverless/src/sdk.ts | 2 +-
packages/core/src/index.ts | 4 ++--
packages/nextjs/src/config/webpack.ts | 2 +-
packages/nextjs/src/config/withSentryConfig.ts | 4 +---
4 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/packages/aws-serverless/src/sdk.ts b/packages/aws-serverless/src/sdk.ts
index 79847adb047e..34921d24c7ff 100644
--- a/packages/aws-serverless/src/sdk.ts
+++ b/packages/aws-serverless/src/sdk.ts
@@ -333,7 +333,7 @@ export function wrapHandler(
// Only start a trace and root span if the handler is not already wrapped by Otel instrumentation
// Otherwise, we create two root spans (one from otel, one from our wrapper).
// If Otel instrumentation didn't work or was filtered by users, we still want to trace the handler.
- // TODO(v9): Since bumping the OTEL Instrumentation, this is likely not needed anymore, we can possibly remove this
+ // TODO: Since bumping the OTEL Instrumentation, this is likely not needed anymore, we can possibly remove this (can be done whenever since it would be non-breaking)
if (options.startTrace && !isWrappedByOtel(handler)) {
const traceData = getAwsTraceData(event as { headers?: Record }, context);
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 6349e342ff1d..6bea39b1db64 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -118,9 +118,9 @@ export type { ReportDialogOptions } from './report-dialog';
// eslint-disable-next-line deprecation/deprecation
export { getCurrentHubShim, getCurrentHub } from './getCurrentHubShim';
-// TODO(v9): Make this structure pretty again and don't do "export *"
+// TODO: Make this structure pretty again and don't do "export *"
export * from './utils-hoist/index';
-// TODO(v9): Make this structure pretty again and don't do "export *"
+// TODO: Make this structure pretty again and don't do "export *"
export * from './types-hoist/index';
export type { FeatureFlag } from './featureFlags';
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index 80b46570c03e..bb73a2fb1859 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -586,7 +586,7 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi
/**
* Adds loaders to inject values on the global object based on user configuration.
*/
-// TODO(v9): Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach.
+// TODO: Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach.
// In order to remove this loader though we need to make sure the minimum supported Next.js version includes this PR (https://github.com/vercel/next.js/pull/61194), otherwise the nextConfig.env based approach will not work, as our SDK code is not processed by Next.js.
function addValueInjectionLoader(
newConfig: WebpackConfigObjectWithModuleRules,
diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts
index 316e86c01708..8f57f51a8c58 100644
--- a/packages/nextjs/src/config/withSentryConfig.ts
+++ b/packages/nextjs/src/config/withSentryConfig.ts
@@ -248,9 +248,7 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s
};
}
-// TODO(v9): Inject the release into all the bundles. This is breaking because grabbing the build ID if the user provides
-// it in `generateBuildId` (https://nextjs.org/docs/app/api-reference/next-config-js/generateBuildId) is async but we do
-// not turn the next config function in the type it was passed.
+// TODO: For Turbopack we need to pass the release name here and pick it up in the SDK
function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void {
const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || '';
const basePath = userNextConfig.basePath ?? '';
From 5021a58462d82cf757cb2d043edb228c9aef213e Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 09:23:47 +0100
Subject: [PATCH 11/59] feat: Pass `parentSampleRate` to `tracesSampler`
(#15024)
---
.../metrics/web-vitals-inp-late/test.ts | 1 -
.../web-vitals-inp-parametrized-late/test.ts | 1 -
.../tracing/trace-lifetime/navigation/test.ts | 4 +-
.../tracing/trace-lifetime/pageload/test.ts | 4 +-
.../astro-4/tests/tracing.dynamic.test.ts | 1 -
.../astro-4/tests/tracing.static.test.ts | 1 -
.../astro-5/tests/tracing.dynamic.test.ts | 1 -
.../tests/tracing.serverIslands.test.ts | 2 -
.../astro-5/tests/tracing.static.test.ts | 1 -
.../tests/events.test.ts | 1 -
.../tests/propagation.test.ts | 2 -
.../nextjs-app-dir/tests/transactions.test.ts | 1 -
.../node-fastify-5/tests/propagation.test.ts | 2 -
.../node-fastify/tests/propagation.test.ts | 2 -
.../node-koa/tests/propagation.test.ts | 2 -
.../baggage-header-assign/test.ts | 44 ++++++++-----
.../error-active-span-unsampled/test.ts | 1 +
.../sample-rand-propagation/server.js | 0
.../sample-rand-propagation/test.ts | 2 +-
.../no-tracing-enabled/server.js | 39 ++++++++++++
.../no-tracing-enabled/test.ts | 25 ++++++++
.../tracesSampleRate-0/server.js | 40 ++++++++++++
.../tracesSampleRate-0/test.ts | 61 +++++++++++++++++++
.../tracesSampleRate/server.js | 40 ++++++++++++
.../tracesSampleRate/test.ts | 61 +++++++++++++++++++
.../tracesSampler/server.js | 46 ++++++++++++++
.../tracesSampler/test.ts | 37 +++++++++++
.../src/tracing/browserTracingIntegration.ts | 6 +-
packages/core/src/semanticAttributes.ts | 3 +-
.../src/tracing/dynamicSamplingContext.ts | 32 ++++++----
packages/core/src/tracing/sampling.ts | 11 ++--
packages/core/src/tracing/sentrySpan.ts | 2 +-
packages/core/src/tracing/trace.ts | 17 +++---
.../tracing/dynamicSamplingContext.test.ts | 8 ++-
packages/core/test/lib/tracing/trace.test.ts | 3 -
.../test/integration/transactions.test.ts | 1 -
packages/opentelemetry/src/constants.ts | 1 +
packages/opentelemetry/src/sampler.ts | 57 +++++++++++------
.../test/integration/transactions.test.ts | 1 -
.../opentelemetry/test/propagator.test.ts | 3 -
packages/opentelemetry/test/sampler.test.ts | 1 -
packages/opentelemetry/test/trace.test.ts | 3 -
42 files changed, 469 insertions(+), 102 deletions(-)
rename dev-packages/node-integration-tests/suites/{ => tracing}/sample-rand-propagation/server.js (100%)
rename dev-packages/node-integration-tests/suites/{ => tracing}/sample-rand-propagation/test.ts (97%)
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/server.js
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/test.ts
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/server.js
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/test.ts
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/server.js
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/test.ts
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js
create mode 100644 dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
index fffa85b89ae2..6b3763f48d4d 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts
@@ -67,7 +67,6 @@ sentryTest('should capture an INP click event span after pageload', async ({ bro
'sentry.exclusive_time': inpValue,
'sentry.op': 'ui.interaction.click',
'sentry.origin': 'auto.http.browser.inp',
- 'sentry.sample_rate': 1,
'sentry.source': 'custom',
transaction: 'test-url',
'user_agent.original': expect.stringContaining('Chrome'),
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
index 65852c734c98..74170dc383f7 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts
@@ -70,7 +70,6 @@ sentryTest(
'sentry.exclusive_time': inpValue,
'sentry.op': 'ui.interaction.click',
'sentry.origin': 'auto.http.browser.inp',
- 'sentry.sample_rate': 1,
'sentry.source': 'custom',
transaction: 'test-route',
'user_agent.original': expect.stringContaining('Chrome'),
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts
index a123099107d2..ca6e9723ce65 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts
@@ -250,7 +250,7 @@ sentryTest(
const navigationTraceId = navigationTraceContext?.trace_id;
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
expect(headers['baggage']).toEqual(
- `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand}`,
+ `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`,
);
},
);
@@ -313,7 +313,7 @@ sentryTest(
const navigationTraceId = navigationTraceContext?.trace_id;
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
expect(headers['baggage']).toEqual(
- `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand}`,
+ `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`,
);
},
);
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
index a4439840da7d..4af462e26aca 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
@@ -240,7 +240,7 @@ sentryTest(
// sampling decision is propagated from active span sampling decision
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
expect(headers['baggage']).toBe(
- `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand}`,
+ `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`,
);
},
);
@@ -297,7 +297,7 @@ sentryTest(
// sampling decision is propagated from active span sampling decision
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
expect(headers['baggage']).toBe(
- `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand}`,
+ `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`,
);
},
);
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts
index 9a295f677d96..644afc377545 100644
--- a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.dynamic.test.ts
@@ -31,7 +31,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.browser',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
op: 'pageload',
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts
index 8817b2b22aa7..c04bbb568f2e 100644
--- a/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/tracing.static.test.ts
@@ -36,7 +36,6 @@ test.describe('tracing in static/pre-rendered routes', () => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.browser',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
op: 'pageload',
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
index 8c0e2c0c8850..2bcf6cbf2362 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
@@ -31,7 +31,6 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.browser',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
op: 'pageload',
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts
index a6b288f4de71..fc396999d76e 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.serverIslands.test.ts
@@ -33,7 +33,6 @@ test.describe('tracing in static routes with server islands', () => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.browser',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
op: 'pageload',
@@ -75,7 +74,6 @@ test.describe('tracing in static routes with server islands', () => {
data: expect.objectContaining({
'sentry.op': 'http.server',
'sentry.origin': 'auto.http.astro',
- 'sentry.sample_rate': 1,
'sentry.source': 'route',
}),
op: 'http.server',
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts
index 9c202da53542..9db35c72a47d 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.static.test.ts
@@ -36,7 +36,6 @@ test.describe('tracing in static/pre-rendered routes', () => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.browser',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
op: 'pageload',
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts
index 227851d458cf..ed4a36303efa 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts
@@ -32,7 +32,6 @@ test('Event emitter', async () => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
data: {
'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
'sentry.op': 'event.nestjs',
'sentry.origin': 'auto.event.nestjs',
},
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts
index ecc67e26ab16..040d5d326809 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts
@@ -89,7 +89,6 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
@@ -204,7 +203,6 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts
index fa5cd062d862..5656ce0e5e57 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts
@@ -36,7 +36,6 @@ test('Sends a pageload transaction', async ({ page }) => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
- 'sentry.sample_rate': 1,
'sentry.source': 'url',
}),
},
diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts
index 7e059b99354b..1b4e0f92a97a 100644
--- a/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/tests/propagation.test.ts
@@ -89,7 +89,6 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
@@ -204,7 +203,6 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts
index 7e4aeee0f220..af2cfddded9a 100644
--- a/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/propagation.test.ts
@@ -89,7 +89,6 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
@@ -204,7 +203,6 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts
index 0ff946d07d3d..00fa3ecfca9d 100644
--- a/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/propagation.test.ts
@@ -89,7 +89,6 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
@@ -204,7 +203,6 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
'sentry.source': 'route',
'sentry.origin': 'auto.http.otel.http',
'sentry.op': 'http.server',
- 'sentry.sample_rate': 1,
url: `http://localhost:3030/test-inbound-headers/${id}`,
'otel.kind': 'SERVER',
'http.response.status_code': 200,
diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts
index b873e4a5c0aa..513cf6146d0f 100644
--- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts
+++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-assign/test.ts
@@ -1,3 +1,4 @@
+import { parseBaggageHeader } from '@sentry/core';
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
import type { TestAPIResponse } from '../server';
@@ -102,14 +103,20 @@ test('Should populate and propagate sentry baggage if sentry-trace header does n
const response = await runner.makeRequest('get', '/test/express');
expect(response).toBeDefined();
- expect(response).toMatchObject({
- test_data: {
- host: 'somewhere.not.sentry',
- // TraceId changes, hence we only expect that the string contains the traceid key
- baggage: expect.stringMatching(
- /sentry-environment=prod,sentry-release=1.0,sentry-public_key=public,sentry-trace_id=[\S]*,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress/,
- ),
- },
+
+ const parsedBaggage = parseBaggageHeader(response?.test_data.baggage);
+
+ expect(response?.test_data.host).toBe('somewhere.not.sentry');
+ expect(parsedBaggage).toStrictEqual({
+ 'sentry-environment': 'prod',
+ 'sentry-release': '1.0',
+ 'sentry-public_key': 'public',
+ // TraceId changes, hence we only expect that the string contains the traceid key
+ 'sentry-trace_id': expect.stringMatching(/[\S]*/),
+ 'sentry-sample_rand': expect.stringMatching(/[\S]*/),
+ 'sentry-sample_rate': '1',
+ 'sentry-sampled': 'true',
+ 'sentry-transaction': 'GET /test/express',
});
});
@@ -123,13 +130,18 @@ test('Should populate Sentry and ignore 3rd party content if sentry-trace header
});
expect(response).toBeDefined();
- expect(response).toMatchObject({
- test_data: {
- host: 'somewhere.not.sentry',
- // TraceId changes, hence we only expect that the string contains the traceid key
- baggage: expect.stringMatching(
- /sentry-environment=prod,sentry-release=1.0,sentry-public_key=public,sentry-trace_id=[\S]*,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress/,
- ),
- },
+ expect(response?.test_data.host).toBe('somewhere.not.sentry');
+
+ const parsedBaggage = parseBaggageHeader(response?.test_data.baggage);
+ expect(parsedBaggage).toStrictEqual({
+ 'sentry-environment': 'prod',
+ 'sentry-release': '1.0',
+ 'sentry-public_key': 'public',
+ // TraceId changes, hence we only expect that the string contains the traceid key
+ 'sentry-trace_id': expect.stringMatching(/[\S]*/),
+ 'sentry-sample_rand': expect.stringMatching(/[\S]*/),
+ 'sentry-sample_rate': '1',
+ 'sentry-sampled': 'true',
+ 'sentry-transaction': 'GET /test/express',
});
});
diff --git a/dev-packages/node-integration-tests/suites/tracing/envelope-header/error-active-span-unsampled/test.ts b/dev-packages/node-integration-tests/suites/tracing/envelope-header/error-active-span-unsampled/test.ts
index 6ba4aaa74b32..105722a43239 100644
--- a/dev-packages/node-integration-tests/suites/tracing/envelope-header/error-active-span-unsampled/test.ts
+++ b/dev-packages/node-integration-tests/suites/tracing/envelope-header/error-active-span-unsampled/test.ts
@@ -10,6 +10,7 @@ test('envelope header for error event during active unsampled span is correct',
public_key: 'public',
environment: 'production',
release: '1.0',
+ sample_rate: '0',
sampled: 'false',
sample_rand: expect.any(String),
},
diff --git a/dev-packages/node-integration-tests/suites/sample-rand-propagation/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rand-propagation/server.js
similarity index 100%
rename from dev-packages/node-integration-tests/suites/sample-rand-propagation/server.js
rename to dev-packages/node-integration-tests/suites/tracing/sample-rand-propagation/server.js
diff --git a/dev-packages/node-integration-tests/suites/sample-rand-propagation/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rand-propagation/test.ts
similarity index 97%
rename from dev-packages/node-integration-tests/suites/sample-rand-propagation/test.ts
rename to dev-packages/node-integration-tests/suites/tracing/sample-rand-propagation/test.ts
index 42e6a0a5e555..7c566c1d8eeb 100644
--- a/dev-packages/node-integration-tests/suites/sample-rand-propagation/test.ts
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rand-propagation/test.ts
@@ -1,4 +1,4 @@
-import { cleanupChildProcesses, createRunner } from '../../utils/runner';
+import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
describe('sample_rand propagation', () => {
afterAll(() => {
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/server.js
new file mode 100644
index 000000000000..dbc6b9009c49
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/server.js
@@ -0,0 +1,39 @@
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const Sentry = require('@sentry/node');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ transport: loggingTransport,
+});
+
+// express must be required after Sentry is initialized
+const express = require('express');
+const cors = require('cors');
+const {
+ startExpressServerAndSendPortToRunner,
+ getPortAppIsRunningOn,
+} = require('@sentry-internal/node-integration-tests');
+
+const app = express();
+
+app.use(cors());
+
+app.get('/check', (req, res) => {
+ const appPort = getPortAppIsRunningOn(app);
+ // eslint-disable-next-line no-undef
+ fetch(`http://localhost:${appPort}/bounce`)
+ .then(r => r.json())
+ .then(bounceRes => {
+ res.json({ propagatedData: bounceRes });
+ });
+});
+
+app.get('/bounce', (req, res) => {
+ res.json({
+ baggage: req.headers['baggage'],
+ });
+});
+
+Sentry.setupExpressErrorHandler(app);
+
+startExpressServerAndSendPortToRunner(app);
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/test.ts
new file mode 100644
index 000000000000..c6800055c84b
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/no-tracing-enabled/test.ts
@@ -0,0 +1,25 @@
+import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
+
+describe('parentSampleRate propagation with no tracing enabled', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ test('should propagate an incoming sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+
+ test('should not propagate a sample rate for root traces', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check');
+ expect((response as any).propagatedData.baggage).not.toMatch(/sentry-sample_rate/);
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/server.js
new file mode 100644
index 000000000000..dc2f49b081cf
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/server.js
@@ -0,0 +1,40 @@
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const Sentry = require('@sentry/node');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ transport: loggingTransport,
+ tracesSampleRate: 0,
+});
+
+// express must be required after Sentry is initialized
+const express = require('express');
+const cors = require('cors');
+const {
+ startExpressServerAndSendPortToRunner,
+ getPortAppIsRunningOn,
+} = require('@sentry-internal/node-integration-tests');
+
+const app = express();
+
+app.use(cors());
+
+app.get('/check', (req, res) => {
+ const appPort = getPortAppIsRunningOn(app);
+ // eslint-disable-next-line no-undef
+ fetch(`http://localhost:${appPort}/bounce`)
+ .then(r => r.json())
+ .then(bounceRes => {
+ res.json({ propagatedData: bounceRes });
+ });
+});
+
+app.get('/bounce', (req, res) => {
+ res.json({
+ baggage: req.headers['baggage'],
+ });
+});
+
+Sentry.setupExpressErrorHandler(app);
+
+startExpressServerAndSendPortToRunner(app);
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/test.ts
new file mode 100644
index 000000000000..c12d2920dd9f
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate-0/test.ts
@@ -0,0 +1,61 @@
+import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
+
+describe('parentSampleRate propagation with tracesSampleRate=0', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ test('should propagate incoming sample rate when inheriting a positive sampling decision', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+
+ test('should propagate incoming sample rate when inheriting a negative sampling decision', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+
+ test('should propagate configured sample rate when receiving a trace without sampling decision and sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
+ baggage: '',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/);
+ });
+
+ test('should propagate configured sample rate when receiving a trace without sampling decision, but with sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/);
+ });
+
+ test('should propagate configured sample rate when there is no incoming trace', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check');
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/);
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/server.js
new file mode 100644
index 000000000000..512681043d4d
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/server.js
@@ -0,0 +1,40 @@
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const Sentry = require('@sentry/node');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ transport: loggingTransport,
+ tracesSampleRate: 0.69,
+});
+
+// express must be required after Sentry is initialized
+const express = require('express');
+const cors = require('cors');
+const {
+ startExpressServerAndSendPortToRunner,
+ getPortAppIsRunningOn,
+} = require('@sentry-internal/node-integration-tests');
+
+const app = express();
+
+app.use(cors());
+
+app.get('/check', (req, res) => {
+ const appPort = getPortAppIsRunningOn(app);
+ // eslint-disable-next-line no-undef
+ fetch(`http://localhost:${appPort}/bounce`)
+ .then(r => r.json())
+ .then(bounceRes => {
+ res.json({ propagatedData: bounceRes });
+ });
+});
+
+app.get('/bounce', (req, res) => {
+ res.json({
+ baggage: req.headers['baggage'],
+ });
+});
+
+Sentry.setupExpressErrorHandler(app);
+
+startExpressServerAndSendPortToRunner(app);
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/test.ts
new file mode 100644
index 000000000000..27afa03e9045
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampleRate/test.ts
@@ -0,0 +1,61 @@
+import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
+
+describe('parentSampleRate propagation with tracesSampleRate', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ test('should propagate incoming sample rate when inheriting a positive sampling decision', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+
+ test('should propagate incoming sample rate when inheriting a negative sampling decision', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+
+ test('should propagate configured sample rate when receiving a trace without sampling decision and sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
+ baggage: '',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
+ });
+
+ test('should propagate configured sample rate when receiving a trace without sampling decision, but with sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
+ });
+
+ test('should propagate configured sample rate when there is no incoming trace', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check');
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js
new file mode 100644
index 000000000000..5dc1d17588e5
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js
@@ -0,0 +1,46 @@
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const Sentry = require('@sentry/node');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ transport: loggingTransport,
+ tracesSampler: ({ parentSampleRate }) => {
+ if (parentSampleRate) {
+ return parentSampleRate;
+ }
+
+ return 0.69;
+ },
+});
+
+// express must be required after Sentry is initialized
+const express = require('express');
+const cors = require('cors');
+const {
+ startExpressServerAndSendPortToRunner,
+ getPortAppIsRunningOn,
+} = require('@sentry-internal/node-integration-tests');
+
+const app = express();
+
+app.use(cors());
+
+app.get('/check', (req, res) => {
+ const appPort = getPortAppIsRunningOn(app);
+ // eslint-disable-next-line no-undef
+ fetch(`http://localhost:${appPort}/bounce`)
+ .then(r => r.json())
+ .then(bounceRes => {
+ res.json({ propagatedData: bounceRes });
+ });
+});
+
+app.get('/bounce', (req, res) => {
+ res.json({
+ baggage: req.headers['baggage'],
+ });
+});
+
+Sentry.setupExpressErrorHandler(app);
+
+startExpressServerAndSendPortToRunner(app);
diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts
new file mode 100644
index 000000000000..304725268f03
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts
@@ -0,0 +1,37 @@
+import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
+
+describe('parentSampleRate propagation with tracesSampler', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming trace', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check');
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
+ });
+
+ test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
+ baggage: '',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
+ });
+
+ test('should propagate sample_rate equivalent to incoming sample_rate (because tracesSampler is configured that way)', async () => {
+ const runner = createRunner(__dirname, 'server.js').start();
+ const response = await runner.makeRequest('get', '/check', {
+ headers: {
+ 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
+ baggage: 'sentry-sample_rate=0.1337',
+ },
+ });
+
+ expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
+ });
+});
diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
index 5bbda349b25d..b32cab7db3f5 100644
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -317,6 +317,9 @@ export const browserTracingIntegration = ((_options: Partial): Partial {
+ if (typeof rootSpanSampleRate === 'number' || typeof rootSpanSampleRate === 'string') {
+ dsc.sample_rate = `${rootSpanSampleRate}`;
+ }
+ return dsc;
+ }
// For core implementation, we freeze the DSC onto the span as a non-enumerable property
const frozenDsc = (rootSpan as SpanWithMaybeDsc)[FROZEN_DSC_FIELD];
if (frozenDsc) {
- return frozenDsc;
+ return applyLocalSampleRateToDsc(frozenDsc);
}
// For OpenTelemetry, we freeze the DSC on the trace state
- const traceState = rootSpan.spanContext().traceState;
const traceStateDsc = traceState?.get('sentry.dsc');
// If the span has a DSC, we want it to take precedence
const dscOnTraceState = traceStateDsc && baggageHeaderToDynamicSamplingContext(traceStateDsc);
if (dscOnTraceState) {
- return dscOnTraceState;
+ return applyLocalSampleRateToDsc(dscOnTraceState);
}
// Else, we generate it from the span
const dsc = getDynamicSamplingContextFromClient(span.spanContext().traceId, client);
- const jsonSpan = spanToJSON(rootSpan);
- const attributes = jsonSpan.data;
- const maybeSampleRate = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE];
-
- if (maybeSampleRate != null) {
- dsc.sample_rate = `${maybeSampleRate}`;
- }
// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
- const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
+ const source = rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
// after JSON conversion, txn.name becomes jsonSpan.description
- const name = jsonSpan.description;
+ const name = rootSpanJson.description;
if (source !== 'url' && name) {
dsc.transaction = name;
}
@@ -127,6 +133,8 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly,
samplingContext: SamplingContext,
sampleRand: number,
-): [sampled: boolean, sampleRate?: number] {
+): [sampled: boolean, sampleRate?: number, localSampleRateWasApplied?: boolean] {
// nothing to do if tracing is not enabled
if (!hasTracingEnabled(options)) {
return [false];
}
+ let localSampleRateWasApplied = undefined;
+
// we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` were defined, so one of these should
// work; prefer the hook if so
let sampleRate;
if (typeof options.tracesSampler === 'function') {
sampleRate = options.tracesSampler(samplingContext);
+ localSampleRateWasApplied = true;
} else if (samplingContext.parentSampled !== undefined) {
sampleRate = samplingContext.parentSampled;
} else if (typeof options.tracesSampleRate !== 'undefined') {
sampleRate = options.tracesSampleRate;
+ localSampleRateWasApplied = true;
}
// Since this is coming from the user (or from a function provided by the user), who knows what we might get.
@@ -51,7 +55,7 @@ export function sampleSpan(
: 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
}`,
);
- return [false, parsedSampleRate];
+ return [false, parsedSampleRate, localSampleRateWasApplied];
}
// We always compare the sample rand for the current execution context against the chosen sample rate.
@@ -66,8 +70,7 @@ export function sampleSpan(
sampleRate,
)})`,
);
- return [false, parsedSampleRate];
}
- return [true, parsedSampleRate];
+ return [shouldSample, parsedSampleRate, localSampleRateWasApplied];
}
diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts
index acc14d37bc31..74478f79903f 100644
--- a/packages/core/src/tracing/sentrySpan.ts
+++ b/packages/core/src/tracing/sentrySpan.ts
@@ -349,7 +349,7 @@ export class SentrySpan implements Span {
/* eslint-disable @typescript-eslint/no-dynamic-delete */
delete this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
spans.forEach(span => {
- span.data && delete span.data[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
+ delete span.data[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
});
// eslint-enabled-next-line @typescript-eslint/no-dynamic-delete
diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts
index 8e14af7e6c71..64a4c8ba5a4c 100644
--- a/packages/core/src/tracing/trace.ts
+++ b/packages/core/src/tracing/trace.ts
@@ -22,6 +22,7 @@ import { generateTraceId } from '../utils-hoist/propagationContext';
import { propagationContextFromHeaders } from '../utils-hoist/tracing';
import { handleCallbackErrors } from '../utils/handleCallbackErrors';
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
+import { parseSampleRate } from '../utils/parseSampleRate';
import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope';
import { addChildSpanToSpan, getRootSpan, spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
import { freezeDscOnSpan, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
@@ -407,8 +408,10 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent
const options: Partial = client?.getOptions() || {};
const { name = '', attributes } = spanArguments;
- const sampleRand = scope.getPropagationContext().sampleRand;
- const [sampled, sampleRate] = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY]
+ const currentPropagationContext = scope.getPropagationContext();
+ const [sampled, sampleRate, localSampleRateWasApplied] = scope.getScopeData().sdkProcessingMetadata[
+ SUPPRESS_TRACING_KEY
+ ]
? [false]
: sampleSpan(
options,
@@ -416,15 +419,17 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent
name,
parentSampled,
attributes,
- // TODO(v9): provide a parentSampleRate here
+ parentSampleRate: parseSampleRate(currentPropagationContext.dsc?.sample_rate),
},
- sampleRand,
+ currentPropagationContext.sampleRand,
);
const rootSpan = new SentrySpan({
...spanArguments,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
+ [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]:
+ sampleRate !== undefined && localSampleRateWasApplied ? sampleRate : undefined,
...spanArguments.attributes,
},
sampled,
@@ -435,10 +440,6 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent
client.recordDroppedEvent('sample_rate', 'transaction');
}
- if (sampleRate !== undefined) {
- rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
- }
-
if (client) {
client.emit('spanStart', rootSpan);
}
diff --git a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
index 3856b9d35d13..63a6910de06b 100644
--- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
+++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts
@@ -42,8 +42,12 @@ describe('getDynamicSamplingContextFromSpan', () => {
spanId: '12345',
traceFlags: 0,
traceState: {
- get() {
- return 'sentry-environment=myEnv2';
+ get(key: string) {
+ if (key === 'sentry.dsc') {
+ return 'sentry-environment=myEnv2';
+ } else {
+ return undefined;
+ }
},
} as unknown as SpanContextData['traceState'],
};
diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts
index cf2db6cb316a..0eee7338a93d 100644
--- a/packages/core/test/lib/tracing/trace.test.ts
+++ b/packages/core/test/lib/tracing/trace.test.ts
@@ -481,7 +481,6 @@ describe('startSpan', () => {
trace: {
data: {
'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
'sentry.origin': 'manual',
},
parent_span_id: innerParentSpanId,
@@ -986,7 +985,6 @@ describe('startSpanManual', () => {
trace: {
data: {
'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
'sentry.origin': 'manual',
},
parent_span_id: innerParentSpanId,
@@ -1317,7 +1315,6 @@ describe('startInactiveSpan', () => {
trace: {
data: {
'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
'sentry.origin': 'manual',
},
parent_span_id: innerParentSpanId,
diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts
index 4768f3176859..1f396b11c3af 100644
--- a/packages/node/test/integration/transactions.test.ts
+++ b/packages/node/test/integration/transactions.test.ts
@@ -492,7 +492,6 @@ describe('Integration | Transactions', () => {
'sentry.op': 'test op',
'sentry.origin': 'auto.test',
'sentry.source': 'task',
- 'sentry.sample_rate': 1,
},
op: 'test op',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
diff --git a/packages/opentelemetry/src/constants.ts b/packages/opentelemetry/src/constants.ts
index d363945a4403..375e42dfdd00 100644
--- a/packages/opentelemetry/src/constants.ts
+++ b/packages/opentelemetry/src/constants.ts
@@ -7,6 +7,7 @@ export const SENTRY_TRACE_STATE_DSC = 'sentry.dsc';
export const SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING = 'sentry.sampled_not_recording';
export const SENTRY_TRACE_STATE_URL = 'sentry.url';
export const SENTRY_TRACE_STATE_SAMPLE_RAND = 'sentry.sample_rand';
+export const SENTRY_TRACE_STATE_SAMPLE_RATE = 'sentry.sample_rate';
export const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes');
diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts
index cdcea1344ef0..9e2f00d565de 100644
--- a/packages/opentelemetry/src/sampler.ts
+++ b/packages/opentelemetry/src/sampler.ts
@@ -1,5 +1,5 @@
/* eslint-disable complexity */
-import type { Attributes, Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api';
+import type { Context, Span, TraceState as TraceStateInterface } from '@opentelemetry/api';
import { SpanKind, isSpanContextValid, trace } from '@opentelemetry/api';
import { TraceState } from '@opentelemetry/core';
import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base';
@@ -11,16 +11,14 @@ import {
SEMATTRS_HTTP_URL,
} from '@opentelemetry/semantic-conventions';
import type { Client, SpanAttributes } from '@sentry/core';
+import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core';
+import { baggageHeaderToDynamicSamplingContext } from '@sentry/core';
+import { SEMANTIC_ATTRIBUTE_SENTRY_OP, hasTracingEnabled, logger, parseSampleRate, sampleSpan } from '@sentry/core';
import {
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
- hasTracingEnabled,
- logger,
- sampleSpan,
-} from '@sentry/core';
-import {
+ SENTRY_TRACE_STATE_DSC,
SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING,
SENTRY_TRACE_STATE_SAMPLE_RAND,
+ SENTRY_TRACE_STATE_SAMPLE_RATE,
SENTRY_TRACE_STATE_URL,
} from './constants';
import { DEBUG_BUILD } from './debug-build';
@@ -106,31 +104,37 @@ export class SentrySampler implements Sampler {
// We only sample based on parameters (like tracesSampleRate or tracesSampler) for root spans (which is done in sampleSpan).
// Non-root-spans simply inherit the sampling decision from their parent.
if (isRootSpan) {
- const { isolationScope, scope } = getScopesFromContext(context) ?? {};
- const sampleRand = scope?.getPropagationContext().sampleRand ?? Math.random();
- const [sampled, sampleRate] = sampleSpan(
+ const { isolationScope } = getScopesFromContext(context) ?? {};
+
+ const dscString = parentContext?.traceState ? parentContext.traceState.get(SENTRY_TRACE_STATE_DSC) : undefined;
+ const dsc = dscString ? baggageHeaderToDynamicSamplingContext(dscString) : undefined;
+
+ const sampleRand = parseSampleRate(dsc?.sample_rand) ?? Math.random();
+
+ const [sampled, sampleRate, localSampleRateWasApplied] = sampleSpan(
options,
{
name: inferredSpanName,
attributes: mergedAttributes,
normalizedRequest: isolationScope?.getScopeData().sdkProcessingMetadata.normalizedRequest,
parentSampled,
- // TODO(v9): provide a parentSampleRate here
+ parentSampleRate: parseSampleRate(dsc?.sample_rate),
},
sampleRand,
);
- const attributes: Attributes = {
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: sampleRate,
- };
-
const method = `${maybeSpanHttpMethod}`.toUpperCase();
if (method === 'OPTIONS' || method === 'HEAD') {
DEBUG_BUILD && logger.log(`[Tracing] Not sampling span because HTTP method is '${method}' for ${spanName}`);
return {
- ...wrapSamplingDecision({ decision: SamplingDecision.NOT_RECORD, context, spanAttributes, sampleRand }),
- attributes,
+ ...wrapSamplingDecision({
+ decision: SamplingDecision.NOT_RECORD,
+ context,
+ spanAttributes,
+ sampleRand,
+ downstreamTraceSampleRate: 0, // we don't want to sample anything in the downstream trace either
+ }),
};
}
@@ -149,8 +153,12 @@ export class SentrySampler implements Sampler {
context,
spanAttributes,
sampleRand,
+ downstreamTraceSampleRate: localSampleRateWasApplied ? sampleRate : undefined,
}),
- attributes,
+ attributes: {
+ // We set the sample rate on the span when a local sample rate was applied to better understand how traces were sampled in Sentry
+ [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: localSampleRateWasApplied ? sampleRate : undefined,
+ },
};
} else {
return {
@@ -159,7 +167,6 @@ export class SentrySampler implements Sampler {
context,
spanAttributes,
}),
- attributes: {},
};
}
}
@@ -201,14 +208,24 @@ export function wrapSamplingDecision({
context,
spanAttributes,
sampleRand,
+ downstreamTraceSampleRate,
}: {
decision: SamplingDecision | undefined;
context: Context;
spanAttributes: SpanAttributes;
sampleRand?: number;
+ downstreamTraceSampleRate?: number;
}): SamplingResult {
let traceState = getBaseTraceState(context, spanAttributes);
+ // We will override the propagated sample rate downstream when
+ // - the tracesSampleRate is applied
+ // - the tracesSampler is invoked
+ // Since unsampled OTEL spans (NonRecordingSpans) cannot hold attributes we need to store this on the (trace)context.
+ if (downstreamTraceSampleRate !== undefined) {
+ traceState = traceState.set(SENTRY_TRACE_STATE_SAMPLE_RATE, `${downstreamTraceSampleRate}`);
+ }
+
if (sampleRand !== undefined) {
traceState = traceState.set(SENTRY_TRACE_STATE_SAMPLE_RAND, `${sampleRand}`);
}
diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts
index 8000c078e3bf..fa2705dec961 100644
--- a/packages/opentelemetry/test/integration/transactions.test.ts
+++ b/packages/opentelemetry/test/integration/transactions.test.ts
@@ -374,7 +374,6 @@ describe('Integration | Transactions', () => {
'sentry.op': 'test op',
'sentry.origin': 'auto.test',
'sentry.source': 'task',
- 'sentry.sample_rate': 1,
},
op: 'test op',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts
index 408a151e95ef..48b6372e65a0 100644
--- a/packages/opentelemetry/test/propagator.test.ts
+++ b/packages/opentelemetry/test/propagator.test.ts
@@ -131,7 +131,6 @@ describe('SentryPropagator', () => {
'sentry-environment=production',
'sentry-release=1.0.0',
'sentry-public_key=abc',
- 'sentry-sample_rate=1',
'sentry-sampled=true',
'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
'sentry-transaction=test',
@@ -185,7 +184,6 @@ describe('SentryPropagator', () => {
'sentry-environment=production',
'sentry-release=1.0.0',
'sentry-public_key=abc',
- 'sentry-sample_rate=1',
'sentry-sampled=true',
'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
'sentry-transaction=test',
@@ -335,7 +333,6 @@ describe('SentryPropagator', () => {
'sentry-environment=production',
'sentry-release=1.0.0',
'sentry-public_key=abc',
- 'sentry-sample_rate=1',
'sentry-sampled=true',
'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
'sentry-transaction=test',
diff --git a/packages/opentelemetry/test/sampler.test.ts b/packages/opentelemetry/test/sampler.test.ts
index fc38037a7ff2..585e3271a93a 100644
--- a/packages/opentelemetry/test/sampler.test.ts
+++ b/packages/opentelemetry/test/sampler.test.ts
@@ -60,7 +60,6 @@ describe('SentrySampler', () => {
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
expect(actual).toEqual({
decision: SamplingDecision.NOT_RECORD,
- attributes: {},
traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'),
});
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts
index 2c677ded4516..a841bec6ffb6 100644
--- a/packages/opentelemetry/test/trace.test.ts
+++ b/packages/opentelemetry/test/trace.test.ts
@@ -434,7 +434,6 @@ describe('trace', () => {
data: {
'sentry.source': 'custom',
'sentry.origin': 'manual',
- 'sentry.sample_rate': 1,
},
parent_span_id: innerParentSpanId,
span_id: expect.stringMatching(/[a-f0-9]{16}/),
@@ -693,7 +692,6 @@ describe('trace', () => {
data: {
'sentry.source': 'custom',
'sentry.origin': 'manual',
- 'sentry.sample_rate': 1,
},
parent_span_id: innerParentSpanId,
span_id: expect.stringMatching(/[a-f0-9]{16}/),
@@ -990,7 +988,6 @@ describe('trace', () => {
data: {
'sentry.source': 'custom',
'sentry.origin': 'manual',
- 'sentry.sample_rate': 1,
},
parent_span_id: innerParentSpanId,
span_id: expect.stringMatching(/[a-f0-9]{16}/),
From 4a5e45d13f88dc1d6ac30929da65ffa8f4d7ddab Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 11:00:39 +0100
Subject: [PATCH 12/59] fix(core): Only fall back to `sendDefaultPii` for IP
collection in `requestDataIntegration` (#15125)
---
packages/core/src/integrations/requestdata.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts
index eea701be09f3..cc80bfe3b4b2 100644
--- a/packages/core/src/integrations/requestdata.ts
+++ b/packages/core/src/integrations/requestdata.ts
@@ -23,7 +23,6 @@ const DEFAULT_INCLUDE: RequestDataIncludeOptions = {
cookies: true,
data: true,
headers: true,
- ip: false,
query_string: true,
url: true,
};
@@ -44,7 +43,7 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) =
const includeWithDefaultPiiApplied: RequestDataIncludeOptions = {
...include,
- ip: include.ip || client.getOptions().sendDefaultPii,
+ ip: include.ip ?? client.getOptions().sendDefaultPii,
};
if (normalizedRequest) {
From 098f205755edfa06d51ae4e1f586a0a9e89a9c0e Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Wed, 22 Jan 2025 05:24:22 -0500
Subject: [PATCH 13/59] feat(node)!: Add support for Prisma v6 and drop v5
support (#15120)
---
.../node-integration-tests/package.json | 2 +-
.../suites/tracing/prisma-orm/package.json | 4 +-
.../tracing/prisma-orm/prisma/schema.prisma | 1 -
.../suites/tracing/prisma-orm/scenario.js | 4 +-
.../suites/tracing/prisma-orm/test.ts | 181 +++++++-----------
.../suites/tracing/prisma-orm/yarn.lock | 84 ++++----
docs/migration/v8-to-v9.md | 16 +-
packages/node/package.json | 10 +-
.../node/src/integrations/tracing/prisma.ts | 20 +-
yarn.lock | 44 ++---
10 files changed, 150 insertions(+), 216 deletions(-)
diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json
index f27d4c7e750e..6680405b3ac1 100644
--- a/dev-packages/node-integration-tests/package.json
+++ b/dev-packages/node-integration-tests/package.json
@@ -30,7 +30,7 @@
"@nestjs/common": "10.4.6",
"@nestjs/core": "10.4.6",
"@nestjs/platform-express": "10.4.6",
- "@prisma/client": "5.22.0",
+ "@prisma/client": "6.2.1",
"@sentry/aws-serverless": "8.45.0",
"@sentry/core": "8.45.0",
"@sentry/node": "8.45.0",
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json
index b8721038c83b..a0b24c52e319 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json
@@ -16,7 +16,7 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@prisma/client": "5.22.0",
- "prisma": "5.22.0"
+ "@prisma/client": "6.2.1",
+ "prisma": "6.2.1"
}
}
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma
index 52682f1b6cf5..4363c97738ee 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma
@@ -5,7 +5,6 @@ datasource db {
generator client {
provider = "prisma-client-js"
- previewFeatures = ["tracing"]
}
model User {
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js
index 82fbc044b973..b6e3805fa595 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js
@@ -16,14 +16,14 @@ const { PrismaClient } = require('@prisma/client');
setInterval(() => {}, 1000);
async function run() {
- const client = new PrismaClient();
-
await Sentry.startSpan(
{
name: 'Test Transaction',
op: 'transaction',
},
async span => {
+ const client = new PrismaClient();
+
await client.user.create({
data: {
name: 'Tilda',
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts
index d5e9f7ba372f..70d2fda9cbe0 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts
@@ -1,3 +1,4 @@
+import type { SpanJSON } from '@sentry/core';
import { createRunner } from '../../../utils/runner';
describe('Prisma ORM Tests', () => {
@@ -10,127 +11,73 @@ describe('Prisma ORM Tests', () => {
const spans = transaction.spans || [];
expect(spans.length).toBeGreaterThanOrEqual(5);
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- method: 'create',
- model: 'User',
- name: 'User.create',
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:client:operation',
- status: 'ok',
- }),
- );
+ function expectPrismaSpanToIncludeSpanWith(span: Partial) {
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ ...span,
+ data: {
+ ...span.data,
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ status: 'ok',
+ }),
+ );
+ }
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:client:serialize',
- status: 'ok',
- }),
- );
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:client:detect_platform',
+ });
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:client:connect',
- status: 'ok',
- }),
- );
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:client:load_engine',
+ });
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:engine',
- status: 'ok',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- 'sentry.op': 'db',
- 'db.system': 'postgresql',
- },
- description: 'prisma:engine:connection',
- status: 'ok',
- op: 'db',
- }),
- );
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:client:operation',
+ data: {
+ method: 'create',
+ model: 'User',
+ name: 'User.create',
+ },
+ });
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'db.statement': expect.stringContaining(
- 'INSERT INTO "public"."User" ("createdAt","email","name") VALUES ($1,$2,$3) RETURNING "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" /* traceparent',
- ),
- 'sentry.origin': 'auto.db.otel.prisma',
- 'sentry.op': 'db',
- 'db.system': 'postgresql',
- 'otel.kind': 'CLIENT',
- },
- description: expect.stringContaining(
- 'INSERT INTO "public"."User" ("createdAt","email","name") VALUES ($1,$2,$3) RETURNING "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" /* traceparent',
- ),
- status: 'ok',
- op: 'db',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:engine:serialize',
- status: 'ok',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:engine:response_json_serialization',
- status: 'ok',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- method: 'findMany',
- model: 'User',
- name: 'User.findMany',
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:client:operation',
- status: 'ok',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:client:serialize',
- status: 'ok',
- }),
- );
- expect(spans).toContainEqual(
- expect.objectContaining({
- data: {
- 'sentry.origin': 'auto.db.otel.prisma',
- },
- description: 'prisma:engine',
- status: 'ok',
- }),
- );
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:client:serialize',
+ });
+
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:client:connect',
+ });
+
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:engine:connect',
+ });
+
+ expectPrismaSpanToIncludeSpanWith({
+ description: 'prisma:engine:query',
+ });
+
+ expectPrismaSpanToIncludeSpanWith({
+ data: {
+ 'sentry.op': 'db',
+ 'db.query.text':
+ 'SELECT "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1',
+ 'db.system': 'postgresql',
+ 'otel.kind': 'CLIENT',
+ },
+ description:
+ 'SELECT "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1',
+ });
+
+ expectPrismaSpanToIncludeSpanWith({
+ data: {
+ 'sentry.op': 'db',
+ 'db.query.text': 'DELETE FROM "public"."User" WHERE "public"."User"."email"::text LIKE $1',
+ 'db.system': 'postgresql',
+ 'otel.kind': 'CLIENT',
+ },
+ description: 'DELETE FROM "public"."User" WHERE "public"."User"."email"::text LIKE $1',
+ });
},
})
.start(done);
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock
index 860aa032d6cc..fbdff7a9505d 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock
@@ -2,57 +2,57 @@
# yarn lockfile v1
-"@prisma/client@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802"
- integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==
-
-"@prisma/debug@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412"
- integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==
-
-"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2":
- version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
- resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4"
- integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==
-
-"@prisma/engines@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84"
- integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==
+"@prisma/client@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.2.1.tgz#3d7d0c8669bba490247e1ffff67b93a516bd789f"
+ integrity sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==
+
+"@prisma/debug@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.2.1.tgz#887719967c4942d125262e48f6c47c45d17c1f61"
+ integrity sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==
+
+"@prisma/engines-version@6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69":
+ version "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69"
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz#b84ce3fab44bfa13a22669da02752330b61745b2"
+ integrity sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==
+
+"@prisma/engines@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.2.1.tgz#14ef56bb780f02871a728667161d997a14aedb69"
+ integrity sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==
dependencies:
- "@prisma/debug" "5.22.0"
- "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
- "@prisma/fetch-engine" "5.22.0"
- "@prisma/get-platform" "5.22.0"
-
-"@prisma/fetch-engine@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e"
- integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==
+ "@prisma/debug" "6.2.1"
+ "@prisma/engines-version" "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69"
+ "@prisma/fetch-engine" "6.2.1"
+ "@prisma/get-platform" "6.2.1"
+
+"@prisma/fetch-engine@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz#cd7eb7428a407105e0f3761dba536aefd41fc7f7"
+ integrity sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==
dependencies:
- "@prisma/debug" "5.22.0"
- "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
- "@prisma/get-platform" "5.22.0"
-
-"@prisma/get-platform@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd"
- integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==
+ "@prisma/debug" "6.2.1"
+ "@prisma/engines-version" "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69"
+ "@prisma/get-platform" "6.2.1"
+
+"@prisma/get-platform@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.2.1.tgz#34313cd0ee3587798ad33a7b57b6342dc8e66426"
+ integrity sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==
dependencies:
- "@prisma/debug" "5.22.0"
+ "@prisma/debug" "6.2.1"
fsevents@2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-prisma@5.22.0:
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197"
- integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==
+prisma@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.2.1.tgz#457b210326d66d0e6f583cc6f9cd2819b984408f"
+ integrity sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==
dependencies:
- "@prisma/engines" "5.22.0"
+ "@prisma/engines" "6.2.1"
optionalDependencies:
fsevents "2.3.3"
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index 3b090a364386..665ae287d9b0 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -40,14 +40,15 @@ If you need to support older browsers, we recommend transpiling your code using
**Deno:** The minimum supported Deno version is now **2.0.0**.
-### Framework Support Changes
+### Framework and Library Support Changes
-Support for the following Framework versions is dropped:
+Support for the following frameworks and library versions are dropped:
- **Remix**: Version `1.x`
- **TanStack Router**: Version `1.63.0` and lower (relevant when using `tanstackRouterBrowserTracingIntegration`)
-- **SvelteKit**: SvelteKit version `1.x`
-- **Ember.js**: Ember.js version `3.x` and lower (minimum supported version is `4.x`)
+- **SvelteKit**: Version `1.x`
+- **Ember.js**: Version `3.x` and lower (minimum supported version is `4.x`)
+- **Prisma**: Version `5.x`
### TypeScript Version Policy
@@ -106,6 +107,13 @@ Older Typescript versions _may_ still work, but we will not test them anymore an
- The `childProcessIntegration`'s (previously `processThreadBreadcrumbIntegration`) `name` value has been changed from `"ProcessAndThreadBreadcrumbs"` to `"ChildProcess"`. This is relevant if you were filtering integrations by name.
+- The Prisma integration no longer supports Prisma v5. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration.
+
+ For performance instrumentation using Prisma v5:
+
+ 1. Install the `@prisma/instrumentation` package on version 5
+ 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the the `Sentry.init()`'s `openTelemetryInstrumentations` option.
+
### `@sentry/browser`
- The SDK no longer instructs the Sentry backend to automatically infer IP addresses by default. This means that places where you previously saw IP addresses in Sentry may now be grouped to anonymous users. Set the `sendDefaultPii` option in `Sentry.init()` to true to instruct the Sentry backend to infer IP addresses.
diff --git a/packages/node/package.json b/packages/node/package.json
index 9565061ff40b..c745c0f8192b 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
- "files": [
- "/build"
- ],
+ "files": ["/build"],
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/types/index.d.ts",
@@ -56,9 +54,7 @@
},
"typesVersions": {
"<5.0": {
- "build/types/index.d.ts": [
- "build/types-ts3.8/index.d.ts"
- ]
+ "build/types/index.d.ts": ["build/types-ts3.8/index.d.ts"]
}
},
"publishConfig": {
@@ -95,7 +91,7 @@
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.28.0",
- "@prisma/instrumentation": "5.22.0",
+ "@prisma/instrumentation": "6.2.1",
"@sentry/core": "8.45.0",
"@sentry/opentelemetry": "8.45.0",
"import-in-the-middle": "^1.12.0"
diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts
index 010493fff77d..bf1013d405ae 100644
--- a/packages/node/src/integrations/tracing/prisma.ts
+++ b/packages/node/src/integrations/tracing/prisma.ts
@@ -29,10 +29,9 @@ const _prismaIntegration = (() => {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma');
}
- // In Prisma v5.22+, the `db.system` attribute is automatically set
- // On older versions, this is missing, so we add it here
- if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data['db.system']) {
- span.setAttribute('db.system', 'prisma');
+ // Make sure we use the query text as the span name, for ex. SELECT * FROM "User" WHERE "id" = $1
+ if (spanJSON.description === 'prisma:engine:db_query' && spanJSON.data?.['db.query.text']) {
+ span.updateName(spanJSON.data['db.query.text'] as string);
}
});
},
@@ -45,19 +44,6 @@ const _prismaIntegration = (() => {
* For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/).
*
* @example
- *
- * Make sure `previewFeatures = ["tracing"]` is set in the prisma client generator block. See the
- * [prisma docs](https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing) for more details.
- *
- * ```prisma
- * generator client {
- * provider = "prisma-client-js"
- * previewFeatures = ["tracing"]
- * }
- * ```
- *
- * Then you can use the integration like this:
- *
* ```javascript
* const Sentry = require('@sentry/node');
*
diff --git a/yarn.lock b/yarn.lock
index b00af64e5f03..5627089a7941 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5560,12 +5560,12 @@
dependencies:
"@opentelemetry/api" "^1.0.0"
-"@opentelemetry/api-logs@0.53.0":
- version "0.53.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz#c478cbd8120ec2547b64edfa03a552cfe42170be"
- integrity sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==
+"@opentelemetry/api-logs@0.56.0":
+ version "0.56.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.56.0.tgz#68f8c51ca905c260b610c8a3c67d3f9fa3d59a45"
+ integrity sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g==
dependencies:
- "@opentelemetry/api" "^1.0.0"
+ "@opentelemetry/api" "^1.3.0"
"@opentelemetry/api-logs@0.57.1":
version "0.57.1"
@@ -5574,7 +5574,7 @@
dependencies:
"@opentelemetry/api" "^1.3.0"
-"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0":
+"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
@@ -5851,12 +5851,12 @@
semver "^7.5.2"
shimmer "^1.2.1"
-"@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0":
- version "0.53.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz#e6369e4015eb5112468a4d45d38dcada7dad892d"
- integrity sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==
+"@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0":
+ version "0.56.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.56.0.tgz#3330ce16d9235a548efa1019a4a7f01414edd44a"
+ integrity sha512-2KkGBKE+FPXU1F0zKww+stnlUxUTlBvLCiWdP63Z9sqXYeNI/ziNzsxAp4LAdUcTQmXjw1IWgvm5CAb/BHy99w==
dependencies:
- "@opentelemetry/api-logs" "0.53.0"
+ "@opentelemetry/api-logs" "0.56.0"
"@types/shimmer" "^1.2.0"
import-in-the-middle "^1.8.1"
require-in-the-middle "^7.1.1"
@@ -5901,7 +5901,7 @@
"@opentelemetry/api" "^0.12.0"
"@opentelemetry/core" "^0.12.0"
-"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.30.1":
+"@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==
@@ -6066,19 +6066,17 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73"
integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
-"@prisma/client@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802"
- integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==
+"@prisma/client@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.2.1.tgz#3d7d0c8669bba490247e1ffff67b93a516bd789f"
+ integrity sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==
-"@prisma/instrumentation@5.22.0":
- version "5.22.0"
- resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.22.0.tgz#c39941046e9886e17bdb47dbac45946c24d579aa"
- integrity sha512-LxccF392NN37ISGxIurUljZSh1YWnphO34V5a0+T7FVQG2u9bhAXRTJpgmQ3483woVhkraQZFF7cbRrpbw/F4Q==
+"@prisma/instrumentation@6.2.1":
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.2.1.tgz#261b885467d36759b7ca01d1b2ca4e1120bda886"
+ integrity sha512-QrcWRAwNHXX4nHXB+Q94nfm701gPQsR4zkaxYV6qCiENopRi8yYvXt6FNIvxbuwEiWW5Zid6DoWwIsBKJ/5r5w==
dependencies:
- "@opentelemetry/api" "^1.8"
- "@opentelemetry/instrumentation" "^0.49 || ^0.50 || ^0.51 || ^0.52.0 || ^0.53.0"
- "@opentelemetry/sdk-trace-base" "^1.22"
+ "@opentelemetry/instrumentation" "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0"
"@protobuf-ts/plugin-framework@^2.0.7", "@protobuf-ts/plugin-framework@^2.9.4":
version "2.9.4"
From 580b8b54f87f807637b8c64f061145389c6ea49d Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Wed, 22 Jan 2025 11:25:48 +0100
Subject: [PATCH 14/59] ref(browser): Improve setting of propagation scope for
navigation spans (#15108)
Extracted this out of
https://github.com/getsentry/sentry-javascript/pull/14955
Instead of registering a general hook & filtering there, we can just use
the `beforeSpanEnd` hook that we already have/use to do this instead.
---
.../src/tracing/browserTracingIntegration.ts | 35 +++++++------------
1 file changed, 13 insertions(+), 22 deletions(-)
diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
index b32cab7db3f5..f34a37542d29 100644
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -24,7 +24,6 @@ import {
getDynamicSamplingContextFromSpan,
getIsolationScope,
getLocationHref,
- getRootSpan,
logger,
propagationContextFromHeaders,
registerSpanErrorInstrumentation,
@@ -276,6 +275,19 @@ export const browserTracingIntegration = ((_options: Partial {
- const op = spanToJSON(span).op;
- if (span !== getRootSpan(span) || (op !== 'navigation' && op !== 'pageload')) {
- return;
- }
-
- const scope = getCurrentScope();
- const oldPropagationContext = scope.getPropagationContext();
-
- scope.setPropagationContext({
- ...oldPropagationContext,
- sampled: oldPropagationContext.sampled !== undefined ? oldPropagationContext.sampled : spanIsSampled(span),
- dsc: oldPropagationContext.dsc || getDynamicSamplingContextFromSpan(span),
- });
- });
-
if (WINDOW.location) {
if (instrumentPageLoad) {
const origin = browserPerformanceTimeOrigin();
From 94e734768b05da95243b4de8ee61ff1af70b46f1 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Wed, 22 Jan 2025 12:32:27 +0100
Subject: [PATCH 15/59] feat(sveltekit)!: Remove `fetchProxyScriptNonce` option
(#15123)
Removes the `fetchProxyScriptNonce` and in turn simplifies our
`transformPageChunk` callback a bit.
---
docs/migration/v8-to-v9.md | 8 ++++++
packages/sveltekit/src/server/handle.ts | 27 +++++--------------
packages/sveltekit/test/server/handle.test.ts | 20 +++-----------
3 files changed, 19 insertions(+), 36 deletions(-)
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index 665ae287d9b0..be7d7d70458c 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -269,6 +269,10 @@ The following changes are unlikely to affect users of the SDK. They are listed h
This function was primarily internally used.
It's functionality was misleading and should not be used.
+### `@sentry/sveltekit`
+
+- The `fetchProxyScriptNonce` option in `sentryHandle()` was removed due to security concerns. If you previously specified this option for your CSP policy, specify a [script hash](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#configure-csp-for-client-side-fetch-instrumentation) in your CSP config or [disable](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#disable-client-side-fetch-proxy-script) the injection of the script entirely.
+
## 5. Build Changes
Previously the CJS versions of the SDK code (wrongfully) contained compatibility statements for default exports in ESM:
@@ -491,6 +495,10 @@ Sentry.init({
- Deprecated the `hideSourceMaps` option. There are no replacements for this option. The SDK emits hidden sourcemaps by default.
+### `@sentry/sveltekit`
+
+- The `fetchProxyScriptNonce` option in `sentryHandle()` was deprecated due to security concerns. If you previously specified this option for your CSP policy, specify a [script hash](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#configure-csp-for-client-side-fetch-instrumentation) in your CSP config or [disable](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#disable-client-side-fetch-proxy-script) the injection of the script entirely.
+
## `@sentry/opentelemetry`
- Deprecated the `generateSpanContextForPropagationContext` method. There are no replacements for this method.
diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts
index 8d5fe21de1c1..d0e5e2e689f1 100644
--- a/packages/sveltekit/src/server/handle.ts
+++ b/packages/sveltekit/src/server/handle.ts
@@ -43,15 +43,6 @@ export type SentryHandleOptions = {
* @default true
*/
injectFetchProxyScript?: boolean;
-
- /**
- * If this option is set, the `sentryHandle` handler will add a nonce attribute to the script
- * tag it injects into the page. This script is used to enable instrumentation of `fetch` calls
- * in `load` functions.
- *
- * Use this if your CSP policy blocks the fetch proxy script injected by `sentryHandle`.
- */
- fetchProxyScriptNonce?: string;
};
/**
@@ -68,21 +59,17 @@ export const FETCH_PROXY_SCRIPT = `
/**
* Adds Sentry tracing tags to the returned html page.
* Adds Sentry fetch proxy script to the returned html page if enabled in options.
- * Also adds a nonce attribute to the script tag if users specified one for CSP.
*
* Exported only for testing
*/
-export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable {
- const { fetchProxyScriptNonce, injectFetchProxyScript } = options;
- // if injectFetchProxyScript is not set, we default to true
- const shouldInjectScript = injectFetchProxyScript !== false;
- const nonce = fetchProxyScriptNonce ? `nonce="${fetchProxyScriptNonce}"` : '';
-
+export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean }): NonNullable<
+ ResolveOptions['transformPageChunk']
+> {
return ({ html }) => {
const metaTags = getTraceMetaTags();
const headWithMetaTags = metaTags ? `\n${metaTags}` : '';
- const headWithFetchScript = shouldInjectScript ? `\n` : '';
+ const headWithFetchScript = options.injectFetchProxyScript ? `\n` : '';
const modifiedHead = `${headWithMetaTags}${headWithFetchScript}`;
@@ -106,7 +93,7 @@ export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable = {
handleUnknownRoutes: false,
injectFetchProxyScript: true,
...handlerOptions,
@@ -144,7 +131,7 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle {
async function instrumentHandle(
{ event, resolve }: Parameters[0],
- options: SentryHandleOptions,
+ options: Required,
): Promise {
if (!event.route?.id && !options.handleUnknownRoutes) {
return resolve(event);
@@ -174,7 +161,7 @@ async function instrumentHandle(
normalizedRequest: winterCGRequestToRequestData(event.request.clone()),
});
const res = await resolve(event, {
- transformPageChunk: addSentryCodeToPage(options),
+ transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript }),
});
if (span) {
setHttpStatus(span, res.status);
diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts
index 7097c46dd4cd..f6556f8ddcea 100644
--- a/packages/sveltekit/test/server/handle.test.ts
+++ b/packages/sveltekit/test/server/handle.test.ts
@@ -432,36 +432,24 @@ describe('addSentryCodeToPage', () => {
`;
it("Adds add meta tags and fetch proxy script if there's no active transaction", () => {
- const transformPageChunk = addSentryCodeToPage({});
+ const transformPageChunk = addSentryCodeToPage({ injectFetchProxyScript: true });
const transformed = transformPageChunk({ html, done: true });
expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`);
+ expect(transformed).toContain(``);
});
it('adds meta tags and the fetch proxy script if there is an active transaction', () => {
- const transformPageChunk = addSentryCodeToPage({});
+ const transformPageChunk = addSentryCodeToPage({ injectFetchProxyScript: true });
SentryNode.startSpan({ name: 'test' }, () => {
const transformed = transformPageChunk({ html, done: true }) as string;
expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`);
- });
- });
-
- it('adds a nonce attribute to the script if the `fetchProxyScriptNonce` option is specified', () => {
- const transformPageChunk = addSentryCodeToPage({ fetchProxyScriptNonce: '123abc' });
- SentryNode.startSpan({ name: 'test' }, () => {
- const transformed = transformPageChunk({ html, done: true }) as string;
-
- expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`);
+ expect(transformed).toContain(``);
});
});
From 2796917b1d6587a2f3b93a2b1c7c14d2ea817cfc Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 12:49:38 +0100
Subject: [PATCH 16/59] feat!: Remove `getCurrentHub()`, `Hub`, and
`getCurrentHubShim()` (#15122)
---
.../sessions/v7-hub-start-session/init.js | 12 -
.../v7-hub-start-session/template.html | 9 -
.../sessions/v7-hub-start-session/test.ts | 34 ---
docs/migration/v8-to-v9.md | 1 +
packages/astro/src/index.server.ts | 2 -
packages/astro/src/index.types.ts | 3 -
packages/aws-serverless/src/index.ts | 2 -
packages/browser/src/exports.ts | 2 -
packages/browser/test/sdk.test.ts | 28 +--
packages/bun/src/index.ts | 2 -
packages/core/src/getCurrentHubShim.ts | 73 ------
packages/core/src/index.ts | 3 -
packages/core/src/types-hoist/hub.ts | 209 ------------------
packages/core/src/types-hoist/index.ts | 2 -
packages/core/src/types-hoist/options.ts | 3 +-
.../core/src/types-hoist/samplingcontext.ts | 2 +-
packages/core/src/utils-hoist/stacktrace.ts | 4 +-
packages/google-cloud-serverless/src/index.ts | 2 -
packages/node/src/index.ts | 2 -
packages/node/src/otel/contextManager.ts | 2 +-
packages/node/src/types.ts | 3 +-
packages/opentelemetry/src/index.ts | 2 -
packages/remix/src/index.server.ts | 2 -
packages/remix/src/index.types.ts | 3 -
.../test/unit/session/createSession.test.ts | 14 +-
packages/solidstart/src/server/index.ts | 2 -
packages/sveltekit/src/index.types.ts | 3 -
packages/sveltekit/src/server/index.ts | 2 -
packages/types/src/index.ts | 4 -
29 files changed, 11 insertions(+), 421 deletions(-)
delete mode 100644 dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/init.js
delete mode 100644 dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/template.html
delete mode 100644 dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts
delete mode 100644 packages/core/src/getCurrentHubShim.ts
delete mode 100644 packages/core/src/types-hoist/hub.ts
diff --git a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/init.js b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/init.js
deleted file mode 100644
index 5b72efb558f8..000000000000
--- a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/init.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import * as Sentry from '@sentry/browser';
-
-window.Sentry = Sentry;
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- release: '0.1',
-});
-
-// simulate old startSessionTracking behavior
-Sentry.getCurrentHub().startSession({ ignoreDuration: true });
-Sentry.getCurrentHub().captureSession();
diff --git a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/template.html b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/template.html
deleted file mode 100644
index 77906444cbce..000000000000
--- a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/template.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
- Navigate
-
-
diff --git a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts
deleted file mode 100644
index 0dd12d17fe95..000000000000
--- a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { Route } from '@playwright/test';
-import { expect } from '@playwright/test';
-import type { SessionContext } from '@sentry/core';
-
-import { sentryTest } from '../../../utils/fixtures';
-import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
-
-sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
- const session = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(session).toBeDefined();
- expect(session.init).toBe(true);
- expect(session.errors).toBe(0);
- expect(session.status).toBe('ok');
-});
-
-sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => {
- const url = await getLocalTestUrl({ testDir: __dirname });
- await page.route('**/foo', (route: Route) => route.continue({ url }));
-
- const initSession = await getFirstSentryEnvelopeRequest(page, url);
-
- await page.locator('#navigate').click();
-
- const newSession = await getFirstSentryEnvelopeRequest(page, url);
-
- expect(newSession).toBeDefined();
- expect(newSession.init).toBe(true);
- expect(newSession.errors).toBe(0);
- expect(newSession.status).toBe('ok');
- expect(newSession.sid).toBeDefined();
- expect(initSession.sid).not.toBe(newSession.sid);
-});
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index be7d7d70458c..9b9ee2b77b24 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -188,6 +188,7 @@ Sentry.init({
```
- The `DEFAULT_USER_INCLUDES` constant has been removed.
+- The `getCurrentHub()`, `Hub` and `getCurrentHubShim()` APIs have been removed. They were on compatibility life support since the release of v8 and have now been fully removed from the SDK.
### `@sentry/browser`
diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts
index e788549c0a78..57abd7efede3 100644
--- a/packages/astro/src/index.server.ts
+++ b/packages/astro/src/index.server.ts
@@ -45,8 +45,6 @@ export {
getActiveSpan,
getAutoPerformanceIntegrations,
getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getCurrentScope,
getDefaultIntegrations,
getGlobalScope,
diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts
index 8a9c1935547b..eeadf11fa3d5 100644
--- a/packages/astro/src/index.types.ts
+++ b/packages/astro/src/index.types.ts
@@ -24,9 +24,6 @@ export declare const defaultStackParser: StackParser;
export declare function close(timeout?: number | undefined): PromiseLike;
export declare function flush(timeout?: number | undefined): PromiseLike;
-// eslint-disable-next-line deprecation/deprecation
-export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
-
export declare const Span: clientSdk.Span;
export default sentryAstro;
diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts
index e2e405768b3b..60747de09dd5 100644
--- a/packages/aws-serverless/src/index.ts
+++ b/packages/aws-serverless/src/index.ts
@@ -12,8 +12,6 @@ export {
endSession,
withMonitor,
createTransport,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getClient,
isInitialized,
generateInstrumentOnce,
diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts
index 289643a6c6c0..ae2ff9c3fa87 100644
--- a/packages/browser/src/exports.ts
+++ b/packages/browser/src/exports.ts
@@ -30,8 +30,6 @@ export {
createTransport,
lastEventId,
flush,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getClient,
isInitialized,
getCurrentScope,
diff --git a/packages/browser/test/sdk.test.ts b/packages/browser/test/sdk.test.ts
index 192915c8442d..a6fc49edee89 100644
--- a/packages/browser/test/sdk.test.ts
+++ b/packages/browser/test/sdk.test.ts
@@ -7,9 +7,9 @@ import type { Mock } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import * as SentryCore from '@sentry/core';
-import { Scope, createTransport } from '@sentry/core';
+import { createTransport } from '@sentry/core';
import { resolvedSyncPromise } from '@sentry/core';
-import type { Client, Integration } from '@sentry/core';
+import type { Integration } from '@sentry/core';
import type { BrowserOptions } from '../src';
import { WINDOW } from '../src';
@@ -34,30 +34,6 @@ export class MockIntegration implements Integration {
}
}
-vi.mock('@sentry/core', async requireActual => {
- return {
- ...((await requireActual()) as any),
- getCurrentHub(): {
- bindClient(client: Client): boolean;
- getClient(): boolean;
- getScope(): Scope;
- } {
- return {
- getClient(): boolean {
- return false;
- },
- getScope(): Scope {
- return new Scope();
- },
- bindClient(client: Client): boolean {
- client.init!();
- return true;
- },
- };
- },
- };
-});
-
describe('init', () => {
beforeEach(() => {
vi.clearAllMocks();
diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts
index 78a62896ef8e..8617030f12d9 100644
--- a/packages/bun/src/index.ts
+++ b/packages/bun/src/index.ts
@@ -31,8 +31,6 @@ export {
endSession,
withMonitor,
createTransport,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getClient,
isInitialized,
generateInstrumentOnce,
diff --git a/packages/core/src/getCurrentHubShim.ts b/packages/core/src/getCurrentHubShim.ts
deleted file mode 100644
index b76febd68b9b..000000000000
--- a/packages/core/src/getCurrentHubShim.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { addBreadcrumb } from './breadcrumbs';
-import type { Client } from './client';
-import { getClient, getCurrentScope, getIsolationScope, withScope } from './currentScopes';
-import {
- captureEvent,
- captureSession,
- endSession,
- setContext,
- setExtra,
- setExtras,
- setTag,
- setTags,
- setUser,
- startSession,
-} from './exports';
-import type { EventHint, Hub, Integration, SeverityLevel } from './types-hoist';
-
-/**
- * This is for legacy reasons, and returns a proxy object instead of a hub to be used.
- *
- * @deprecated Use the methods directly from the top level Sentry API (e.g. `Sentry.withScope`)
- * For more information see our migration guide for
- * [replacing `getCurrentHub` and `Hub`](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md#deprecate-hub)
- * usage
- */
-// eslint-disable-next-line deprecation/deprecation
-export function getCurrentHubShim(): Hub {
- return {
- bindClient(client: Client): void {
- const scope = getCurrentScope();
- scope.setClient(client);
- },
-
- withScope,
- getClient: () => getClient() as C | undefined,
- getScope: getCurrentScope,
- getIsolationScope,
- captureException: (exception: unknown, hint?: EventHint) => {
- return getCurrentScope().captureException(exception, hint);
- },
- captureMessage: (message: string, level?: SeverityLevel, hint?: EventHint) => {
- return getCurrentScope().captureMessage(message, level, hint);
- },
- captureEvent,
- addBreadcrumb,
- setUser,
- setTags,
- setTag,
- setExtra,
- setExtras,
- setContext,
-
- getIntegration(_integration: unknown): T | null {
- return null;
- },
-
- startSession,
- endSession,
- captureSession,
- };
-}
-
-/**
- * Returns the default hub instance.
- *
- * If a hub is already registered in the global carrier but this module
- * contains a more recent version, it replaces the registered version.
- * Otherwise, the currently registered hub will be returned.
- *
- * @deprecated Use the respective replacement method directly instead.
- */
-// eslint-disable-next-line deprecation/deprecation
-export const getCurrentHub = getCurrentHubShim;
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 6bea39b1db64..2c89d0e8a60b 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -115,9 +115,6 @@ export { trpcMiddleware } from './trpc';
export { captureFeedback } from './feedback';
export type { ReportDialogOptions } from './report-dialog';
-// eslint-disable-next-line deprecation/deprecation
-export { getCurrentHubShim, getCurrentHub } from './getCurrentHubShim';
-
// TODO: Make this structure pretty again and don't do "export *"
export * from './utils-hoist/index';
// TODO: Make this structure pretty again and don't do "export *"
diff --git a/packages/core/src/types-hoist/hub.ts b/packages/core/src/types-hoist/hub.ts
deleted file mode 100644
index bd270df39f5c..000000000000
--- a/packages/core/src/types-hoist/hub.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import type { Client } from '../client';
-import type { Scope } from '../scope';
-import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
-import type { Event, EventHint } from './event';
-import type { Extra, Extras } from './extra';
-import type { Integration } from './integration';
-import type { Primitive } from './misc';
-import type { Session } from './session';
-import type { SeverityLevel } from './severity';
-import type { User } from './user';
-
-/**
- * Internal class used to make sure we always have the latest internal functions
- * working in case we have a version conflict.
- *
- * @deprecated This interface will be removed in a future major version of the SDK in favour of
- * `Scope` and `Client` objects and APIs.
- *
- * Most APIs referencing `Hub` are themselves and will be removed in version 8 of the SDK. More information:
- * - [Migration Guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md#deprecate-hub)
- *
- */
-export interface Hub {
- /**
- * This binds the given client to the current scope.
- * @param client An SDK client (client) instance.
- *
- * @deprecated Use `initAndBind()` directly.
- */
- bindClient(client?: Client): void;
-
- /**
- * Creates a new scope with and executes the given operation within.
- * The scope is automatically removed once the operation
- * finishes or throws.
- *
- * This is essentially a convenience function for:
- *
- * pushScope();
- * callback();
- * popScope();
- *
- * @param callback that will be enclosed into push/popScope.
- *
- * @deprecated Use `Sentry.withScope()` instead.
- */
- withScope(callback: (scope: Scope) => T): T;
-
- /**
- * Returns the client of the top stack.
- * @deprecated Use `Sentry.getClient()` instead.
- */
- getClient(): C | undefined;
-
- /**
- * Returns the scope of the top stack.
- * @deprecated Use `Sentry.getCurrentScope()` instead.
- */
- getScope(): Scope;
-
- /**
- * Get the currently active isolation scope.
- * The isolation scope is used to isolate data between different hubs.
- *
- * @deprecated Use `Sentry.getIsolationScope()` instead.
- */
- getIsolationScope(): Scope;
-
- /**
- * Captures an exception event and sends it to Sentry.
- *
- * @param exception An exception-like object.
- * @param hint May contain additional information about the original exception.
- * @returns The generated eventId.
- *
- * @deprecated Use `Sentry.captureException()` instead.
- */
- captureException(exception: any, hint?: EventHint): string;
-
- /**
- * Captures a message event and sends it to Sentry.
- *
- * @param message The message to send to Sentry.
- * @param level Define the level of the message.
- * @param hint May contain additional information about the original exception.
- * @returns The generated eventId.
- *
- * @deprecated Use `Sentry.captureMessage()` instead.
- */
- captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string;
-
- /**
- * Captures a manually created event and sends it to Sentry.
- *
- * @param event The event to send to Sentry.
- * @param hint May contain additional information about the original exception.
- *
- * @deprecated Use `Sentry.captureEvent()` instead.
- */
- captureEvent(event: Event, hint?: EventHint): string;
-
- /**
- * Records a new breadcrumb which will be attached to future events.
- *
- * Breadcrumbs will be added to subsequent events to provide more context on
- * user's actions prior to an error or crash.
- *
- * @param breadcrumb The breadcrumb to record.
- * @param hint May contain additional information about the original breadcrumb.
- *
- * @deprecated Use `Sentry.addBreadcrumb()` instead.
- */
- addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void;
-
- /**
- * Updates user context information for future events.
- *
- * @param user User context object to be set in the current context. Pass `null` to unset the user.
- *
- * @deprecated Use `Sentry.setUser()` instead.
- */
- setUser(user: User | null): void;
-
- /**
- * Set an object that will be merged sent as tags data with the event.
- *
- * @param tags Tags context object to merge into current context.
- *
- * @deprecated Use `Sentry.setTags()` instead.
- */
- setTags(tags: { [key: string]: Primitive }): void;
-
- /**
- * Set key:value that will be sent as tags data with the event.
- *
- * Can also be used to unset a tag, by passing `undefined`.
- *
- * @param key String key of tag
- * @param value Value of tag
- *
- * @deprecated Use `Sentry.setTag()` instead.
- */
- setTag(key: string, value: Primitive): void;
-
- /**
- * Set key:value that will be sent as extra data with the event.
- * @param key String of extra
- * @param extra Any kind of data. This data will be normalized.
- *
- * @deprecated Use `Sentry.setExtra()` instead.
- */
- setExtra(key: string, extra: Extra): void;
-
- /**
- * Set an object that will be merged sent as extra data with the event.
- * @param extras Extras object to merge into current context.
- *
- * @deprecated Use `Sentry.setExtras()` instead.
- */
- setExtras(extras: Extras): void;
-
- /**
- * Sets context data with the given name.
- * @param name of the context
- * @param context Any kind of data. This data will be normalized.
- *
- * @deprecated Use `Sentry.setContext()` instead.
- */
- setContext(name: string, context: { [key: string]: any } | null): void;
-
- /**
- * Returns the integration if installed on the current client.
- *
- * @deprecated Use `Sentry.getClient().getIntegrationByName()` instead.
- */
- getIntegration(integration: unknown): T | null;
-
- /**
- * Starts a new `Session`, sets on the current scope and returns it.
- *
- * To finish a `session`, it has to be passed directly to `client.captureSession`, which is done automatically
- * when using `hub.endSession()` for the session currently stored on the scope.
- *
- * When there's already an existing session on the scope, it'll be automatically ended.
- *
- * @param context Optional properties of the new `Session`.
- *
- * @returns The session which was just started
- *
- * @deprecated Use top-level `startSession` instead.
- */
- startSession(context?: Session): Session;
-
- /**
- * Ends the session that lives on the current scope and sends it to Sentry
- *
- * @deprecated Use top-level `endSession` instead.
- */
- endSession(): void;
-
- /**
- * Sends the current session on the scope to Sentry
- *
- * @param endSession If set the session will be marked as exited and removed from the scope
- *
- * @deprecated Use top-level `captureSession` instead.
- */
- captureSession(endSession?: boolean): void;
-}
diff --git a/packages/core/src/types-hoist/index.ts b/packages/core/src/types-hoist/index.ts
index b1b1b942f32b..c1cbe5284808 100644
--- a/packages/core/src/types-hoist/index.ts
+++ b/packages/core/src/types-hoist/index.ts
@@ -55,8 +55,6 @@ export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent } from '
export type { EventProcessor } from './eventprocessor';
export type { Exception } from './exception';
export type { Extra, Extras } from './extra';
-// eslint-disable-next-line deprecation/deprecation
-export type { Hub } from './hub';
export type { Integration, IntegrationFn } from './integration';
export type { Mechanism } from './mechanism';
export type { ExtractedNodeRequestData, HttpHeaderValue, Primitive, WorkerLocation } from './misc';
diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts
index bbe501ffd88b..49f3daa93b8e 100644
--- a/packages/core/src/types-hoist/options.ts
+++ b/packages/core/src/types-hoist/options.ts
@@ -247,8 +247,7 @@ export interface ClientOptions): S
localStack.pop();
// When using synthetic events, we will have a 2 levels deep stack, as `new Error('Sentry syntheticException')`
- // is produced within the hub itself, making it:
+ // is produced within the scope itself, making it:
//
// Sentry.captureException()
- // getCurrentHub().captureException()
+ // scope.captureException()
//
// instead of just the top `Sentry` call itself.
// This forces us to possibly strip an additional frame in the exact same was as above.
diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts
index e7c1e4296dde..dd7558351911 100644
--- a/packages/google-cloud-serverless/src/index.ts
+++ b/packages/google-cloud-serverless/src/index.ts
@@ -12,8 +12,6 @@ export {
endSession,
withMonitor,
createTransport,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getClient,
isInitialized,
generateInstrumentOnce,
diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts
index a62d190ac8ed..6493e0280b3b 100644
--- a/packages/node/src/index.ts
+++ b/packages/node/src/index.ts
@@ -93,8 +93,6 @@ export {
getSpanDescendants,
parameterize,
getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getCurrentScope,
getIsolationScope,
getTraceData,
diff --git a/packages/node/src/otel/contextManager.ts b/packages/node/src/otel/contextManager.ts
index e0e7da218326..252508eb7c88 100644
--- a/packages/node/src/otel/contextManager.ts
+++ b/packages/node/src/otel/contextManager.ts
@@ -3,7 +3,7 @@ import { wrapContextManagerClass } from '@sentry/opentelemetry';
/**
* This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager.
- * It ensures that we create a new hub per context, so that the OTEL Context & the Sentry Hub are always in sync.
+ * It ensures that we create a new hub per context, so that the OTEL Context & the Sentry Scopes are always in sync.
*
* Note that we currently only support AsyncHooks with this,
* but since this should work for Node 14+ anyhow that should be good enough.
diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts
index 24b88263c97b..43a93d16b563 100644
--- a/packages/node/src/types.ts
+++ b/packages/node/src/types.ts
@@ -35,8 +35,7 @@ export interface BaseNodeOptions {
* Profiling is enabled if either this or `profilesSampleRate` is defined. If both are defined, `profilesSampleRate` is
* ignored.
*
- * Will automatically be passed a context object of default and optional custom data. See
- * {@link Transaction.samplingContext} and {@link Hub.startTransaction}.
+ * Will automatically be passed a context object of default and optional custom data.
*
* @returns A sample rate between 0 and 1 (0 drops the profile, 1 guarantees it will be sent). Returning `true` is
* equivalent to returning 1 and returning `false` is equivalent to returning 0.
diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts
index 19a61f0f130f..e3ae6536e11e 100644
--- a/packages/opentelemetry/src/index.ts
+++ b/packages/opentelemetry/src/index.ts
@@ -37,8 +37,6 @@ export {
export { suppressTracing } from './utils/suppressTracing';
-// eslint-disable-next-line deprecation/deprecation
-export { getCurrentHubShim } from '@sentry/core';
export { setupEventContextTrace } from './setupEventContextTrace';
export { setOpenTelemetryContextAsyncContextStrategy } from './asyncContextStrategy';
diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts
index 640282b57bed..9084284217a9 100644
--- a/packages/remix/src/index.server.ts
+++ b/packages/remix/src/index.server.ts
@@ -47,8 +47,6 @@ export {
getActiveSpan,
getAutoPerformanceIntegrations,
getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getCurrentScope,
getDefaultIntegrations,
getGlobalScope,
diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts
index 27325bbe621f..5cfb7114bbbc 100644
--- a/packages/remix/src/index.types.ts
+++ b/packages/remix/src/index.types.ts
@@ -25,9 +25,6 @@ export declare function captureRemixServerException(err: unknown, name: string,
// methods from `@sentry/core`.
declare const runtime: 'client' | 'server';
-// eslint-disable-next-line deprecation/deprecation
-export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
-
export const close = runtime === 'client' ? clientSdk.close : serverSdk.close;
export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush;
export const lastEventId = runtime === 'client' ? clientSdk.lastEventId : serverSdk.lastEventId;
diff --git a/packages/replay-internal/test/unit/session/createSession.test.ts b/packages/replay-internal/test/unit/session/createSession.test.ts
index 8cd186f4aeea..05db8c2fd96e 100644
--- a/packages/replay-internal/test/unit/session/createSession.test.ts
+++ b/packages/replay-internal/test/unit/session/createSession.test.ts
@@ -2,12 +2,10 @@
* @vitest-environment jsdom
*/
-import type { MockedFunction } from 'vitest';
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
-import * as Sentry from '@sentry/core';
+import type * as Sentry from '@sentry/core';
-import type { Hub } from '@sentry/core';
import { WINDOW } from '../../../src/constants';
import { createSession } from '../../../src/session/createSession';
import { saveSession } from '../../../src/session/saveSession';
@@ -21,19 +19,11 @@ vi.mock('@sentry/core', async () => {
};
});
-type CaptureEventMockType = MockedFunction;
-
describe('Unit | session | createSession', () => {
- const captureEventMock: CaptureEventMockType = vi.fn();
+ const captureEventMock = vi.fn();
beforeAll(() => {
WINDOW.sessionStorage.clear();
- vi.spyOn(Sentry, 'getCurrentHub').mockImplementation(() => {
- return {
- captureEvent: captureEventMock,
- // eslint-disable-next-line deprecation/deprecation
- } as unknown as Hub;
- });
});
afterEach(() => {
diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts
index 599739c07084..a6362af1c7f4 100644
--- a/packages/solidstart/src/server/index.ts
+++ b/packages/solidstart/src/server/index.ts
@@ -39,8 +39,6 @@ export {
getActiveSpan,
getAutoPerformanceIntegrations,
getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getCurrentScope,
getDefaultIntegrations,
getGlobalScope,
diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts
index 6f45425e33e0..3ad8b728bb5f 100644
--- a/packages/sveltekit/src/index.types.ts
+++ b/packages/sveltekit/src/index.types.ts
@@ -42,9 +42,6 @@ export declare const contextLinesIntegration: typeof clientSdk.contextLinesInteg
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
-// eslint-disable-next-line deprecation/deprecation
-export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
-
export declare function close(timeout?: number | undefined): PromiseLike;
export declare function flush(timeout?: number | undefined): PromiseLike;
export declare function lastEventId(): string | undefined;
diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts
index 690869593a7b..232e0562eb22 100644
--- a/packages/sveltekit/src/server/index.ts
+++ b/packages/sveltekit/src/server/index.ts
@@ -39,8 +39,6 @@ export {
getActiveSpan,
getAutoPerformanceIntegrations,
getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
getCurrentScope,
getDefaultIntegrations,
getGlobalScope,
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 346ef1cda36a..91950b2c8b38 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -73,7 +73,6 @@ import type {
HandlerDataUnhandledRejection as HandlerDataUnhandledRejection_imported,
HandlerDataXhr as HandlerDataXhr_imported,
HttpHeaderValue as HttpHeaderValue_imported,
- Hub as Hub_imported,
InProgressCheckIn as InProgressCheckIn_imported,
InformationUnit as InformationUnit_imported,
Integration as Integration_imported,
@@ -299,9 +298,6 @@ export type Extra = Extra_imported;
/** @deprecated This type has been moved to `@sentry/core`. */
export type Extras = Extras_imported;
/** @deprecated This type has been moved to `@sentry/core`. */
-// eslint-disable-next-line deprecation/deprecation
-export type Hub = Hub_imported;
-/** @deprecated This type has been moved to `@sentry/core`. */
export type Integration = Integration_imported;
/** @deprecated This type has been moved to `@sentry/core`. */
// eslint-disable-next-line deprecation/deprecation
From 6e4b593adcc4ce951afa8ae0cda0605ecd226cda Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 14:00:32 +0100
Subject: [PATCH 17/59] feat(node): Add `prismaInstrumentation` option to
Prisma integration as escape hatch for all Prisma versions (#15127)
---
docs/migration/v8-to-v9.md | 35 ++++-
.../node/src/integrations/tracing/prisma.ts | 120 ++++++++++++------
2 files changed, 110 insertions(+), 45 deletions(-)
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index 9b9ee2b77b24..a6bd0aa35e7c 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -107,12 +107,35 @@ Older Typescript versions _may_ still work, but we will not test them anymore an
- The `childProcessIntegration`'s (previously `processThreadBreadcrumbIntegration`) `name` value has been changed from `"ProcessAndThreadBreadcrumbs"` to `"ChildProcess"`. This is relevant if you were filtering integrations by name.
-- The Prisma integration no longer supports Prisma v5. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration.
-
- For performance instrumentation using Prisma v5:
-
- 1. Install the `@prisma/instrumentation` package on version 5
- 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the the `Sentry.init()`'s `openTelemetryInstrumentations` option.
+- The Prisma integration no longer supports Prisma v5 and supports Prisma v6 by default. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration.
+
+ For performance instrumentation using other/older Prisma versions:
+
+ 1. Install the `@prisma/instrumentation` package with the desired version.
+ 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the `prismaInstrumentation` option of this integration:
+
+ ```js
+ import { PrismaInstrumentation } from '@prisma/instrumentation';
+ Sentry.init({
+ integrations: [
+ prismaIntegration({
+ // Override the default instrumentation that Sentry uses
+ prismaInstrumentation: new PrismaInstrumentation(),
+ }),
+ ],
+ });
+ ```
+
+ The passed instrumentation instance will override the default instrumentation instance the integration would use, while the `prismaIntegration` will still ensure data compatibility for the various Prisma versions.
+
+ 1. Depending on your Prisma version (prior to Prisma version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema:
+
+ ```
+ generator client {
+ provider = "prisma-client-js"
+ previewFeatures = ["tracing"]
+ }
+ ```
### `@sentry/browser`
diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts
index bf1013d405ae..ec9bc149410d 100644
--- a/packages/node/src/integrations/tracing/prisma.ts
+++ b/packages/node/src/integrations/tracing/prisma.ts
@@ -1,55 +1,97 @@
+import type { Instrumentation } from '@opentelemetry/instrumentation';
// When importing CJS modules into an ESM module, we cannot import the named exports directly.
import * as prismaInstrumentation from '@prisma/instrumentation';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
-import type { IntegrationFn } from '@sentry/core';
import { generateInstrumentOnce } from '../../otel/instrument';
const INTEGRATION_NAME = 'Prisma';
-export const instrumentPrisma = generateInstrumentOnce(INTEGRATION_NAME, () => {
- const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
- // @ts-expect-error We need to do the following for interop reasons
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
+export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>(
+ INTEGRATION_NAME,
+ options => {
+ // Use a passed instrumentation instance to support older Prisma versions
+ if (options?.prismaInstrumentation) {
+ return options.prismaInstrumentation;
+ }
- return new EsmInteropPrismaInstrumentation({});
-});
+ const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
+ // @ts-expect-error We need to do the following for interop reasons
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
-const _prismaIntegration = (() => {
- return {
- name: INTEGRATION_NAME,
- setupOnce() {
- instrumentPrisma();
- },
-
- setup(client) {
- client.on('spanStart', span => {
- const spanJSON = spanToJSON(span);
- if (spanJSON.description?.startsWith('prisma:')) {
- span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma');
- }
-
- // Make sure we use the query text as the span name, for ex. SELECT * FROM "User" WHERE "id" = $1
- if (spanJSON.description === 'prisma:engine:db_query' && spanJSON.data?.['db.query.text']) {
- span.updateName(spanJSON.data['db.query.text'] as string);
- }
- });
- },
- };
-}) satisfies IntegrationFn;
+ return new EsmInteropPrismaInstrumentation({});
+ },
+);
/**
* Adds Sentry tracing instrumentation for the [prisma](https://www.npmjs.com/package/prisma) library.
- *
* For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/).
*
- * @example
- * ```javascript
- * const Sentry = require('@sentry/node');
+ * NOTE: By default, this integration works with Prisma version 6.
+ * To get performance instrumentation for other Prisma versions,
+ * 1. Install the `@prisma/instrumentation` package with the desired version.
+ * 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the `prismaInstrumentation` option of this integration:
+ *
+ * ```js
+ * import { PrismaInstrumentation } from '@prisma/instrumentation'
*
- * Sentry.init({
- * integrations: [Sentry.prismaIntegration()],
- * });
- * ```
+ * Sentry.init({
+ * integrations: [
+ * prismaIntegration({
+ * // Override the default instrumentation that Sentry uses
+ * prismaInstrumentation: new PrismaInstrumentation()
+ * })
+ * ]
+ * })
+ * ```
+ *
+ * The passed instrumentation instance will override the default instrumentation instance the integration would use, while the `prismaIntegration` will still ensure data compatibility for the various Prisma versions.
+ * 1. Depending on your Prisma version (prior to version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema:
+ *
+ * ```
+ * generator client {
+ * provider = "prisma-client-js"
+ * previewFeatures = ["tracing"]
+ * }
+ * ```
*/
-export const prismaIntegration = defineIntegration(_prismaIntegration);
+export const prismaIntegration = defineIntegration(
+ ({
+ prismaInstrumentation,
+ }: {
+ /**
+ * Overrides the instrumentation used by the Sentry SDK with the passed in instrumentation instance.
+ *
+ * NOTE: By default, the Sentry SDK uses the Prisma v6 instrumentation. Use this option if you need performance instrumentation different Prisma versions.
+ *
+ * For more information refer to the documentation of `prismaIntegration()` or see https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/
+ */
+ prismaInstrumentation?: Instrumentation;
+ } = {}) => {
+ return {
+ name: INTEGRATION_NAME,
+ setupOnce() {
+ instrumentPrisma({ prismaInstrumentation });
+ },
+ setup(client) {
+ client.on('spanStart', span => {
+ const spanJSON = spanToJSON(span);
+ if (spanJSON.description?.startsWith('prisma:')) {
+ span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.prisma');
+ }
+
+ // Make sure we use the query text as the span name, for ex. SELECT * FROM "User" WHERE "id" = $1
+ if (spanJSON.description === 'prisma:engine:db_query' && spanJSON.data['db.query.text']) {
+ span.updateName(spanJSON.data['db.query.text'] as string);
+ }
+
+ // In Prisma v5.22+, the `db.system` attribute is automatically set
+ // On older versions, this is missing, so we add it here
+ if (spanJSON.description === 'prisma:engine:db_query' && !spanJSON.data['db.system']) {
+ span.setAttribute('db.system', 'prisma');
+ }
+ });
+ },
+ };
+ },
+);
From fe9c34cbfea9d47b81da33e625f88142f93e39cc Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 13:35:56 +0000
Subject: [PATCH 18/59] meta(changelog): Update changelog for 9.0.0-alpha.0
---
CHANGELOG.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb3cdbfd1fea..dcbfecab7da4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,14 @@ Now, there are two options to set up the SDK:
);
```
+## 9.0.0-alpha.0
+
+This is an alpha release of the upcoming major release of version 9.
+This release does not yet entail a comprehensive changelog as version 9 is not yet stable.
+
+For this release's iteration of the migration guide, see the [Migration Guide as per `9.0.0-alpha.0`](https://github.com/getsentry/sentry-javascript/blob/6e4b593adcc4ce951afa8ae0cda0605ecd226cda/docs/migration/v8-to-v9.md).
+Please note that the migration guide is work in progress and subject to change.
+
## 8.45.0
- feat(core): Add `handled` option to `captureConsoleIntegration` ([#14664](https://github.com/getsentry/sentry-javascript/pull/14664))
From 021732657919f90b0cfa9a8e4a047d3e06b52b2d Mon Sep 17 00:00:00 2001
From: getsentry-bot
Date: Wed, 22 Jan 2025 14:42:38 +0000
Subject: [PATCH 19/59] release: 9.0.0-alpha.0
---
.../browser-integration-tests/package.json | 4 ++--
.../bundle-analyzer-scenarios/package.json | 2 +-
dev-packages/clear-cache-gh-action/package.json | 2 +-
dev-packages/e2e-tests/package.json | 2 +-
.../external-contributor-gh-action/package.json | 2 +-
dev-packages/node-integration-tests/package.json | 8 ++++----
dev-packages/rollup-utils/package.json | 2 +-
dev-packages/size-limit-gh-action/package.json | 2 +-
dev-packages/test-utils/package.json | 4 ++--
lerna.json | 2 +-
packages/angular/package.json | 6 +++---
packages/astro/package.json | 8 ++++----
packages/aws-serverless/package.json | 6 +++---
packages/browser-utils/package.json | 4 ++--
packages/browser/package.json | 14 +++++++-------
packages/bun/package.json | 8 ++++----
packages/cloudflare/package.json | 4 ++--
packages/core/package.json | 2 +-
packages/deno/package.json | 4 ++--
packages/ember/package.json | 6 +++---
packages/eslint-config-sdk/package.json | 6 +++---
packages/eslint-plugin-sdk/package.json | 2 +-
packages/feedback/package.json | 4 ++--
packages/gatsby/package.json | 6 +++---
packages/google-cloud-serverless/package.json | 6 +++---
packages/integration-shims/package.json | 4 ++--
packages/nestjs/package.json | 6 +++---
packages/nextjs/package.json | 14 +++++++-------
packages/node/package.json | 14 +++++++++-----
packages/nuxt/package.json | 12 ++++++------
packages/opentelemetry/package.json | 4 ++--
packages/profiling-node/package.json | 6 +++---
packages/react/package.json | 6 +++---
packages/remix/package.json | 10 +++++-----
packages/replay-canvas/package.json | 6 +++---
packages/replay-internal/package.json | 8 ++++----
packages/replay-worker/package.json | 2 +-
packages/solid/package.json | 6 +++---
packages/solidstart/package.json | 10 +++++-----
packages/svelte/package.json | 6 +++---
packages/sveltekit/package.json | 10 +++++-----
packages/types/package.json | 4 ++--
packages/typescript/package.json | 2 +-
packages/vercel-edge/package.json | 6 +++---
packages/vue/package.json | 6 +++---
packages/wasm/package.json | 6 +++---
46 files changed, 134 insertions(+), 130 deletions(-)
diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json
index 58e378a0c582..bfd1ed3d2717 100644
--- a/dev-packages/browser-integration-tests/package.json
+++ b/dev-packages/browser-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/browser-integration-tests",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"main": "index.js",
"license": "MIT",
"engines": {
@@ -43,7 +43,7 @@
"@babel/preset-typescript": "^7.16.7",
"@playwright/test": "^1.44.1",
"@sentry-internal/rrweb": "2.31.0",
- "@sentry/browser": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
"axios": "1.7.7",
"babel-loader": "^8.2.2",
"fflate": "0.8.2",
diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json
index c5146697c4d4..fc9c659d8509 100644
--- a/dev-packages/bundle-analyzer-scenarios/package.json
+++ b/dev-packages/bundle-analyzer-scenarios/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/bundle-analyzer-scenarios",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Scenarios to test bundle analysis with",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios",
diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json
index acf92547dca5..47764f3756c6 100644
--- a/dev-packages/clear-cache-gh-action/package.json
+++ b/dev-packages/clear-cache-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/clear-cache-gh-action",
"description": "An internal Github Action to clear GitHub caches.",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json
index 958b1645e1c0..669be320b4a1 100644
--- a/dev-packages/e2e-tests/package.json
+++ b/dev-packages/e2e-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/e2e-tests",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"license": "MIT",
"private": true,
"scripts": {
diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json
index a4857bd41082..aa3aeec3d29f 100644
--- a/dev-packages/external-contributor-gh-action/package.json
+++ b/dev-packages/external-contributor-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/external-contributor-gh-action",
"description": "An internal Github Action to add external contributors to the CHANGELOG.md file.",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json
index 6680405b3ac1..571397d12c8c 100644
--- a/dev-packages/node-integration-tests/package.json
+++ b/dev-packages/node-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/node-integration-tests",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -31,9 +31,9 @@
"@nestjs/core": "10.4.6",
"@nestjs/platform-express": "10.4.6",
"@prisma/client": "6.2.1",
- "@sentry/aws-serverless": "8.45.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
+ "@sentry/aws-serverless": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
"@types/mongodb": "^3.6.20",
"@types/mysql": "^2.15.21",
"@types/pg": "^8.6.5",
diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json
index 763f043da327..30c4e5c8fc34 100644
--- a/dev-packages/rollup-utils/package.json
+++ b/dev-packages/rollup-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/rollup-utils",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils",
diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json
index c40c18645d6a..9021bc543c5d 100644
--- a/dev-packages/size-limit-gh-action/package.json
+++ b/dev-packages/size-limit-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/size-limit-gh-action",
"description": "An internal Github Action to compare the current size of a PR against the one on develop.",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json
index 8e8afec9f698..5758b5229e80 100644
--- a/dev-packages/test-utils/package.json
+++ b/dev-packages/test-utils/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"name": "@sentry-internal/test-utils",
"author": "Sentry",
"license": "MIT",
@@ -45,7 +45,7 @@
},
"devDependencies": {
"@playwright/test": "^1.44.1",
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/lerna.json b/lerna.json
index febf4090d555..aeec6a76eb9b 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"npmClient": "yarn"
}
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 1ec948299d01..80626cc5cdbe 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/angular",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Angular",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular",
@@ -21,8 +21,8 @@
"rxjs": "^6.5.5 || ^7.x"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 1103c6df1093..316907ee1d30 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/astro",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Astro",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro",
@@ -56,9 +56,9 @@
"astro": ">=3.x || >=4.0.0-beta || >=5.x"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
"@sentry/vite-plugin": "^2.22.6"
},
"devDependencies": {
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index ed3900d0d86f..a441069071e1 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/aws-serverless",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless",
@@ -68,8 +68,8 @@
"@opentelemetry/instrumentation": "^0.57.1",
"@opentelemetry/instrumentation-aws-lambda": "0.50.2",
"@opentelemetry/instrumentation-aws-sdk": "0.49.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
"@types/aws-lambda": "^8.10.62"
},
"devDependencies": {
diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json
index b18c3e3dfa51..b96ebb51962c 100644
--- a/packages/browser-utils/package.json
+++ b/packages/browser-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/browser-utils",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Browser Utilities for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/browser/package.json b/packages/browser/package.json
index 23563cf5e752..33c3e0e3e350 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/browser",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for browsers",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser",
@@ -39,14 +39,14 @@
"access": "public"
},
"dependencies": {
- "@sentry-internal/browser-utils": "8.45.0",
- "@sentry-internal/feedback": "8.45.0",
- "@sentry-internal/replay": "8.45.0",
- "@sentry-internal/replay-canvas": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry-internal/browser-utils": "9.0.0-alpha.0",
+ "@sentry-internal/feedback": "9.0.0-alpha.0",
+ "@sentry-internal/replay": "9.0.0-alpha.0",
+ "@sentry-internal/replay-canvas": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"devDependencies": {
- "@sentry-internal/integration-shims": "8.45.0",
+ "@sentry-internal/integration-shims": "9.0.0-alpha.0",
"fake-indexeddb": "^4.0.1"
},
"scripts": {
diff --git a/packages/bun/package.json b/packages/bun/package.json
index 7f72b54d9894..a3f131277e50 100644
--- a/packages/bun/package.json
+++ b/packages/bun/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/bun",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for bun",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun",
@@ -39,9 +39,9 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0"
},
"devDependencies": {
"bun-types": "latest"
diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json
index 2d114652b6ab..2e32fc07ff1e 100644
--- a/packages/cloudflare/package.json
+++ b/packages/cloudflare/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/cloudflare",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Cloudflare Workers and Pages",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"optionalDependencies": {
"@cloudflare/workers-types": "^4.x"
diff --git a/packages/core/package.json b/packages/core/package.json
index 03d947c37bca..06e8d253a628 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/core",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Base implementation for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core",
diff --git a/packages/deno/package.json b/packages/deno/package.json
index ea9aaaa3c02a..979aac81380a 100644
--- a/packages/deno/package.json
+++ b/packages/deno/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/deno",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Deno",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno",
@@ -24,7 +24,7 @@
"/build"
],
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"scripts": {
"deno-types": "node ./scripts/download-deno-types.mjs",
diff --git a/packages/ember/package.json b/packages/ember/package.json
index c0b03b81be2d..57caba8029e4 100644
--- a/packages/ember/package.json
+++ b/packages/ember/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/ember",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Ember.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember",
@@ -32,8 +32,8 @@
"dependencies": {
"@babel/core": "^7.24.4",
"@embroider/macros": "^1.16.0",
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
"ember-auto-import": "^2.7.2",
"ember-cli-babel": "^8.2.0",
"ember-cli-htmlbars": "^6.1.1",
diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json
index fc85554ac878..97b01fdbac88 100644
--- a/packages/eslint-config-sdk/package.json
+++ b/packages/eslint-config-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/eslint-config-sdk",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK eslint config",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk",
@@ -22,8 +22,8 @@
"access": "public"
},
"dependencies": {
- "@sentry-internal/eslint-plugin-sdk": "8.45.0",
- "@sentry-internal/typescript": "8.45.0",
+ "@sentry-internal/eslint-plugin-sdk": "9.0.0-alpha.0",
+ "@sentry-internal/typescript": "9.0.0-alpha.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint-config-prettier": "^6.11.0",
diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json
index 402a304747d5..5fd9990b411c 100644
--- a/packages/eslint-plugin-sdk/package.json
+++ b/packages/eslint-plugin-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/eslint-plugin-sdk",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK eslint plugin",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk",
diff --git a/packages/feedback/package.json b/packages/feedback/package.json
index 3aaa8c2838b8..d2a72405c400 100644
--- a/packages/feedback/package.json
+++ b/packages/feedback/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/feedback",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Sentry SDK integration for user feedback",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"devDependencies": {
"preact": "^10.19.4"
diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json
index 460ee1cc1b51..4231b055c59b 100644
--- a/packages/gatsby/package.json
+++ b/packages/gatsby/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/gatsby",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Gatsby.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby",
@@ -45,8 +45,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/react": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/react": "9.0.0-alpha.0",
"@sentry/webpack-plugin": "2.22.7"
},
"peerDependencies": {
diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json
index 8f0fd7cde687..0f24baa37e8b 100644
--- a/packages/google-cloud-serverless/package.json
+++ b/packages/google-cloud-serverless/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/google-cloud-serverless",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Google Cloud Functions",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud-serverless",
@@ -48,8 +48,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
"@types/express": "^4.17.14"
},
"devDependencies": {
diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json
index f65568707e25..4f2c8c3af599 100644
--- a/packages/integration-shims/package.json
+++ b/packages/integration-shims/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/integration-shims",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Shims for integrations in Sentry SDK.",
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
@@ -55,7 +55,7 @@
"url": "https://github.com/getsentry/sentry-javascript/issues"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json
index 234d7df490bd..84e572984005 100644
--- a/packages/nestjs/package.json
+++ b/packages/nestjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nestjs",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for NestJS",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs",
@@ -47,8 +47,8 @@
"@opentelemetry/core": "^1.30.1",
"@opentelemetry/instrumentation": "^0.57.1",
"@opentelemetry/instrumentation-nestjs-core": "0.44.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0"
},
"devDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index cc51b50f6fe1..51b57300a532 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nextjs",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Next.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs",
@@ -79,12 +79,12 @@
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@rollup/plugin-commonjs": "28.0.1",
- "@sentry-internal/browser-utils": "8.45.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
- "@sentry/react": "8.45.0",
- "@sentry/vercel-edge": "8.45.0",
+ "@sentry-internal/browser-utils": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
+ "@sentry/react": "9.0.0-alpha.0",
+ "@sentry/vercel-edge": "9.0.0-alpha.0",
"@sentry/webpack-plugin": "2.22.7",
"chalk": "3.0.0",
"resolve": "1.22.8",
diff --git a/packages/node/package.json b/packages/node/package.json
index c745c0f8192b..d7260379a48f 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/node",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Sentry Node SDK using OpenTelemetry for performance instrumentation",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node",
@@ -9,7 +9,9 @@
"engines": {
"node": ">=18"
},
- "files": ["/build"],
+ "files": [
+ "/build"
+ ],
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/types/index.d.ts",
@@ -54,7 +56,9 @@
},
"typesVersions": {
"<5.0": {
- "build/types/index.d.ts": ["build/types-ts3.8/index.d.ts"]
+ "build/types/index.d.ts": [
+ "build/types-ts3.8/index.d.ts"
+ ]
}
},
"publishConfig": {
@@ -92,8 +96,8 @@
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@prisma/instrumentation": "6.2.1",
- "@sentry/core": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
"import-in-the-middle": "^1.12.0"
},
"devDependencies": {
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index bee5e78f7582..eabcb85da71e 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nuxt",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Nuxt (EXPERIMENTAL)",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nuxt",
@@ -43,13 +43,13 @@
},
"dependencies": {
"@nuxt/kit": "^3.13.2",
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
"@sentry/rollup-plugin": "2.22.7",
"@sentry/vite-plugin": "2.22.6",
- "@sentry/vue": "8.45.0"
+ "@sentry/vue": "9.0.0-alpha.0"
},
"devDependencies": {
"@nuxt/module-builder": "^0.8.4",
diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json
index 5436676e1359..06d1034ecc06 100644
--- a/packages/opentelemetry/package.json
+++ b/packages/opentelemetry/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/opentelemetry",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry utilities for OpenTelemetry",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0",
diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json
index 0c50ead2bd53..d07295ba2679 100644
--- a/packages/profiling-node/package.json
+++ b/packages/profiling-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/profiling-node",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Node.js Profiling",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/profiling-node",
@@ -76,8 +76,8 @@
"test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js"
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
"detect-libc": "^2.0.2",
"node-abi": "^3.61.0"
},
diff --git a/packages/react/package.json b/packages/react/package.json
index c4ec86d36fc4..43937b525be2 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/react",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for React.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
diff --git a/packages/remix/package.json b/packages/remix/package.json
index afca2a11055a..6c88c8f08797 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/remix",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Remix",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix",
@@ -55,10 +55,10 @@
"@opentelemetry/api": "^1.9.0",
"@remix-run/router": "1.x",
"@sentry/cli": "^2.39.1",
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
- "@sentry/react": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
+ "@sentry/react": "9.0.0-alpha.0",
"glob": "^10.3.4",
"opentelemetry-instrumentation-remix": "0.8.0",
"yargs": "^17.6.0"
diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json
index 8e75f5764e91..c7345547378f 100644
--- a/packages/replay-canvas/package.json
+++ b/packages/replay-canvas/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay-canvas",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Replay canvas integration",
"main": "build/npm/cjs/index.js",
"module": "build/npm/esm/index.js",
@@ -68,8 +68,8 @@
"@sentry-internal/rrweb": "2.31.0"
},
"dependencies": {
- "@sentry-internal/replay": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry-internal/replay": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json
index 9c03c07ba691..15307b073666 100644
--- a/packages/replay-internal/package.json
+++ b/packages/replay-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "User replays for Sentry",
"main": "build/npm/cjs/index.js",
"module": "build/npm/esm/index.js",
@@ -68,7 +68,7 @@
"homepage": "https://docs.sentry.io/platforms/javascript/session-replay/",
"devDependencies": {
"@babel/core": "^7.17.5",
- "@sentry-internal/replay-worker": "8.45.0",
+ "@sentry-internal/replay-worker": "9.0.0-alpha.0",
"@sentry-internal/rrweb": "2.31.0",
"@sentry-internal/rrweb-snapshot": "2.31.0",
"fflate": "0.8.2",
@@ -76,8 +76,8 @@
"jsdom-worker": "^0.2.1"
},
"dependencies": {
- "@sentry-internal/browser-utils": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry-internal/browser-utils": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json
index 04e2835b5f51..f161467d64be 100644
--- a/packages/replay-worker/package.json
+++ b/packages/replay-worker/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay-worker",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Worker for @sentry-internal/replay",
"main": "build/esm/index.js",
"module": "build/esm/index.js",
diff --git a/packages/solid/package.json b/packages/solid/package.json
index 266e1b197b52..dbc847d346cd 100644
--- a/packages/solid/package.json
+++ b/packages/solid/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/solid",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Solid",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solid",
@@ -44,8 +44,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"peerDependencies": {
"@solidjs/router": "^0.13.4",
diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json
index e64c0d5d4b20..35a060b9e99b 100644
--- a/packages/solidstart/package.json
+++ b/packages/solidstart/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/solidstart",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Solid Start",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solidstart",
@@ -66,10 +66,10 @@
}
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
- "@sentry/solid": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
+ "@sentry/solid": "9.0.0-alpha.0",
"@sentry/vite-plugin": "2.22.6"
},
"devDependencies": {
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index f09d41642f2b..b2d492d6c08d 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/svelte",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Svelte",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0",
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0",
"magic-string": "^0.30.0"
},
"peerDependencies": {
diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json
index caf46d50b3d7..077988a47bd1 100644
--- a/packages/sveltekit/package.json
+++ b/packages/sveltekit/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/sveltekit",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for SvelteKit",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit",
@@ -40,10 +40,10 @@
}
},
"dependencies": {
- "@sentry/core": "8.45.0",
- "@sentry/node": "8.45.0",
- "@sentry/opentelemetry": "8.45.0",
- "@sentry/svelte": "8.45.0",
+ "@sentry/core": "9.0.0-alpha.0",
+ "@sentry/node": "9.0.0-alpha.0",
+ "@sentry/opentelemetry": "9.0.0-alpha.0",
+ "@sentry/svelte": "9.0.0-alpha.0",
"@sentry/vite-plugin": "2.22.6",
"magic-string": "0.30.7",
"magicast": "0.2.8",
diff --git a/packages/types/package.json b/packages/types/package.json
index 5f62f34c4f98..edfa3f034810 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/types",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Types for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types",
@@ -56,7 +56,7 @@
"yalc:publish": "yalc publish --push --sig"
},
"dependencies": {
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/packages/typescript/package.json b/packages/typescript/package.json
index ed0c5d8bc75e..cc18fd6ab488 100644
--- a/packages/typescript/package.json
+++ b/packages/typescript/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/typescript",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Typescript configuration used at Sentry",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript",
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
index 1541a445d9bd..7878acf97cce 100644
--- a/packages/vercel-edge/package.json
+++ b/packages/vercel-edge/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/vercel-edge",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for the Vercel Edge Runtime",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge",
@@ -40,7 +40,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@sentry/core": "8.45.0"
+ "@sentry/core": "9.0.0-alpha.0"
},
"devDependencies": {
"@edge-runtime/types": "3.0.1",
@@ -48,7 +48,7 @@
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.28.0",
- "@sentry/opentelemetry": "8.45.0"
+ "@sentry/opentelemetry": "9.0.0-alpha.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/vue/package.json b/packages/vue/package.json
index 1bab4c86066e..5a431738253b 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/vue",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Official Sentry SDK for Vue.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"peerDependencies": {
"pinia": "2.x",
diff --git a/packages/wasm/package.json b/packages/wasm/package.json
index 78c19b19c30b..6d863483c1c9 100644
--- a/packages/wasm/package.json
+++ b/packages/wasm/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/wasm",
- "version": "8.45.0",
+ "version": "9.0.0-alpha.0",
"description": "Support for WASM.",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "8.45.0",
- "@sentry/core": "8.45.0"
+ "@sentry/browser": "9.0.0-alpha.0",
+ "@sentry/core": "9.0.0-alpha.0"
},
"scripts": {
"build": "run-p build:transpile build:bundle build:types",
From 035022b3a4a59e99f00b4c6c6b0bd5c0206123e8 Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Wed, 22 Jan 2025 15:27:58 +0000
Subject: [PATCH 20/59] fix test
---
dev-packages/e2e-tests/test-applications/astro-5/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json
index 41724788f169..170c716be756 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json
@@ -15,7 +15,7 @@
"@astrojs/node": "^9.0.0",
"@playwright/test": "^1.46.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
- "@sentry/astro": "^8.42.0",
+ "@sentry/astro": "latest || *",
"astro": "^5.0.3"
},
"pnpm": {
From 38b7b56762c43298973bde3f458638d3c0a81734 Mon Sep 17 00:00:00 2001
From: Philipp Hofmann
Date: Wed, 22 Jan 2025 16:34:19 +0100
Subject: [PATCH 21/59] chore: Increase date range for MIT licence
It's 2025 now.
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/LICENSE b/LICENSE
index 4e1c2c384991..98482f33b3be 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2012-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
From 7b0cff11d0a6d4368d171535c27e68f7d0efd519 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Wed, 22 Jan 2025 10:38:38 -0500
Subject: [PATCH 22/59] update remaining libraries
---
packages/angular/LICENSE | 2 +-
packages/astro/LICENSE | 2 +-
packages/aws-serverless/LICENSE | 2 +-
packages/browser-utils/LICENSE | 2 +-
packages/browser/LICENSE | 2 +-
packages/bun/LICENSE | 2 +-
packages/cloudflare/LICENSE | 2 +-
packages/core/LICENSE | 2 +-
packages/deno/LICENSE | 2 +-
packages/ember/LICENSE | 2 +-
packages/eslint-config-sdk/LICENSE | 2 +-
packages/eslint-plugin-sdk/LICENSE | 2 +-
packages/feedback/LICENSE | 2 +-
packages/gatsby/LICENSE | 2 +-
packages/google-cloud-serverless/LICENSE | 2 +-
packages/integration-shims/LICENSE | 2 +-
packages/nestjs/LICENSE | 2 +-
packages/nextjs/LICENSE | 2 +-
packages/node/LICENSE | 2 +-
packages/nuxt/LICENSE | 2 +-
packages/opentelemetry/LICENSE | 2 +-
packages/profiling-node/LICENSE | 2 +-
packages/react/LICENSE | 2 +-
packages/remix/LICENSE | 2 +-
packages/replay-canvas/LICENSE | 2 +-
packages/replay-internal/LICENSE | 2 +-
packages/replay-worker/LICENSE | 2 +-
packages/solid/LICENSE | 2 +-
packages/solidstart/LICENSE | 2 +-
packages/svelte/LICENSE | 2 +-
packages/sveltekit/LICENSE | 2 +-
packages/types/LICENSE | 2 +-
packages/typescript/LICENSE | 2 +-
packages/vercel-edge/LICENSE | 2 +-
packages/vue/LICENSE | 2 +-
packages/wasm/LICENSE | 2 +-
36 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/packages/angular/LICENSE b/packages/angular/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/angular/LICENSE
+++ b/packages/angular/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/astro/LICENSE
+++ b/packages/astro/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/aws-serverless/LICENSE b/packages/aws-serverless/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/aws-serverless/LICENSE
+++ b/packages/aws-serverless/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/browser-utils/LICENSE b/packages/browser-utils/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/browser-utils/LICENSE
+++ b/packages/browser-utils/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/browser/LICENSE
+++ b/packages/browser/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/bun/LICENSE b/packages/bun/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/bun/LICENSE
+++ b/packages/bun/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/cloudflare/LICENSE b/packages/cloudflare/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/cloudflare/LICENSE
+++ b/packages/cloudflare/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/core/LICENSE b/packages/core/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/core/LICENSE
+++ b/packages/core/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/deno/LICENSE
+++ b/packages/deno/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/ember/LICENSE
+++ b/packages/ember/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/eslint-config-sdk/LICENSE b/packages/eslint-config-sdk/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/eslint-config-sdk/LICENSE
+++ b/packages/eslint-config-sdk/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/eslint-plugin-sdk/LICENSE b/packages/eslint-plugin-sdk/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/eslint-plugin-sdk/LICENSE
+++ b/packages/eslint-plugin-sdk/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/feedback/LICENSE
+++ b/packages/feedback/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/gatsby/LICENSE b/packages/gatsby/LICENSE
index 5af93a5bdae5..58ef1814e188 100644
--- a/packages/gatsby/LICENSE
+++ b/packages/gatsby/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/google-cloud-serverless/LICENSE b/packages/google-cloud-serverless/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/google-cloud-serverless/LICENSE
+++ b/packages/google-cloud-serverless/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/integration-shims/LICENSE b/packages/integration-shims/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/integration-shims/LICENSE
+++ b/packages/integration-shims/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nestjs/LICENSE b/packages/nestjs/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/nestjs/LICENSE
+++ b/packages/nestjs/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nextjs/LICENSE b/packages/nextjs/LICENSE
index 5b55ec3c5dcb..6d1ad17eb71b 100644
--- a/packages/nextjs/LICENSE
+++ b/packages/nextjs/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/node/LICENSE b/packages/node/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/node/LICENSE
+++ b/packages/node/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nuxt/LICENSE b/packages/nuxt/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/nuxt/LICENSE
+++ b/packages/nuxt/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/opentelemetry/LICENSE b/packages/opentelemetry/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/opentelemetry/LICENSE
+++ b/packages/opentelemetry/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/profiling-node/LICENSE b/packages/profiling-node/LICENSE
index 048dee5adaa8..a69d859b8939 100644
--- a/packages/profiling-node/LICENSE
+++ b/packages/profiling-node/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/react/LICENSE b/packages/react/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/react/LICENSE
+++ b/packages/react/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/remix/LICENSE b/packages/remix/LICENSE
index 048dee5adaa8..a69d859b8939 100644
--- a/packages/remix/LICENSE
+++ b/packages/remix/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/replay-canvas/LICENSE
+++ b/packages/replay-canvas/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-internal/LICENSE b/packages/replay-internal/LICENSE
index 048dee5adaa8..a69d859b8939 100644
--- a/packages/replay-internal/LICENSE
+++ b/packages/replay-internal/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-worker/LICENSE b/packages/replay-worker/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/replay-worker/LICENSE
+++ b/packages/replay-worker/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/solid/LICENSE b/packages/solid/LICENSE
index 63e7eb28e19c..9739499d2513 100644
--- a/packages/solid/LICENSE
+++ b/packages/solid/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/solidstart/LICENSE b/packages/solidstart/LICENSE
index 6bfafc44539c..9739499d2513 100644
--- a/packages/solidstart/LICENSE
+++ b/packages/solidstart/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/svelte/LICENSE b/packages/svelte/LICENSE
index 048dee5adaa8..a69d859b8939 100644
--- a/packages/svelte/LICENSE
+++ b/packages/svelte/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/sveltekit/LICENSE
+++ b/packages/sveltekit/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/types/LICENSE b/packages/types/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/types/LICENSE
+++ b/packages/types/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/typescript/LICENSE b/packages/typescript/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/typescript/LICENSE
+++ b/packages/typescript/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE
index 6bfafc44539c..eaf96aacc0c8 100644
--- a/packages/vercel-edge/LICENSE
+++ b/packages/vercel-edge/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE
index d5b40b7c4219..9440728b1ced 100644
--- a/packages/vue/LICENSE
+++ b/packages/vue/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/wasm/LICENSE b/packages/wasm/LICENSE
index 5b55ec3c5dcb..6d1ad17eb71b 100644
--- a/packages/wasm/LICENSE
+++ b/packages/wasm/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
From 5bc0894f31f5ed55847fafeed6c3a5ff72228ba1 Mon Sep 17 00:00:00 2001
From: David Turissini
Date: Wed, 22 Jan 2025 23:59:19 -0800
Subject: [PATCH 23/59] chore(repo): Removing unused files (#15140)
[remove-unused](https://removeunused.com/) found more unused
files, this time in `replay-internal` and `vercel-edge`.
---
packages/replay-internal/src/session/index.ts | 1 -
packages/vercel-edge/src/transports/types.ts | 8 --------
2 files changed, 9 deletions(-)
delete mode 100644 packages/replay-internal/src/session/index.ts
delete mode 100644 packages/vercel-edge/src/transports/types.ts
diff --git a/packages/replay-internal/src/session/index.ts b/packages/replay-internal/src/session/index.ts
deleted file mode 100644
index 02c9d7a43422..000000000000
--- a/packages/replay-internal/src/session/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './createSession';
diff --git a/packages/vercel-edge/src/transports/types.ts b/packages/vercel-edge/src/transports/types.ts
deleted file mode 100644
index 70bf888f2666..000000000000
--- a/packages/vercel-edge/src/transports/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { BaseTransportOptions } from '@sentry/core';
-
-export interface VercelEdgeTransportOptions extends BaseTransportOptions {
- /** Fetch API init parameters. */
- fetchOptions?: RequestInit;
- /** Custom headers for the transport. */
- headers?: { [key: string]: string };
-}
From b49c1cc05e79f3854b642066ef1176ef9eb3f8d0 Mon Sep 17 00:00:00 2001
From: Onur Temizkan
Date: Thu, 23 Jan 2025 08:43:22 +0000
Subject: [PATCH 24/59] fix(react): Support lazy-loaded routes and components.
(#15039)
Fixes: https://github.com/getsentry/sentry-javascript/issues/15027
This PR adds support for lazily loaded components and routes inside
`Suspend` on react-router pageloads / navigations.
---
.../react-create-browser-router/src/index.tsx | 11 +-
.../src/pages/Index.tsx | 3 +
.../src/pages/LazyLoadedInnerRoute.tsx | 14 +++
.../src/pages/LazyLoadedUser.tsx | 23 ++++
.../tests/transactions.test.ts | 102 ++++++++++++++++++
.../react/src/reactrouterv6-compat-utils.tsx | 62 ++++++++---
packages/react/src/types.ts | 11 +-
7 files changed, 205 insertions(+), 21 deletions(-)
create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx
create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx
index 88f8cfa502ec..c7ad16eebcf7 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx
@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/react';
-import React from 'react';
+import React, { lazy, Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import {
RouterProvider,
@@ -42,6 +42,7 @@ Sentry.init({
});
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
+const LazyLoadedUser = lazy(() => import('./pages/LazyLoadedUser'));
const router = sentryCreateBrowserRouter(
[
@@ -49,6 +50,14 @@ const router = sentryCreateBrowserRouter(
path: '/',
element: ,
},
+ {
+ path: '/lazy-loaded-user/*',
+ element: (
+ Loading...}>
+
+
+ ),
+ },
{
path: '/user/:id',
element: ,
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx
index d6b71a1d1279..12bfb12ec3a9 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx
@@ -16,6 +16,9 @@ const Index = () => {
navigate
+
+ lazy navigate
+
>
);
};
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx
new file mode 100644
index 000000000000..1410df69124b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx
@@ -0,0 +1,14 @@
+import * as Sentry from '@sentry/react';
+// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX
+import * as React from 'react';
+import { Route, Routes } from 'react-router-dom';
+
+const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
+
+const InnerRoute = () => (
+
+ I am a lazy loaded user} />
+
+);
+
+export default InnerRoute;
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx
new file mode 100644
index 000000000000..636f99d9c8cb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx
@@ -0,0 +1,23 @@
+import * as Sentry from '@sentry/react';
+import * as React from 'react';
+import { Route, Routes } from 'react-router-dom';
+
+const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
+const InnerRoute = React.lazy(() => import('./LazyLoadedInnerRoute'));
+
+const LazyLoadedUser = () => {
+ return (
+
+ Loading...}>
+
+
+ }
+ />
+
+ );
+};
+
+export default LazyLoadedUser;
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts
index 5ecd098daf94..c35d731915d6 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts
@@ -76,3 +76,105 @@ test('Captures a navigation transaction', async ({ page }) => {
expect(transactionEvent.spans).toEqual([]);
});
+
+test('Captures a lazy pageload transaction', async ({ page }) => {
+ const transactionEventPromise = waitForTransaction('react-create-browser-router', event => {
+ return event.contexts?.trace?.op === 'pageload';
+ });
+
+ await page.goto('/lazy-loaded-user/5/foo');
+
+ const transactionEvent = await transactionEventPromise;
+ expect(transactionEvent.contexts?.trace).toEqual({
+ data: expect.objectContaining({
+ 'sentry.idle_span_finish_reason': 'idleTimeout',
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.react.reactrouter_v6',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ op: 'pageload',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ origin: 'auto.pageload.react.reactrouter_v6',
+ });
+
+ expect(transactionEvent).toEqual(
+ expect.objectContaining({
+ transaction: '/lazy-loaded-user/:id/:innerId',
+ type: 'transaction',
+ transaction_info: {
+ source: 'route',
+ },
+ }),
+ );
+
+ expect(await page.innerText('id=content')).toContain('I am a lazy loaded user');
+
+ expect(transactionEvent.spans).toEqual(
+ expect.arrayContaining([
+ // This one is the outer lazy route
+ expect.objectContaining({
+ op: 'resource.script',
+ origin: 'auto.resource.browser.metrics',
+ }),
+ // This one is the inner lazy route
+ expect.objectContaining({
+ op: 'resource.script',
+ origin: 'auto.resource.browser.metrics',
+ }),
+ ]),
+ );
+});
+
+test('Captures a lazy navigation transaction', async ({ page }) => {
+ const transactionEventPromise = waitForTransaction('react-create-browser-router', event => {
+ return event.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto('/');
+ const linkElement = page.locator('id=lazy-navigation');
+ await linkElement.click();
+
+ const transactionEvent = await transactionEventPromise;
+ expect(transactionEvent.contexts?.trace).toEqual({
+ data: expect.objectContaining({
+ 'sentry.idle_span_finish_reason': 'idleTimeout',
+ 'sentry.op': 'navigation',
+ 'sentry.origin': 'auto.navigation.react.reactrouter_v6',
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'route',
+ }),
+ op: 'navigation',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ origin: 'auto.navigation.react.reactrouter_v6',
+ });
+
+ expect(transactionEvent).toEqual(
+ expect.objectContaining({
+ transaction: '/lazy-loaded-user/:id/:innerId',
+ type: 'transaction',
+ transaction_info: {
+ source: 'route',
+ },
+ }),
+ );
+
+ expect(await page.innerText('id=content')).toContain('I am a lazy loaded user');
+
+ expect(transactionEvent.spans).toEqual(
+ expect.arrayContaining([
+ // This one is the outer lazy route
+ expect.objectContaining({
+ op: 'resource.script',
+ origin: 'auto.resource.browser.metrics',
+ }),
+ // This one is the inner lazy route
+ expect.objectContaining({
+ op: 'resource.script',
+ origin: 'auto.resource.browser.metrics',
+ }),
+ ]),
+ );
+});
diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx
index 9ca96a03b0de..db65c32cee99 100644
--- a/packages/react/src/reactrouterv6-compat-utils.tsx
+++ b/packages/react/src/reactrouterv6-compat-utils.tsx
@@ -61,6 +61,9 @@ export interface ReactRouterOptions {
type V6CompatibleVersion = '6' | '7';
+// Keeping as a global variable for cross-usage in multiple functions
+const allRoutes = new Set();
+
/**
* Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions.
*/
@@ -81,6 +84,10 @@ export function createV6CompatibleWrapCreateBrowserRouter<
}
return function (routes: RouteObject[], opts?: Record & { basename?: string }): TRouter {
+ routes.forEach(route => {
+ allRoutes.add(route);
+ });
+
const router = createRouterFunction(routes, opts);
const basename = opts?.basename;
@@ -90,19 +97,40 @@ export function createV6CompatibleWrapCreateBrowserRouter<
// This is the earliest convenient time to update the transaction name.
// Callbacks to `router.subscribe` are not called for the initial load.
if (router.state.historyAction === 'POP' && activeRootSpan) {
- updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename);
+ updatePageloadTransaction(
+ activeRootSpan,
+ router.state.location,
+ routes,
+ undefined,
+ basename,
+ Array.from(allRoutes),
+ );
}
router.subscribe((state: RouterState) => {
- const location = state.location;
if (state.historyAction === 'PUSH' || state.historyAction === 'POP') {
- handleNavigation({
- location,
- routes,
- navigationType: state.historyAction,
- version,
- basename,
- });
+ // Wait for the next render if loading an unsettled route
+ if (state.navigation.state !== 'idle') {
+ requestAnimationFrame(() => {
+ handleNavigation({
+ location: state.location,
+ routes,
+ navigationType: state.historyAction,
+ version,
+ basename,
+ allRoutes: Array.from(allRoutes),
+ });
+ });
+ } else {
+ handleNavigation({
+ location: state.location,
+ routes,
+ navigationType: state.historyAction,
+ version,
+ basename,
+ allRoutes: Array.from(allRoutes),
+ });
+ }
}
});
@@ -137,6 +165,10 @@ export function createV6CompatibleWrapCreateMemoryRouter<
initialIndex?: number;
},
): TRouter {
+ routes.forEach(route => {
+ allRoutes.add(route);
+ });
+
const router = createRouterFunction(routes, opts);
const basename = opts?.basename;
@@ -162,7 +194,7 @@ export function createV6CompatibleWrapCreateMemoryRouter<
: router.state.location;
if (router.state.historyAction === 'POP' && activeRootSpan) {
- updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename);
+ updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes));
}
router.subscribe((state: RouterState) => {
@@ -174,6 +206,7 @@ export function createV6CompatibleWrapCreateMemoryRouter<
navigationType: state.historyAction,
version,
basename,
+ allRoutes: Array.from(allRoutes),
});
}
});
@@ -248,8 +281,6 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio
return origUseRoutes;
}
- const allRoutes: Set = new Set();
-
const SentryRoutes: React.FC<{
children?: React.ReactNode;
routes: RouteObject[];
@@ -319,7 +350,6 @@ export function handleNavigation(opts: {
allRoutes?: RouteObject[];
}): void {
const { location, routes, navigationType, version, matches, basename, allRoutes } = opts;
-
const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename);
const client = getClient();
@@ -553,7 +583,7 @@ function updatePageloadTransaction(
): void {
const branches = Array.isArray(matches)
? matches
- : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]);
+ : (_matchRoutes(allRoutes || routes, location, basename) as unknown as RouteMatch[]);
if (branches) {
let name,
@@ -569,7 +599,7 @@ function updatePageloadTransaction(
[name, source] = getNormalizedName(routes, location, branches, basename);
}
- getCurrentScope().setTransactionName(name);
+ getCurrentScope().setTransactionName(name || '/');
if (activeRootSpan) {
activeRootSpan.updateName(name);
@@ -592,8 +622,6 @@ export function createV6CompatibleWithSentryReactRouterRouting = new Set();
-
const SentryRoutes: React.FC
= (props: P) => {
const isMountRenderPass = React.useRef(true);
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index 1a40ec4fce91..b29a2dbd1cad 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -182,10 +182,14 @@ export interface RouterInit {
hydrationData?: HydrationState;
}
+export type NavigationState = {
+ state: 'idle' | 'loading' | 'submitting';
+};
+
export type NavigationStates = {
- Idle: any;
- Loading: any;
- Submitting: any;
+ Idle: NavigationState;
+ Loading: NavigationState;
+ Submitting: NavigationState;
};
export type Navigation = NavigationStates[keyof NavigationStates];
@@ -202,6 +206,7 @@ export declare enum HistoryAction {
export interface RouterState {
historyAction: Action | HistoryAction | any;
location: Location;
+ navigation: Navigation;
}
export interface Router {
state: TState;
From 1b68b7f17b71e80705f052d1002daec7dfb124f0 Mon Sep 17 00:00:00 2001
From: Philipp Hofmann
Date: Thu, 23 Jan 2025 11:07:05 +0100
Subject: [PATCH 25/59] Remove date range for LICENSEs
In our internal Open Source Legal Policy, we decided that licenses don't
require a data range. This also has the advantage of not updating the
date range yearly.
---
LICENSE | 2 +-
packages/angular/LICENSE | 2 +-
packages/astro/LICENSE | 2 +-
packages/aws-serverless/LICENSE | 2 +-
packages/browser-utils/LICENSE | 2 +-
packages/browser/LICENSE | 2 +-
packages/bun/LICENSE | 2 +-
packages/cloudflare/LICENSE | 2 +-
packages/core/LICENSE | 2 +-
packages/deno/LICENSE | 2 +-
packages/ember/LICENSE | 2 +-
packages/eslint-config-sdk/LICENSE | 2 +-
packages/eslint-plugin-sdk/LICENSE | 2 +-
packages/feedback/LICENSE | 2 +-
packages/gatsby/LICENSE | 2 +-
packages/google-cloud-serverless/LICENSE | 2 +-
packages/integration-shims/LICENSE | 2 +-
packages/nestjs/LICENSE | 2 +-
packages/nextjs/LICENSE | 2 +-
packages/node/LICENSE | 2 +-
packages/nuxt/LICENSE | 2 +-
packages/opentelemetry/LICENSE | 2 +-
packages/profiling-node/LICENSE | 2 +-
packages/react/LICENSE | 2 +-
packages/remix/LICENSE | 2 +-
packages/replay-canvas/LICENSE | 2 +-
packages/replay-internal/LICENSE | 2 +-
packages/replay-worker/LICENSE | 2 +-
packages/solid/LICENSE | 2 +-
packages/solidstart/LICENSE | 2 +-
packages/svelte/LICENSE | 2 +-
packages/sveltekit/LICENSE | 2 +-
packages/types/LICENSE | 2 +-
packages/typescript/LICENSE | 2 +-
packages/vercel-edge/LICENSE | 2 +-
packages/vue/LICENSE | 2 +-
packages/wasm/LICENSE | 2 +-
37 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/LICENSE b/LICENSE
index 98482f33b3be..84d8d8c065fc 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2012 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/angular/LICENSE b/packages/angular/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/angular/LICENSE
+++ b/packages/angular/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/astro/LICENSE
+++ b/packages/astro/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/aws-serverless/LICENSE b/packages/aws-serverless/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/aws-serverless/LICENSE
+++ b/packages/aws-serverless/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/browser-utils/LICENSE b/packages/browser-utils/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/browser-utils/LICENSE
+++ b/packages/browser-utils/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/browser/LICENSE
+++ b/packages/browser/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/bun/LICENSE b/packages/bun/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/bun/LICENSE
+++ b/packages/bun/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/cloudflare/LICENSE b/packages/cloudflare/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/cloudflare/LICENSE
+++ b/packages/cloudflare/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/core/LICENSE b/packages/core/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/core/LICENSE
+++ b/packages/core/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/deno/LICENSE
+++ b/packages/deno/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/ember/LICENSE
+++ b/packages/ember/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/eslint-config-sdk/LICENSE b/packages/eslint-config-sdk/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/eslint-config-sdk/LICENSE
+++ b/packages/eslint-config-sdk/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/eslint-plugin-sdk/LICENSE b/packages/eslint-plugin-sdk/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/eslint-plugin-sdk/LICENSE
+++ b/packages/eslint-plugin-sdk/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/feedback/LICENSE
+++ b/packages/feedback/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/gatsby/LICENSE b/packages/gatsby/LICENSE
index 58ef1814e188..b956a1944c7b 100644
--- a/packages/gatsby/LICENSE
+++ b/packages/gatsby/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2020 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/google-cloud-serverless/LICENSE b/packages/google-cloud-serverless/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/google-cloud-serverless/LICENSE
+++ b/packages/google-cloud-serverless/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/integration-shims/LICENSE b/packages/integration-shims/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/integration-shims/LICENSE
+++ b/packages/integration-shims/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nestjs/LICENSE b/packages/nestjs/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/nestjs/LICENSE
+++ b/packages/nestjs/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nextjs/LICENSE b/packages/nextjs/LICENSE
index 6d1ad17eb71b..917e31f85b7a 100644
--- a/packages/nextjs/LICENSE
+++ b/packages/nextjs/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2021 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/node/LICENSE b/packages/node/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/node/LICENSE
+++ b/packages/node/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/nuxt/LICENSE b/packages/nuxt/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/nuxt/LICENSE
+++ b/packages/nuxt/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/opentelemetry/LICENSE b/packages/opentelemetry/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/opentelemetry/LICENSE
+++ b/packages/opentelemetry/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/profiling-node/LICENSE b/packages/profiling-node/LICENSE
index a69d859b8939..293314012679 100644
--- a/packages/profiling-node/LICENSE
+++ b/packages/profiling-node/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2022 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/react/LICENSE b/packages/react/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/react/LICENSE
+++ b/packages/react/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/remix/LICENSE b/packages/remix/LICENSE
index a69d859b8939..293314012679 100644
--- a/packages/remix/LICENSE
+++ b/packages/remix/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2022 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/replay-canvas/LICENSE
+++ b/packages/replay-canvas/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-internal/LICENSE b/packages/replay-internal/LICENSE
index a69d859b8939..293314012679 100644
--- a/packages/replay-internal/LICENSE
+++ b/packages/replay-internal/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2022 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/replay-worker/LICENSE b/packages/replay-worker/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/replay-worker/LICENSE
+++ b/packages/replay-worker/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/solid/LICENSE b/packages/solid/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/solid/LICENSE
+++ b/packages/solid/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/solidstart/LICENSE b/packages/solidstart/LICENSE
index 9739499d2513..63e7eb28e19c 100644
--- a/packages/solidstart/LICENSE
+++ b/packages/solidstart/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2024 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/svelte/LICENSE b/packages/svelte/LICENSE
index a69d859b8939..293314012679 100644
--- a/packages/svelte/LICENSE
+++ b/packages/svelte/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2022 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/sveltekit/LICENSE
+++ b/packages/sveltekit/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/types/LICENSE b/packages/types/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/types/LICENSE
+++ b/packages/types/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/typescript/LICENSE b/packages/typescript/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/typescript/LICENSE
+++ b/packages/typescript/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE
index eaf96aacc0c8..b3c4b18a6317 100644
--- a/packages/vercel-edge/LICENSE
+++ b/packages/vercel-edge/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2023 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE
index 9440728b1ced..9f2152e89993 100644
--- a/packages/vue/LICENSE
+++ b/packages/vue/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2019 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
diff --git a/packages/wasm/LICENSE b/packages/wasm/LICENSE
index 6d1ad17eb71b..917e31f85b7a 100644
--- a/packages/wasm/LICENSE
+++ b/packages/wasm/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry
+Copyright (c) 2021 Functional Software, Inc. dba Sentry
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
From d789766b6fcdc468819ba25b18c557c8232d1ed6 Mon Sep 17 00:00:00 2001
From: Tim Fish
Date: Thu, 23 Jan 2025 15:33:22 +0100
Subject: [PATCH 26/59] fix(node): Missing `release` from ANR sessions (#15138)
---
.../node-integration-tests/suites/anr/basic-session.js | 2 +-
dev-packages/node-integration-tests/suites/anr/test.ts | 3 +++
packages/node/src/integrations/anr/worker.ts | 8 +++++++-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/dev-packages/node-integration-tests/suites/anr/basic-session.js b/dev-packages/node-integration-tests/suites/anr/basic-session.js
index 9700131a6040..7971d547c884 100644
--- a/dev-packages/node-integration-tests/suites/anr/basic-session.js
+++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js
@@ -9,7 +9,7 @@ setTimeout(() => {
Sentry.init({
dsn: process.env.SENTRY_DSN,
- release: '1.0',
+ release: '1.0.0',
integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })],
});
diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts
index ec980f07f123..9a91d4f205c6 100644
--- a/dev-packages/node-integration-tests/suites/anr/test.ts
+++ b/dev-packages/node-integration-tests/suites/anr/test.ts
@@ -188,6 +188,9 @@ describe('should report ANR when event loop blocked', () => {
session: {
status: 'abnormal',
abnormal_mechanism: 'anr_foreground',
+ attrs: {
+ release: '1.0.0',
+ },
},
})
.expect({ event: ANR_EVENT_WITH_SCOPE })
diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts
index 8900b423710b..2eebfe40309b 100644
--- a/packages/node/src/integrations/anr/worker.ts
+++ b/packages/node/src/integrations/anr/worker.ts
@@ -46,7 +46,13 @@ async function sendAbnormalSession(): Promise {
// of we have an existing session passed from the main thread, send it as abnormal
if (session) {
log('Sending abnormal session');
- updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' });
+
+ updateSession(session, {
+ status: 'abnormal',
+ abnormal_mechanism: 'anr_foreground',
+ release: options.release,
+ environment: options.environment,
+ });
const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata, options.tunnel);
// Log the envelope so to aid in testing
From 9622bba15719fa82e5b4b6863297cd01337e4525 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Thu, 23 Jan 2025 09:33:50 -0500
Subject: [PATCH 27/59] feat(core): Add client outcomes for breadcrumbs buffer
(#15082)
ref https://github.com/getsentry/team-sdks/issues/116
This PR implements a new client discard reason for `buffer_overflow`.
This will be used to track when the internal breadcrumbs buffer
overflows for the new logs product that we are working on. This is
documented in develop here:
https://github.com/getsentry/sentry-docs/pull/12395
Note: The reason we have `buffer_overflow` as a separate item to
`queue_overflow` is that in the future when we send log items in
envelopes we'll increment `queue_overflow` for the transport queue. We
want to differentiate between the transport queue and the internal
buffer explicitly.
---
packages/core/src/scope.ts | 8 +++++---
packages/core/src/types-hoist/clientreport.ts | 3 ++-
packages/core/src/types-hoist/datacategory.ts | 6 +++++-
packages/core/test/lib/client.test.ts | 16 ++++++++++++++++
4 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts
index ce559d589fe3..a302e5a14c34 100644
--- a/packages/core/src/scope.ts
+++ b/packages/core/src/scope.ts
@@ -479,9 +479,11 @@ export class Scope {
...breadcrumb,
};
- const breadcrumbs = this._breadcrumbs;
- breadcrumbs.push(mergedBreadcrumb);
- this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs;
+ this._breadcrumbs.push(mergedBreadcrumb);
+ if (this._breadcrumbs.length > maxCrumbs) {
+ this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs);
+ this._client?.recordDroppedEvent('buffer_overflow', 'log_item');
+ }
this._notifyScopeListeners();
diff --git a/packages/core/src/types-hoist/clientreport.ts b/packages/core/src/types-hoist/clientreport.ts
index b6ab1766e68c..069adec43c62 100644
--- a/packages/core/src/types-hoist/clientreport.ts
+++ b/packages/core/src/types-hoist/clientreport.ts
@@ -8,7 +8,8 @@ export type EventDropReason =
| 'ratelimit_backoff'
| 'sample_rate'
| 'send_error'
- | 'internal_sdk_error';
+ | 'internal_sdk_error'
+ | 'buffer_overflow';
export type Outcome = {
reason: EventDropReason;
diff --git a/packages/core/src/types-hoist/datacategory.ts b/packages/core/src/types-hoist/datacategory.ts
index da90cc0ca90b..2e636b605fcf 100644
--- a/packages/core/src/types-hoist/datacategory.ts
+++ b/packages/core/src/types-hoist/datacategory.ts
@@ -14,7 +14,7 @@ export type DataCategory =
| 'replay'
// Events with `event_type` csp, hpkp, expectct, expectstaple
| 'security'
- // Attachment bytes stored (unused for rate limiting
+ // Attachment bytes stored (unused for rate limiting)
| 'attachment'
// Session update events
| 'session'
@@ -28,5 +28,9 @@ export type DataCategory =
| 'feedback'
// Span
| 'span'
+ // Log event
+ | 'log_item'
+ // Log bytes stored (unused for rate limiting)
+ | 'log_byte'
// Unknown data category
| 'unknown';
diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts
index 19a10f7f509a..c415f1ceb411 100644
--- a/packages/core/test/lib/client.test.ts
+++ b/packages/core/test/lib/client.test.ts
@@ -164,6 +164,22 @@ describe('Client', () => {
expect(isolationScopeBreadcrumbs).toEqual([{ message: 'hello3', timestamp: expect.any(Number) }]);
});
+ test('it records `buffer_overflow` client discard reason when buffer overflows', () => {
+ const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 });
+ const client = new TestClient(options);
+ const recordLostEventSpy = jest.spyOn(client, 'recordDroppedEvent');
+ setCurrentClient(client);
+ getIsolationScope().setClient(client);
+ client.init();
+
+ addBreadcrumb({ message: 'hello1' });
+ addBreadcrumb({ message: 'hello2' });
+ addBreadcrumb({ message: 'hello3' });
+
+ expect(recordLostEventSpy).toHaveBeenCalledTimes(2);
+ expect(recordLostEventSpy).toHaveBeenLastCalledWith('buffer_overflow', 'log_item');
+ });
+
test('calls `beforeBreadcrumb` and adds the breadcrumb without any changes', () => {
const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb);
const options = getDefaultTestClientOptions({ beforeBreadcrumb });
From ad5418d67ba4ef28c359cecf97bc70eafc71d40a Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Thu, 23 Jan 2025 16:04:05 +0100
Subject: [PATCH 28/59] fix(nextjs): Flush with `waitUntil` in
`captureRequestError` (#15146)
---
packages/nextjs/src/common/captureRequestError.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/nextjs/src/common/captureRequestError.ts b/packages/nextjs/src/common/captureRequestError.ts
index 26fdaab4953b..c872d70f8334 100644
--- a/packages/nextjs/src/common/captureRequestError.ts
+++ b/packages/nextjs/src/common/captureRequestError.ts
@@ -1,5 +1,7 @@
import type { RequestEventData } from '@sentry/core';
+import { vercelWaitUntil } from '@sentry/core';
import { captureException, headersToDict, withScope } from '@sentry/core';
+import { flushSafelyWithTimeout } from './utils/responseEnd';
type RequestInfo = {
path: string;
@@ -39,5 +41,7 @@ export function captureRequestError(error: unknown, request: RequestInfo, errorC
handled: false,
},
});
+
+ vercelWaitUntil(flushSafelyWithTimeout());
});
}
From 6b66a2844a5dc95249e789e2139246c639158e0f Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Thu, 23 Jan 2025 17:33:08 +0100
Subject: [PATCH 29/59] feat(solidstart)!: No longer export
`sentrySolidStartVite` (#15143)
Since the default way of setting up the SDK and passing the Sentry
options is by wrapping the SolidStart config with `withSentry`, the
previous method of using the `sentrySolidStartVite` plugin is no longer
supported.
---
CHANGELOG.md | 44 --------------------
docs/migration/v8-to-v9.md | 16 +++++++
packages/solidstart/src/config/withSentry.ts | 2 +-
packages/solidstart/src/index.server.ts | 1 -
packages/solidstart/src/index.types.ts | 1 -
packages/solidstart/src/vite/index.ts | 1 -
6 files changed, 17 insertions(+), 48 deletions(-)
delete mode 100644 packages/solidstart/src/vite/index.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcbfecab7da4..10de3a8c8ca5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,50 +12,6 @@
Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions!
-- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))**
-
-To enable the SolidStart SDK, wrap your SolidStart Config with `withSentry`. The `sentrySolidStartVite` plugin is now automatically
-added by `withSentry` and you can pass the Sentry build-time options like this:
-
-```js
-import { defineConfig } from '@solidjs/start/config';
-import { withSentry } from '@sentry/solidstart';
-
-export default defineConfig(
- withSentry(
- {
- /* Your SolidStart config options... */
- },
- {
- // Options for setting up source maps
- org: process.env.SENTRY_ORG,
- project: process.env.SENTRY_PROJECT,
- authToken: process.env.SENTRY_AUTH_TOKEN,
- },
- ),
-);
-```
-
-With the `withSentry` wrapper, the Sentry server config should not be added to the `public` directory anymore.
-Add the Sentry server config in `src/instrument.server.ts`. Then, the server config will be placed inside the server build output as `instrument.server.mjs`.
-
-Now, there are two options to set up the SDK:
-
-1. **(recommended)** Provide an `--import` CLI flag to the start command like this (path depends on your server setup):
- `node --import ./.output/server/instrument.server.mjs .output/server/index.mjs`
-2. Add `autoInjectServerSentry: 'top-level-import'` and the Sentry config will be imported at the top of the server entry (comes with tracing limitations)
- ```js
- withSentry(
- {
- /* Your SolidStart config options... */
- },
- {
- // Optional: Install Sentry with a top-level import
- autoInjectServerSentry: 'top-level-import',
- },
- );
- ```
-
## 9.0.0-alpha.0
This is an alpha release of the upcoming major release of version 9.
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
index a6bd0aa35e7c..e279fb0ebc86 100644
--- a/docs/migration/v8-to-v9.md
+++ b/docs/migration/v8-to-v9.md
@@ -234,6 +234,22 @@ Sentry.init({
- The `addNormalizedRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`.
- A `sampleRand` field on `PropagationContext` is now required. This is relevant if you used `scope.setPropagationContext(...)`
+### `@sentry/solidstart`
+
+- The `sentrySolidStartVite` plugin is no longer exported. Instead, wrap the SolidStart config with `withSentry` and
+ provide Sentry options as the second parameter.
+
+ ```
+ // app.config.ts
+ import { defineConfig } from '@solidjs/start/config';
+ import { withSentry } from '@sentry/solidstart';
+
+ export default defineConfig(withSentry(
+ { /* SolidStart config */ },
+ { /* Sentry build-time config (like project and org) */ })
+ );
+ ```
+
#### Other/Internal Changes
The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior.
diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts
index c1050f0da1cc..aa045ade00ab 100644
--- a/packages/solidstart/src/config/withSentry.ts
+++ b/packages/solidstart/src/config/withSentry.ts
@@ -1,6 +1,6 @@
import { logger } from '@sentry/core';
import type { Nitro } from 'nitropack';
-import { addSentryPluginToVite } from '../vite';
+import { addSentryPluginToVite } from '../vite/sentrySolidStartVite';
import type { SentrySolidStartPluginOptions } from '../vite/types';
import {
addDynamicImportEntryFileWrapper,
diff --git a/packages/solidstart/src/index.server.ts b/packages/solidstart/src/index.server.ts
index a20a0367f557..82b6fe6cbff4 100644
--- a/packages/solidstart/src/index.server.ts
+++ b/packages/solidstart/src/index.server.ts
@@ -1,3 +1,2 @@
export * from './server';
-export * from './vite';
export * from './config';
diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts
index 39f9831c543c..54a5ec6d6a3c 100644
--- a/packages/solidstart/src/index.types.ts
+++ b/packages/solidstart/src/index.types.ts
@@ -3,7 +3,6 @@
// exports in this file - which we do below.
export * from './client';
export * from './server';
-export * from './vite';
export * from './config';
import type { Client, Integration, Options, StackParser } from '@sentry/core';
diff --git a/packages/solidstart/src/vite/index.ts b/packages/solidstart/src/vite/index.ts
deleted file mode 100644
index 464bbd604fbe..000000000000
--- a/packages/solidstart/src/vite/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './sentrySolidStartVite';
From 965185cbb8bf801fcddebea3a0fead4e3f152c29 Mon Sep 17 00:00:00 2001
From: Jacob Hands
Date: Thu, 23 Jan 2025 11:41:14 -0600
Subject: [PATCH 30/59] feat(core): Improve error formatting in ZodErrors
integration (#15111)
- Include full key path rather than the top level key in title
- Improve message for validation issues with no path
- Add option to include extended issue information as an attachment
---
packages/core/package.json | 5 +-
packages/core/src/integrations/zoderrors.ts | 170 +++++--
.../test/lib/integrations/zoderrrors.test.ts | 432 +++++++++++++++++-
yarn.lock | 43 +-
4 files changed, 576 insertions(+), 74 deletions(-)
diff --git a/packages/core/package.json b/packages/core/package.json
index 06e8d253a628..7724f703833e 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -61,5 +61,8 @@
"volta": {
"extends": "../../package.json"
},
- "sideEffects": false
+ "sideEffects": false,
+ "devDependencies": {
+ "zod": "^3.24.1"
+ }
}
diff --git a/packages/core/src/integrations/zoderrors.ts b/packages/core/src/integrations/zoderrors.ts
index a408285800d9..4859ca5167fa 100644
--- a/packages/core/src/integrations/zoderrors.ts
+++ b/packages/core/src/integrations/zoderrors.ts
@@ -5,33 +5,45 @@ import { truncate } from '../utils-hoist/string';
interface ZodErrorsOptions {
key?: string;
+ /**
+ * Limits the number of Zod errors inlined in each Sentry event.
+ *
+ * @default 10
+ */
limit?: number;
+ /**
+ * Save full list of Zod issues as an attachment in Sentry
+ *
+ * @default false
+ */
+ saveZodIssuesAsAttachment?: boolean;
}
const DEFAULT_LIMIT = 10;
const INTEGRATION_NAME = 'ZodErrors';
-// Simplified ZodIssue type definition
+/**
+ * Simplified ZodIssue type definition
+ */
interface ZodIssue {
path: (string | number)[];
message?: string;
- expected?: string | number;
- received?: string | number;
+ expected?: unknown;
+ received?: unknown;
unionErrors?: unknown[];
keys?: unknown[];
+ invalid_literal?: unknown;
}
interface ZodError extends Error {
issues: ZodIssue[];
-
- get errors(): ZodError['issues'];
}
function originalExceptionIsZodError(originalException: unknown): originalException is ZodError {
return (
isError(originalException) &&
originalException.name === 'ZodError' &&
- Array.isArray((originalException as ZodError).errors)
+ Array.isArray((originalException as ZodError).issues)
);
}
@@ -45,9 +57,18 @@ type SingleLevelZodIssue = {
/**
* Formats child objects or arrays to a string
- * That is preserved when sent to Sentry
+ * that is preserved when sent to Sentry.
+ *
+ * Without this, we end up with something like this in Sentry:
+ *
+ * [
+ * [Object],
+ * [Object],
+ * [Object],
+ * [Object]
+ * ]
*/
-function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue {
+export function flattenIssue(issue: ZodIssue): SingleLevelZodIssue {
return {
...issue,
path: 'path' in issue && Array.isArray(issue.path) ? issue.path.join('.') : undefined,
@@ -56,64 +77,145 @@ function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue {
};
}
+/**
+ * Takes ZodError issue path array and returns a flattened version as a string.
+ * This makes it easier to display paths within a Sentry error message.
+ *
+ * Array indexes are normalized to reduce duplicate entries
+ *
+ * @param path ZodError issue path
+ * @returns flattened path
+ *
+ * @example
+ * flattenIssuePath([0, 'foo', 1, 'bar']) // -> '.foo..bar'
+ */
+export function flattenIssuePath(path: Array): string {
+ return path
+ .map(p => {
+ if (typeof p === 'number') {
+ return '';
+ } else {
+ return p;
+ }
+ })
+ .join('.');
+}
+
/**
* Zod error message is a stringified version of ZodError.issues
* This doesn't display well in the Sentry UI. Replace it with something shorter.
*/
-function formatIssueMessage(zodError: ZodError): string {
+export function formatIssueMessage(zodError: ZodError): string {
const errorKeyMap = new Set();
for (const iss of zodError.issues) {
- if (iss.path?.[0]) {
- errorKeyMap.add(iss.path[0]);
+ const issuePath = flattenIssuePath(iss.path);
+ if (issuePath.length > 0) {
+ errorKeyMap.add(issuePath);
}
}
- const errorKeys = Array.from(errorKeyMap);
+ const errorKeys = Array.from(errorKeyMap);
+ if (errorKeys.length === 0) {
+ // If there are no keys, then we're likely validating the root
+ // variable rather than a key within an object. This attempts
+ // to extract what type it was that failed to validate.
+ // For example, z.string().parse(123) would return "string" here.
+ let rootExpectedType = 'variable';
+ if (zodError.issues.length > 0) {
+ const iss = zodError.issues[0];
+ if (iss !== undefined && 'expected' in iss && typeof iss.expected === 'string') {
+ rootExpectedType = iss.expected;
+ }
+ }
+ return `Failed to validate ${rootExpectedType}`;
+ }
return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`;
}
/**
- * Applies ZodError issues to an event extras and replaces the error message
+ * Applies ZodError issues to an event extra and replaces the error message
*/
-export function applyZodErrorsToEvent(limit: number, event: Event, hint?: EventHint): Event {
+export function applyZodErrorsToEvent(
+ limit: number,
+ saveZodIssuesAsAttachment: boolean = false,
+ event: Event,
+ hint: EventHint,
+): Event {
if (
!event.exception?.values ||
- !hint?.originalException ||
+ !hint.originalException ||
!originalExceptionIsZodError(hint.originalException) ||
hint.originalException.issues.length === 0
) {
return event;
}
- return {
- ...event,
- exception: {
- ...event.exception,
- values: [
- {
- ...event.exception.values[0],
- value: formatIssueMessage(hint.originalException),
+ try {
+ const issuesToFlatten = saveZodIssuesAsAttachment
+ ? hint.originalException.issues
+ : hint.originalException.issues.slice(0, limit);
+ const flattenedIssues = issuesToFlatten.map(flattenIssue);
+
+ if (saveZodIssuesAsAttachment) {
+ // Sometimes having the full error details can be helpful.
+ // Attachments have much higher limits, so we can include the full list of issues.
+ if (!Array.isArray(hint.attachments)) {
+ hint.attachments = [];
+ }
+ hint.attachments.push({
+ filename: 'zod_issues.json',
+ data: JSON.stringify({
+ issues: flattenedIssues,
+ }),
+ });
+ }
+
+ return {
+ ...event,
+ exception: {
+ ...event.exception,
+ values: [
+ {
+ ...event.exception.values[0],
+ value: formatIssueMessage(hint.originalException),
+ },
+ ...event.exception.values.slice(1),
+ ],
+ },
+ extra: {
+ ...event.extra,
+ 'zoderror.issues': flattenedIssues.slice(0, limit),
+ },
+ };
+ } catch (e) {
+ // Hopefully we never throw errors here, but record it
+ // with the event just in case.
+ return {
+ ...event,
+ extra: {
+ ...event.extra,
+ 'zoderrors sentry integration parse error': {
+ message: 'an exception was thrown while processing ZodError within applyZodErrorsToEvent()',
+ error: e instanceof Error ? `${e.name}: ${e.message}\n${e.stack}` : 'unknown',
},
- ...event.exception.values.slice(1),
- ],
- },
- extra: {
- ...event.extra,
- 'zoderror.issues': hint.originalException.errors.slice(0, limit).map(formatIssueTitle),
- },
- };
+ },
+ };
+ }
}
const _zodErrorsIntegration = ((options: ZodErrorsOptions = {}) => {
- const limit = options.limit || DEFAULT_LIMIT;
+ const limit = options.limit ?? DEFAULT_LIMIT;
return {
name: INTEGRATION_NAME,
- processEvent(originalEvent, hint) {
- const processedEvent = applyZodErrorsToEvent(limit, originalEvent, hint);
+ processEvent(originalEvent, hint): Event {
+ const processedEvent = applyZodErrorsToEvent(limit, options.saveZodIssuesAsAttachment, originalEvent, hint);
return processedEvent;
},
};
}) satisfies IntegrationFn;
+/**
+ * Sentry integration to process Zod errors, making them easier to work with in Sentry.
+ */
export const zodErrorsIntegration = defineIntegration(_zodErrorsIntegration);
diff --git a/packages/core/test/lib/integrations/zoderrrors.test.ts b/packages/core/test/lib/integrations/zoderrrors.test.ts
index 924ee5dd27da..cd80e2347f36 100644
--- a/packages/core/test/lib/integrations/zoderrrors.test.ts
+++ b/packages/core/test/lib/integrations/zoderrrors.test.ts
@@ -1,6 +1,12 @@
+import { z } from 'zod';
import type { Event, EventHint } from '../../../src/types-hoist';
-import { applyZodErrorsToEvent } from '../../../src/integrations/zoderrors';
+import {
+ applyZodErrorsToEvent,
+ flattenIssue,
+ flattenIssuePath,
+ formatIssueMessage,
+} from '../../../src/integrations/zoderrors';
// Simplified type definition
interface ZodIssue {
@@ -40,13 +46,13 @@ describe('applyZodErrorsToEvent()', () => {
test('should not do anything if exception is not a ZodError', () => {
const event: Event = {};
const eventHint: EventHint = { originalException: new Error() };
- applyZodErrorsToEvent(100, event, eventHint);
+ applyZodErrorsToEvent(100, false, event, eventHint);
// no changes
expect(event).toStrictEqual({});
});
- test('should add ZodError issues to extras and format message', () => {
+ test('should add ZodError issues to extra and format message', () => {
const issues = [
{
code: 'invalid_type',
@@ -71,13 +77,13 @@ describe('applyZodErrorsToEvent()', () => {
};
const eventHint: EventHint = { originalException };
- const processedEvent = applyZodErrorsToEvent(100, event, eventHint);
+ const processedEvent = applyZodErrorsToEvent(100, false, event, eventHint);
expect(processedEvent.exception).toStrictEqual({
values: [
{
type: 'Error',
- value: 'Failed to validate keys: names',
+ value: 'Failed to validate keys: names.',
},
],
});
@@ -92,5 +98,421 @@ describe('applyZodErrorsToEvent()', () => {
},
],
});
+
+ // No attachments added
+ expect(eventHint.attachments).toBe(undefined);
+ });
+
+ test('should add all ZodError issues as attachment', () => {
+ const issues = [
+ {
+ code: 'invalid_type',
+ expected: 'string',
+ received: 'number',
+ path: ['names', 1],
+ keys: ['extra'],
+ message: 'Invalid input: expected string, received number',
+ },
+ {
+ code: 'invalid_type',
+ expected: 'string',
+ received: 'number',
+ path: ['foo', 1],
+ keys: ['extra2'],
+ message: 'Invalid input: expected string, received number',
+ },
+ ] satisfies ZodIssue[];
+ const originalException = ZodError.create(issues);
+
+ const event: Event = {
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: originalException.message,
+ },
+ ],
+ },
+ };
+
+ const eventHint: EventHint = { originalException };
+ const processedEvent = applyZodErrorsToEvent(1, true, event, eventHint);
+
+ expect(processedEvent.exception).toStrictEqual({
+ values: [
+ {
+ type: 'Error',
+ value: 'Failed to validate keys: names., foo.',
+ },
+ ],
+ });
+
+ // Only adds the first issue to extra due to the limit
+ expect(processedEvent.extra).toStrictEqual({
+ 'zoderror.issues': [
+ {
+ ...issues[0],
+ path: issues[0]?.path.join('.'),
+ keys: JSON.stringify(issues[0]?.keys),
+ unionErrors: undefined,
+ },
+ ],
+ });
+
+ // hint attachments contains the full issue list
+ expect(Array.isArray(eventHint.attachments)).toBe(true);
+ expect(eventHint.attachments?.length).toBe(1);
+ const attachment = eventHint.attachments?.[0];
+ if (attachment === undefined) {
+ throw new Error('attachment is undefined');
+ }
+ expect(attachment.filename).toBe('zod_issues.json');
+ expect(JSON.parse(attachment.data.toString())).toMatchInlineSnapshot(`
+Object {
+ "issues": Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "string",
+ "keys": "[\\"extra\\"]",
+ "message": "Invalid input: expected string, received number",
+ "path": "names.1",
+ "received": "number",
+ },
+ Object {
+ "code": "invalid_type",
+ "expected": "string",
+ "keys": "[\\"extra2\\"]",
+ "message": "Invalid input: expected string, received number",
+ "path": "foo.1",
+ "received": "number",
+ },
+ ],
+}
+`);
+ });
+});
+
+describe('flattenIssue()', () => {
+ it('flattens path field', () => {
+ const zodError = z
+ .object({
+ foo: z.string().min(1),
+ nested: z.object({
+ bar: z.literal('baz'),
+ }),
+ })
+ .safeParse({
+ foo: '',
+ nested: {
+ bar: 'not-baz',
+ },
+ }).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "too_small",
+ "exact": false,
+ "inclusive": true,
+ "message": "String must contain at least 1 character(s)",
+ "minimum": 1,
+ "path": Array [
+ "foo",
+ ],
+ "type": "string",
+ },
+ Object {
+ "code": "invalid_literal",
+ "expected": "baz",
+ "message": "Invalid literal value, expected \\"baz\\"",
+ "path": Array [
+ "nested",
+ "bar",
+ ],
+ "received": "not-baz",
+ },
+]
+`);
+
+ const issues = zodError.issues;
+ expect(issues.length).toBe(2);
+
+ // Format it for use in Sentry
+ expect(issues.map(flattenIssue)).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "too_small",
+ "exact": false,
+ "inclusive": true,
+ "keys": undefined,
+ "message": "String must contain at least 1 character(s)",
+ "minimum": 1,
+ "path": "foo",
+ "type": "string",
+ "unionErrors": undefined,
+ },
+ Object {
+ "code": "invalid_literal",
+ "expected": "baz",
+ "keys": undefined,
+ "message": "Invalid literal value, expected \\"baz\\"",
+ "path": "nested.bar",
+ "received": "not-baz",
+ "unionErrors": undefined,
+ },
+]
+`);
+
+ expect(zodError.flatten(flattenIssue)).toMatchInlineSnapshot(`
+Object {
+ "fieldErrors": Object {
+ "foo": Array [
+ Object {
+ "code": "too_small",
+ "exact": false,
+ "inclusive": true,
+ "keys": undefined,
+ "message": "String must contain at least 1 character(s)",
+ "minimum": 1,
+ "path": "foo",
+ "type": "string",
+ "unionErrors": undefined,
+ },
+ ],
+ "nested": Array [
+ Object {
+ "code": "invalid_literal",
+ "expected": "baz",
+ "keys": undefined,
+ "message": "Invalid literal value, expected \\"baz\\"",
+ "path": "nested.bar",
+ "received": "not-baz",
+ "unionErrors": undefined,
+ },
+ ],
+ },
+ "formErrors": Array [],
+}
+`);
+ });
+
+ it('flattens keys field to string', () => {
+ const zodError = z
+ .object({
+ foo: z.string().min(1),
+ })
+ .strict()
+ .safeParse({
+ foo: 'bar',
+ extra_key_abc: 'hello',
+ extra_key_def: 'world',
+ }).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "unrecognized_keys",
+ "keys": Array [
+ "extra_key_abc",
+ "extra_key_def",
+ ],
+ "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'",
+ "path": Array [],
+ },
+]
+`);
+
+ const issues = zodError.issues;
+ expect(issues.length).toBe(1);
+
+ // Format it for use in Sentry
+ const iss = issues[0];
+ if (iss === undefined) {
+ throw new Error('iss is undefined');
+ }
+ const formattedIssue = flattenIssue(iss);
+
+ // keys is now a string rather than array.
+ // Note: path is an empty string because the issue is at the root.
+ // TODO: Maybe somehow make it clearer that this is at the root?
+ expect(formattedIssue).toMatchInlineSnapshot(`
+Object {
+ "code": "unrecognized_keys",
+ "keys": "[\\"extra_key_abc\\",\\"extra_key_def\\"]",
+ "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'",
+ "path": "",
+ "unionErrors": undefined,
+}
+`);
+ expect(typeof formattedIssue.keys === 'string').toBe(true);
+ });
+});
+
+describe('flattenIssuePath()', () => {
+ it('returns single path', () => {
+ expect(flattenIssuePath(['foo'])).toBe('foo');
+ });
+
+ it('flattens nested string paths', () => {
+ expect(flattenIssuePath(['foo', 'bar'])).toBe('foo.bar');
+ });
+
+ it('uses placeholder for path index within array', () => {
+ expect(flattenIssuePath([0, 'foo', 1, 'bar', 'baz'])).toBe('.foo..bar.baz');
+ });
+});
+
+describe('formatIssueMessage()', () => {
+ it('adds invalid keys to message', () => {
+ const zodError = z
+ .object({
+ foo: z.string().min(1),
+ nested: z.object({
+ bar: z.literal('baz'),
+ }),
+ })
+ .safeParse({
+ foo: '',
+ nested: {
+ bar: 'not-baz',
+ },
+ }).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate keys: foo, nested.bar"');
+ });
+
+ describe('adds expected type if root variable is invalid', () => {
+ test('object', () => {
+ const zodError = z
+ .object({
+ foo: z.string().min(1),
+ })
+ .safeParse(123).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "object",
+ "message": "Expected object, received number",
+ "path": Array [],
+ "received": "number",
+ },
+]
+`);
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate object"');
+ });
+
+ test('number', () => {
+ const zodError = z.number().safeParse('123').error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "number",
+ "message": "Expected number, received string",
+ "path": Array [],
+ "received": "string",
+ },
+]
+`);
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate number"');
+ });
+
+ test('string', () => {
+ const zodError = z.string().safeParse(123).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "string",
+ "message": "Expected string, received number",
+ "path": Array [],
+ "received": "number",
+ },
+]
+`);
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate string"');
+ });
+
+ test('array', () => {
+ const zodError = z.string().array().safeParse('123').error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "array",
+ "message": "Expected array, received string",
+ "path": Array [],
+ "received": "string",
+ },
+]
+`);
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate array"');
+ });
+
+ test('wrong type in array', () => {
+ const zodError = z.string().array().safeParse([123]).error;
+ if (zodError === undefined) {
+ throw new Error('zodError is undefined');
+ }
+
+ // Original zod error
+ expect(zodError.issues).toMatchInlineSnapshot(`
+Array [
+ Object {
+ "code": "invalid_type",
+ "expected": "string",
+ "message": "Expected string, received number",
+ "path": Array [
+ 0,
+ ],
+ "received": "number",
+ },
+]
+`);
+
+ const message = formatIssueMessage(zodError);
+ expect(message).toMatchInlineSnapshot('"Failed to validate keys: "');
+ });
});
});
diff --git a/yarn.lock b/yarn.lock
index 5627089a7941..2e69bdb6d480 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7890,12 +7890,7 @@
dependencies:
"@types/unist" "*"
-"@types/history-4@npm:@types/history@4.7.8":
- version "4.7.8"
- resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
- integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
-
-"@types/history-5@npm:@types/history@4.7.8":
+"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
@@ -27426,16 +27421,7 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -27538,14 +27524,7 @@ stringify-object@^3.2.1:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
-strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -30335,16 +30314,7 @@ wrangler@^3.67.1:
optionalDependencies:
fsevents "~2.3.2"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
-wrap-ansi@7.0.0, wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -30646,6 +30616,11 @@ zod@^3.22.3, zod@^3.22.4:
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
+zod@^3.24.1:
+ version "3.24.1"
+ resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee"
+ integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==
+
zone.js@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.12.0.tgz#a4a6e5fab6d34bd37d89c77e89ac2e6f4a3d2c30"
From 2e708dcdd257a75fa585475a9e536e7840d71616 Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Thu, 23 Jan 2025 19:18:23 +0100
Subject: [PATCH 31/59] chore: Add external contributor to CHANGELOG.md
(#15152)
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #15111
Co-authored-by: Abhijeet Prasad
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10de3a8c8ca5..c0861fcc85cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
-Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions!
+Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, and @jahands. Thank you for your contributions!
## 9.0.0-alpha.0
From 5bc956dba00cdeb94462c2eaf9a2a18a6653b0c5 Mon Sep 17 00:00:00 2001
From: Philipp Hofmann
Date: Fri, 24 Jan 2025 16:09:15 +0100
Subject: [PATCH 32/59] Reference LOGAF Scale from develop docs (#15154)
Link to the Sentry develop docs for explaining the LOGAF scale.
---
docs/pr-reviews.md | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/docs/pr-reviews.md b/docs/pr-reviews.md
index 87b5faafb950..65703c2cebb6 100644
--- a/docs/pr-reviews.md
+++ b/docs/pr-reviews.md
@@ -2,12 +2,8 @@
Make sure to open PRs against `develop` branch.
-For feedback in PRs, we use the [LOGAF scale](https://blog.danlew.net/2020/04/15/the-logaf-scale/) to specify how
-important a comment is:
-
-- `l`: low - nitpick. You may address this comment, but you don't have to.
-- `m`: medium - normal comment. Worth addressing and fixing.
-- `h`: high - Very important. We must not merge this PR without addressing this issue.
+For feedback in PRs, we use the [LOGAF scale](https://develop.sentry.dev/engineering-practices/code-review/#logaf-scale) to specify how
+important a comment is.
You only need one approval from a maintainer to be able to merge. For some PRs, asking specific or multiple people for
review might be adequate. You can either assign SDK team members directly (e.g. if you have some people in mind who are
From 8e37842bff9a4930967859157f469a5a9f145aef Mon Sep 17 00:00:00 2001
From: Nathan Kleyn
Date: Fri, 24 Jan 2025 18:08:30 +0000
Subject: [PATCH 33/59] fix(bun): Ensure instrumentation of `Bun.serve`
survives a server reload (#15148)
If `#reload` is called on an instance of `Bun.serve`, the Sentry
intrumentation doesn't surive. This is because the Bun instrumentation
works by using `Proxy` on the call to `Bun.serve`, which isn't called
for a reload.
We can't wrap the serve created by calling `Bun.serve` with a `Proxy` as
Bun seems to do some internal checks using `instanceof` which break if
the instance is now reporting itself as a `ProxyObject`.
---
packages/bun/src/integrations/bunserver.ts | 13 +-
.../bun/test/integrations/bunserver.test.ts | 130 +++++++++++++-----
packages/bun/test/sdk.test.ts | 20 ++-
3 files changed, 119 insertions(+), 44 deletions(-)
diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts
index d8ee46abae73..1f1974839455 100644
--- a/packages/bun/src/integrations/bunserver.ts
+++ b/packages/bun/src/integrations/bunserver.ts
@@ -47,7 +47,18 @@ export function instrumentBunServe(): void {
Bun.serve = new Proxy(Bun.serve, {
apply(serveTarget, serveThisArg, serveArgs: Parameters) {
instrumentBunServeOptions(serveArgs[0]);
- return serveTarget.apply(serveThisArg, serveArgs);
+ const server: ReturnType = serveTarget.apply(serveThisArg, serveArgs);
+
+ // A Bun server can be reloaded, re-wrap any fetch function passed to it
+ // We can't use a Proxy for this as Bun does `instanceof` checks internally that fail if we
+ // wrap the Server instance.
+ const originalReload: typeof server.reload = server.reload.bind(server);
+ server.reload = (serveOptions: Parameters[0]) => {
+ instrumentBunServeOptions(serveOptions);
+ return originalReload(serveOptions);
+ };
+
+ return server;
},
});
}
diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts
index b1dc17381ccb..dd1f738a334b 100644
--- a/packages/bun/test/integrations/bunserver.test.ts
+++ b/packages/bun/test/integrations/bunserver.test.ts
@@ -1,67 +1,87 @@
-import { beforeAll, beforeEach, describe, expect, test } from 'bun:test';
+import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test';
+import type { Span } from '@sentry/core';
import { getDynamicSamplingContextFromSpan, setCurrentClient, spanIsSampled, spanToJSON } from '@sentry/core';
import { BunClient } from '../../src/client';
import { instrumentBunServe } from '../../src/integrations/bunserver';
import { getDefaultBunClientOptions } from '../helpers';
-// Fun fact: Bun = 2 21 14 :)
-const DEFAULT_PORT = 22114;
-
describe('Bun Serve Integration', () => {
let client: BunClient;
+ // Fun fact: Bun = 2 21 14 :)
+ let port: number = 22114;
beforeAll(() => {
instrumentBunServe();
});
beforeEach(() => {
- const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true });
+ const options = getDefaultBunClientOptions({ tracesSampleRate: 1 });
client = new BunClient(options);
setCurrentClient(client);
client.init();
});
+ afterEach(() => {
+ // Don't reuse the port; Bun server stops lazily so tests may accidentally hit a server still closing from a
+ // previous test
+ port += 1;
+ });
+
test('generates a transaction around a request', async () => {
+ let generatedSpan: Span | undefined;
+
client.on('spanEnd', span => {
- expect(spanToJSON(span).status).toBe('ok');
- expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200);
- expect(spanToJSON(span).op).toEqual('http.server');
- expect(spanToJSON(span).description).toEqual('GET /');
+ generatedSpan = span;
});
const server = Bun.serve({
async fetch(_req) {
return new Response('Bun!');
},
- port: DEFAULT_PORT,
+ port,
});
+ await fetch(`http://localhost:${port}/`);
+ server.stop();
- await fetch('http://localhost:22114/');
+ if (!generatedSpan) {
+ throw 'No span was generated in the test';
+ }
- server.stop();
+ 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 /');
});
test('generates a post transaction', async () => {
+ let generatedSpan: Span | undefined;
+
client.on('spanEnd', span => {
- expect(spanToJSON(span).status).toBe('ok');
- expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200);
- expect(spanToJSON(span).op).toEqual('http.server');
- expect(spanToJSON(span).description).toEqual('POST /');
+ generatedSpan = span;
});
const server = Bun.serve({
async fetch(_req) {
return new Response('Bun!');
},
- port: DEFAULT_PORT,
+ port,
});
- await fetch('http://localhost:22114/', {
+ await fetch(`http://localhost:${port}/`, {
method: 'POST',
});
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('POST /');
});
test('continues a trace', async () => {
@@ -70,55 +90,93 @@ describe('Bun Serve Integration', () => {
const PARENT_SAMPLED = '1';
const SENTRY_TRACE_HEADER = `${TRACE_ID}-${PARENT_SPAN_ID}-${PARENT_SAMPLED}`;
- const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-environment=production';
+ const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-sample_rand=0.42,sentry-environment=production';
- client.on('spanEnd', span => {
- expect(span.spanContext().traceId).toBe(TRACE_ID);
- expect(spanToJSON(span).parent_span_id).toBe(PARENT_SPAN_ID);
- expect(spanIsSampled(span)).toBe(true);
- expect(span.isRecording()).toBe(false);
+ let generatedSpan: Span | undefined;
- expect(getDynamicSamplingContextFromSpan(span)).toStrictEqual({
- version: '1.0',
- environment: 'production',
- });
+ client.on('spanEnd', span => {
+ generatedSpan = span;
});
const server = Bun.serve({
async fetch(_req) {
return new Response('Bun!');
},
- port: DEFAULT_PORT,
+ port,
});
- await fetch('http://localhost:22114/', {
+ await fetch(`http://localhost:${port}/`, {
headers: { 'sentry-trace': SENTRY_TRACE_HEADER, baggage: SENTRY_BAGGAGE_HEADER },
});
server.stop();
+
+ if (!generatedSpan) {
+ throw 'No span was generated in the test';
+ }
+
+ expect(generatedSpan.spanContext().traceId).toBe(TRACE_ID);
+ expect(spanToJSON(generatedSpan).parent_span_id).toBe(PARENT_SPAN_ID);
+ expect(spanIsSampled(generatedSpan)).toBe(true);
+ expect(generatedSpan.isRecording()).toBe(false);
+
+ expect(getDynamicSamplingContextFromSpan(generatedSpan)).toStrictEqual({
+ version: '1.0',
+ sample_rand: '0.42',
+ environment: 'production',
+ });
});
test('does not create transactions for OPTIONS or HEAD requests', async () => {
- client.on('spanEnd', () => {
- // This will never run, but we want to make sure it doesn't run.
- expect(false).toEqual(true);
+ let generatedSpan: Span | undefined;
+
+ client.on('spanEnd', span => {
+ generatedSpan = span;
});
const server = Bun.serve({
async fetch(_req) {
return new Response('Bun!');
},
- port: DEFAULT_PORT,
+ port,
});
- await fetch('http://localhost:22114/', {
+ await fetch(`http://localhost:${port}/`, {
method: 'OPTIONS',
});
- await fetch('http://localhost:22114/', {
+ await fetch(`http://localhost:${port}/`, {
method: 'HEAD',
});
server.stop();
+
+ expect(generatedSpan).toBeUndefined();
+ });
+
+ test('intruments the server again if it is reloaded', async () => {
+ let serverWasInstrumented = false;
+ client.on('spanEnd', () => {
+ serverWasInstrumented = true;
+ });
+
+ const server = Bun.serve({
+ async fetch(_req) {
+ return new Response('Bun!');
+ },
+ port,
+ });
+
+ server.reload({
+ async fetch(_req) {
+ return new Response('Reloaded Bun!');
+ },
+ });
+
+ await fetch(`http://localhost:${port}/`);
+
+ server.stop();
+
+ expect(serverWasInstrumented).toBeTrue();
});
});
diff --git a/packages/bun/test/sdk.test.ts b/packages/bun/test/sdk.test.ts
index a548cc2614c7..11870f30c101 100644
--- a/packages/bun/test/sdk.test.ts
+++ b/packages/bun/test/sdk.test.ts
@@ -1,14 +1,20 @@
-import { expect, test } from 'bun:test';
+import { describe, expect, test } from 'bun:test';
import { init } from '../src/index';
-test("calling init shouldn't fail", () => {
- init({
+describe('Bun SDK', () => {
+ const initOptions = {
dsn: 'https://00000000000000000000000000000000@o000000.ingest.sentry.io/0000000',
+ tracesSampleRate: 1,
+ };
+
+ test("calling init shouldn't fail", () => {
+ expect(() => {
+ init(initOptions);
+ }).not.toThrow();
});
- expect(true).toBe(true);
-});
-test('should return client from init', () => {
- expect(init({})).not.toBeUndefined();
+ test('should return client from init', () => {
+ expect(init(initOptions)).not.toBeUndefined();
+ });
});
From a67d3ca606589326719481fa5405e8f67ed273b9 Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Fri, 24 Jan 2025 19:21:02 +0100
Subject: [PATCH 34/59] chore: Add external contributor to CHANGELOG.md
(#15156)
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #15148
---------
Co-authored-by: AbhiPrasad <18689448+AbhiPrasad@users.noreply.github.com>
Co-authored-by: Abhijeet Prasad
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0861fcc85cc..9afee28eb184 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
-Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, and @jahands. Thank you for your contributions!
+Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, and @nathankleyn. Thank you for your contributions!
## 9.0.0-alpha.0
From 70d53a992836945fc80db2e0a16b74ed1acaecbe Mon Sep 17 00:00:00 2001
From: "Randolf J." <34705014+jrandolf@users.noreply.github.com>
Date: Mon, 27 Jan 2025 01:00:30 -0800
Subject: [PATCH 35/59] fix(core): Pass `module` into `loadModule` (#15139)
The `loadModule` function currently utilizes `require` to load modules
starting from the `@sentry/core` directory. This approach is
incompatible with package managers like pnpm, which do not hoist
dependencies. Consequently, when another module, such as @sentry/nextjs,
invokes `loadModule`, it fails to locate its own dependencies because
the search initiates within the @sentry/core tree.
---
packages/core/src/utils-hoist/node.ts | 10 ++++++----
packages/nextjs/src/config/webpack.ts | 2 +-
packages/remix/src/utils/instrumentServer.ts | 2 +-
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/packages/core/src/utils-hoist/node.ts b/packages/core/src/utils-hoist/node.ts
index a0311efc7a93..94e8001863aa 100644
--- a/packages/core/src/utils-hoist/node.ts
+++ b/packages/core/src/utils-hoist/node.ts
@@ -41,21 +41,23 @@ function dynamicRequire(mod: any, request: string): any {
* That is to mimic the behavior of `require.resolve` exactly.
*
* @param moduleName module name to require
+ * @param existingModule module to use for requiring
* @returns possibly required module
*/
-export function loadModule(moduleName: string): T | undefined {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function loadModule(moduleName: string, existingModule: any = module): T | undefined {
let mod: T | undefined;
try {
- mod = dynamicRequire(module, moduleName);
+ mod = dynamicRequire(existingModule, moduleName);
} catch (e) {
// no-empty
}
if (!mod) {
try {
- const { cwd } = dynamicRequire(module, 'process');
- mod = dynamicRequire(module, `${cwd()}/node_modules/${moduleName}`) as T;
+ const { cwd } = dynamicRequire(existingModule, 'process');
+ mod = dynamicRequire(existingModule, `${cwd()}/node_modules/${moduleName}`) as T;
} catch (e) {
// no-empty
}
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index bb73a2fb1859..8dbebf3935df 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -332,7 +332,7 @@ export function constructWebpackConfigFunction(
// Symbolication for dev-mode errors is done elsewhere.
if (!isDev) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin') ?? {};
+ const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin', module) ?? {};
if (sentryWebpackPlugin) {
if (!userSentryOptions.sourcemaps?.disable) {
diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts
index a0dd11874416..c6113ea7f0a3 100644
--- a/packages/remix/src/utils/instrumentServer.ts
+++ b/packages/remix/src/utils/instrumentServer.ts
@@ -301,7 +301,7 @@ const makeWrappedCreateRequestHandler = () =>
export function instrumentServer(): void {
const pkg = loadModule<{
createRequestHandler: CreateRequestHandlerFunction;
- }>('@remix-run/server-runtime');
+ }>('@remix-run/server-runtime', module);
if (!pkg) {
DEBUG_BUILD && logger.warn('Remix SDK was unable to require `@remix-run/server-runtime` package.');
From 6571e0c608e70b462253ebcbbc158004fa57ae75 Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Mon, 27 Jan 2025 10:16:11 +0100
Subject: [PATCH 36/59] chore: Add external contributor to CHANGELOG.md
(#15165)
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #15139
---------
Co-authored-by: Lukas Stracke
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9afee28eb184..a30b5747e9dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
-Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, and @nathankleyn. Thank you for your contributions!
+Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions!
## 9.0.0-alpha.0
From 7a208e17d23edb9c5d0afc7709c46fb9ffffa3e3 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Mon, 27 Jan 2025 15:26:39 +0100
Subject: [PATCH 37/59] test(e2e): Unflake replay recording data optional e2e
test (#15168)
Relax the assertion for the replay recording data test so that we allow
arbitrary order but still assert on the exact amount and contents of the
received recording items.
closes #15167
---
.../react-send-to-sentry/tests/send-to-sentry.test.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts
index d9c3e09f2ad2..dc33d271bc18 100644
--- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts
@@ -190,7 +190,7 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => {
if (response.ok) {
const data = await response.json();
- return data[0];
+ return { data: data[0], length: data[0].length };
}
return response.status;
@@ -199,5 +199,6 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => {
timeout: EVENT_POLLING_TIMEOUT,
},
)
- .toEqual(ReplayRecordingData);
+ // Check that that all expected data is present but relax the order to avoid flakes
+ .toEqual({ data: expect.arrayContaining(ReplayRecordingData), length: ReplayRecordingData.length });
});
From a0c126b954ab2e7e3a13543ce04924e5d3162681 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Mon, 27 Jan 2025 16:46:13 +0100
Subject: [PATCH 38/59] test(e2e/nextjs): Avoid making request to example.com
(#15170)
example.com still seems flaky, let's avoid making requests to it
---
.../nextjs-14/app/request-instrumentation/page.tsx | 4 ++--
.../nextjs-14/tests/request-instrumentation.test.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx
index 0d877296cced..c73e6f79db00 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx
@@ -3,9 +3,9 @@ import https from 'https';
export const dynamic = 'force-dynamic';
export default async function Page() {
- await fetch('https://example.com/', { cache: 'no-cache' }).then(res => res.text());
+ await fetch('https://github.com/', { cache: 'no-cache' }).then(res => res.text());
await new Promise(resolve => {
- https.get('https://example.com/', res => {
+ https.get('https://github.com/', res => {
res.on('data', () => {
// Noop consuming some data so that request can close :)
});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts
index d26d4e871b6e..2446ffa68659 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts
@@ -19,7 +19,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => {
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.node_fetch',
}),
- description: 'GET https://example.com/',
+ description: 'GET https://github.com/',
}),
);
@@ -30,7 +30,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => {
'sentry.op': 'http.client',
'sentry.origin': 'auto.http.otel.http',
}),
- description: 'GET https://example.com/',
+ description: 'GET https://github.com/',
}),
);
});
From 6ce53653c19105681784ae4b9d614b69970f5d84 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 27 Jan 2025 15:47:51 +0000
Subject: [PATCH 39/59] feat(deps): bump @sentry/cli from 2.39.1 to 2.41.1
(#15173)
---
packages/remix/package.json | 2 +-
yarn.lock | 95 +++++++++++++++++++++++++++++++++++--
2 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/packages/remix/package.json b/packages/remix/package.json
index 6c88c8f08797..c40305ee8140 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -54,7 +54,7 @@
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@remix-run/router": "1.x",
- "@sentry/cli": "^2.39.1",
+ "@sentry/cli": "^2.41.1",
"@sentry/core": "9.0.0-alpha.0",
"@sentry/node": "9.0.0-alpha.0",
"@sentry/opentelemetry": "9.0.0-alpha.0",
diff --git a/yarn.lock b/yarn.lock
index 2e69bdb6d480..d80fe6f3225e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6612,37 +6612,72 @@
resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781"
integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==
+"@sentry/cli-darwin@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.41.1.tgz#ca7e12bf1ad59bc2df35868ae98abc8869108efa"
+ integrity sha512-7pS3pu/SuhE6jOn3wptstAg6B5nUP878O6s+2svT7b5fKNfYUi/6NPK6dAveh2Ca0rwVq40TO4YFJabWMgTpdQ==
+
"@sentry/cli-linux-arm64@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6"
integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==
+"@sentry/cli-linux-arm64@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.41.1.tgz#948e8af8290418b1562db3531db08e69e39d74bb"
+ integrity sha512-EzYCEnnENBnS5kpNW+2dBcrPZn1MVfywh2joGVQZTpmgDL5YFJ59VOd+K0XuEwqgFI8BSNI14KXZ75s4DD1/Vw==
+
"@sentry/cli-linux-arm@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c"
integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==
+"@sentry/cli-linux-arm@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.41.1.tgz#1e5fa971ae8dfb3ea5564c8503b4e635ae6aed8a"
+ integrity sha512-wNUvquD6qjOCczvuBGf9OiD29nuQ6yf8zzfyPJa5Bdx1QXuteKsKb6HBrMwuIR3liyuu0duzHd+H/+p1n541Hg==
+
"@sentry/cli-linux-i686@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201"
integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==
+"@sentry/cli-linux-i686@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.41.1.tgz#3f01aff314f2ad8fd761f3e6e807a5ec09ae4eb4"
+ integrity sha512-urpQCWrdYnSAsZY3udttuMV88wTJzKZL10xsrp7sjD/Hd+O6qSLVLkxebIlxts70jMLLFHYrQ2bkRg5kKuX6Fg==
+
"@sentry/cli-linux-x64@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889"
integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==
+"@sentry/cli-linux-x64@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.41.1.tgz#30dbf966a4b4c1721ffccd901dfcb6f967db073d"
+ integrity sha512-ZqpYwHXAaK4MMEFlyaLYr6mJTmpy9qP6n30jGhLTW7kHKS3s6GPLCSlNmIfeClrInEt0963fM633ZRnXa04VPw==
+
"@sentry/cli-win32-i686@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35"
integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==
+"@sentry/cli-win32-i686@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.41.1.tgz#f88eeb5d2d4ee46c38d8616ae1eb484108ea71c2"
+ integrity sha512-AuRimCeVsx99DIOr9cwdYBHk39tlmAuPDdy2r16iNzY0InXs4xOys4gGzM7N4vlFQvFkzuc778Su0HkfasgprA==
+
"@sentry/cli-win32-x64@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb"
integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==
-"@sentry/cli@2.39.1", "@sentry/cli@^2.36.1", "@sentry/cli@^2.39.1":
+"@sentry/cli-win32-x64@2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.41.1.tgz#eefd95a2aa184adb464334e265b55a9142070f6f"
+ integrity sha512-6JcPvXGye61+wPp0xdzfc2YLE/Dcud8JdaK8VxLM3b/8+Em7E+UyliDu3uF8+YGUqizY5JYTd3fs17DC8DZhLw==
+
+"@sentry/cli@2.39.1":
version "2.39.1"
resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29"
integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ==
@@ -6661,6 +6696,25 @@
"@sentry/cli-win32-i686" "2.39.1"
"@sentry/cli-win32-x64" "2.39.1"
+"@sentry/cli@^2.36.1", "@sentry/cli@^2.41.1":
+ version "2.41.1"
+ resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.41.1.tgz#a9467ca3ff4acfcdedec1565c9ff726b93758d29"
+ integrity sha512-0GVmDiTV7R1492wkVY4bGcfC0fSmRmQjuxaaPI8CIV9B2VP9pBVCUizi1mevXaaE4I3fM60LI+XYrKFEneuVog==
+ dependencies:
+ https-proxy-agent "^5.0.0"
+ node-fetch "^2.6.7"
+ progress "^2.0.3"
+ proxy-from-env "^1.1.0"
+ which "^2.0.2"
+ optionalDependencies:
+ "@sentry/cli-darwin" "2.41.1"
+ "@sentry/cli-linux-arm" "2.41.1"
+ "@sentry/cli-linux-arm64" "2.41.1"
+ "@sentry/cli-linux-i686" "2.41.1"
+ "@sentry/cli-linux-x64" "2.41.1"
+ "@sentry/cli-win32-i686" "2.41.1"
+ "@sentry/cli-win32-x64" "2.41.1"
+
"@sentry/rollup-plugin@2.22.7":
version "2.22.7"
resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.7.tgz#994bb859437eb1e5fd34c485aaa79ba14354778f"
@@ -7890,7 +7944,12 @@
dependencies:
"@types/unist" "*"
-"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8":
+"@types/history-4@npm:@types/history@4.7.8":
+ version "4.7.8"
+ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
+ integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
+
+"@types/history-5@npm:@types/history@4.7.8":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
@@ -27421,7 +27480,16 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
-"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -27524,7 +27592,14 @@ stringify-object@^3.2.1:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -27689,6 +27764,7 @@ stylus@0.59.0, stylus@^0.59.0:
sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills:
version "3.36.0"
+ uid fd682f6129e507c00bb4e6319cc5d6b767e36061
resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061"
dependencies:
"@jridgewell/gen-mapping" "^0.3.2"
@@ -30314,7 +30390,16 @@ wrangler@^3.67.1:
optionalDependencies:
fsevents "~2.3.2"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@7.0.0, wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
From 5ef37773fb9bb8c9baa929a1eddab59fcaf1c6c9 Mon Sep 17 00:00:00 2001
From: chris-basebone <100691954+chris-basebone@users.noreply.github.com>
Date: Tue, 28 Jan 2025 09:48:22 +0200
Subject: [PATCH 40/59] feat(nuxt): Add `url` to `SourcemapsUploadOptions`
(#15171)
---------
Co-authored-by: Lukas Stracke
---
packages/nuxt/src/common/types.ts | 7 +++++++
packages/nuxt/src/vite/sourceMaps.ts | 1 +
packages/nuxt/test/vite/sourceMaps.test.ts | 5 +++++
3 files changed, 13 insertions(+)
diff --git a/packages/nuxt/src/common/types.ts b/packages/nuxt/src/common/types.ts
index 93ca94016924..8a9a453ff7db 100644
--- a/packages/nuxt/src/common/types.ts
+++ b/packages/nuxt/src/common/types.ts
@@ -32,6 +32,13 @@ type SourceMapsOptions = {
*/
org?: string;
+ /**
+ * The URL of your Sentry instance if you're using self-hosted Sentry.
+ *
+ * @default https://sentry.io by default the plugin will point towards the Sentry SaaS URL
+ */
+ url?: string;
+
/**
* The project slug of your Sentry project.
* Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
diff --git a/packages/nuxt/src/vite/sourceMaps.ts b/packages/nuxt/src/vite/sourceMaps.ts
index 2f90094e6138..0b264e822bcc 100644
--- a/packages/nuxt/src/vite/sourceMaps.ts
+++ b/packages/nuxt/src/vite/sourceMaps.ts
@@ -91,6 +91,7 @@ export function getPluginOptions(
project: sourceMapsUploadOptions.project ?? process.env.SENTRY_PROJECT,
authToken: sourceMapsUploadOptions.authToken ?? process.env.SENTRY_AUTH_TOKEN,
telemetry: sourceMapsUploadOptions.telemetry ?? true,
+ url: sourceMapsUploadOptions.url ?? process.env.SENTRY_URL,
debug: moduleOptions.debug ?? false,
_metaOptions: {
telemetry: {
diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts
index 0c90429fa8d5..b33d314f5166 100644
--- a/packages/nuxt/test/vite/sourceMaps.test.ts
+++ b/packages/nuxt/test/vite/sourceMaps.test.ts
@@ -20,6 +20,7 @@ describe('getPluginOptions', () => {
SENTRY_ORG: 'default-org',
SENTRY_PROJECT: 'default-project',
SENTRY_AUTH_TOKEN: 'default-token',
+ SENTRY_URL: 'https://santry.io',
};
process.env = { ...defaultEnv };
@@ -31,6 +32,7 @@ describe('getPluginOptions', () => {
org: 'default-org',
project: 'default-project',
authToken: 'default-token',
+ url: 'https://santry.io',
telemetry: true,
sourcemaps: expect.objectContaining({
rewriteSources: expect.any(Function),
@@ -114,6 +116,7 @@ describe('getPluginOptions', () => {
assets: ['custom-assets/**/*'],
filesToDeleteAfterUpload: ['delete-this.js'],
},
+ url: 'https://santry.io',
},
debug: true,
unstable_sentryBundlerPluginOptions: {
@@ -124,6 +127,7 @@ describe('getPluginOptions', () => {
release: {
name: 'test-release',
},
+ url: 'https://suntry.io',
},
};
const options = getPluginOptions(customOptions, false);
@@ -140,6 +144,7 @@ describe('getPluginOptions', () => {
release: expect.objectContaining({
name: 'test-release',
}),
+ url: 'https://suntry.io',
}),
);
});
From 3a1006ea49d2aa67c27f917c30aceae16cf01eef Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Tue, 28 Jan 2025 08:58:36 +0100
Subject: [PATCH 41/59] chore: Add external contributor to CHANGELOG.md
(#15203)
---------
Co-authored-by: Lukas Stracke
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a30b5747e9dc..ad2232acfbb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
-Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions!
+Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions!
## 9.0.0-alpha.0
From 76b5c33173157bcf3b1997135f0c1fc54f021abf Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Tue, 28 Jan 2025 16:51:30 +0100
Subject: [PATCH 42/59] test: Fix nextjs build warning (#15207)
---
.../e2e-tests/test-applications/nextjs-app-dir/assert-build.ts | 2 +-
packages/nextjs/src/config/webpack.ts | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts
index 955988101724..70564e0c12bb 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts
@@ -10,7 +10,7 @@ const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8');
// Assert that there was no funky build time warning when we are on a stable (pinned) version
if (nextjsVersion !== 'latest' && !nextjsVersion.includes('-canary') && !nextjsVersion.includes('-rc')) {
- assert.doesNotMatch(buildStderr, /Import trace for requested module/); // This is Next.js/Webpack speech for "something is off"
+ assert.doesNotMatch(buildStderr, /Import trace for requested module/, `Build warning in output:\n${buildStderr}`); // This is Next.js/Webpack speech for "something is off"
}
// Assert that all static components stay static and all dynamic components stay dynamic
diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts
index 8dbebf3935df..f82bb4a0476e 100644
--- a/packages/nextjs/src/config/webpack.ts
+++ b/packages/nextjs/src/config/webpack.ts
@@ -728,6 +728,7 @@ function addOtelWarningIgnoreRule(newConfig: WebpackConfigObjectWithModuleRules)
// We provide these objects in addition to the hook above to provide redundancy in case the hook fails.
{ module: /@opentelemetry\/instrumentation/, message: /Critical dependency/ },
{ module: /@prisma\/instrumentation/, message: /Critical dependency/ },
+ { module: /require-in-the-middle/, message: /Critical dependency/ },
] satisfies IgnoreWarningsOption;
if (newConfig.ignoreWarnings === undefined) {
From a27652f09538e91d3c6c23f8eeadb75743067428 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Tue, 28 Jan 2025 17:59:05 +0100
Subject: [PATCH 43/59] chore(deps): Update playwright to 1.50.0 (#15164)
Also align to use the same version everywhere, tilde-restricted.
---
.github/workflows/build.yml | 2 +-
.../browser-integration-tests/package.json | 3 +-
.../scripts/detectFlakyTests.ts | 49 ++----
.../onError/syntax-errors/test.ts | 2 +-
.../test.ts-snapshots/seg-0-snap-full | 113 -------------
.../test.ts-snapshots/seg-1-snap-incremental | 45 -----
.../seg-1-snap-incremental-chromium | 4 +-
.../test.ts-snapshots/seg-2-snap-full | 113 -------------
.../test.ts-snapshots/seg-3-snap-incremental | 45 -----
.../seg-3-snap-incremental-chromium | 4 +-
.../test.ts-snapshots/seg-4-snap-full | 156 ------------------
.../test.ts-snapshots/seg-5-snap-incremental | 45 -----
.../test.ts-snapshots/seg-6-snap-incremental | 54 ------
.../test.ts-snapshots/seg-7-snap-incremental | 54 ------
.../test.ts-snapshots/seg-8-snap-full | 113 -------------
.../test.ts-snapshots/seg-9-snap-incremental | 45 -----
.../seg-9-snap-incremental-chromium | 4 +-
.../suites/replay/sessionExpiry/test.ts | 2 +-
.../test.ts-snapshots/snapshot-0-webkit.json | 113 -------------
.../test.ts-snapshots/snapshot-2-webkit.json | 127 --------------
.../suites/stacktraces/init.js | 6 +
.../suites/stacktraces/template.html | 2 -
.../http-timings/init.js | 6 +
.../interactions/init.js | 6 +
.../long-animation-frame-enabled/test.ts | 9 +-
.../test.ts | 9 +-
.../tracing/metrics/web-vitals-lcp/test.ts | 53 +-----
.../test-applications/angular-17/package.json | 2 +-
.../test-applications/angular-18/package.json | 2 +-
.../test-applications/angular-19/package.json | 2 +-
.../test-applications/astro-4/package.json | 2 +-
.../test-applications/astro-5/package.json | 2 +-
.../aws-lambda-layer-cjs/package.json | 2 +-
.../aws-serverless-esm/package.json | 2 +-
.../create-next-app/package.json | 2 +-
.../package.json | 2 +-
.../create-remix-app-express/package.json | 2 +-
.../create-remix-app-v2/package.json | 2 +-
.../default-browser/package.json | 2 +-
.../ember-classic/package.json | 2 +-
.../ember-embroider/package.json | 2 +-
.../test-applications/nestjs-8/package.json | 2 +-
.../nestjs-basic-with-graphql/package.json | 2 +-
.../nestjs-basic/package.json | 2 +-
.../nestjs-distributed-tracing/package.json | 2 +-
.../nestjs-fastify/package.json | 2 +-
.../nestjs-graphql/package.json | 2 +-
.../package.json | 2 +-
.../nestjs-with-submodules/package.json | 2 +-
.../test-applications/nextjs-13/package.json | 2 +-
.../test-applications/nextjs-14/package.json | 2 +-
.../test-applications/nextjs-15/package.json | 2 +-
.../nextjs-app-dir/package.json | 2 +-
.../test-applications/nextjs-t3/package.json | 2 +-
.../nextjs-turbo/package.json | 2 +-
.../node-connect/package.json | 2 +-
.../node-express-cjs-preload/package.json | 2 +-
.../node-express-esm-loader/package.json | 2 +-
.../node-express-esm-preload/package.json | 2 +-
.../package.json | 2 +-
.../package.json | 2 +-
.../node-express-send-to-sentry/package.json | 2 +-
.../node-express/package.json | 2 +-
.../node-fastify-5/package.json | 2 +-
.../node-fastify/package.json | 2 +-
.../test-applications/node-hapi/package.json | 2 +-
.../test-applications/node-koa/package.json | 2 +-
.../node-otel-custom-sampler/package.json | 2 +-
.../node-otel-sdk-node/package.json | 2 +-
.../node-otel-without-tracing/package.json | 2 +-
.../test-applications/node-otel/package.json | 2 +-
.../node-profiling/package.json | 2 +-
.../nuxt-3-dynamic-import/package.json | 2 +-
.../test-applications/nuxt-3-min/package.json | 2 +-
.../nuxt-3-top-level-import/package.json | 2 +-
.../test-applications/nuxt-3/package.json | 2 +-
.../test-applications/nuxt-4/package.json | 2 +-
.../test-applications/react-17/package.json | 2 +-
.../test-applications/react-19/package.json | 2 +-
.../react-create-browser-router/package.json | 2 +-
.../react-create-hash-router/package.json | 2 +-
.../react-create-memory-router/package.json | 2 +-
.../react-router-5/package.json | 2 +-
.../package.json | 2 +-
.../react-router-6-use-routes/package.json | 2 +-
.../react-router-6/package.json | 2 +-
.../react-router-7-spa/package.json | 2 +-
.../react-send-to-sentry/package.json | 2 +-
.../solid-solidrouter/package.json | 2 +-
.../test-applications/solid/package.json | 2 +-
.../solidstart-dynamic-import/package.json | 2 +-
.../solidstart-spa/package.json | 2 +-
.../solidstart-top-level-import/package.json | 2 +-
.../test-applications/solidstart/package.json | 2 +-
.../test-applications/svelte-5/package.json | 2 +-
.../sveltekit-2-svelte-5/package.json | 2 +-
.../sveltekit-2-twp/package.json | 2 +-
.../sveltekit-2/package.json | 2 +-
.../tanstack-router/package.json | 2 +-
.../test-applications/vue-3/package.json | 2 +-
.../test-applications/webpack-4/package.json | 2 +-
.../test-applications/webpack-5/package.json | 2 +-
dev-packages/test-utils/package.json | 4 +-
packages/browser/src/transports/fetch.ts | 2 +-
.../browser/src/utils/lazyLoadIntegration.ts | 2 +-
.../browser/test/transports/fetch.test.ts | 2 +-
packages/deno/src/transports/index.ts | 2 +-
yarn.lock | 28 ++--
108 files changed, 148 insertions(+), 1226 deletions(-)
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json
delete mode 100644 dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 24f3ee0454f2..34bb9eb5799f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -600,7 +600,7 @@ jobs:
env:
PW_BUNDLE: ${{ matrix.bundle }}
working-directory: dev-packages/browser-integration-tests
- run: yarn test:ci${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }}
+ run: yarn test:all${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }}
- name: Upload Playwright Traces
uses: actions/upload-artifact@v4
diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json
index bfd1ed3d2717..77cf36078a2a 100644
--- a/dev-packages/browser-integration-tests/package.json
+++ b/dev-packages/browser-integration-tests/package.json
@@ -35,13 +35,12 @@
"test:loader:replay_buffer": "PW_BUNDLE=loader_replay_buffer yarn test:loader",
"test:loader:full": "PW_BUNDLE=loader_tracing_replay yarn test:loader",
"test:loader:debug": "PW_BUNDLE=loader_debug yarn test:loader",
- "test:ci": "yarn test:all",
"test:update-snapshots": "yarn test:all --update-snapshots",
"test:detect-flaky": "ts-node scripts/detectFlakyTests.ts"
},
"dependencies": {
"@babel/preset-typescript": "^7.16.7",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/rrweb": "2.31.0",
"@sentry/browser": "9.0.0-alpha.0",
"axios": "1.7.7",
diff --git a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts
index bf653dfad6b7..6fa8e8ddd416 100644
--- a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts
+++ b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts
@@ -4,14 +4,9 @@ import * as path from 'path';
import * as glob from 'glob';
/**
- * The number of browsers we run the tests in.
+ * Assume that each test runs for 3s.
*/
-const NUM_BROWSERS = 3;
-
-/**
- * Assume that each test runs for 2s.
- */
-const ASSUMED_TEST_DURATION_SECONDS = 2;
+const ASSUMED_TEST_DURATION_SECONDS = 3;
/**
* We keep the runtime of the detector if possible under 30min.
@@ -51,22 +46,12 @@ ${changedPaths.join('\n')}
try {
await new Promise((resolve, reject) => {
const cp = childProcess.spawn(
- `npx playwright test ${
- testPaths.length ? testPaths.join(' ') : './suites'
- } --reporter='line' --repeat-each ${repeatEachCount}`,
- { shell: true, cwd },
+ `npx playwright test ${testPaths.length ? testPaths.join(' ') : './suites'} --repeat-each ${repeatEachCount}`,
+ { shell: true, cwd, stdio: 'inherit' },
);
let error: Error | undefined;
- cp.stdout.on('data', data => {
- console.log(data ? (data as object).toString() : '');
- });
-
- cp.stderr.on('data', data => {
- console.log(data ? (data as object).toString() : '');
- });
-
cp.on('error', e => {
console.error(e);
error = e;
@@ -107,15 +92,16 @@ function getPerTestRunCount(testPaths: string[]) {
const estimatedNumberOfTests = testPaths.map(getApproximateNumberOfTests).reduce((a, b) => a + b);
console.log(`Estimated number of tests: ${estimatedNumberOfTests}`);
- // tests are usually run against all browsers we test with, so let's assume this
- const testRunCount = estimatedNumberOfTests * NUM_BROWSERS;
+ const testRunCount = estimatedNumberOfTests;
console.log(`Estimated test runs for one round: ${testRunCount}`);
const estimatedTestRuntime = testRunCount * ASSUMED_TEST_DURATION_SECONDS;
console.log(`Estimated test runtime: ${estimatedTestRuntime}s`);
const expectedPerTestRunCount = Math.floor(MAX_TARGET_TEST_RUNTIME_SECONDS / estimatedTestRuntime);
- console.log(`Expected per-test run count: ${expectedPerTestRunCount}`);
+ console.log(
+ `Calculated # of repetitions: ${expectedPerTestRunCount} (min ${MIN_PER_TEST_RUN_COUNT}, max ${MAX_PER_TEST_RUN_COUNT})`,
+ );
return Math.min(MAX_PER_TEST_RUN_COUNT, Math.max(expectedPerTestRunCount, MIN_PER_TEST_RUN_COUNT));
}
@@ -128,22 +114,7 @@ function getTestPaths(): string[] {
cwd: path.join(__dirname, '../'),
});
- return paths.map(p => path.dirname(p));
-}
-
-function logError(error: unknown) {
- if (process.env.CI) {
- console.log('::group::Test failed');
- } else {
- console.error(' ⚠️ Test failed:');
- }
-
- console.log((error as any).stdout);
- console.log((error as any).stderr);
-
- if (process.env.CI) {
- console.log('::endgroup::');
- }
+ return paths.map(p => `${path.dirname(p)}/`);
}
/**
@@ -156,7 +127,7 @@ function logError(error: unknown) {
function getApproximateNumberOfTests(testPath: string): number {
try {
const content = fs.readFileSync(path.join(process.cwd(), testPath, 'test.ts'), 'utf-8');
- const matches = content.match(/it\(|test\(|sentryTest\(/g);
+ const matches = content.match(/sentryTest\(/g);
return Math.max(matches ? matches.length : 1, 1);
} catch (e) {
console.error(`Could not read file ${testPath}`);
diff --git a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts
index c11d1897e1c2..51ac86e0cf62 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts
@@ -27,7 +27,7 @@ sentryTest('should catch syntax errors', async ({ getLocalTestUrl, page, browser
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'SyntaxError',
- value: "Unexpected token '{'",
+ value: "Failed to execute 'appendChild' on 'Node': Unexpected token '{'",
mechanism: {
type: 'onerror',
handled: false,
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full
deleted file mode 100644
index 0d77b67cb862..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full
+++ /dev/null
@@ -1,113 +0,0 @@
-[
- {
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "id": 2
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "id": 5
- }
- ],
- "id": 4
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 6
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "id": 8
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "id": "go-background"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "*** ***",
- "id": 10
- }
- ],
- "id": 9
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 11
- },
- {
- "type": 2,
- "tagName": "a",
- "attributes": {
- "href": "http://sentry-test.io/page-0.html"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "** ** *** ****",
- "id": 13
- }
- ],
- "id": 12
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 14
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "id": 15
- }
- ],
- "id": 7
- }
- ],
- "id": 3
- }
- ],
- "id": 1
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
- }
-]
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental
deleted file mode 100644
index 02a3e3f893d6..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental
+++ /dev/null
@@ -1,45 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 9
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 9,
- "x": 41,
- "y": 18,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium
index 02a3e3f893d6..4e76cedf3b0c 100644
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium
+++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium
@@ -6,7 +6,7 @@
"type": 1,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
@@ -26,7 +26,7 @@
"type": 0,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full
deleted file mode 100644
index 0d77b67cb862..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full
+++ /dev/null
@@ -1,113 +0,0 @@
-[
- {
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "id": 2
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "id": 5
- }
- ],
- "id": 4
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 6
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "id": 8
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "id": "go-background"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "*** ***",
- "id": 10
- }
- ],
- "id": 9
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 11
- },
- {
- "type": 2,
- "tagName": "a",
- "attributes": {
- "href": "http://sentry-test.io/page-0.html"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "** ** *** ****",
- "id": 13
- }
- ],
- "id": 12
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 14
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "id": 15
- }
- ],
- "id": 7
- }
- ],
- "id": 3
- }
- ],
- "id": 1
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
- }
-]
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental
deleted file mode 100644
index 02a3e3f893d6..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental
+++ /dev/null
@@ -1,45 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 9
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 9,
- "x": 41,
- "y": 18,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium
index 02a3e3f893d6..4e76cedf3b0c 100644
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium
+++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium
@@ -6,7 +6,7 @@
"type": 1,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
@@ -26,7 +26,7 @@
"type": 0,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full
deleted file mode 100644
index 1c3d1f22aeba..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full
+++ /dev/null
@@ -1,156 +0,0 @@
-[
- {
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "id": 2
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "id": 5
- }
- ],
- "id": 4
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 6
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "id": 8
- },
- {
- "type": 2,
- "tagName": "h1",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "********* ****",
- "id": 10
- }
- ],
- "id": 9
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 11
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "id": "go-background"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "*** ***",
- "id": 13
- }
- ],
- "id": 12
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 14
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "id": "spa-navigation"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "***** * *** **********",
- "id": 16
- }
- ],
- "id": 15
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 17
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 18
- },
- {
- "type": 2,
- "tagName": "a",
- "attributes": {
- "href": "http://sentry-test.io/index.html"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "** **** ** ***** ****",
- "id": 20
- }
- ],
- "id": 19
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 21
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "id": 22
- }
- ],
- "id": 7
- }
- ],
- "id": 3
- }
- ],
- "id": 1
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
- }
-]
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental
deleted file mode 100644
index 6dd84be3e2dc..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental
+++ /dev/null
@@ -1,45 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 12,
- "x": 41.810001373291016,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 12
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 12,
- "x": 41.810001373291016,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 12,
- "x": 41,
- "y": 90,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental
deleted file mode 100644
index 575f1210087b..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental
+++ /dev/null
@@ -1,54 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 15,
- "x": 157.13999938964844,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 6,
- "id": 12
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 15
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 15,
- "x": 157.13999938964844,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 15,
- "x": 157,
- "y": 90,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental
deleted file mode 100644
index f952a6e3bfaa..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental
+++ /dev/null
@@ -1,54 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 12,
- "x": 41.810001373291016,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 6,
- "id": 15
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 12
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 12,
- "x": 41.810001373291016,
- "y": 90.37000274658203
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 12,
- "x": 41,
- "y": 90,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full
deleted file mode 100644
index 0d77b67cb862..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full
+++ /dev/null
@@ -1,113 +0,0 @@
-[
- {
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "id": 2
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "id": 5
- }
- ],
- "id": 4
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 6
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "id": 8
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "id": "go-background"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "*** ***",
- "id": 10
- }
- ],
- "id": 9
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 11
- },
- {
- "type": 2,
- "tagName": "a",
- "attributes": {
- "href": "http://sentry-test.io/page-0.html"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "** ** *** ****",
- "id": 13
- }
- ],
- "id": 12
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 14
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "id": 15
- }
- ],
- "id": 7
- }
- ],
- "id": 3
- }
- ],
- "id": 1
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
- }
-]
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental
deleted file mode 100644
index 02a3e3f893d6..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental
+++ /dev/null
@@ -1,45 +0,0 @@
-[
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 1,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 5,
- "id": 9
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 0,
- "id": 9,
- "x": 41.810001373291016,
- "y": 18.479999542236328
- },
- "timestamp": [timestamp]
- },
- {
- "type": 3,
- "data": {
- "source": 2,
- "type": 2,
- "id": 9,
- "x": 41,
- "y": 18,
- "pointerType": 0
- },
- "timestamp": [timestamp]
- }
-]
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium
index 02a3e3f893d6..4e76cedf3b0c 100644
--- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium
+++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium
@@ -6,7 +6,7 @@
"type": 1,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
@@ -26,7 +26,7 @@
"type": 0,
"id": 9,
"x": 41.810001373291016,
- "y": 18.479999542236328
+ "y": 18.5
},
"timestamp": [timestamp]
},
diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts
index 79c7758ec099..6bb199c42146 100644
--- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts
@@ -15,7 +15,7 @@ import {
const SESSION_TIMEOUT = 2000;
sentryTest('handles an expired session', async ({ browserName, forceFlushReplay, getLocalTestUrl, page }) => {
- if (shouldSkipReplayTest() || browserName === 'webkit') {
+ if (shouldSkipReplayTest() || browserName !== 'chromium') {
sentryTest.skip();
}
diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json
deleted file mode 100644
index d510b410a343..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json
+++ /dev/null
@@ -1,113 +0,0 @@
-{
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "id": 2
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "id": 5
- }
- ],
- "id": 4
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 6
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "id": 8
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "onclick": "console.log('Test log 1')",
- "id": "button1"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "***** **",
- "id": 10
- }
- ],
- "id": 9
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 11
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "onclick": "console.log('Test log 2')",
- "id": "button2"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "***** **",
- "id": 13
- }
- ],
- "id": 12
- },
- {
- "type": 3,
- "textContent": "\n ",
- "id": 14
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "id": 15
- }
- ],
- "id": 7
- }
- ],
- "id": 3
- }
- ],
- "id": 1
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
-}
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json
deleted file mode 100644
index 13e5b1b70103..000000000000
--- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json
+++ /dev/null
@@ -1,127 +0,0 @@
-{
- "type": 2,
- "data": {
- "node": {
- "type": 0,
- "childNodes": [
- {
- "type": 1,
- "name": "html",
- "publicId": "",
- "systemId": "",
- "rootId": 16,
- "id": 17
- },
- {
- "type": 2,
- "tagName": "html",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "head",
- "attributes": {},
- "childNodes": [
- {
- "type": 2,
- "tagName": "meta",
- "attributes": {
- "charset": "utf-8"
- },
- "childNodes": [],
- "rootId": 16,
- "id": 20
- }
- ],
- "rootId": 16,
- "id": 19
- },
- {
- "type": 3,
- "textContent": "\n ",
- "rootId": 16,
- "id": 21
- },
- {
- "type": 2,
- "tagName": "body",
- "attributes": {},
- "childNodes": [
- {
- "type": 3,
- "textContent": "\n ",
- "rootId": 16,
- "id": 23
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "onclick": "console.log('Test log 1')",
- "id": "button1"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "***** **",
- "rootId": 16,
- "id": 25
- }
- ],
- "rootId": 16,
- "id": 24
- },
- {
- "type": 3,
- "textContent": "\n ",
- "rootId": 16,
- "id": 26
- },
- {
- "type": 2,
- "tagName": "button",
- "attributes": {
- "onclick": "console.log('Test log 2')",
- "id": "button2"
- },
- "childNodes": [
- {
- "type": 3,
- "textContent": "***** **",
- "rootId": 16,
- "id": 28
- }
- ],
- "rootId": 16,
- "id": 27
- },
- {
- "type": 3,
- "textContent": "\n ",
- "rootId": 16,
- "id": 29
- },
- {
- "type": 3,
- "textContent": "\n\n",
- "rootId": 16,
- "id": 30
- }
- ],
- "rootId": 16,
- "id": 22
- }
- ],
- "rootId": 16,
- "id": 18
- }
- ],
- "id": 16
- },
- "initialOffset": {
- "left": 0,
- "top": 0
- }
- },
- "timestamp": [timestamp]
-}
\ No newline at end of file
diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/init.js b/dev-packages/browser-integration-tests/suites/stacktraces/init.js
index d8c94f36fdd0..ce283e32d303 100644
--- a/dev-packages/browser-integration-tests/suites/stacktraces/init.js
+++ b/dev-packages/browser-integration-tests/suites/stacktraces/init.js
@@ -4,4 +4,10 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ transportOptions: {
+ fetchOptions: {
+ // See: https://github.com/microsoft/playwright/issues/34497
+ keepalive: false,
+ },
+ },
});
diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/template.html b/dev-packages/browser-integration-tests/suites/stacktraces/template.html
index d91677daaab5..39082f45e532 100644
--- a/dev-packages/browser-integration-tests/suites/stacktraces/template.html
+++ b/dev-packages/browser-integration-tests/suites/stacktraces/template.html
@@ -3,9 +3,7 @@
-
-
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js
index e32d09a13fab..f6291c3183c7 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js
@@ -13,4 +13,10 @@ Sentry.init({
}),
],
tracesSampleRate: 1,
+ transportOptions: {
+ fetchOptions: {
+ // See: https://github.com/microsoft/playwright/issues/34497
+ keepalive: false,
+ },
+ },
});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js
index 846538e7f3f0..718f7cca0005 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js
@@ -14,4 +14,10 @@ Sentry.init({
}),
],
tracesSampleRate: 1,
+ transportOptions: {
+ fetchOptions: {
+ // See: https://github.com/microsoft/playwright/issues/34497
+ keepalive: false,
+ },
+ },
});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts
index b127a1f674a1..ca46bc078e90 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts
@@ -84,13 +84,11 @@ sentryTest(
const eventData = await promise;
- const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame'));
+ const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || [];
- expect(uiSpans?.length).toBeGreaterThanOrEqual(2);
+ expect(uiSpans.length).toBeGreaterThanOrEqual(2);
- const eventListenerUISpan = (uiSpans || []).find(
- span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick',
- )!;
+ const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!;
expect(eventListenerUISpan).toEqual(
expect.objectContaining({
@@ -100,6 +98,7 @@ sentryTest(
data: {
'browser.script.invoker': 'BUTTON#clickme.onclick',
'browser.script.invoker_type': 'event-listener',
+ 'code.filepath': 'https://example.com/path/to/script.js',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics',
},
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts
index 51447ee84586..b43e383be985 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts
@@ -86,13 +86,11 @@ sentryTest(
const eventData = await promise;
- const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame'));
+ const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || [];
- expect(uiSpans?.length).toBeGreaterThanOrEqual(2);
+ expect(uiSpans.length).toBeGreaterThanOrEqual(2);
- const eventListenerUISpan = (uiSpans || []).find(
- span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick',
- )!;
+ const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!;
expect(eventListenerUISpan).toEqual(
expect.objectContaining({
@@ -102,6 +100,7 @@ sentryTest(
data: {
'browser.script.invoker': 'BUTTON#clickme.onclick',
'browser.script.invoker_type': 'event-listener',
+ 'code.filepath': 'https://example.com/path/to/script.js',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics',
},
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts
index 1cfee3b670a7..c4b41923a082 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts
@@ -5,21 +5,16 @@ import type { Event } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
-/*
- Because we "serve" the html test page as a static file, all requests for the image
- are considered 3rd party requests. So the LCP value we obtain for the image is also
- considered a 3rd party LCP value, meaning `renderTime` is only set if we also
- return the `Timing-Allow-Origin` header.
-*/
-
-sentryTest('captures LCP vitals with element details.', async ({ browserName, getLocalTestUrl, page }) => {
+sentryTest('captures LCP vitals with element details', async ({ browserName, getLocalTestUrl, page }) => {
if (shouldSkipTracingTest() || browserName !== 'chromium') {
sentryTest.skip();
}
page.route('**', route => route.continue());
page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({ path: `${__dirname}/assets/sentry-logo-600x179.png` });
+ return route.fulfill({
+ path: `${__dirname}/assets/sentry-logo-600x179.png`,
+ });
});
const url = await getLocalTestUrl({ testDir: __dirname });
@@ -31,42 +26,8 @@ sentryTest('captures LCP vitals with element details.', async ({ browserName, ge
expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true);
expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0);
expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0);
+ expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0);
- // renderTime is 0 because we don't return the `Timing-Allow-Origin` header
- // and the image is loaded from a 3rd party origin
- expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBe(0);
-
- // The LCP value should be the loadTime because the renderTime is not set
- expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.loadTime']);
+ // The LCP value should be the renderTime because the renderTime is set
+ expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']);
});
-
-sentryTest(
- 'captures LCP renderTime when returning Timing-Allow-Origin header.',
- async ({ browserName, getLocalTestUrl, page }) => {
- if (shouldSkipTracingTest() || browserName !== 'chromium') {
- sentryTest.skip();
- }
-
- page.route('**', route => route.continue());
- page.route('**/my/image.png', async (route: Route) => {
- return route.fulfill({
- path: `${__dirname}/assets/sentry-logo-600x179.png`,
- headers: { 'Timing-Allow-Origin': '*' },
- });
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
- const [eventData] = await Promise.all([getFirstSentryEnvelopeRequest(page), page.goto(url)]);
-
- expect(eventData.measurements).toBeDefined();
- expect(eventData.measurements?.lcp?.value).toBeDefined();
-
- expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true);
- expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0);
- expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0);
- expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0);
-
- // The LCP value should be the renderTime because the renderTime is set
- expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']);
- },
-);
diff --git a/dev-packages/e2e-tests/test-applications/angular-17/package.json b/dev-packages/e2e-tests/test-applications/angular-17/package.json
index 682c47d30329..e6b1df6cd387 100644
--- a/dev-packages/e2e-tests/test-applications/angular-17/package.json
+++ b/dev-packages/e2e-tests/test-applications/angular-17/package.json
@@ -29,7 +29,7 @@
"zone.js": "~0.14.3"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@angular-devkit/build-angular": "^17.1.1",
"@angular/cli": "^17.1.1",
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/package.json b/dev-packages/e2e-tests/test-applications/angular-18/package.json
index aec1b1d9dac0..288b1b119912 100644
--- a/dev-packages/e2e-tests/test-applications/angular-18/package.json
+++ b/dev-packages/e2e-tests/test-applications/angular-18/package.json
@@ -29,7 +29,7 @@
"zone.js": "~0.14.3"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@angular-devkit/build-angular": "^18.0.0",
"@angular/cli": "^18.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/angular-19/package.json b/dev-packages/e2e-tests/test-applications/angular-19/package.json
index c8ae32b52378..f7544ccb5239 100644
--- a/dev-packages/e2e-tests/test-applications/angular-19/package.json
+++ b/dev-packages/e2e-tests/test-applications/angular-19/package.json
@@ -32,7 +32,7 @@
"@angular-devkit/build-angular": "^19.0.0",
"@angular/cli": "^19.0.0",
"@angular/compiler-cli": "^19.0.0",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@types/jasmine": "~5.1.0",
"http-server": "^14.1.1",
diff --git a/dev-packages/e2e-tests/test-applications/astro-4/package.json b/dev-packages/e2e-tests/test-applications/astro-4/package.json
index b80e408b3e32..4e49cf150bb4 100644
--- a/dev-packages/e2e-tests/test-applications/astro-4/package.json
+++ b/dev-packages/e2e-tests/test-applications/astro-4/package.json
@@ -14,7 +14,7 @@
"dependencies": {
"@astrojs/check": "0.9.2",
"@astrojs/node": "8.3.4",
- "@playwright/test": "^1.46.0",
+ "@playwright/test": "~1.50.0",
"@sentry/astro": "* || latest",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@spotlightjs/astro": "2.1.6",
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json
index 170c716be756..189c2834f3ae 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json
@@ -13,7 +13,7 @@
"dependencies": {
"@astrojs/internal-helpers": "^0.4.2",
"@astrojs/node": "^9.0.0",
- "@playwright/test": "^1.46.0",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/astro": "latest || *",
"astro": "^5.0.3"
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
index 01375fe0c346..ab822e9d669d 100644
--- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
@@ -14,7 +14,7 @@
},
"devDependencies": {
"@sentry-internal/test-utils": "link:../../../test-utils",
- "@playwright/test": "^1.44.1"
+ "@playwright/test": "~1.50.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json
index 67aa6bc247d5..746abf5b4cbc 100644
--- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json
+++ b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json
@@ -14,7 +14,7 @@
},
"devDependencies": {
"@sentry-internal/test-utils": "link:../../../test-utils",
- "@playwright/test": "^1.41.1"
+ "@playwright/test": "~1.50.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/package.json b/dev-packages/e2e-tests/test-applications/create-next-app/package.json
index e70e7ed4c797..3e8416b3aaee 100644
--- a/dev-packages/e2e-tests/test-applications/create-next-app/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-next-app/package.json
@@ -22,7 +22,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
index b37c7a8c0705..20433c4cd15f 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json
@@ -25,7 +25,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@remix-run/dev": "^2.7.2",
"@sentry/core": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json
index 5c362ffb97a1..c44b7a8fe5bc 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json
@@ -28,7 +28,7 @@
"source-map-support": "^0.5.21"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@remix-run/dev": "^2.7.2",
"@sentry/core": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
index 977408d0945a..e8a03932560a 100644
--- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
+++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json
@@ -21,7 +21,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@remix-run/dev": "2.7.2",
"@remix-run/eslint-config": "2.7.2",
diff --git a/dev-packages/e2e-tests/test-applications/default-browser/package.json b/dev-packages/e2e-tests/test-applications/default-browser/package.json
index a147fa576d8a..5f70156c1e1c 100644
--- a/dev-packages/e2e-tests/test-applications/default-browser/package.json
+++ b/dev-packages/e2e-tests/test-applications/default-browser/package.json
@@ -28,7 +28,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"webpack": "^5.91.0",
"serve": "14.0.1",
diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json
index a0c0b4101d09..d29714415897 100644
--- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json
+++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json
@@ -24,7 +24,7 @@
"@ember/optional-features": "~2.0.0",
"@glimmer/component": "~1.1.2",
"@glimmer/tracking": "~1.1.2",
- "@playwright/test": "~1.44.1",
+ "@playwright/test": "~1.50.0",
"@ember/string": "~3.1.1",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/ember": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json
index b96b70876f53..a7b63be1218f 100644
--- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json
+++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json
@@ -50,7 +50,7 @@
"loader.js": "^4.7.0",
"tracked-built-ins": "^3.3.0",
"webpack": "^5.91.0",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry/ember": "latest || *",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@tsconfig/ember": "^3.0.6",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json
index 2c749afae8a4..a4a046b9106d 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json
@@ -25,7 +25,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json
index dea9e11f1423..816c5ec0fcce 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json
@@ -27,7 +27,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json
index 0de1aa1b3e6a..3d367978c6c7 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json
@@ -25,7 +25,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json
index 7ea84e7afe05..133c56648f1f 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json
@@ -24,7 +24,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json
index d456c22370df..88092675cc98 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json
@@ -26,7 +26,7 @@
"fastify": "^4.28.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json
index d6e198cd7567..0f645056e025 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json
@@ -27,7 +27,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json
index 34d6004ebd8e..791c3df2305b 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json
@@ -23,7 +23,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json
index a54eb72275a8..dfd015acade9 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json
+++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json
@@ -23,7 +23,7 @@
"rxjs": "^7.8.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json
index d81ca4fa6443..cb78ab4ecb4b 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json
@@ -23,7 +23,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/browser-utils": "latest || *",
"@sentry-internal/feedback": "latest || *",
"@sentry-internal/replay": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
index b041f71f7f7e..a1e33dbb10ec 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
@@ -23,7 +23,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry-internal/feedback": "latest || *",
"@sentry-internal/replay-canvas": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json
index 9e99e182f4db..66b38e2e5cc0 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json
@@ -24,7 +24,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry-internal/feedback": "latest || *",
"@sentry-internal/replay-canvas": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
index 492f8d94ef71..f779d9ece306 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
@@ -25,7 +25,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry-internal/feedback": "latest || *",
"@sentry-internal/replay-canvas": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json
index 1ebef0ce37ae..4c6f9f281406 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json
@@ -29,7 +29,7 @@
"zod": "^3.23.3"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@types/eslint": "^8.56.10",
"@types/node": "^18.19.1",
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json
index d0d1a3abe349..d2cdcfb89561 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json
@@ -23,7 +23,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry-internal/feedback": "latest || *",
"@sentry-internal/replay-canvas": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/node-connect/package.json b/dev-packages/e2e-tests/test-applications/node-connect/package.json
index 743b079c8af1..f882e55d8797 100644
--- a/dev-packages/e2e-tests/test-applications/node-connect/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-connect/package.json
@@ -20,7 +20,7 @@
"ts-node": "10.9.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json
index 363c1e06636c..c1544b4df93b 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json
@@ -14,7 +14,7 @@
"express": "4.20.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json
index 6156211e27f8..2d61a1c4f7ef 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json
@@ -14,7 +14,7 @@
"express": "4.20.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json
index 03f483307290..fac430272a92 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json
@@ -14,7 +14,7 @@
"express": "4.20.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json
index 844ca51fd038..81a659a7dbfb 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json
@@ -14,7 +14,7 @@
"express": "4.20.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json
index b6daa54355f3..e9989a5790a6 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json
@@ -22,7 +22,7 @@
"zod": "~3.22.4"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json
index 5f3442eb3af9..338a7ccbd604 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json
@@ -19,7 +19,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1"
+ "@playwright/test": "~1.50.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/dev-packages/e2e-tests/test-applications/node-express/package.json b/dev-packages/e2e-tests/test-applications/node-express/package.json
index 37d5ed592db3..c1a608833bdb 100644
--- a/dev-packages/e2e-tests/test-applications/node-express/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-express/package.json
@@ -22,7 +22,7 @@
"zod": "~3.22.4"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"resolutions": {
diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json
index f720b711d1fa..f9f4f726eb0e 100644
--- a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json
@@ -20,7 +20,7 @@
"ts-node": "10.9.2"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/package.json b/dev-packages/e2e-tests/test-applications/node-fastify/package.json
index 255238f0f74f..9b9f584cc359 100644
--- a/dev-packages/e2e-tests/test-applications/node-fastify/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-fastify/package.json
@@ -20,7 +20,7 @@
"ts-node": "10.9.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/package.json b/dev-packages/e2e-tests/test-applications/node-hapi/package.json
index 2eda8acc7589..3f18419d1d4a 100644
--- a/dev-packages/e2e-tests/test-applications/node-hapi/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-hapi/package.json
@@ -16,7 +16,7 @@
"@sentry/node": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-koa/package.json b/dev-packages/e2e-tests/test-applications/node-koa/package.json
index 9bcb3cc8754b..55299faa3e4a 100644
--- a/dev-packages/e2e-tests/test-applications/node-koa/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-koa/package.json
@@ -18,7 +18,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
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 ff4017ca0f3c..c5fe928a931e 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
@@ -21,7 +21,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
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 1240dc5e9923..efb74e7346ad 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
@@ -22,7 +22,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
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 a45eb3470c7d..b097e5b91930 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
@@ -25,7 +25,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
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 e01886a3318f..3112ce669479 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json
@@ -22,7 +22,7 @@
"typescript": "~5.0.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/package.json b/dev-packages/e2e-tests/test-applications/node-profiling/package.json
index d78ca10fa25d..c48ab9c3d42d 100644
--- a/dev-packages/e2e-tests/test-applications/node-profiling/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-profiling/package.json
@@ -13,7 +13,7 @@
},
"dependencies": {
"@electron/rebuild": "^3.7.0",
- "@playwright/test": "^1.48.2",
+ "@playwright/test": "~1.50.0",
"@sentry/electron": "latest || *",
"@sentry/node": "latest || *",
"@sentry/profiling-node": "latest || *",
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json
index ac18cebec975..8555f0da7a58 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json
+++ b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json
@@ -19,7 +19,7 @@
},
"devDependencies": {
"@nuxt/test-utils": "^3.14.1",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"overrides": {
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json
index 54bacf4ee358..7fb034434fdf 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json
+++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json
@@ -21,7 +21,7 @@
},
"devDependencies": {
"@nuxt/test-utils": "^3.14.1",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"overrides": {
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json
index 9d3dc0066912..1e6590da7a10 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json
+++ b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json
@@ -19,7 +19,7 @@
},
"devDependencies": {
"@nuxt/test-utils": "^3.14.1",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
}
}
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json
index 80b76aed58ac..df88dd717a8d 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json
+++ b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@nuxt/test-utils": "^3.14.1",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
}
}
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json
index 0a278f07eedd..a1cbc9164e9e 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json
+++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json
@@ -21,7 +21,7 @@
},
"devDependencies": {
"@nuxt/test-utils": "^3.14.2",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"overrides": {
diff --git a/dev-packages/e2e-tests/test-applications/react-17/package.json b/dev-packages/e2e-tests/test-applications/react-17/package.json
index ab3022bb3c80..c9273d01f090 100644
--- a/dev-packages/e2e-tests/test-applications/react-17/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-17/package.json
@@ -42,7 +42,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-19/package.json b/dev-packages/e2e-tests/test-applications/react-19/package.json
index 1abc74715831..e2f4ec9cfd16 100644
--- a/dev-packages/e2e-tests/test-applications/react-19/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-19/package.json
@@ -42,7 +42,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json
index a4e7dae6d1e2..fae000b256e3 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json
@@ -30,7 +30,7 @@
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json
index 757b27c65b84..b086b8228aec 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json
@@ -41,7 +41,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json
index dc6c9b4340f0..1f00f44ff43b 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json
@@ -30,7 +30,7 @@
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/package.json b/dev-packages/e2e-tests/test-applications/react-router-5/package.json
index 16c7f16df16d..f0c52de6990e 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-5/package.json
@@ -44,7 +44,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json
index 3c3323d2c4cc..768c836abba8 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json
@@ -44,7 +44,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1",
"npm-run-all2": "^6.2.0"
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json
index 7f68ec2a7ec4..fdb36e4d3b7b 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json
@@ -41,7 +41,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1"
},
diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/package.json b/dev-packages/e2e-tests/test-applications/react-router-6/package.json
index 575f417e2bc2..0a9f0cd2a5c0 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-6/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-6/package.json
@@ -44,7 +44,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"serve": "14.0.1",
"npm-run-all2": "^6.2.0"
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json
index 1c505f3195a3..e8730c8f9091 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json
@@ -11,7 +11,7 @@
"react-router": "^7.0.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"vite": "^6.0.1",
"@vitejs/plugin-react": "^4.3.4",
diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json
index 35b01833874a..07eec924f7b7 100644
--- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json
+++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json
@@ -42,7 +42,7 @@
]
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"serve": "14.0.1"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json
index 0c727d46de50..36d0fc2854f7 100644
--- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json
+++ b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json
@@ -14,7 +14,7 @@
},
"license": "MIT",
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"autoprefixer": "^10.4.17",
diff --git a/dev-packages/e2e-tests/test-applications/solid/package.json b/dev-packages/e2e-tests/test-applications/solid/package.json
index d61ac0a0a322..830450e7328e 100644
--- a/dev-packages/e2e-tests/test-applications/solid/package.json
+++ b/dev-packages/e2e-tests/test-applications/solid/package.json
@@ -14,7 +14,7 @@
},
"license": "MIT",
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"autoprefixer": "^10.4.17",
diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json
index 62393e038dce..495b9cb8c94d 100644
--- a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json
+++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json
@@ -15,7 +15,7 @@
"@sentry/solidstart": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.4",
"@solidjs/start": "^1.0.2",
diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json
index 9495309f0464..57a83c35b0e8 100644
--- a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json
+++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json
@@ -15,7 +15,7 @@
"@sentry/solidstart": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.4",
"@solidjs/start": "^1.0.2",
diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json
index 559477a58baa..3d96e0481158 100644
--- a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json
+++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json
@@ -15,7 +15,7 @@
"@sentry/solidstart": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.4",
"@solidjs/start": "^1.0.2",
diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json
index f4059823617a..2bb3afaa29b1 100644
--- a/dev-packages/e2e-tests/test-applications/solidstart/package.json
+++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json
@@ -15,7 +15,7 @@
"@sentry/solidstart": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.13.4",
"@solidjs/start": "^1.0.2",
diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/package.json b/dev-packages/e2e-tests/test-applications/svelte-5/package.json
index ed6cf3ada0d7..39d6250f3b75 100644
--- a/dev-packages/e2e-tests/test-applications/svelte-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/svelte-5/package.json
@@ -13,7 +13,7 @@
"test:assert": "pnpm test:prod"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json
index 88d9a37ab98c..4fa8983ae153 100644
--- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json
@@ -19,7 +19,7 @@
"@spotlightjs/spotlight": "2.0.0-alpha.1"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"@sveltejs/adapter-auto": "^3.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json
index 5a2d9ce7b4d5..f961077f9977 100644
--- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json
+++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json
@@ -18,7 +18,7 @@
"@sentry/sveltekit": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"@sveltejs/adapter-auto": "^3.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json
index 3f2f87500e25..f02253198aaa 100644
--- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json
+++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json
@@ -18,7 +18,7 @@
"@sentry/sveltekit": "latest || *"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"@sveltejs/adapter-auto": "^3.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json
index 96a26ee98447..ae25dcc0870b 100644
--- a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json
+++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json
@@ -25,7 +25,7 @@
"@vitejs/plugin-react-swc": "^3.5.0",
"typescript": "^5.2.2",
"vite": "^5.4.11",
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
diff --git a/dev-packages/e2e-tests/test-applications/vue-3/package.json b/dev-packages/e2e-tests/test-applications/vue-3/package.json
index 3a2c38f43633..54cbc679e0f4 100644
--- a/dev-packages/e2e-tests/test-applications/vue-3/package.json
+++ b/dev-packages/e2e-tests/test-applications/vue-3/package.json
@@ -21,7 +21,7 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/core": "latest || *",
"@tsconfig/node20": "^20.1.2",
diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/package.json b/dev-packages/e2e-tests/test-applications/webpack-4/package.json
index 95d3d5c39a3e..c143fd9ab82e 100644
--- a/dev-packages/e2e-tests/test-applications/webpack-4/package.json
+++ b/dev-packages/e2e-tests/test-applications/webpack-4/package.json
@@ -8,7 +8,7 @@
"test:assert": "playwright test"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/browser": "latest || *",
"babel-loader": "^8.0.0",
diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/package.json b/dev-packages/e2e-tests/test-applications/webpack-5/package.json
index 389f817292cd..00cff9198262 100644
--- a/dev-packages/e2e-tests/test-applications/webpack-5/package.json
+++ b/dev-packages/e2e-tests/test-applications/webpack-5/package.json
@@ -8,7 +8,7 @@
"test:assert": "playwright test"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sentry/browser": "latest || *",
"webpack": "^5.91.0",
diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json
index 5758b5229e80..a0d9d7e51def 100644
--- a/dev-packages/test-utils/package.json
+++ b/dev-packages/test-utils/package.json
@@ -41,10 +41,10 @@
"clean": "rimraf -g ./node_modules ./build"
},
"peerDependencies": {
- "@playwright/test": "^1.44.1"
+ "@playwright/test": "~1.50.0"
},
"devDependencies": {
- "@playwright/test": "^1.44.1",
+ "@playwright/test": "~1.50.0",
"@sentry/core": "9.0.0-alpha.0"
},
"volta": {
diff --git a/packages/browser/src/transports/fetch.ts b/packages/browser/src/transports/fetch.ts
index 5712c78f0c18..9a414a95e927 100644
--- a/packages/browser/src/transports/fetch.ts
+++ b/packages/browser/src/transports/fetch.ts
@@ -22,7 +22,7 @@ export function makeFetchTransport(
const requestOptions: RequestInit = {
body: request.body,
method: 'POST',
- referrerPolicy: 'origin',
+ referrerPolicy: 'strict-origin',
headers: options.headers,
// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to
// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".
diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts
index 2e215fbf764e..e6fea13c4e2a 100644
--- a/packages/browser/src/utils/lazyLoadIntegration.ts
+++ b/packages/browser/src/utils/lazyLoadIntegration.ts
@@ -56,7 +56,7 @@ export async function lazyLoadIntegration(
const script = WINDOW.document.createElement('script');
script.src = url;
script.crossOrigin = 'anonymous';
- script.referrerPolicy = 'origin';
+ script.referrerPolicy = 'strict-origin';
if (scriptNonce) {
script.setAttribute('nonce', scriptNonce);
diff --git a/packages/browser/test/transports/fetch.test.ts b/packages/browser/test/transports/fetch.test.ts
index 9a760439733b..89ba25fd5fd8 100644
--- a/packages/browser/test/transports/fetch.test.ts
+++ b/packages/browser/test/transports/fetch.test.ts
@@ -50,7 +50,7 @@ describe('NewFetchTransport', () => {
body: serializeEnvelope(ERROR_ENVELOPE),
method: 'POST',
keepalive: true,
- referrerPolicy: 'origin',
+ referrerPolicy: 'strict-origin',
});
});
diff --git a/packages/deno/src/transports/index.ts b/packages/deno/src/transports/index.ts
index 3d4e26a9f805..9a83dadfff63 100644
--- a/packages/deno/src/transports/index.ts
+++ b/packages/deno/src/transports/index.ts
@@ -31,7 +31,7 @@ export function makeFetchTransport(options: DenoTransportOptions): Transport {
const requestOptions: RequestInit = {
body: request.body,
method: 'POST',
- referrerPolicy: 'origin',
+ referrerPolicy: 'strict-origin',
headers: options.headers,
};
diff --git a/yarn.lock b/yarn.lock
index d80fe6f3225e..505d52f463d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6054,12 +6054,12 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
-"@playwright/test@^1.44.1":
- version "1.44.1"
- resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb"
- integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==
+"@playwright/test@~1.50.0":
+ version "1.50.0"
+ resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.50.0.tgz#25c63a09f833f89da4d54ad67db7900359e2d11d"
+ integrity sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==
dependencies:
- playwright "1.44.1"
+ playwright "1.50.0"
"@polka/url@^1.0.0-next.24":
version "1.0.0-next.28"
@@ -23984,17 +23984,17 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
-playwright-core@1.44.1:
- version "1.44.1"
- resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c"
- integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==
+playwright-core@1.50.0:
+ version "1.50.0"
+ resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.50.0.tgz#28dd6a1488211c193933695ed337a5b44d46867c"
+ integrity sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==
-playwright@1.44.1:
- version "1.44.1"
- resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892"
- integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==
+playwright@1.50.0:
+ version "1.50.0"
+ resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.50.0.tgz#ccaf334f948d78139922844de55a18f8ae785410"
+ integrity sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==
dependencies:
- playwright-core "1.44.1"
+ playwright-core "1.50.0"
optionalDependencies:
fsevents "2.3.2"
From ec02f84fc6597e73bbc1c6d905da7b12b15dbc07 Mon Sep 17 00:00:00 2001
From: Tim Fish
Date: Tue, 28 Jan 2025 21:48:07 +0100
Subject: [PATCH 44/59] feat(profiling-node): Use
`@sentry-internal/node-cpu-profiler` (#15208)
This PR removes all the code and CI steps that were moved to
`@sentry-internal/node-cpu-profiler`.
---------
Co-authored-by: Abhijeet Prasad
---
.github/workflows/build.yml | 438 +-----
.../bin/darwin-arm64-130/profiling-node.node | Bin 115304 -> 0 bytes
packages/profiling-node/binding.gyp | 20 -
.../profiling-node/bindings/cpu_profiler.cc | 1226 -----------------
packages/profiling-node/clang-format.js | 26 -
packages/profiling-node/package.json | 35 +-
packages/profiling-node/rollup.npm.config.mjs | 23 +-
packages/profiling-node/scripts/binaries.js | 27 -
.../profiling-node/scripts/check-build.js | 56 -
.../profiling-node/scripts/copy-target.js | 27 -
packages/profiling-node/src/cpu_profiler.ts | 224 ---
packages/profiling-node/src/integration.ts | 16 +-
.../profiling-node/src/spanProfileUtils.ts | 2 +-
packages/profiling-node/src/utils.ts | 13 +-
packages/profiling-node/test/bindings.test.ts | 30 -
.../profiling-node/test/cpu_profiler.test.ts | 364 -----
.../test/spanProfileUtils.test.ts | 2 +-
scripts/ci-unit-tests.ts | 3 +-
yarn.lock | 34 +-
19 files changed, 49 insertions(+), 2517 deletions(-)
delete mode 100755 packages/profiling-node/bin/darwin-arm64-130/profiling-node.node
delete mode 100644 packages/profiling-node/binding.gyp
delete mode 100644 packages/profiling-node/bindings/cpu_profiler.cc
delete mode 100644 packages/profiling-node/clang-format.js
delete mode 100644 packages/profiling-node/scripts/binaries.js
delete mode 100644 packages/profiling-node/scripts/check-build.js
delete mode 100644 packages/profiling-node/scripts/copy-target.js
delete mode 100644 packages/profiling-node/src/cpu_profiler.ts
delete mode 100644 packages/profiling-node/test/bindings.test.ts
delete mode 100644 packages/profiling-node/test/cpu_profiler.test.ts
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 34bb9eb5799f..c58d8606d4de 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -84,8 +84,6 @@ jobs:
echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV
# Most changed packages are determined in job_build via Nx
- # However, for profiling-node we only want to run certain things when in this specific package
- # something changed, not in any of the dependencies (which include core, utils, ...)
- name: Determine changed packages
uses: dorny/paths-filter@v3.0.1
id: changed
@@ -93,9 +91,6 @@ jobs:
filters: |
workflow:
- '.github/**'
- profiling_node:
- - 'packages/profiling-node/**'
- - 'dev-packages/e2e-tests/test-applications/node-profiling/**'
any_code:
- '!**/*.md'
@@ -109,7 +104,6 @@ jobs:
# Note: These next three have to be checked as strings ('true'/'false')!
is_base_branch: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v9' || github.ref == 'refs/heads/v8'}}
is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }}
- changed_profiling_node: ${{ steps.changed.outputs.profiling_node == 'true' }}
changed_ci: ${{ steps.changed.outputs.workflow == 'true' }}
changed_any_code: ${{ steps.changed.outputs.any_code == 'true' }}
@@ -198,7 +192,6 @@ jobs:
changed_deno: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/deno') }}
changed_bun: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/bun') }}
changed_browser_integration: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry-internal/browser-integration-tests') }}
- # If you are looking for changed_profiling_node, this is defined in job_get_metadata
job_check_branches:
name: Check PR branches
@@ -316,7 +309,7 @@ jobs:
job_artifacts:
name: Upload Artifacts
- needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node]
+ needs: [job_get_metadata, job_build]
runs-on: ubuntu-20.04
# Build artifacts are only needed for releasing workflow.
if: needs.job_get_metadata.outputs.is_release == 'true'
@@ -334,13 +327,6 @@ jobs:
with:
dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
- - name: Extract Profiling Node Prebuilt Binaries
- uses: actions/download-artifact@v4
- with:
- pattern: profiling-node-binaries-${{ github.sha }}-*
- path: ${{ github.workspace }}/packages/profiling-node/lib/
- merge-multiple: true
-
- name: Pack tarballs
run: yarn build:tarball
@@ -498,37 +484,6 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
- job_profiling_node_unit_tests:
- name: Node Profiling Unit Tests
- needs: [job_get_metadata, job_build]
- if: |
- needs.job_build.outputs.changed_node == 'true' ||
- needs.job_get_metadata.outputs.changed_profiling_node == 'true' ||
- github.event_name != 'pull_request'
- runs-on: ubuntu-latest
- timeout-minutes: 10
- steps:
- - name: Check out current commit
- uses: actions/checkout@v4
- with:
- ref: ${{ env.HEAD_COMMIT }}
- - uses: actions/setup-node@v4
- with:
- node-version: 20
- - uses: actions/setup-python@v5
- with:
- python-version: '3.11.7'
- - name: Restore caches
- uses: ./.github/actions/restore-cache
- with:
- dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
- - name: Build Configure node-gyp
- run: yarn lerna run build:bindings:configure --scope @sentry/profiling-node
- - name: Build Bindings for Current Environment
- run: yarn build --scope @sentry/profiling-node
- - name: Unit Test
- run: yarn lerna run test --scope @sentry/profiling-node
-
job_browser_playwright_tests:
name: Playwright ${{ matrix.bundle }}${{ matrix.project && matrix.project != 'chromium' && format(' {0}', matrix.project) || ''}}${{ matrix.shard && format(' ({0}/{1})', matrix.shard, matrix.shards) || ''}} Tests
needs: [job_get_metadata, job_build]
@@ -786,12 +741,10 @@ jobs:
name: Prepare E2E tests
# We want to run this if:
# - The build job was successful, not skipped
- # - AND if the profiling node bindings were either successful or skipped
if: |
always() &&
- needs.job_build.result == 'success' &&
- (needs.job_compile_bindings_profiling_node.result == 'success' || needs.job_compile_bindings_profiling_node.result == 'skipped')
- needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node]
+ needs.job_build.result == 'success'
+ needs: [job_get_metadata, job_build]
runs-on: ubuntu-20.04-large-js
timeout-minutes: 15
outputs:
@@ -823,26 +776,6 @@ jobs:
# On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it
restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }}
- # Rebuild profiling by compiling TS and pull the precompiled binary artifacts
- - name: Build Profiling Node
- if: |
- (needs.job_get_metadata.outputs.changed_profiling_node == 'true') ||
- (needs.job_get_metadata.outputs.is_release == 'true') ||
- (github.event_name != 'pull_request')
- run: yarn lerna run build:lib --scope @sentry/profiling-node
-
- - name: Extract Profiling Node Prebuilt Binaries
- if: |
- (needs.job_get_metadata.outputs.changed_profiling_node == 'true') ||
- (needs.job_get_metadata.outputs.is_release == 'true') ||
- (github.event_name != 'pull_request')
- uses: actions/download-artifact@v4
- with:
- pattern: profiling-node-binaries-${{ github.sha }}-*
- path: ${{ github.workspace }}/packages/profiling-node/lib/
- merge-multiple: true
- # End rebuild profiling
-
- name: Build tarballs
run: yarn build:tarball
@@ -1089,137 +1022,20 @@ jobs:
directory: dist
workingDirectory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
- job_profiling_e2e_tests:
- name: E2E ${{ matrix.label || matrix.test-application }} Test
- # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks
- # Dependabot specifically also has access to secrets
- # We need to add the `always()` check here because the previous step has this as well :(
- # See: https://github.com/actions/runner/issues/2205
- if:
- # Only run profiling e2e tests if profiling node bindings have changed
- always() && needs.job_e2e_prepare.result == 'success' &&
- (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
- (
- (needs.job_get_metadata.outputs.changed_profiling_node == 'true') ||
- (needs.job_get_metadata.outputs.is_release == 'true')
- )
- needs: [job_get_metadata, job_build, job_e2e_prepare]
- runs-on: ubuntu-22.04
- timeout-minutes: 15
- env:
- E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }}
- E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
- E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks'
- E2E_TEST_SENTRY_PROJECT: 'sentry-javascript-e2e-tests'
- strategy:
- fail-fast: false
- matrix:
- test-application: ['node-profiling']
- build-command:
- - false
- label:
- - false
- steps:
- - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
- uses: actions/checkout@v4
- with:
- ref: ${{ env.HEAD_COMMIT }}
-
- - uses: pnpm/action-setup@v4
- with:
- version: 9.4.0
-
- - name: Set up Node
- uses: actions/setup-node@v4
- with:
- node-version: 22
-
- - name: Restore caches
- uses: ./.github/actions/restore-cache
- with:
- dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
-
- - name: Build Profiling Node
- run: yarn lerna run build:lib --scope @sentry/profiling-node
-
- - name: Extract Profiling Node Prebuilt Binaries
- uses: actions/download-artifact@v4
- with:
- pattern: profiling-node-binaries-${{ github.sha }}-*
- path: ${{ github.workspace }}/packages/profiling-node/lib/
- merge-multiple: true
-
- - name: Restore tarball cache
- uses: actions/cache/restore@v4
- id: restore-tarball-cache
- with:
- path: ${{ github.workspace }}/packages/*/*.tgz
- key: ${{ env.BUILD_CACHE_TARBALL_KEY }}
-
- - name: Build tarballs if not cached
- if: steps.restore-tarball-cache.outputs.cache-hit != 'true'
- run: yarn build:tarball
-
- - name: Install Playwright
- uses: ./.github/actions/install-playwright
- with:
- browsers: chromium
-
- - name: Get node version
- id: versions
- run: |
- echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT
-
- - name: Validate Verdaccio
- run: yarn test:validate
- working-directory: dev-packages/e2e-tests
-
- - name: Prepare Verdaccio
- run: yarn test:prepare
- working-directory: dev-packages/e2e-tests
- env:
- E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}
-
- - name: Setup xvfb and update ubuntu dependencies
- run: |
- sudo apt-get install xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps
- sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \
- libnotify-dev libgconf2-dev \
- libasound2-dev libcap-dev libcups2-dev libxtst-dev \
- libxss1 libnss3-dev gcc-multilib g++-multilib
-
- - name: Install dependencies
- working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
- run: yarn install --ignore-engines --frozen-lockfile
-
- - name: Build E2E app
- working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
- timeout-minutes: 7
- run: yarn ${{ matrix.build-command || 'test:build' }}
-
- - name: Run E2E test
- working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
- timeout-minutes: 10
- run: |
- xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert
-
job_required_jobs_passed:
name: All required jobs passed or were skipped
needs:
[
job_build,
- job_compile_bindings_profiling_node,
job_browser_unit_tests,
job_bun_unit_tests,
job_deno_unit_tests,
job_node_unit_tests,
- job_profiling_node_unit_tests,
job_node_integration_tests,
job_browser_playwright_tests,
job_browser_loader_tests,
job_remix_integration_tests,
job_e2e_tests,
- job_profiling_e2e_tests,
job_artifacts,
job_lint,
job_check_format,
@@ -1233,251 +1049,3 @@ jobs:
if: contains(needs.*.result, 'failure')
run: |
echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1
-
- job_compile_bindings_profiling_node:
- name: Compile profiling-node (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}
- needs: [job_get_metadata, job_build]
- # Compiling bindings can be very slow (especially on windows), so only run precompile
- # Skip precompile unless we are on a release branch as precompile slows down CI times.
- if: |
- (needs.job_get_metadata.outputs.changed_profiling_node == 'true') ||
- (needs.job_get_metadata.outputs.is_release == 'true')
- runs-on: ${{ matrix.os }}
- container:
- image: ${{ matrix.container }}
- timeout-minutes: 30
- strategy:
- fail-fast: false
- matrix:
- include:
- # x64 glibc
- - os: ubuntu-20.04
- node: 18
- binary: linux-x64-glibc-108
- - os: ubuntu-20.04
- node: 20
- binary: linux-x64-glibc-115
- - os: ubuntu-20.04
- node: 22
- binary: linux-x64-glibc-127
-
- # x64 musl
- - os: ubuntu-20.04
- container: node:18-alpine3.17
- node: 18
- binary: linux-x64-musl-108
- - os: ubuntu-20.04
- container: node:20-alpine3.17
- node: 20
- binary: linux-x64-musl-115
- - os: ubuntu-20.04
- container: node:22-alpine3.18
- node: 22
- binary: linux-x64-musl-127
-
- # arm64 glibc
- - os: ubuntu-20.04
- arch: arm64
- node: 18
- binary: linux-arm64-glibc-108
- - os: ubuntu-20.04
- arch: arm64
- node: 20
- binary: linux-arm64-glibc-115
- - os: ubuntu-20.04
- arch: arm64
- node: 22
- binary: linux-arm64-glibc-127
-
- # arm64 musl
- - os: ubuntu-20.04
- arch: arm64
- container: node:18-alpine3.17
- node: 18
- binary: linux-arm64-musl-108
- - os: ubuntu-20.04
- arch: arm64
- container: node:20-alpine3.17
- node: 20
- binary: linux-arm64-musl-115
- - os: ubuntu-20.04
- arch: arm64
- container: node:22-alpine3.18
- node: 22
- binary: linux-arm64-musl-127
-
- # macos x64
- - os: macos-13
- node: 18
- arch: x64
- binary: darwin-x64-108
- - os: macos-13
- node: 20
- arch: x64
- binary: darwin-x64-115
- - os: macos-13
- node: 22
- arch: x64
- binary: darwin-x64-127
-
- # macos arm64
- - os: macos-13
- arch: arm64
- node: 18
- target_platform: darwin
- binary: darwin-arm64-108
- - os: macos-13
- arch: arm64
- node: 20
- target_platform: darwin
- binary: darwin-arm64-115
- - os: macos-13
- arch: arm64
- node: 22
- target_platform: darwin
- binary: darwin-arm64-127
-
- # windows x64
- - os: windows-2022
- node: 18
- arch: x64
- binary: win32-x64-108
- - os: windows-2022
- node: 20
- arch: x64
- binary: win32-x64-115
- - os: windows-2022
- node: 22
- arch: x64
- binary: win32-x64-127
-
- steps:
- - name: Setup (alpine)
- if: contains(matrix.container, 'alpine')
- run: |
- apk add --no-cache build-base git g++ make curl python3
- ln -sf python3 /usr/bin/python
-
- - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
- uses: actions/checkout@v4
- with:
- ref: ${{ env.HEAD_COMMIT }}
-
- # Note: On alpine images, this does nothing
- # The node version will be the one that is installed in the image
- # If you want to change the node version, you need to change the image
- # For non-alpine imgages, this will install the correct version of node
- - name: Setup Node
- uses: actions/setup-node@v4
- if: contains(matrix.container, 'alpine') == false
- with:
- node-version: ${{ matrix.node }}
-
- - name: Restore dependency cache
- uses: actions/cache/restore@v4
- id: restore-dependencies
- with:
- path: ${{ env.CACHED_DEPENDENCY_PATHS }}
- key: ${{ needs.job_build.outputs.dependency_cache_key }}
- enableCrossOsArchive: true
-
- - name: Increase yarn network timeout on Windows
- if: contains(matrix.os, 'windows')
- run: yarn config set network-timeout 600000 -g
-
- - name: Install dependencies
- if: steps.restore-dependencies.outputs.cache-hit != 'true'
- run: yarn install --ignore-engines --frozen-lockfile
- env:
- SKIP_PLAYWRIGHT_BROWSER_INSTALL: "1"
-
- - name: Configure safe directory
- run: |
- git config --global --add safe.directory "*"
-
- - name: Setup python
- uses: actions/setup-python@v5
- if: ${{ !contains(matrix.container, 'alpine') }}
- id: python-setup
- with:
- python-version: '3.8.10'
-
- - name: Setup (arm64| ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }})
- if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin'
- run: |
- sudo apt-get update
- sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
-
- - name: Setup Musl
- if: contains(matrix.container, 'alpine')
- run: |
- cd packages/profiling-node
- curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz
- tar -xzvf aarch64-linux-musl-cross.tgz
- $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version
-
- # configure node-gyp
- - name: Configure node-gyp
- if: matrix.arch != 'arm64'
- run: |
- cd packages/profiling-node
- yarn build:bindings:configure
-
- - name: Configure node-gyp (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }})
- if: matrix.arch == 'arm64' && matrix.target_platform != 'darwin'
- run: |
- cd packages/profiling-node
- yarn build:bindings:configure:arm64
-
- - name: Configure node-gyp (arm64, darwin)
- if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin'
- run: |
- cd packages/profiling-node
- yarn build:bindings:configure:arm64
-
- # build bindings
- - name: Build Bindings
- if: matrix.arch != 'arm64'
- run: |
- yarn lerna run build:bindings --scope @sentry/profiling-node
-
- - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }})
- if: matrix.arch == 'arm64' && contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin'
- run: |
- cd packages/profiling-node
- CC=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \
- CXX=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ \
- BUILD_ARCH=arm64 \
- yarn build:bindings
-
- - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }})
- if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin'
- run: |
- cd packages/profiling-node
- CC=aarch64-linux-gnu-gcc \
- CXX=aarch64-linux-gnu-g++ \
- BUILD_ARCH=arm64 \
- yarn build:bindings:arm64
-
- - name: Build Bindings (arm64, darwin)
- if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin'
- run: |
- cd packages/profiling-node
- BUILD_PLATFORM=darwin \
- BUILD_ARCH=arm64 \
- yarn build:bindings:arm64
-
- - name: Build profiling-node & its dependencies
- run: yarn build --scope @sentry/profiling-node
-
- - name: Test Bindings
- if: matrix.arch != 'arm64'
- run: |
- yarn lerna run test --scope @sentry/profiling-node
-
- - name: Archive Binary
- uses: actions/upload-artifact@v4
- with:
- name: profiling-node-binaries-${{ github.sha }}-${{ matrix.binary }}
- path: ${{ github.workspace }}/packages/profiling-node/lib/sentry_cpu_profiler-${{matrix.binary}}.node
- if-no-files-found: error
diff --git a/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node b/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node
deleted file mode 100755
index 65e97eca7e48d05a45e91933dd230fcc8c543d8b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 115304
zcmeEP3t&{m)tU^fXQNPzH=ETA?)ge1HYi?T@w5J1Eb#roQ8l7$44Y}jmoh{*D=
z27;CaR0@`twk9iBw828F62wTVMR^%Sgr~hMxgn6u#R#GxmT>4q|-($LkogSaLFN&jFzQ)oTJc
z+zG+-Au0K1u{doDog9FpO>gKh1?O@pF2Tsw_X}e#A3*fw&WgP9)*x+qJDyQcHG0J*
zSe>4Mr-!=BKP5d&p{=;g=0J!xy`1Nicp^Q5)#sl-or3;Em3oJ#Yv+Q#E{_R`C)4QD$
z66{xBO@bsRDahPn8K0JuX34mD+6+Zn>Ul&ncQr6IkNBeDw^(M|eYFgpUJ6f+AwAy2
zatO)WG=YO_v6R@Ul0{C-tcue7HswpHS0a<_(~n?vdc1jL9R1U_9sKKS20i`OC=Mf7
zogNQvb^Q6(8VnC_NwWy^hXv0>+7?T()lyzrl4mcrlsO%|o`RNKg+8~n5hcHJbwP|<
zRnaY$0;|*N3sBnC2j}XP^h`X6U{MxdET7L}$t(BuR?)6Lyo;wtG9Z5H^my$BO4du0
z09h@G^=|eXBJ-n+P@TAAd$=xzkoMES-~3s_Ytkl<&z~
zB(@7~0*8rD^dnvnKKCVwLHCA!Ar6E%5aK|H10fECI1u7Mhyx)Ggg6l5K!^h&4um)m
z;y{Q4Ar6E%5aK|H10fECI1u7Mhyx)Ggg6l5K!^h&4um)m;y{Q4Ar6E%5aK|H10fEC
zIPfiT;KHJPwHNfvUC7wmhJoR3!^`_p4oGY?!7MGU8OH?c(iqz{u*&2V_F
z($ZVxp9Q$FdGq1IIWL^9alUi9D)#tk!+n!B881v+XSy({TI6AfF*h6I&CMpsP)+%_
z=yiwA&g@is=H|||7Z!D?y|7<$f39bqNmQSn=4OLrZmwB=rI#g|n}4%iav#z6@JPt3
z4&U=vn46#P!s^Z-eq#o6_p~xM&RoX4r!zO7?ab;9Y_3m@m%L*Rq%${@>ThW1;<~|L
zG&eW)V9oV|%*_pq?Z&yvn3u!TY8ycRnSrgR@LZzZ&fIa#&;mipAH=9bY75vWrfTmL8Uyk_~`-Ym3D0{D{v{*03hO#@NR
z3yUU#j}vOo&djU@U)?f)d3lR_vGrcipSk%Z@L8(8fchTO>pY)lRUE!r?8i~
zxo8OTNBhKeVyAG_HYc~UxwZiO&h2Kd#mUh%1DW2fW3Iy&7NsG-S88%iEJpY#&s@|=
zolg(crvY_;prz$8az$L6fsIjQKst2hW*y3D@6xlnBh<#=
zA%*3{q8?q$%`Iq$IvuNP%r~dvKE%kCXfqeuFOC`8@Rs=6pf`Hdysd(K6f*Bi~6>XFYc^ZQHUs#lZ`lf@olET~l(xHzA>r2kg3`2M)lpiv@Tym2<
z8gljSMwAco;y%)Yxz8ir`W|dlV;A#Y@O9qY9xO+XG^wsf5Eg%*dEK}$HZKDF{s?s&
z7tQA3_;pS=_#A;R3ZEJLBA4K)@JGV!#^J7Tqw(SBv>dlP+#$~U*A9|&Xy8`(x&2W7%HZ$71bAs#Du@hbU$Bt)rdNW=3cqc@gnKwvo=4e
z3#(quBCD+{j>=(IA#4TfRQ)jPR=sV-aaCC7>S`8IePqS|s=~TdKg7CLhmDP$wuVJl
zkB19+uU-N7XDq7vRk&`}y*k~ccjrg5Q_{a%a-tD$xGt*tQz^Robt$s?X(^(5E?kQw
zHQ}IiPGlEXcGlH9HYL2w+(TIS!GY3}okJhU@QN~Dhw|+}{A$oeQa(~t^<>EK%U`zS
zd}KVZ(In+>E`%$=WwF))QgrEn_>|lMhJpB`S(^>b=C#HzKRwKD$@*dn+TC!R4S(bs
zbMv1&vAXp~72UEMvM>tpXHl%VF@jy#3VEvs+}h9c(U&qv-;8^2D|G1dh~J1Xk}c8?
zry72HD!O%u8)Nt9|3fVn5fMmC)=M%V}>w9@d|GQ;3
z$y5X6r~qBTccQAC*qxsHQ$G@U}I)H#>~Rb
ztl71h#+6>4K}csXK3r+KA^~){chDHh>I(C8LB}cFlZk18{>SBln8qiV8~W2XRskj%
zbuE+JXW>7FkIH=%-=pAv6~=&-H~Pi_3A)Jj)AZV-$lLXBy5Bg^fUt(aB)h%Z$gZq+
zk+xdz=1%5iKPm+-JH}_Z>~Q6>4@TMHZyjd=%VkHOJPZFZe4^~TV}h6cHv!82ES0@S
zJ7q`O>axe94GMq9_68b*iARCP;6Gi-)-T7n9gDoYmKV?uCGansizE8pp6v)D*;)@BBJ|9)U(#5s=o}Nq-ATOML?gzrvoq6by_Q$y
zdEi#0@iEHCa+$0fSUq>m+%+jWgSognNrWqIi(Y@|i`iX}o&ohUqJH5R4oA|w
zd(L34ahCTThWWsee97%ao(s@Ns7#yTr!oF$hJ-rw_A~*earao4%-VF!InoPa-95lR
z5r2b-KUj(XA&-A#IB0%kZkB0WvF^^)zDR#1;UzYb(mcAHxp9*y`-qwbG9c4zh;=t3
zT;#J9ek#{7y^;_5^V&DmbdYu`+ZvzLcxUsBV?aoYD|
z-5&%BKOgIU58*9KbhR!0Fqc3Y#{oBfy7)O7CkAzAIV1~lcd=93FBVae1DX4yaPxEH?q;V9(#nl}I+2X@@ytRxUeLzt)1EswSgLKHxr#m(^3)4D
zptolU@Nsj;q%fgqn9tCzjY}{W?&2Mt3YhpxFwK?XP_BW`KDLqaBOcpOcEXeXUDnsU
z_lf0qdhbV=JQqX$V;R~MWj~7V7+@-&y)XER{0b%S=qr%^5Y#zhS9+}lXN>caH6zB;rSweO@|RT~csLSE>rxI)*2aVlrdJ@u(`
zP~R-QcMN0oHy8}&=E87>I=OBjT-KH5wUe%7SF8f>a7QgB9Q~WpN_*C|k;;1%>&2UFRYVl9@uZ
zX`B?^)H7+-#(lcjrow?NXWu>S)Us|Y=X3NW8e4WpvE3ie8I@9qdd5e4$22UjA4~Oa
zj8b@DxUldYDt`phEke2{^oAizkap8^>oyL3cGbq!-C12R=A@tKBRv1oM|$2$V(yb^
zmXuGfV~65?C(YZ6e)}o<{7NO9`k$Q6^GF|a?~{Fa
z`XU_pZu#BQb<1WbbKEk11SRWi*(uqBr3xDM(
z=C0DGjQI#_opX?-ar*G4ER^k0@Uw>N!CI9b>@DlT-kz(_KS)nE>e8P3yj2gC@8wf#@FjHZ4ZgRuKij&V|-ru|VYXQb5U;1-e}__siQ@*-Kz8ORT81JHGSnyg(R
zS93gcevly!mc5-D;#rg*qKVuR)v0JdN
zIYfPIC-_zZ*(^o5_5pt5*>xMADVme=A^1h>o;ZX(ELHb?rf5itp%d$V3gs@m$DC@A
zQpOZPj>hdpKLwwvjBI2hWS4kHb1stUMv^naqYQ;8U!j53#UNj*s{wLIbxj9O)YYkH
zyIboCU7_tO>UD_Mk^#Miwe)^vK2gQnXr-!+ddY3n%k%qww60;E18Ac&yp4`@CcRDb
z)fmsaz>9nf&ns>8G{PEC*Y?u=WuSC*eX2#eFR9Z#t)%-1!qy<&8n|S;qV>Pm5(d5M
zZ^JsP5AztzxdYB%?sE=vpU>5EpC_1mFX%~@Xf8r?II1JT$GW7~4#Jh)^s#;1e?b_v
z1IB50BR(4c9SCc*VBFJ-ac{%MWBG*ZEzd80#qshOf;dGy-6I%_jzMS1Y3KHF*CTvi
zXEx6T`N2ALlw20%L2~~yH4V~`X{!3TaphVbcQGGwv$Wg+_XUb~ypOvU;SI=7Js0*`
z_}Ww0r7|Sg{d5?|VDniN1{)^XO(gJ%VSQ!r#NSn)TEo0!w&++YEV;;tJ8vA?8
zc+52}z|Mwow?AN-3s<3EO~N?k($(KUI=>2QZ5sD3EE@amB-Tk0MG4cWjZVXi_pu)hO$4)rG6z&6-bTb4_QycQkk^+*S8v-aWu%R{n7sD=8SpgMG=T>sr$;c)tU%af@_L2fUY?dO
zXuYEN&-`1xUQyOC&*)*3M!V8nT-X68V16L%1S7#ivIEvIHd1ZZt3^Ik@26z9hv%oD
zc@q8$i!xEC@u*t{=0#}7Z+v
zeuEt?xX^8tD=sO~h6@l__Ug;i8d7#h8d4QJkAfEf>
z5j((*H#K~Dd@w-C;aPyN`WV=FC&I?t9XbYcDZ(E=M*6u2>F2}L?zbb}REK|8n@Hb$s
zQ;l_=U)-1<$@mefxIN*=m@@KuZQT131wIp(ike{cKd;+xm^SJMsUhg(o$hOy3zs&@m^wL(Y-);mv<_Y4RqR$RA`*hoj&~uX@
zQ-`OHM1slL-CM5W$*{AkN=OH}W!4++!Kc{R`|>(HfTc
zvj;R=CGZjbuyG&8&CfB8fv0m2?tv~#LR(yeuRFfZ`2Gg_-WT{j#fNe?50vvXHy=XW
z285LYF2mQ7p@;rAg74wrKWsURA~8NOC{VN?|)W6j6MgMXF2OFjwA1|@gZPVZB6@7Xca3;c0pT>IC
zigngD$m&tOlIOjY*OkC$DEOBNPxPNDbs^cv?axNZH2amXTPeSO&>c@Ga3)|EV6qp)
zBY*0@)OMo(ruSnxWOJteJIDzC7OXA6s~O>J_pRFg8-K0Li)o-2{7i%&vN`f6;HMw&
zPbzUURB^9{-yFt9?tvWm#jU$ePB%prw=evp7y4<_EgUY#y-pQ33VzZH=e6mYlI6IA
zRB^-LH$gvshOw(=`CV=F!nP}fUYH8GoC5ir484$rxtL-DDdcO3q5x}&8kCj#H?1W;
zdjfVZ^f8({DmtNu=Oy6x%^8tG^H<{E8VPlTPLRiW=meTa|4g2%D0<@T2=rn7U62=Q
z%U+(dpwI1!_NK5XCF~u9of~J~+Y-*^9m877kFQUTkm-&hj?$ldd0vIT0lZoQUNs)aD5w5QPZB&2M
z;qTy!yjT87%jo^6>vMpg!nYRRgZS>lHyd9zzKQt8;xpn)#MciW>BtfIT9)ge^EyMO
zx9`DRl+XSf<0(Lx&emUF)2he&r5GEC_QIkp@O~27U?Tc8#(K=nXiah_=%(Y##CHH6rNbmwHsN9}_|Brd
zjPOAFET8dpIPu$ZJ3BQA`K`m+@+|c2I`nbUyGnoP;n{~YD7+W+YrNz-Y+xr(|4HvU
zY=m2=!`TSQ*z_mZKB{1sS@Ik^Rk*BbBhH9CC+tcO%k=0Ma{ub#sR2wjrghs{?W0(y
zeMmY6=?B`2aU<>*vwx4eOW=Y38d+Tjvcnp&XFX2RH6@@f8?feDhjnqeJO?COKK1P$
zp4CWi0P4{TcG9!3lg`l@55j)rJ_vUS?4QdqCrpK1his`;dhc&*5U;W2pQp#^4NbMs
zSESeOKpM1$`wjZLvQMC-?LhdrJK3pR$REvjZURiYmFl?x^^BV<>gk({do5|0!~H?t
zcX|wER_9j(8)f{zm%Ot?XJ{gxex!3{ho4%!b~5%cBEa7z*vFVGRd0-gJ9^YAtlxWi
zM)0!Dz`pn2(D%~-YuDPe|0LFL)b@oaZynk`8hoMtv<3MsqxNja4%LU(tqygATsKqS
z!+aX@PU8p3t#7?Oyf=%9Lws6C25LuP>xk#}_aQ@tU1F(Xml(&~i?IIR1H0QotZhj*
zGyUw%>~>Q(mXbKkz^)lU=W`0t?#3?c&}ga0Dzcr^`46@Ifp{%!6lYP!C9vJ)qwYoF
z?{6IaM(n0q@IRq)zBeI))s2$O&7X&}x|OgG(*B19eY$#xxppS()x
zYC5x#M$me6#_Gvw=G5vrMJX03V(wDdwPE9QQy&`$n>d}bij!iSmP0m-#`ibIZ;Rb@
z1ns;PHVYcJ-^974hJUw=!nhh^kYZO+xwgSxEz0%CIbXRXq}N`#s$dsw#CUfxdDu=
zvT>4RcEYt$=T5JV`0vv@oZi#drI>5yj*8qgd}LZ}nbfs9`taN9<~?$7
z-CU_#b*U6pJ@?9k>*^Q33Aj7XkUz)eQ{>2QhW(?{M~*Zs-^>kN{y
z+9-ur8x|i}=aJ)dN4)52l(G5%z7xi(!{MiYd3w&oH%^>$B5yC03n)vpWfkBR3hY
z4p-`M9sH;ZTO7WL>QOjt^I^j~zdhaf#(SqHtv!1>A>LdYzu2&ec+`aSB9Pu(q<1aS
zOGsK$TMZsWO5q1r?|gk-N#((HpPQ(l_
zl-DA3ihu5lQ(h}W*wbjc>vR#-JHg9e-ulM6)!-ZLF}a?7eceN7n+Tl6nk#j}*)3!B
zRD_$*Mwok7vz+>Mr8v9wF!I{}?3?S}LYoxhd>7FV3rqJVU2ohpEsPy{^B?!F{Y2mG
zV3MwDbrSZ|1{?0LP15PAlO8>^?y8uy+R136$x>MLM2Jro@p;YVFADQ^?4t$m`Qv-&&WBvtD_q$4#I~-}TD6
zDRP;kP?j#$Lv@kWQTRSZ9c~8QNyLAPg;mpeuA|SswT{B4%H@u(9>^s3N$AEmCF$9v
z*suB*+HNO!^vR1iz4iw8?}UHyi<4i=l8l>buQqI2B}Ke88F8u+Hb)AV{e+*KY1o7~
zuO$FC8Eq>33GkZ`Cif$pRm(s>MZerD_sf?pdkpigoMAv+NCvd>K{Am5`Hh$RrN4YM
zKwcT-H5c+~g1qW3wA3Np)U5Mq?<5o!rA%g0_2g5Q6vIHHC(A(nh;2FnS$z$%dIGXK
z1hPurIGJ!*&!yvxlqH+RPwI&~ZxU&t1sJ!@KovrcsG>0-~O<|dnEdw=d|
z*6hWZrrc{dJOt;D3GU0`0j&8X!5CvP2dm4yf;E2(SeZvF=X0rk+W8RGF$O%MIcPcR
zK(3R!xp2Y5kxu04#GW4Q2g++!%r`0C1mFp`UdMLl4#2(x%7FH&dlUIOaVC3x7{W0&
zFTq|3g&$}wk8gfNWumlR29EY4{tVfd_am?;;2K1Hy0~+YWa~eGeH$0@dj@GWVO~J#
zJ}uJy_vNR%fu~D#c_c
zxyzQ1&0V%)?EE{}-dvr2=lo@CZx-^%oxqxN({*k${H%v7dof(t=WuSSho}Bw9nRhN
z^t2$X9&;_SLsGkw{~TP{txt;c|5Z9RnqY?W{|zWJ+U{-n{6FH+`TwIh|F1)v4wB_G
z#xoFZU*K{#lP$!59!u?HrnGP`AP;fO7g|P|=$-)X1-`m*5N^$;lcj13yo--Q-(O=b^9Do>s*@`c&A3l`|l)pE%La
z3{uR&xW!kWGGc8s8GLMSogInv#X7rUdt_~H9`V?>&b}IW`Fw#U?JBIvD$tkM(52b`
z?t!yYuB+Wo>tdRUuog06T&J@#^_b%pC`dkVf%%Qb*nC9yaWC3F8*lo+Eu3(zq3;pesBolJ+q7*ii*S-y_6j~3Els)WZt>o
zxMb%MiS0aQ9JdqWD|D`OD0&b(l!*Pv&-GnB@_2xJpMKclo$^qecOS|}W5t|bNwtN&
zShEwdM?5}&@)x2{JE5}#@8tSHcToNIUqbyRV;(_e|4!<6jlQ;UkgtAgsD8sxKj=ii
z`VCR)SN%h)-><@I3y1mYcRSVZdejeNl3)EsEA^Z4J+I%^hb`yL55=8tmCtKB*A`yy
zs~>F5e7sc7f2+^M4g-#KVDBC*MmtAfuua4O4?Xj?&_i^N$8Z&c-OxAos`2nQ8!$JE
z55pZKq)Q(|+H^);Ha;{9w(^OlTtD?eMj
zwuc_;M9eK{y}uoMreynZVGTTI5cY;qudMSK>ms1%mf+mzGVEop2F*j5Z|{&|s~?74
zC{F6u)NSd*8$Z>jU>y)ujd}mUeb9;dxMR~ziahu+&Xary9jFJq1N!eF=)Y&6|6Ygw
zqx&V1_=dy(Y1ckjbHtJkbVuVmrSIlx=(J?#(XOxTV4cV9)OB07BWVyjWrdF0hdh@?
zvz!N^Z((ydXn<}7zue&$7QZw33bvchI-kI}k^tPb7t+^J*$bDF{Uf}|$j-0rm$Y(Y
zKg??jTNc057>)VTXN%uidWCYfyKiKl>Zr(m)#LR2nyk><5j4i29py0&_wQDAX3g^u
z)*H5yFS?=3-ITa={(MUpcIs*HZd@1kYjElMB@mx!?mb;5oBcucujc`-U2>mK&IwOeGiJTCE(=~C|4GEIRtf{0$vt_
ze?_{;gERG!2P32URIgsj4&Ahz9inrOOEH!#$5^rrW62X3OIBkX*@^E>)@F-{~7^
zsE%U2s{eAf1>;t4%)6pIedK(4d8&{{8{VO8X*jF+9_BlgPssyj??Y%GYU?LZ{?%xw
zIJCD53w2_y<$n(4i`i+l=k0%#ZKF|$I|Vj((h=vn2i8Alx^w+A
zP?_fh)ju2$p0v?FI7<-IvY+9Oj_+&|)}ffYn=jy8_=Ongp_SK=9*XgVuO9EM##lEO
z{c>E_>ZUwd-^6(4cAMZWh91J*D>-~S#UJ9K{_kiOZfGhb*#
zyWqa>dCb+C7hIygp>#;!Z2c)a|9#UpriU!&8y=23e=+*z$KX{keG?5F>6>Bg=o>?4
zpT6mW_`&qeg=or0(Kl^)r_ndL517xp&_3SSTQ{!V>`tF_<
zUrp&_9dM#Qz|+B>m_vGCaq@*c)fq&pVr4ncZxQs1Vf7%$f&4IH$t6Fj2*iz>c<
zu?%ZIIy0*5U-WC*#`iDKFXVlKReb*h%`xkvS9mSYI
zHVZlnxf%Rr*gG(xJO-&h_AmN2Vcjp6sSf*+qD%v6Ol+r2>b;AO%0#w5k^lY3e=G8*
z^K&$xVVzys_aKbsYH>MBYZ`G5_5jY}HsBmA=7u*kqTB`pYt|#~5uAIy8nST!{Hwy6
zuaMTqm=iE*@Mh~E^NW>e+d=xln+F*N4KSNb1CE3D)L!vuFYI&F8PQ%VF%}jeE!y`>
zz`Sw@+6L$MPn>|AVq6qEm4N#huyJ}=?7#s=mav)TiM^Jt+<2ff=3+?iHqhYDK*fBh
zGn{Y%rZGJZ<)?M`0?5~gy53E-a(EBy)AjPq#8^YT#(0!NKk&fDI0HMy600_TsAEm%Rxz0yn
zjjsydkNXxR_c-f=dKym9`TD+)alx0ub1l0`_l9>(^R5DKVqm|Fue{z{1-=zR9t^OB
zQok6dH=d9{i}7_8-C3f3@e1lmeMCvC!fW`vW%s`zYc=RAeR*FY-cLHyvYXy-h{OE^
zvi};5id@QcST|6fqAd%%;fxsSN#lQ@GyLc6*kjzUtP8Yjq?hrQ0@;1y&o6$bFp}+d
zonHLTe}NyE56E}pXdYySonRlr43Ix3#u_TaxXO4h$r0_#E`{7Tfggq7Ms
z&U6nU{KU(cyC%RETnw6O*o(=o)rdJ~1mvn3yedZA)xd9o%$Si*3F19dS?qlna#?~n
zT~SX8TTQl*qGIn(gq85H9e}Ihm-$yY)Jy4p1l!0*;9D+e#ChvADEktWTfOERC&kEo
z9b$ZUW7aQYHqAl&DJWk!(jNkQ65Y+33V$Thr?SsQ{;P}TddcQKm4{UWo&!Jb6nUCp
z$D#WnlYxH?`>az?uO&Q>V&qZ1GiK8g)Uh7AiPC$ha;~@Qg_hk_mDi%&3f)I2|76??
zqxtIYbI|AdF3{(_pwD|_y%vkJ27PeuqA%92{cvxyv-_OBljrytw)a?X%-w#(_6ohu
zFgHB1Tyj^T-cIB-iS9!4w?_&S*=URdZVDs&<0zcz+`ZZ4Za^F3-rkzjY7@>J_3``~
zv}B7MrGI4Ov3v=0K;`TO&hV|R0FL;3Bm*{c;U6ry?-qW&oX+
z2>GO!jdDMz4#)W#Ieg71_fmEEU>?3>lzR~mKlWf|tsM8jDEEAX(Rqh|c2n%Q!cS*O
z4S2I*-~K*($Ua#QJFXEn-&%d2J@w0^owfR2d+K#;=SRn2zl1Ktm4(gmVeHgW^tCkf
zrL#DnX@IPbV?t+1p3}%fw)X-Dy;iqXuRk;mXHr{Qbg3;*=u$t#eT^pU37?B%d;21d
ze)zmNzeZ=PiC;(e(|N9bo_J9|I-C4s@C3Y_D8imH?>+hi?p+HQd^tJ>aWc%!HzMqg
zF0A|KsIT|VwAw}RFVXj!yEPU%4CK0Wv~v=v&zj(%G+Eu?vcas_x$d3
zP1ftD4JZD2TH<&E@E_p}9rR{3(G$&4@P+0zKzzs{--rD6ey+zP@&~HObs{OYJ?7!izn!NvpHS;Z#
zB?D9#i=n0Mnp$2%0d?osr2jAM06_}L5Ezv>CPgIAdM
zV%|5`jJeBuX#4kIOE`!zh30}L=sD5_q}Pj)X7x@ZbVInT8;Hk(mhR0F?ODj0nszS6
z6N*piY|YReI`Wz1K8o*&b<&|4+`}LqY}@~nJtS`rq299WL8j=u(Mk>
zhxv$q4*jedvUn7_;TZa?%xlPn<7CTY$7IJ3ph+=I#xZQb_Jz{G<(2x
zK}RMDdbBIa_w6D+^&g@^|5<=A(I2+`vn8jo<(1TPQRcn!nYNkc<|yFL%*Eap>SdeyA^)ghimCdWs33C$+A8Z@p{Yg
znh{R?eT?C}F!c@8@&rS_bqwfXbtTfeE?VYU^(=o{VXBe|QSD9-uCpegZ@NozqggMey@C^H4
zYX&O!9M+^y8tpcKXPD38yp;cW!!KrMf@l1`+H&RWA-G7l)p8P$4AQd%4sZng@^NdeQVN>r?`*sup{}*T?79Z0~<-UJcB-eGv1?G6Z`KJ
z_X~$N9{wQg#nbn_{D;$HU-{>0dF=dWANTdZ-%;JiO*(+`E5yet7o2DBTkl^=YbaA%
z9L5m$BO;Fol3(g~eLVYH%aT@0XMXnrK8N#bOZ2^)80JxOT{4(^Ess})w57@gUb4~P
zaG6}cV-K1^8|&Exn8qWVyULk=p=FdPiyR;M-_PS8Y0bZ{rvWn2$|KPZWn@gUgDiVzd~z|ChpXk{P98>bF$n&1
z-+)|1i9A}{rH|)^*0LyaF@o~ua#636i%1@iT6sAQ8VXa(%U=V@%cSewBoA_2$OgaDD$7d*@*sIx3;e~b
zLw<=oF;4rfLwp);}_pONhZ+4!!{$R5Xi
zkIOkD`*qqMuY%l!?2pj57|ZWRyL^}S$9s^!*8X@S!Y{#77>chM{iKE7cmd8B@t2LMM|$&phf42CtkXwgot^`i?noR(oJQCu
zdx4JZt4p~pKd04}AKSDIajLL}$GwKRWY?$ny5ev*Vma)0M&no{z14y<=c~pc
z4J+)<>oGqXi88OmI)5DQLlE6cqyfL22HEdjNQdcN*`;W6x&P#{TIw6M_zYL+614qe
z0n$6F3uK?K^csvl`@9Y5Hh!Vl=TD$+EwH_keV%OPf$Z}=VV}o3UAE8P3L6RWaUbmS
zTTyS?%O8XCk^Uw7{60VXd{579-=KYdAMEhEVV|cw={^bBV6b1bUL8*Mc`6sRVGGJk
zXE5Nur4Qsmv8z{lCk3?8r~29GAw!_|x6zXf{RuoEc~a6^=xu~8o@~qp$W&dvSM|#;v6|hrbkfj2+E}uI6Ui
zQ>8aowd2QkHNSca_tn2I`@1Vpmj9Ib)y0ezP6
z-{m~E6!~lCvC|OtP0nKtpZ@=t$9|4?-o7vMSUhYPn#ZcnDgFZd(9UBYLD&y<9y<$V
z|DnxetN(R*^H|R(p?NIXi932j=R(+bKac$YWw@Mq?B%R?Od&bBsJE8zw%B*RkBmQp
zba`@eQTJFnzQeK(x{2nX3$a&lJN8imz0XwxTfY%!^;1=MSemi_v5&vQRf~7HQvV#=
zx|gKB!}5{p9j+$4!*vJluq?peIT3qI{gAG>Tk_TJu=EXjhoy!pODw;`qP)T7
zyTj5Of7?Pm2knPDEPcM>9Tufbf$p#bE0gaIOK;B`$Y0DstH_pWaAogAn3#i-9n*Ms
zed?cZ&XNAk4B1QR4hznC-9YzPB%`^u{Nt9#wtT6p-GcW_w02HL{kBs_8
zT$_6=vGP3@>Lkz%@6fWm>*u0KG)&QZpS)(0CYgeuAtZz7Si2|@92xn~Qh@z27xu0W0A~QMp5IwK!0#+-_4g&
z3Bt5A-_@Si3A{xeY%F;9TRv0tUnq-qEcpL=r;*WnSJ-349_rfzw;J8sTUy>uXUx4x
zqQ7&h{QYgj*~MNp;crmjZ*8-I^!F`x<)>WbUgmIRXBaN%AV9w
zJo6xTp52vwV~6nux^;+$vr+zhP8`yvJqA_ce(}Y`TkOg9q(!0w2ip!P#$ByEyALf2#i7Wesj^aVCj^6Leey*c<4MW{J#KYRG
z{qjNX2i@n&-rP|<$o=+3u54TyX+J$G-@UHvRUO5H-0KhzXZhMs&pgnbzR;CDUmI`N
zkNxF-><#YiKXzpocNEVs*1i3DS9V@U@lfAEh<7{UB|;}gV{OHBaqr1~tOr{C7;{_w
zbkAPIp?B?sANR~!{dkwE)lYB3i8zB){%chJAu9hcl^^fRw9?WWWg;ECVb}bZKk+UC|Def4<6p
zugZVF%D+tI|EbEqO67l8RQ@+r{=+K&dn*55RsO%L{6|&(|5f=E35O_tRO`)7*aoe%QX`IP{*xV(vcg0p@6Fu51^(Z0|Ksq#$NfKtAAbW@PV-UtFL3`l_#<&Znd(S?
zbFv@zKLq~}?q3CeD)&DKe-`(zfIpx6m%@KP_tW1k-NgNi;NQ*t3*mo@`zzqb8E-jX
z2mG*O$$mThS95E`j7thlJuR%&PY
z_G0{7RBEx7m9beBrTNYxdnvQ#m0QZJ&O$b;sMuC&EwQl@dqG99jg>pC`ExBr1s|ib{)|Mb_e?du^r}
zwo<2~(p2WKQx=ZvO-1GQVyn{z*Hl_jTs%xgR&H~G6p0}NQ-RfKHO;a+T1kgdp&ZWX
za!N&|vsHxoC?k^e$D2egwOMGQp=f0`hqKaTFE!Z~mf0Q7a>~kH)^1KJbUPWTV+G7-
zw#_-g0lwd)u-(T(UL}8g(3~{U0%lcgy|f`
ztz|_PBx}jfvlNxivd7y>=MP2e%(4$PU1N33&L3*RzX+Ok>(r^rzX_%rZZOGo_PGkZ
zv$D))pQVse>^l?h26eu*xHu2uMc70?qG@T^
zXSvCW_I5xHOxAMKtRhFblL}N(0(l$eD?N#rlK(Yw$q4q#vbCP`t;H22)|8~B!Z|BN
zQ*5Q4i6}pM%B3aYEOHjxhMGWcy4I8wAf=!+Ewz_kXDca#kOr&sP}6KXH~M5wD0E2;$9
zrM${*`S#L+*1B=AY?E2CHnZf5%B)p0gZWMyx;16#EG%-g$&tFTBHoBOB3UKaay^w&
z`{k+W6(AcuQbi`0xHW%2O25|YKu+Xb+OT7UmK`I4XQ1LeDhL7Ys0H#WofzY6C3Z)p
zWtQFUEJJs9GU)$&=nY0nu%a9)5>c$>6%HF|lX6y$(XH49mj=ahHj5N?ITQWFQf!|+
zo0JBIP8)OB%Iy_Mt(@H?=VDT&WGbf-5d&W_G!@#*S&07XEVN-DEXf0GpJkGpsoaEN
zuXS7vm=cYuMK&lwhs~64cQ`7_oI^P?Fa+~dtPnn5GBhyf*(ej+yb5bEsiD~z`71D1
zGnqj7x1426zxC@VwKkcrugDYq2F;lcOlfQdKJ6w~rX5W#COz$GVp%kmF}Q6?${cA}
zK4Ib$F!<51m3C{+&6cT|()d^xyT`eElDp@*Yxs?lPYicW+#Sl@
z6z-1a?sV?n#oa>gI=SoO?rQFC;O>*$-NoG(xO;%R?{c?+yB~Ac%iXX#rJT{+?a$qK
z?vCWHnY&ZDdpmavxLd~E`?y=h-Synv%H18@-NW5ix%)PEKj3a7ch7K_?N-Ve$=z7)
zUd7!+?vCZ|B<|*LH*TU8R(a{aASX
zU3%6c^b2ty#DNe8LL3NjAjE+X2SOYOaUjHj5C=jW2yr09fe;5m90+kB#DNe8LL3Nj
zAjE+X2SOYOaUjHj5C=jW2yr09fe;5m90+kB#DNe8LL3NjAjE+X2SOYOaUjHj5C=jW
z2yr09fe;5m90+kB#DNe8LL3NjAjE+X2SOYOaUjHj5C=jW00+7cG-Qu2i=LT2a}dj&
z3fOG1+%av2bA-i`l$4xjEicNqlsg?orL(j0r_DfEexcQ2aXPF;&ho7M%uIx(T8oSA
z`BtahK>?ZLlQQSCTjsGflu|+Q^!dycE&EFsl(02dAQTB&^XFRf3+Gy9S6Cedh+0rt
zT!7$;JWF0tX@Skb*6`L*7bmr>$d+$gP*iTStQs{H
zj4IB8Z}beAso)o-5FI3iku#iDhqJ>Zs9{F8=8U{}u|bkfondp%u$Gh+
zQ`43@ZI1cY;>;qVFa@R+R+X$mXL!|e+B6php+B_j=4Byx&MIE!+FL>)y@seNtb
z8TJZMuEHR(Q(9wF%IKdZWlCks0u!XR##f>(R7D%v+U&Mzki(=T#GO>+RF%RdwG({=
zqE9Uiw{!FMS&36{r
zOCjYp$b?d|t?f`Hj|A0>d`D54Guu`!m(Y*4rX5;hFmW%dK*?uU2XUuceZsd#^4H=P
zOQ~(4(^BRjp@9x&j(SauOVRVAtz~7l(t=F%6gkZUerb-*FLc;T?McbymE}%bi6tLH
z#M}|3_616H-u8=}>a_DgCR5f~?@RJ9VJ$DWIh+=Uvy{r*pakYGv|6Bi78W@vuu)FO
zLV@y7VzK7Wt0;2V2zOG8b3kWX%VnHbNeqI{2clWlqGBR)>5#QG&+d?uuOetR6n&{8
zRRosE!yU!lAcMA&{E{*Pw+gVVlE4}cme}XZS?AX(V+T>*r#n&lGyefL3$>Di9E@K4=51Se<6y;&J*
zv!_f?N|gIJGzMnSBb2F|ZGzoVVs*}0STKhfM@#^((h3UfrQ;#&e(|Tzr^#p(4I8TI
z(tOr)l#M{OK{N1XW{Y|Jh>B9X1G8#KUx~F0)6X%MwsU)gwVm9L
z(a!EOXJlH4eq^Dwyf6y@Edj<@Qf%`oti=|mT?A!VY#mLBf$WSJ`nhP
zK8brZY_zqYK<;Y-=WFOT^-K&@&%`$MoHirTk|KtdtOMSlrc;djtzC#`6pdMji4B=&w-aw0Fl*UW?fB?!)jPrHo9
zG6sv;OlS*eYDEKi_9GsU=7TgyXe{D^H8r9&{(?BBzP6&Rty_*
z7c7VH{g-i!TtiLp>qb3WPlQj>Kj+Ip?IT{`D8ej;mg@_V!UN%|1X}wrrM)U#&sG!t
z4$_y2z6AVYxx$m!&%%>ciTJ1ghwu>?A8GAP<^xO%Td9%7YvD|n#10vg*<0aZVNbQ_
zE#bL{u&@okhZ^dX#Nv|Kr@E0@7-#%-v_^X^4V%XHL|l&X
z;bcS@Yg`cq4Oq9GMwlJ=m+5Gt=&;SE?(eXa*b6F9@%c%tDH2N0LP#D)(LR!5UN?q?rSuOAJ0QK=
zS@G?WKJNUaVTXQw*9~F2!glCu!k#od-g$>Uahqv`m@MmsS@PFg5`3?n+?Wy3McPelqhjZ>y;F~!7o{D@;h263zKp{z4m&ygHx3WQ5J|tQ`z!G`
z6f5xhD;4+!=py<(H9%?qLWcsM=J5H23VcVLf{$6Gz<&Wu?LU680{@RmfluDAz=lBz
z9J^G3ZG#nf>;nq?;8hBIdzAvuxLSe3Rw?iU9FFF2*B>eP#~)Vk*T*aHq%{h>CP9JA
z)++EnhA8lp>lFCvp$h!|dIjEaodOs9T!D8DQ{d=L3Vdp~0;l{!flWyYoV!JVpG#KY
z6;CMeo)HS1_)7(j9i{MVO|1f_aCirY7jpPr4)5Xcc@BFye3ggOk57Ih^=og}#`>
zWgK45;awblLC|yfxS)sLB>frrjFNsChevYww;aBU!(VcE1&1?lfP5HT%v`U~@8)n7
zhr8W~^dlI{)gcr7t|T~;F&Bp^v_L`0`0Ie@szrvEYv6T&XEBMro{>D8A`s~?{4;cz
z9G%?>=PDIUcyvkT!bd-Xr)ls+zk}1?0zc6U7+#q^k%;jlnCR7bqW^%?{|ZwJq8Bj5
zr~EO@$UlOKUX3UEk2(E)oL<0!zKlHhkwf%qJkj693phisuf`L74fGKGX7Kt8SkS+UaQYEEO%JkhV_^nE%j
z?JwY9^lCiOujlk@c>V$oMz6*b{o|be22L;FVDxG{(Lc%QU*z-x4o0uW6a5ZOKZ)00
zz`^L%c%rZ4^krx#(jNjA^p8U>=|}K14W8)5{I}mYgu%PF~Z_QtgCwj5oqV*S*U%-MMQwI4*FqL18Cwj3y
zqxBlm3z*_l`Rnn~k6@x#U#d?v}k3=tEK_9OfKh$`l7wbz}ZxX$L1^ras00dL{)p(*8>rq;t61{*a
zKGlCDdGI5L=+$_l7wcDA&l0_WDL&CR;G-YGM6bpZy;$$k`j_YhO!0|+JbCaVhv?OK
zqTg`0jAlRQ@+V-5PxM;(QR9hTtgmUkP5BF0(3{AEA32o28c+0MJx=R$q8Bj5r~F?J
z!$0^DO!R6z(Tnvvt>=kez!ab8Ie9C+8c+0My-(|Zq8D&5dNrQtg?)hR1w=1kLC=S$
z*8J6YqJKLdg~X5S3q&tqLEm3Nx6-TeL@(?QWRDuf`Mo
z8BS034$5D^g8qP}{AxVW3wsFJM~Gg)f<8y1|J8V+7xojfrx3k>1--WZYCO>kdkfiL
zh+e?K=+$_l7xo#l*ATsc1--WY)p(+xUZ^mL>^np+U_q~Ke>I-yh5d)@K}0WLL9ea9
z8c+0PJb$tu5xsze(W~)9FYHTXZz6gD3;I2p_E+PHUf83^K1K8b7WBoM_E+PHUf8e5
zo<;Nm7W7wf1`s?=gC}}n?;`sb(F>U3UxjZ6dGI5L=+$_l7xpo-ml3^yDL&D!hhC*0
z!9=gd6TPsfk$sKm1x)dYK2|e+sqsWF>~Ca`BYFV~`a})A8c+1XUPtyjq8G5B*VbQ+
zCwgJu`yZ1+FJM8h9e>q$q8Ii+vJX=J0v7bz_EY1DUf2)Go=Efp7WDl!@}tHRy|6cu
z{gLPeEa`c%m2fShCL&y?}$!
ztMNoH?6+jkC3*o1dM*Fdc%m2fUb6oZy?_P1mVatI(F^-9*^7x@z=B@OKQ*4{g*}<<
z%S12WVDxG{(F^-C*`tYGz=B@ef7E!Q7xrqhUlYB61--WZYCO>k`!?CTiC(~hK35~Z
zYCO>k`#0IciC(~h9%3o~2%e_F6TPsPll`3N1x)ct{Mi@kB4|{bc_qdI1NcSL2CZ><`d>0nrOM7`+-#
z^kP4O_7{j=z`^L%c%m2k4=J$Y5xsze(W~)9FZL@Ia(V#=qgUgJUhHqseh1|*;9&G>
zJkg8&5ZWIhdI1NcSL2CZ?4Qtn3egKV7`+-#^kTn-_Fsrzz`^L%c%m2kGqhhr^a2h>
zuf`L-*v~n`=>;5&UX3SuvHwH+L6pCMgVC$;L@)M>X#a@l1ssfCjVF4szeM{@L@(fA
z^lCiOi~T6tpCWnz2cuWxiC*kq(S8=u3pg0P8c+0Mzl-+2h+e?K=+$_l7yDzhUqJ
zQvL!CMz6*bz1YvB{Y|15a4>p-Ux;;)3FEW)(SE2&0B~tPR1LqciBJ2X*I;rTY(JEi
zJy9rxu(xA)l3`*0zLUd4`Tk)UhsFK@?Pn7G4vsJ7u&}?Ay_@hWxILTpp9mKFOSGRw
zu-LDn{W*fg{v7RB5-j#3ej_M>S3hG4NjL+u;Y
z$;F=9r=&;r#VEYp?`!~@G`!?Cr2^RM9fnDS^(3eVTjNPb#@72I>
zY2bfr;5!Ze>D{Y=w`$-QH1Gut+|THrUXBK?)WF*`@ar1*UmCb)xPSWNG;omyenbPm
zs)3s|@Bp+0*xkzC3qbzfJ>>(!gUC7*enNCTrl^HStR{
z@G?#O^&0po4g8D-eqIB=q=Emaf#1}?e^KC8{+-akUuxi(uKxV~kp{kA15eYyvo-J{
z4g8P>-lBnjtAYQhf&WJXpVGixy7`xXum&Ebfp5~lvovs}2Cmk?PiWv>3fw9$do*yp
zCjM(0_%9l`Q3HRWfxBUURNX)NXyAAaoT!1vY2X|UT%du=HSl5uZmsVW_@?5!3Ewn)
zH{+X*?-qR7_-MSLvlF-CyA9t=e7EEK3BEh<-HGome0Sr!2cHFBEgJbVs(<@lWVD)7z6w*cQle3kg_#kU9_jeU#p-H&ex
zJ{P{F_?F>Yj&B9N2k`wA--Gz7@U6tR3STw8)%YI5_b|Sn;ah{xjc+Z!b@_J`#Kb<&6OPr
z{C|CG2Ng^XdRK>#-@$DiWZUt9eMFk3xfamoMo)k^ZLj#qkpf(|NYdWy2^z7@g`c1?
z+uj2T8ueSd9u%|$-|?-Xpym2@FB1hT#`oe%QLw@VzGy_TwO4790$n2t7%||@qChc|
zgWU}Z7&*yzk;wn1P~h0Ai$$$Bhv;@vz}UgBAq9-y&dnr>-|m$oM7W&GLl)H?ALVv=
zfJ;M`(vB|;Q9(7&P52%;5y(q<-XtLHM0UHsy9eDi$6Ug-W(B+jaW*QS4LzP&x?>Nzbg{pK@qA;T|-
zzZT*brhY5K55eCDQNrk+s(2X07b0E+@rC%mq2UXc-vIG}s>eUHf$Ex2u1ZvdwpPX$
z+P3WK2R@XPDI`#-C|vWThmQbvm6caMlpysZ9_kSF>m58?dA38m*Fi2l)Zu%fL-vW6
zHVE*0lY;{MALF19?JFE|nDYDvc~$Rj5J(STkW2F<{?djIR6Vhw3Y6d0P{Z~zISJ)8
zjW(~lp<_*z*Ua*Z7I>`;x0~ro{AD~lmzh0dl!az6BNn
z)OE0+)0G!moL0Q#L=XN^`Q#8q9XZj><={3?b_9B{qs@aIS{arfVoXl9So~gkku$V8
zvhYC5j5IvkB3^A7Bfr|Bezb)`lm?-P`;xTj$gjKbWM)jT%ouMGkw;mwkTkeFau!~K
zpeS-tlE5mtw)FUb{4^xJ9GHZJuLvQTPfg-PFhhKa(uz%}PLo+C629`P!rhass)@ZR*z*R(ZX^zlRs
znh#3=bnkR<|66V1F$H)y%3-JXKvf#y8)th`3eOiaG5!>;Xo7F6<@1X(ZzeHU)qC`O
zJnM%ic`N<IbG_aGVt#^q`AJCX81ZfNqZIgx_d)=dC6jbbAgIcLh1|r
z#gLveYe(vV7ORKWIp}}dJc1^-Har(>#Vec@rA71bwB}q}B~}Hq%V}0OIeQBJ&7P48
z!6e0@JnTmrEhp4lq1X=NrmHEDDX
z%mU@~#5RA{O*_9*UOQAhN1my9X|i%o9(9VT$w{qAsh>vIq^BXq9IMjVx+oSs3ggIV
zwPvS*By&cFvV=_@6EL6g@;kPdk{kN3UvAWaG0mi>CTT=V!M_xu>iOTa2+iquiIwv8
zEU{8(eTCVRdWl8jL1u87`IfZS_kahNu*^)+E;EHC%(N~RqZip&gASluyma_>y8H6m!@qJP#
zhqMY;dzLep&8AppWVK2nS{N!x>ldU|T{FNtO6TTKHq-oxz*(rZW?a-l^
zf74Tp9}=@JVH_5+*lO0_7?6
zE~(NC8l^9de;Hg!hWK}JLlGHvzxtF>aQX5kL|@}j^!*Uo@878N{ouelG<0JJ*BX3odjhmnbEckzc2jO`LjjdfHSRPQ)MI%##1m3jaGBj$!%o
z7ClaCef5(~!}9IL1;dhLEA*w)CtoK0h|8qM=|}mb+NIWi)Me6-zD)Wtmr0*;ne?fb
zNuQLcB=FTw$oa|NrIK^;yHs*6eV0nkh3`_yx$IpkITyW4CFhcNspMSnE|r|i-KCOW
zw%BpKcggu*rr;%ArsO4ErsySIrtBqMrtl?Qrt~FUruZc(@^={rbk*wXvXfDy>9UiN
zg(~MZvDf(3P6h?Bzmq*ky(6POu_y2DUTS{r9UGy9Z!MSquJ7
zOqy`+`#L(@Iy|qUsJLMGuwldL@0laCd^n@~it4G=*Op7R@^@{A<8sC@9K^@%23}CV
z*_^K=eg(1fA=>{edF%H7CEESv<1}>d{)@jy*5maxyvo!1#!3vr@QQ){6vH1^WIFtZ
zXBqI_j*ou*ne(toNMdXY>LP~_A>1N%k_&Fvzh!OSpU~-si|tcwYS@&Q)jti
zf>CBiMxh#S@y+=^G@LK(p40#Cm$b}QCLaA$jJM*|Teo#hKYdc@*hg)STeJE@?G`Vx
z+^zq;PV8^tuglMV^08e0>u)!Iuh={pp68plotC&|Gp+H{#N4G3YhNqcD$6@O`Sj|+
z<_WX(1a4ego~I`{Q};{w@#nSz^LypFn0*BIe3lcs)AQ*41bOx&JNDTBX0VNPzT=S2
z@@;3{Yt!eaMSsoOK3{qlYsQh}H9FI_S!`Roed=z)F!B{+;NNN;U8UF+yDQr%(A~NVlZ{f&w8On%~Lz9PKRHgdHgDKyegykm849e
z+2xlVm}~q@P9;@_Z+YT*ytc5R=fKNSGmbz#gAG4S?%Q-;TXVN#RZPBebNSZSMOkNh
z_~vG@8fv`gzOqI@a+&s`2<7sn{~``m9=G3qk@4=Pz*8BA5}mm>_PyqMr=9lND&%kL
z$-h0)-TD6rg+Hb8k2dA${CK7Nq$Pf1n@8Rn;ma-Ims_VZ9rjOW)ZF>%Ya*rQ6Vl(C
zP;rCtr{)=zh^Q@pBE8@6xL@))dG<>6hEJx|rGMmid48*n5ic=VrSdrEqQu?F8k6^2
z?O8c~XxcxR%>M>&Bm1_=ory#R`ZO^aoAKfp>WTv$Y;yNmU6cy8eMdf@N!`(xqeT%eEKVYZ~qq?|eE(=G*5c={XnHJQT57ZF#}i
z{!0JBSxX){XNh^UEiSRyBhBGmXs~N`Ynor from defining macros that conflict with
- # std::min() and std::max(). We don't use (much)
- # but we still inherit it from uv.h.
- 'NOMINMAX',
- ]
- }],
- ],
-}
diff --git a/packages/profiling-node/bindings/cpu_profiler.cc b/packages/profiling-node/bindings/cpu_profiler.cc
deleted file mode 100644
index bf3762867769..000000000000
--- a/packages/profiling-node/bindings/cpu_profiler.cc
+++ /dev/null
@@ -1,1226 +0,0 @@
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-static const uint8_t kMaxStackDepth(128);
-static const float kSamplingFrequency(99.0); // 99 to avoid lockstep sampling
-static const float kSamplingHz(1 / kSamplingFrequency);
-static const int kSamplingInterval(kSamplingHz * 1e6);
-static const v8::CpuProfilingNamingMode
- kNamingMode(v8::CpuProfilingNamingMode::kDebugNaming);
-static const v8::CpuProfilingLoggingMode
- kDefaultLoggingMode(v8::CpuProfilingLoggingMode::kEagerLogging);
-
-enum ProfileFormat {
- kFormatThread = 0,
- kFormatChunk = 1,
-};
-
-// Allow users to override the default logging mode via env variable. This is
-// useful because sometimes the flow of the profiled program can be to execute
-// many sequential transaction - in that case, it may be preferable to set eager
-// logging to avoid paying the high cost of profiling for each individual
-// transaction (one example for this are jest tests when run with --runInBand
-// option).
-static const char *kEagerLoggingMode = "eager";
-static const char *kLazyLoggingMode = "lazy";
-
-v8::CpuProfilingLoggingMode GetLoggingMode() {
- static const char *logging_mode(getenv("SENTRY_PROFILER_LOGGING_MODE"));
-
- // most times this wont be set so just bail early
- if (!logging_mode) {
- return kDefaultLoggingMode;
- }
-
- // other times it'll likely be set to lazy as eager is the default
- if (strcmp(logging_mode, kLazyLoggingMode) == 0) {
- return v8::CpuProfilingLoggingMode::kLazyLogging;
- } else if (strcmp(logging_mode, kEagerLoggingMode) == 0) {
- return v8::CpuProfilingLoggingMode::kEagerLogging;
- }
-
- return kDefaultLoggingMode;
-}
-
-uint64_t timestamp_milliseconds() {
- return std::chrono::duration_cast(
- std::chrono::system_clock::now().time_since_epoch())
- .count();
-}
-
-class SentryProfile;
-class Profiler;
-
-enum class ProfileStatus {
- kNotStarted,
- kStarted,
- kStopped,
-};
-
-class MeasurementsTicker {
-private:
- uv_timer_t *timer;
- uint64_t period_ms;
- std::unordered_map>
- heap_listeners;
- std::unordered_map>
- cpu_listeners;
- v8::Isolate *isolate;
- v8::HeapStatistics heap_stats;
- uv_cpu_info_t cpu_stats;
-
-public:
- MeasurementsTicker(uv_loop_t *loop)
- : period_ms(100), isolate(v8::Isolate::GetCurrent()) {
- timer = new uv_timer_t;
- uv_timer_init(loop, timer);
- uv_handle_set_data((uv_handle_t *)timer, this);
- uv_ref((uv_handle_t *)timer);
- }
-
- static void ticker(uv_timer_t *);
- // Memory listeners
- void heap_callback();
- void add_heap_listener(
- std::string &profile_id,
- const std::function cb);
- void remove_heap_listener(
- std::string &profile_id,
- const std::function &cb);
-
- // CPU listeners
- void cpu_callback();
- void add_cpu_listener(std::string &profile_id,
- const std::function cb);
- void remove_cpu_listener(std::string &profile_id,
- const std::function &cb);
-
- size_t listener_count();
-
- ~MeasurementsTicker() {
- uv_handle_t *handle = (uv_handle_t *)timer;
-
- uv_timer_stop(timer);
- uv_unref(handle);
-
- if (!uv_is_closing(handle)) {
- uv_close(handle, [](uv_handle_t *handle) { delete handle; });
- }
- }
-};
-
-size_t MeasurementsTicker::listener_count() {
- return heap_listeners.size() + cpu_listeners.size();
-}
-
-// Heap tickers
-void MeasurementsTicker::heap_callback() {
- isolate->GetHeapStatistics(&heap_stats);
- uint64_t ts = uv_hrtime();
-
- for (auto cb : heap_listeners) {
- cb.second(ts, heap_stats);
- }
-}
-
-void MeasurementsTicker::add_heap_listener(
- std::string &profile_id,
- const std::function cb) {
- heap_listeners.emplace(profile_id, cb);
-
- if (listener_count() == 1) {
- uv_timer_set_repeat(timer, period_ms);
- uv_timer_start(timer, ticker, 0, period_ms);
- }
-}
-
-void MeasurementsTicker::remove_heap_listener(
- std::string &profile_id,
- const std::function &cb) {
- heap_listeners.erase(profile_id);
-
- if (listener_count() == 0) {
- uv_timer_stop(timer);
- }
-};
-
-// CPU tickers
-void MeasurementsTicker::cpu_callback() {
- uv_cpu_info_t *cpu = &cpu_stats;
- int count;
- int err = uv_cpu_info(&cpu, &count);
-
- if (err) {
- return;
- }
-
- if (count < 1) {
- return;
- }
-
- uint64_t ts = uv_hrtime();
- uint64_t total = 0;
- uint64_t idle_total = 0;
-
- for (int i = 0; i < count; i++) {
- uv_cpu_info_t *core = cpu + i;
-
- total += core->cpu_times.user;
- total += core->cpu_times.nice;
- total += core->cpu_times.sys;
- total += core->cpu_times.idle;
- total += core->cpu_times.irq;
-
- idle_total += core->cpu_times.idle;
- }
-
- double idle_avg = idle_total / count;
- double total_avg = total / count;
- double rate = 1.0 - idle_avg / total_avg;
-
- if (rate < 0.0 || isinf(rate) || isnan(rate)) {
- rate = 0.0;
- }
-
- auto it = cpu_listeners.begin();
- while (it != cpu_listeners.end()) {
- if (it->second(ts, rate)) {
- it = cpu_listeners.erase(it);
- } else {
- ++it;
- }
- };
-
- uv_free_cpu_info(cpu, count);
-};
-
-void MeasurementsTicker::ticker(uv_timer_t *handle) {
- if (handle == nullptr) {
- return;
- }
-
- MeasurementsTicker *self = static_cast(handle->data);
- self->heap_callback();
- self->cpu_callback();
-}
-
-void MeasurementsTicker::add_cpu_listener(
- std::string &profile_id, const std::function cb) {
- cpu_listeners.emplace(profile_id, cb);
-
- if (listener_count() == 1) {
- uv_timer_set_repeat(timer, period_ms);
- uv_timer_start(timer, ticker, 0, period_ms);
- }
-}
-
-void MeasurementsTicker::remove_cpu_listener(
- std::string &profile_id, const std::function &cb) {
- cpu_listeners.erase(profile_id);
-
- if (listener_count() == 0) {
- uv_timer_stop(timer);
- }
-};
-
-class Profiler {
-public:
- std::unordered_map active_profiles;
-
- MeasurementsTicker measurements_ticker;
- v8::CpuProfiler *cpu_profiler;
-
- explicit Profiler(const napi_env &env, v8::Isolate *isolate)
- : measurements_ticker(uv_default_loop()),
- cpu_profiler(
- v8::CpuProfiler::New(isolate, kNamingMode, GetLoggingMode())) {}
-};
-
-class SentryProfile {
-private:
- uint64_t started_at;
- uint64_t timestamp;
- uint16_t heap_write_index = 0;
- uint16_t cpu_write_index = 0;
-
- std::vector heap_stats_ts;
- std::vector heap_stats_usage;
-
- std::vector cpu_stats_ts;
- std::vector cpu_stats_usage;
-
- const std::function memory_sampler_cb;
- const std::function cpu_sampler_cb;
-
- ProfileStatus status = ProfileStatus::kNotStarted;
- std::string id;
-
-public:
- explicit SentryProfile(const char *id)
- : started_at(uv_hrtime()), timestamp(timestamp_milliseconds()),
- memory_sampler_cb([this](uint64_t ts, v8::HeapStatistics &stats) {
- if ((heap_write_index >= heap_stats_ts.capacity()) ||
- heap_write_index >= heap_stats_usage.capacity()) {
- return true;
- }
-
- heap_stats_ts.insert(heap_stats_ts.begin() + heap_write_index,
- ts - started_at);
- heap_stats_usage.insert(
- heap_stats_usage.begin() + heap_write_index,
- static_cast(stats.used_heap_size()));
- ++heap_write_index;
-
- return false;
- }),
-
- cpu_sampler_cb([this](uint64_t ts, double rate) {
- if (cpu_write_index >= cpu_stats_ts.capacity() ||
- cpu_write_index >= cpu_stats_usage.capacity()) {
- return true;
- }
- cpu_stats_ts.insert(cpu_stats_ts.begin() + cpu_write_index,
- ts - started_at);
- cpu_stats_usage.insert(cpu_stats_usage.begin() + cpu_write_index,
- rate);
- ++cpu_write_index;
- return false;
- }),
-
- status(ProfileStatus::kNotStarted), id(id) {
- heap_stats_ts.reserve(300);
- heap_stats_usage.reserve(300);
- cpu_stats_ts.reserve(300);
- cpu_stats_usage.reserve(300);
- }
-
- const std::vector &heap_usage_timestamps() const;
- const std::vector &heap_usage_values() const;
- const uint16_t &heap_usage_write_index() const;
-
- const std::vector &cpu_usage_timestamps() const;
- const std::vector &cpu_usage_values() const;
- const uint16_t &cpu_usage_write_index() const;
- const uint64_t &profile_start_timestamp() const;
-
- void Start(Profiler *profiler);
- v8::CpuProfile *Stop(Profiler *profiler);
-};
-
-void SentryProfile::Start(Profiler *profiler) {
- v8::Local profile_title =
- v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(),
- v8::NewStringType::kNormal)
- .ToLocalChecked();
-
- started_at = uv_hrtime();
- timestamp = timestamp_milliseconds();
-
- // Initialize the CPU Profiler
- profiler->cpu_profiler->StartProfiling(
- profile_title, v8::CpuProfilingMode::kCallerLineNumbers, true,
- v8::CpuProfilingOptions::kNoSampleLimit);
-
- // listen for memory sample ticks
- profiler->measurements_ticker.add_cpu_listener(id, cpu_sampler_cb);
- profiler->measurements_ticker.add_heap_listener(id, memory_sampler_cb);
-
- status = ProfileStatus::kStarted;
-}
-
-v8::CpuProfile *SentryProfile::Stop(Profiler *profiler) {
- // Stop the CPU Profiler
- v8::CpuProfile *profile = profiler->cpu_profiler->StopProfiling(
- v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(),
- v8::NewStringType::kNormal)
- .ToLocalChecked());
-
- // Remove the memory sampler
- profiler->measurements_ticker.remove_heap_listener(id, memory_sampler_cb);
- profiler->measurements_ticker.remove_cpu_listener(id, cpu_sampler_cb);
- // If for some reason stopProfiling was called with an invalid profile title
- // or if that title had somehow been stopped already, profile will be null.
- status = ProfileStatus::kStopped;
- return profile;
-}
-
-// Memory getters
-const std::vector &SentryProfile::heap_usage_timestamps() const {
- return heap_stats_ts;
-};
-
-const std::vector &SentryProfile::heap_usage_values() const {
- return heap_stats_usage;
-};
-
-const uint16_t &SentryProfile::heap_usage_write_index() const {
- return heap_write_index;
-};
-
-// CPU getters
-const std::vector &SentryProfile::cpu_usage_timestamps() const {
- return cpu_stats_ts;
-};
-
-const std::vector &SentryProfile::cpu_usage_values() const {
- return cpu_stats_usage;
-};
-const uint16_t &SentryProfile::cpu_usage_write_index() const {
- return cpu_write_index;
-};
-const uint64_t &SentryProfile::profile_start_timestamp() const {
- return timestamp;
-}
-
-static void CleanupSentryProfile(Profiler *profiler,
- SentryProfile *sentry_profile,
- const std::string &profile_id) {
- if (sentry_profile == nullptr) {
- return;
- }
-
- sentry_profile->Stop(profiler);
- profiler->active_profiles.erase(profile_id);
- delete sentry_profile;
-};
-
-#ifdef _WIN32
-static const char kPlatformSeparator = '\\';
-static const char kWinDiskPrefix = ':';
-#else
-static const char kPlatformSeparator = '/';
-#endif
-
-static const char kSentryPathDelimiter = '.';
-static const char kSentryFileDelimiter = ':';
-static const std::string kNodeModulesPath =
- std::string("node_modules") + kPlatformSeparator;
-
-static void GetFrameModule(const std::string &abs_path, std::string &module) {
- if (abs_path.empty()) {
- return;
- }
-
- module = abs_path;
-
- // Drop .js extension
- size_t module_len = module.length();
- if (module.compare(module_len - 3, 3, ".js") == 0) {
- module = module.substr(0, module_len - 3);
- }
-
- // Drop anything before and including node_modules/
- size_t node_modules_pos = module.rfind(kNodeModulesPath);
- if (node_modules_pos != std::string::npos) {
- module = module.substr(node_modules_pos + 13);
- }
-
- // Replace all path separators with dots except the last one, that one is
- // replaced with a colon
- int match_count = 0;
- for (int pos = module.length() - 1; pos >= 0; pos--) {
- // if there is a match and it's not the first character, replace it
- if (module[pos] == kPlatformSeparator) {
- module[pos] =
- match_count == 0 ? kSentryFileDelimiter : kSentryPathDelimiter;
- match_count++;
- }
- }
-
-#ifdef _WIN32
- // Strip out C: prefix. On Windows, the drive letter is not part of the module
- // name
- if (module[1] == kWinDiskPrefix) {
- // We will try and strip our the disk prefix.
- module = module.substr(2, std::string::npos);
- }
-#endif
-
- if (module[0] == '.') {
- module = module.substr(1, std::string::npos);
- }
-}
-
-static napi_value GetFrameModuleWrapped(napi_env env, napi_callback_info info) {
- size_t argc = 2;
- napi_value argv[2];
- napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
-
- size_t len;
- assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
-
- char *abs_path = (char *)malloc(len + 1);
- assert(napi_get_value_string_utf8(env, argv[0], abs_path, len + 1, &len) ==
- napi_ok);
-
- std::string module;
- napi_value napi_module;
-
- GetFrameModule(abs_path, module);
-
- assert(napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH,
- &napi_module) == napi_ok);
- return napi_module;
-}
-
-napi_value
-CreateFrameNode(const napi_env &env, const v8::CpuProfileNode &node,
- std::unordered_map &module_cache,
- napi_value &resources) {
- napi_value js_node;
- napi_create_object(env, &js_node);
-
- napi_value lineno_prop;
- napi_create_int32(env, node.GetLineNumber(), &lineno_prop);
- napi_set_named_property(env, js_node, "lineno", lineno_prop);
-
- napi_value colno_prop;
- napi_create_int32(env, node.GetColumnNumber(), &colno_prop);
- napi_set_named_property(env, js_node, "colno", colno_prop);
-
- if (node.GetSourceType() != v8::CpuProfileNode::SourceType::kScript) {
- napi_value system_frame_prop;
- napi_get_boolean(env, false, &system_frame_prop);
- napi_set_named_property(env, js_node, "in_app", system_frame_prop);
- }
-
- napi_value function;
- napi_create_string_utf8(env, node.GetFunctionNameStr(), NAPI_AUTO_LENGTH,
- &function);
- napi_set_named_property(env, js_node, "function", function);
-
- const char *resource = node.GetScriptResourceNameStr();
-
- if (resource != nullptr) {
- // resource is absolute path, set it on the abs_path property
- napi_value abs_path_prop;
- napi_create_string_utf8(env, resource, NAPI_AUTO_LENGTH, &abs_path_prop);
- napi_set_named_property(env, js_node, "abs_path", abs_path_prop);
- // Error stack traces are not relative to root dir, doing our own path
- // normalization breaks people's code mapping configs so we need to leave it
- // as is.
- napi_set_named_property(env, js_node, "filename", abs_path_prop);
-
- std::string module;
- std::string resource_str = std::string(resource);
-
- if (resource_str.empty()) {
- return js_node;
- }
-
- if (module_cache.find(resource_str) != module_cache.end()) {
- module = module_cache[resource_str];
- } else {
- napi_value resource;
- napi_create_string_utf8(env, resource_str.c_str(), NAPI_AUTO_LENGTH,
- &resource);
- napi_set_element(env, resources, module_cache.size(), resource);
-
- GetFrameModule(resource_str, module);
- module_cache.emplace(resource_str, module);
- }
-
- if (!module.empty()) {
- napi_value filename_prop;
- napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH,
- &filename_prop);
- napi_set_named_property(env, js_node, "module", filename_prop);
- }
- }
-
- return js_node;
-};
-
-napi_value CreateSample(const napi_env &env, const enum ProfileFormat format,
- const uint32_t stack_id,
- const int64_t sample_timestamp_ns,
- const double chunk_timestamp,
- const uint32_t thread_id) {
- napi_value js_node;
- napi_create_object(env, &js_node);
-
- napi_value stack_id_prop;
- napi_create_uint32(env, stack_id, &stack_id_prop);
- napi_set_named_property(env, js_node, "stack_id", stack_id_prop);
-
- napi_value thread_id_prop;
- napi_create_string_utf8(env, std::to_string(thread_id).c_str(),
- NAPI_AUTO_LENGTH, &thread_id_prop);
- napi_set_named_property(env, js_node, "thread_id", thread_id_prop);
-
- switch (format) {
- case ProfileFormat::kFormatThread: {
- napi_value timestamp;
- napi_create_int64(env, sample_timestamp_ns, ×tamp);
- napi_set_named_property(env, js_node, "elapsed_since_start_ns", timestamp);
- } break;
- case ProfileFormat::kFormatChunk: {
- napi_value timestamp;
- napi_create_double(env, chunk_timestamp, ×tamp);
- napi_set_named_property(env, js_node, "timestamp", timestamp);
- } break;
- default:
- break;
- }
-
- return js_node;
-};
-
-std::string kDelimiter = std::string(";");
-std::string hashCpuProfilerNodeByPath(const v8::CpuProfileNode *node,
- std::string &path) {
- path.clear();
-
- while (node != nullptr) {
- path.append(std::to_string(node->GetNodeId()));
- node = node->GetParent();
- }
-
- return path;
-}
-
-static void GetSamples(const napi_env &env, const v8::CpuProfile *profile,
- ProfileFormat format,
- const uint64_t profile_start_timestamp_ms,
- const uint32_t thread_id, napi_value &samples,
- napi_value &stacks, napi_value &frames,
- napi_value &resources) {
- const int64_t profile_start_time_us = profile->GetStartTime();
- const int64_t sampleCount = profile->GetSamplesCount();
-
- uint32_t unique_stack_id = 0;
- uint32_t unique_frame_id = 0;
-
- // Initialize the lookup tables for stacks and frames, both of these are
- // indexed in the sample format we are using to optimize for size.
- std::unordered_map frame_lookup_table;
- std::unordered_map stack_lookup_table;
- std::unordered_map module_cache;
-
- // At worst, all stacks are unique so reserve the maximum amount of space
- stack_lookup_table.reserve(sampleCount);
-
- std::string node_hash = "";
-
- for (int i = 0; i < sampleCount; i++) {
- uint32_t stack_index = unique_stack_id;
-
- const v8::CpuProfileNode *node = profile->GetSample(i);
- const int64_t sample_timestamp_us = profile->GetSampleTimestamp(i);
-
- // If a node was only on top of the stack once, then it will only ever
- // be inserted once and there is no need for hashing.
- if (node->GetHitCount() > 1) {
- hashCpuProfilerNodeByPath(node, node_hash);
-
- std::unordered_map::iterator
- stack_index_cache_hit = stack_lookup_table.find(node_hash);
-
- // If we have a hit, update the stack index, otherwise
- // insert it into the hash table and continue.
- if (stack_index_cache_hit == stack_lookup_table.end()) {
- stack_lookup_table.emplace(node_hash, stack_index);
- } else {
- stack_index = stack_index_cache_hit->second;
- }
- }
-
- uint64_t sample_delta_us = sample_timestamp_us - profile_start_time_us;
- uint64_t sample_timestamp_ns = sample_delta_us * 1e3;
- uint64_t sample_offset_from_profile_start_ms =
- (sample_timestamp_us - profile_start_time_us) * 1e-3;
- double seconds_since_start =
- (profile_start_timestamp_ms + sample_offset_from_profile_start_ms) *
- 1e-3;
-
- napi_value sample = nullptr;
- sample = CreateSample(env, format, stack_index, sample_timestamp_ns,
- seconds_since_start, thread_id);
-
- if (stack_index != unique_stack_id) {
- napi_value index;
- napi_create_uint32(env, i, &index);
- napi_set_property(env, samples, index, sample);
- continue;
- }
-
- // A stack is a list of frames ordered from outermost (top) to innermost
- // frame (bottom)
- napi_value stack;
- napi_create_array(env, &stack);
-
- uint32_t stack_depth = 0;
-
- while (node != nullptr && stack_depth < kMaxStackDepth) {
- auto nodeId = node->GetNodeId();
- auto frame_index = frame_lookup_table.find(nodeId);
-
- // If the frame does not exist in the index
- if (frame_index == frame_lookup_table.end()) {
- frame_lookup_table.emplace(nodeId, unique_frame_id);
-
- napi_value frame_id;
- napi_create_uint32(env, unique_frame_id, &frame_id);
-
- napi_value depth;
- napi_create_uint32(env, stack_depth, &depth);
- napi_set_property(env, stack, depth, frame_id);
- napi_set_property(env, frames, frame_id,
- CreateFrameNode(env, *node, module_cache, resources));
-
- unique_frame_id++;
- } else {
- // If it was already indexed, just add it's id to the stack
- napi_value depth;
- napi_create_uint32(env, stack_depth, &depth);
-
- napi_value frame;
- napi_create_uint32(env, frame_index->second, &frame);
- napi_set_property(env, stack, depth, frame);
- };
-
- // Continue walking down the stack
- node = node->GetParent();
- stack_depth++;
- }
-
- napi_value napi_sample_index;
- napi_value napi_stack_index;
-
- napi_create_uint32(env, i, &napi_sample_index);
- napi_set_property(env, samples, napi_sample_index, sample);
- napi_create_uint32(env, stack_index, &napi_stack_index);
- napi_set_property(env, stacks, napi_stack_index, stack);
-
- unique_stack_id++;
- }
-}
-
-static napi_value TranslateMeasurementsDouble(
- const napi_env &env, const enum ProfileFormat format, const char *unit,
- const uint64_t profile_start_timestamp_ms, const uint16_t size,
- const std::vector &values,
- const std::vector ×tamps_ns) {
- if (size > values.size() || size > timestamps_ns.size()) {
- napi_throw_range_error(env, "NAPI_ERROR",
- "CPU measurement size is larger than the number of "
- "values or timestamps");
- return nullptr;
- }
-
- if (values.size() != timestamps_ns.size()) {
- napi_throw_range_error(env, "NAPI_ERROR",
- "CPU measurement entries are corrupt, expected "
- "values and timestamps to be of equal length");
- return nullptr;
- }
-
- napi_value measurement;
- napi_create_object(env, &measurement);
-
- napi_value unit_string;
- napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string);
- napi_set_named_property(env, measurement, "unit", unit_string);
-
- napi_value values_array;
- napi_create_array(env, &values_array);
-
- uint16_t idx = size;
-
- for (size_t i = 0; i < idx; i++) {
- napi_value entry;
- napi_create_object(env, &entry);
-
- napi_value value;
- if (napi_create_double(env, values[i], &value) != napi_ok) {
- if (napi_create_double(env, 0.0, &value) != napi_ok) {
- continue;
- }
- }
-
- napi_set_named_property(env, entry, "value", value);
-
- if (format == ProfileFormat::kFormatThread) {
- napi_value ts;
- napi_create_int64(env, timestamps_ns[i], &ts);
- napi_set_named_property(env, entry, "elapsed_since_start_ns", ts);
- } else if (format == ProfileFormat::kFormatChunk) {
- napi_value ts;
- napi_create_double(
- env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
- napi_set_named_property(env, entry, "timestamp", ts);
- }
-
- napi_set_element(env, values_array, i, entry);
- }
-
- napi_set_named_property(env, measurement, "values", values_array);
-
- return measurement;
-}
-
-static napi_value
-TranslateMeasurements(const napi_env &env, const enum ProfileFormat format,
- const char *unit,
- const uint64_t profile_start_timestamp_ms,
- const uint16_t size, const std::vector &values,
- const std::vector ×tamps_ns) {
- if (size > values.size() || size > timestamps_ns.size()) {
- napi_throw_range_error(env, "NAPI_ERROR",
- "Memory measurement size is larger than the number "
- "of values or timestamps");
- return nullptr;
- }
-
- if (values.size() != timestamps_ns.size()) {
- napi_throw_range_error(env, "NAPI_ERROR",
- "Memory measurement entries are corrupt, expected "
- "values and timestamps to be of equal length");
- return nullptr;
- }
-
- napi_value measurement;
- napi_create_object(env, &measurement);
-
- napi_value unit_string;
- napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string);
- napi_set_named_property(env, measurement, "unit", unit_string);
-
- napi_value values_array;
- napi_create_array(env, &values_array);
-
- for (size_t i = 0; i < size; i++) {
- napi_value entry;
- napi_create_object(env, &entry);
-
- napi_value value;
- napi_create_int64(env, values[i], &value);
-
- napi_set_named_property(env, entry, "value", value);
- switch (format) {
- case ProfileFormat::kFormatThread: {
- napi_value ts;
- napi_create_int64(env, timestamps_ns[i], &ts);
- napi_set_named_property(env, entry, "elapsed_since_start_ns", ts);
- } break;
- case ProfileFormat::kFormatChunk: {
- napi_value ts;
- napi_create_double(
- env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts);
- napi_set_named_property(env, entry, "timestamp", ts);
- } break;
- default:
- break;
- }
- napi_set_element(env, values_array, i, entry);
- }
-
- napi_set_named_property(env, measurement, "values", values_array);
-
- return measurement;
-}
-
-static napi_value TranslateProfile(const napi_env &env,
- const v8::CpuProfile *profile,
- const enum ProfileFormat format,
- const uint64_t profile_start_timestamp_ms,
- const uint32_t thread_id,
- bool collect_resources) {
- napi_value js_profile;
-
- napi_create_object(env, &js_profile);
-
- napi_value logging_mode;
- napi_value samples;
- napi_value stacks;
- napi_value frames;
- napi_value resources;
-
- napi_create_string_utf8(
- env,
- GetLoggingMode() == v8::CpuProfilingLoggingMode::kEagerLogging ? "eager"
- : "lazy",
- NAPI_AUTO_LENGTH, &logging_mode);
-
- napi_create_array(env, &samples);
- napi_create_array(env, &stacks);
- napi_create_array(env, &frames);
- napi_create_array(env, &resources);
-
- napi_set_named_property(env, js_profile, "samples", samples);
- napi_set_named_property(env, js_profile, "stacks", stacks);
- napi_set_named_property(env, js_profile, "frames", frames);
- napi_set_named_property(env, js_profile, "profiler_logging_mode",
- logging_mode);
-
- GetSamples(env, profile, format, profile_start_timestamp_ms, thread_id,
- samples, stacks, frames, resources);
-
- if (collect_resources) {
- napi_set_named_property(env, js_profile, "resources", resources);
- } else {
- napi_create_array(env, &resources);
- napi_set_named_property(env, js_profile, "resources", resources);
- }
-
- return js_profile;
-}
-
-static napi_value StartProfiling(napi_env env, napi_callback_info info) {
- size_t argc = 1;
- napi_value argv[1];
-
- assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok);
-
- napi_valuetype callbacktype0;
- assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok);
-
- if (callbacktype0 != napi_string) {
- napi_throw_error(
- env, "NAPI_ERROR",
- "TypeError: StartProfiling expects a string as first argument.");
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- size_t len;
- assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
-
- char *title = (char *)malloc(len + 1);
- assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) ==
- napi_ok);
-
- if (len < 1) {
- napi_throw_error(env, "NAPI_ERROR",
- "StartProfiling expects a non-empty string as first "
- "argument, got an empty string.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- v8::Isolate *isolate = v8::Isolate::GetCurrent();
- assert(isolate != 0);
-
- Profiler *profiler;
- assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok);
-
- if (!profiler) {
- napi_throw_error(env, "NAPI_ERROR",
- "StartProfiling: Profiler is not initialized.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- const std::string profile_id(title);
- // In case we have a collision, cleanup the old profile first
- auto existing_profile = profiler->active_profiles.find(profile_id);
- if (existing_profile != profiler->active_profiles.end()) {
- existing_profile->second->Stop(profiler);
- CleanupSentryProfile(profiler, existing_profile->second, profile_id);
- }
-
- SentryProfile *sentry_profile = new SentryProfile(title);
- sentry_profile->Start(profiler);
-
- profiler->active_profiles.emplace(profile_id, sentry_profile);
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
-}
-
-// StopProfiling(string title)
-// https://v8docs.nodesource.com/node-18.2/d2/d34/classv8_1_1_cpu_profiler.html#a40ca4c8a8aa4c9233aa2a2706457cc80
-static napi_value StopProfiling(napi_env env, napi_callback_info info) {
- size_t argc = 4;
- napi_value argv[4];
-
- assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok);
-
- if (argc < 3) {
- napi_throw_error(env, "NAPI_ERROR",
- "StopProfiling expects at least three arguments.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- // Verify the first argument is a string
- napi_valuetype callbacktype0;
- assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok);
-
- if (callbacktype0 != napi_string) {
- napi_throw_error(env, "NAPI_ERROR",
- "StopProfiling expects a string as first argument.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- size_t len;
- assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok);
-
- char *title = (char *)malloc(len + 1);
- assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) ==
- napi_ok);
-
- if (len < 1) {
- napi_throw_error(
- env, "NAPI_ERROR",
- "StopProfiling expects a non empty string as first argument.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- // Verify the second argument is a number
- napi_valuetype callbacktype1;
- assert(napi_typeof(env, argv[1], &callbacktype1) == napi_ok);
-
- if (callbacktype1 != napi_number) {
- napi_throw_error(env, "NAPI_ERROR",
- "StopProfiling expects a format type as second argument.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
-
- return napi_null;
- }
-
- // Verify the second argument is a number
- napi_valuetype callbacktype2;
- assert(napi_typeof(env, argv[2], &callbacktype2) == napi_ok);
-
- if (callbacktype2 != napi_number) {
- napi_throw_error(
- env, "NAPI_ERROR",
- "StopProfiling expects a thread_id integer as third argument.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
- return napi_null;
- }
-
- // Get the value of the second argument and convert it to uint8
- int32_t format;
- assert(napi_get_value_int32(env, argv[1], &format) == napi_ok);
-
- // Get the value of the second argument and convert it to uint64
- int64_t thread_id;
- assert(napi_get_value_int64(env, argv[2], &thread_id) == napi_ok);
-
- // Get profiler from instance data
- Profiler *profiler;
- assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok);
-
- if (!profiler) {
- napi_throw_error(env, "NAPI_ERROR",
- "StopProfiling: Profiler is not initialized.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
- return napi_null;
- }
-
- const std::string profile_id(title);
- auto profile = profiler->active_profiles.find(profile_id);
-
- // If the profile was never started, silently ignore the call and return null
- if (profile == profiler->active_profiles.end()) {
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
- return napi_null;
- }
-
- v8::CpuProfile *cpu_profile = profile->second->Stop(profiler);
-
- // If for some reason stopProfiling was called with an invalid profile title
- // or if that title had somehow been stopped already, profile will be null.
- if (!cpu_profile) {
- CleanupSentryProfile(profiler, profile->second, profile_id);
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
- return napi_null;
- };
-
- napi_valuetype callbacktype3;
- assert(napi_typeof(env, argv[3], &callbacktype3) == napi_ok);
-
- bool collect_resources;
- napi_get_value_bool(env, argv[3], &collect_resources);
-
- const ProfileFormat format_type = static_cast(format);
-
- if (format_type != ProfileFormat::kFormatThread &&
- format_type != ProfileFormat::kFormatChunk) {
- napi_throw_error(
- env, "NAPI_ERROR",
- "StopProfiling expects a valid format type as second argument.");
-
- napi_value napi_null;
- assert(napi_get_null(env, &napi_null) == napi_ok);
- return napi_null;
- }
-
- napi_value js_profile = TranslateProfile(
- env, cpu_profile, format_type, profile->second->profile_start_timestamp(),
- thread_id, collect_resources);
-
- napi_value measurements;
- napi_create_object(env, &measurements);
-
- if (profile->second->heap_usage_write_index() > 0) {
- static const char *memory_unit = "byte";
- napi_value heap_usage_measurements =
- TranslateMeasurements(env, format_type, memory_unit,
- profile->second->profile_start_timestamp(),
- profile->second->heap_usage_write_index(),
- profile->second->heap_usage_values(),
- profile->second->heap_usage_timestamps());
-
- if (heap_usage_measurements != nullptr) {
- napi_set_named_property(env, measurements, "memory_footprint",
- heap_usage_measurements);
- };
- };
-
- if (profile->second->cpu_usage_write_index() > 0) {
- static const char *cpu_unit = "percent";
- napi_value cpu_usage_measurements = TranslateMeasurementsDouble(
- env, format_type, cpu_unit, profile->second->profile_start_timestamp(),
- profile->second->cpu_usage_write_index(),
- profile->second->cpu_usage_values(),
- profile->second->cpu_usage_timestamps());
-
- if (cpu_usage_measurements != nullptr) {
- napi_set_named_property(env, measurements, "cpu_usage",
- cpu_usage_measurements);
- };
- };
-
- napi_set_named_property(env, js_profile, "measurements", measurements);
-
- CleanupSentryProfile(profiler, profile->second, profile_id);
- cpu_profile->Delete();
-
- return js_profile;
-};
-
-void FreeAddonData(napi_env env, void *data, void *hint) {
- Profiler *profiler = static_cast(data);
-
- if (profiler == nullptr) {
- return;
- }
-
- if (!profiler->active_profiles.empty()) {
- for (auto &profile : profiler->active_profiles) {
- CleanupSentryProfile(profiler, profile.second, profile.first);
- }
- }
-
- if (profiler->cpu_profiler != nullptr) {
- profiler->cpu_profiler->Dispose();
- profiler->cpu_profiler = nullptr;
- }
-
- delete profiler;
-}
-
-napi_value Init(napi_env env, napi_value exports) {
- v8::Isolate *isolate = v8::Isolate::GetCurrent();
-
- if (isolate == nullptr) {
- napi_throw_error(env, nullptr,
- "Failed to initialize Sentry profiler: isolate is null.");
- return NULL;
- }
-
- Profiler *profiler = new Profiler(env, isolate);
- profiler->cpu_profiler->SetSamplingInterval(kSamplingInterval);
-
- if (napi_set_instance_data(env, profiler, FreeAddonData, NULL) != napi_ok) {
- napi_throw_error(env, nullptr, "Failed to set instance data for profiler.");
- return NULL;
- }
-
- napi_value start_profiling;
- if (napi_create_function(env, "startProfiling", NAPI_AUTO_LENGTH,
- StartProfiling, exports,
- &start_profiling) != napi_ok) {
- napi_throw_error(env, nullptr, "Failed to create startProfiling function.");
- return NULL;
- }
-
- if (napi_set_named_property(env, exports, "startProfiling",
- start_profiling) != napi_ok) {
- napi_throw_error(env, nullptr,
- "Failed to set startProfiling property on exports.");
- return NULL;
- }
-
- napi_value stop_profiling;
- if (napi_create_function(env, "stopProfiling", NAPI_AUTO_LENGTH,
- StopProfiling, exports,
- &stop_profiling) != napi_ok) {
- napi_throw_error(env, nullptr, "Failed to create stopProfiling function.");
- return NULL;
- }
-
- if (napi_set_named_property(env, exports, "stopProfiling", stop_profiling) !=
- napi_ok) {
- napi_throw_error(env, nullptr,
- "Failed to set stopProfiling property on exports.");
- return NULL;
- }
-
- napi_value get_frame_module;
- if (napi_create_function(env, "getFrameModule", NAPI_AUTO_LENGTH,
- GetFrameModuleWrapped, exports,
- &get_frame_module) != napi_ok) {
- napi_throw_error(env, nullptr, "Failed to create getFrameModule function.");
- return NULL;
- }
-
- if (napi_set_named_property(env, exports, "getFrameModule",
- get_frame_module) != napi_ok) {
- napi_throw_error(env, nullptr,
- "Failed to set getFrameModule property on exports.");
- return NULL;
- }
-
- return exports;
-}
-
-NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
diff --git a/packages/profiling-node/clang-format.js b/packages/profiling-node/clang-format.js
deleted file mode 100644
index dd001cf28ad7..000000000000
--- a/packages/profiling-node/clang-format.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const child_process = require('child_process');
-
-const args = ['--Werror', '-i', '--style=file', 'bindings/cpu_profiler.cc'];
-const cmd = `./node_modules/.bin/clang-format ${args.join(' ')}`;
-
-try {
- child_process.execSync(cmd);
-} catch (e) {
- // This fails on linux_arm64
- // eslint-disable-next-line no-console
- console.log('Running clang format command failed.');
-}
-
-// eslint-disable-next-line no-console
-console.log('clang-format: done, checking tree...');
-
-const diff = child_process.execSync('git status --short').toString();
-
-if (diff) {
- // eslint-disable-next-line no-console
- console.error('clang-format: check failed ❌');
- process.exit(1);
-}
-
-// eslint-disable-next-line no-console
-console.log('clang-format: check passed ✅');
diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json
index d07295ba2679..f6713484381b 100644
--- a/packages/profiling-node/package.json
+++ b/packages/profiling-node/package.json
@@ -40,53 +40,34 @@
},
"files": [
"/lib",
- "/bindings",
- "/binding.gyp",
"package.json",
- "/scripts/binaries.js",
- "/scripts/check-build.js",
- "/scripts/copy-target.js",
"/scripts/prune-profiler-binaries.js"
],
"scripts": {
- "install": "node scripts/check-build.js",
"clean": "rm -rf build && rm -rf lib",
- "lint": "yarn lint:eslint && yarn lint:clang",
- "lint:eslint": "eslint . --format stylish",
- "lint:clang": "node clang-format.js",
+ "lint": "eslint . --format stylish",
"fix": "eslint . --format stylish --fix",
- "lint:fix": "yarn fix:eslint && yarn fix:clang",
- "lint:fix:clang": "node clang-format.js --fix",
- "build": "yarn build:lib && yarn build:bindings:configure && yarn build:bindings",
+ "build": "yarn build:lib",
"build:lib": "yarn build:types && rollup -c rollup.npm.config.mjs",
- "build:transpile": "yarn build:bindings:configure && yarn build:bindings && yarn build:lib",
+ "build:transpile": "yarn build:lib",
"build:types:downlevel": "yarn downlevel-dts lib/types lib/types-ts3.8 --to ts3.8",
"build:types": "tsc -p tsconfig.types.json && yarn build:types:downlevel",
"build:types:watch": "tsc -p tsconfig.types.json --watch",
- "build:bindings:configure": "node-gyp configure",
- "build:bindings:configure:arm64": "node-gyp configure --arch=arm64 --target_arch=arm64",
- "build:bindings": "node-gyp build && node scripts/copy-target.js",
- "build:bindings:arm64": "node-gyp build --arch=arm64 && node scripts/copy-target.js",
- "build:dev": "yarn clean && yarn build:bindings:configure && yarn build",
+ "build:dev": "yarn clean && yarn build",
"build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch",
"build:watch": "run-p build:transpile:watch build:types:watch",
"build:tarball": "npm pack",
- "test:watch": "cross-env SENTRY_PROFILER_BINARY_DIR=build jest --watch",
+ "test:watch": "jest --watch",
"test:bundle": "node test-binaries.esbuild.js",
- "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js"
+ "test": "jest --config jest.config.js"
},
"dependencies": {
"@sentry/core": "9.0.0-alpha.0",
"@sentry/node": "9.0.0-alpha.0",
- "detect-libc": "^2.0.2",
- "node-abi": "^3.61.0"
+ "@sentry-internal/node-cpu-profiler": "^2.0.0"
},
"devDependencies": {
- "@types/node": "^18.19.1",
- "@types/node-abi": "^3.0.3",
- "clang-format": "^1.8.0",
- "cross-env": "^7.0.3",
- "node-gyp": "^9.4.1"
+ "@types/node": "^18.19.1"
},
"volta": {
"extends": "../../package.json"
diff --git a/packages/profiling-node/rollup.npm.config.mjs b/packages/profiling-node/rollup.npm.config.mjs
index a9c148306709..05327bc1a29a 100644
--- a/packages/profiling-node/rollup.npm.config.mjs
+++ b/packages/profiling-node/rollup.npm.config.mjs
@@ -1,20 +1,19 @@
-import commonjs from '@rollup/plugin-commonjs';
-import replace from '@rollup/plugin-replace';
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
export default makeNPMConfigVariants(
makeBaseNPMConfig({
packageSpecificConfig: {
- output: { dir: 'lib', preserveModules: false },
- plugins: [
- commonjs(),
- replace({
- preventAssignment: false,
- values: {
- __IMPORT_META_URL_REPLACEMENT__: 'import.meta.url',
- },
- }),
- ],
+ output: {
+ dir: 'lib',
+ // set exports to 'named' or 'auto' so that rollup doesn't warn
+ exports: 'named',
+ // set preserveModules to false because for profiling we actually want
+ // to bundle everything into one file.
+ preserveModules:
+ process.env.SENTRY_BUILD_PRESERVE_MODULES === undefined
+ ? false
+ : Boolean(process.env.SENTRY_BUILD_PRESERVE_MODULES),
+ },
},
}),
);
diff --git a/packages/profiling-node/scripts/binaries.js b/packages/profiling-node/scripts/binaries.js
deleted file mode 100644
index 2c0c6be2642b..000000000000
--- a/packages/profiling-node/scripts/binaries.js
+++ /dev/null
@@ -1,27 +0,0 @@
-const os = require('os');
-const path = require('path');
-
-const abi = require('node-abi');
-const libc = require('detect-libc');
-
-function getModuleName() {
- const stdlib = libc.familySync();
- const platform = process.env['BUILD_PLATFORM'] || os.platform();
- const arch = process.env['BUILD_ARCH'] || os.arch();
-
- if (platform === 'darwin' && arch === 'arm64') {
- const identifier = [platform, 'arm64', abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-');
- return `sentry_cpu_profiler-${identifier}.node`;
- }
-
- const identifier = [platform, arch, stdlib, abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-');
-
- return `sentry_cpu_profiler-${identifier}.node`;
-}
-
-const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node');
-const target = path.join(__dirname, '..', 'lib', getModuleName());
-
-module.exports.source = source;
-module.exports.target = target;
-module.exports.getModuleName = getModuleName;
diff --git a/packages/profiling-node/scripts/check-build.js b/packages/profiling-node/scripts/check-build.js
deleted file mode 100644
index dda96e66b900..000000000000
--- a/packages/profiling-node/scripts/check-build.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// This is a build script, so some logging is desirable as it allows
-// us to follow the code path that triggered the error.
-/* eslint-disable no-console */
-const fs = require('fs');
-const child_process = require('child_process');
-const binaries = require('./binaries.js');
-
-function clean(err) {
- return err.toString().trim();
-}
-
-function recompileFromSource() {
- console.log('@sentry/profiling-node: Compiling from source...');
- let spawn = child_process.spawnSync('npm', ['run', 'build:bindings:configure'], {
- stdio: ['inherit', 'inherit', 'pipe'],
- env: process.env,
- shell: true,
- });
-
- if (spawn.status !== 0) {
- console.log('@sentry/profiling-node: Failed to configure gyp');
- console.log('@sentry/profiling-node:', clean(spawn.stderr));
- return;
- }
-
- spawn = child_process.spawnSync('npm', ['run', 'build:bindings'], {
- stdio: ['inherit', 'inherit', 'pipe'],
- env: process.env,
- shell: true,
- });
- if (spawn.status !== 0) {
- console.log('@sentry/profiling-node: Failed to build bindings');
- console.log('@sentry/profiling-node:', clean(spawn.stderr));
- return;
- }
-}
-
-if (fs.existsSync(binaries.target)) {
- try {
- console.log(`@sentry/profiling-node: Precompiled binary found, attempting to load ${binaries.target}`);
- require(binaries.target);
- console.log('@sentry/profiling-node: Precompiled binary found, skipping build from source.');
- } catch (e) {
- console.log('@sentry/profiling-node: Precompiled binary found but failed loading');
- console.log('@sentry/profiling-node:', e);
- try {
- recompileFromSource();
- } catch (e) {
- console.log('@sentry/profiling-node: Failed to compile from source');
- throw e;
- }
- }
-} else {
- console.log('@sentry/profiling-node: No precompiled binary found');
- recompileFromSource();
-}
diff --git a/packages/profiling-node/scripts/copy-target.js b/packages/profiling-node/scripts/copy-target.js
deleted file mode 100644
index 8277f1d45290..000000000000
--- a/packages/profiling-node/scripts/copy-target.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// This is a build script, so some logging is desirable as it allows
-// us to follow the code path that triggered the error.
-/* eslint-disable no-console */
-const fs = require('fs');
-const path = require('path');
-const process = require('process');
-const binaries = require('./binaries.js');
-
-const build = path.resolve(__dirname, '..', 'lib');
-
-if (!fs.existsSync(build)) {
- fs.mkdirSync(build, { recursive: true });
-}
-
-const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node');
-const target = path.join(__dirname, '..', 'lib', binaries.getModuleName());
-
-if (!fs.existsSync(source)) {
- console.log('Source file does not exist:', source);
- process.exit(1);
-} else {
- if (fs.existsSync(target)) {
- console.log('Target file already exists, overwriting it');
- }
- console.log('Renaming', source, 'to', target);
- fs.renameSync(source, target);
-}
diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts
deleted file mode 100644
index a9a6d65ce191..000000000000
--- a/packages/profiling-node/src/cpu_profiler.ts
+++ /dev/null
@@ -1,224 +0,0 @@
-import { createRequire } from 'node:module';
-import { arch as _arch, platform as _platform } from 'node:os';
-import { join, resolve } from 'node:path';
-import { dirname } from 'node:path';
-import { env, versions } from 'node:process';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import { threadId } from 'node:worker_threads';
-import { familySync } from 'detect-libc';
-import { getAbi } from 'node-abi';
-
-import { GLOBAL_OBJ, logger } from '@sentry/core';
-import { DEBUG_BUILD } from './debug-build';
-import type {
- PrivateV8CpuProfilerBindings,
- RawChunkCpuProfile,
- RawThreadCpuProfile,
- V8CpuProfilerBindings,
-} from './types';
-import type { ProfileFormat } from './types';
-
-declare const __IMPORT_META_URL_REPLACEMENT__: string;
-
-const stdlib = familySync();
-const platform = process.env['BUILD_PLATFORM'] || _platform();
-const arch = process.env['BUILD_ARCH'] || _arch();
-const abi = getAbi(versions.node, 'node');
-const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-');
-
-/**
- * Imports cpp bindings based on the current platform and architecture.
- */
-// eslint-disable-next-line complexity
-export function importCppBindingsModule(): PrivateV8CpuProfilerBindings {
- // We need to work around using import.meta.url directly with __IMPORT_META_URL_REPLACEMENT__ because jest complains about it.
- const importMetaUrl =
- typeof __IMPORT_META_URL_REPLACEMENT__ !== 'undefined'
- ? // This case is always hit when the SDK is built
- __IMPORT_META_URL_REPLACEMENT__
- : // This case is hit when the tests are run
- pathToFileURL(__filename).href;
-
- const createdRequire = createRequire(importMetaUrl);
- const esmCompatibleDirname = dirname(fileURLToPath(importMetaUrl));
-
- // If a binary path is specified, use that.
- if (env['SENTRY_PROFILER_BINARY_PATH']) {
- const envPath = env['SENTRY_PROFILER_BINARY_PATH'];
- return createdRequire(envPath);
- }
-
- // If a user specifies a different binary dir, they are in control of the binaries being moved there
- if (env['SENTRY_PROFILER_BINARY_DIR']) {
- const binaryPath = join(resolve(env['SENTRY_PROFILER_BINARY_DIR']), `sentry_cpu_profiler-${identifier}`);
- return createdRequire(`${binaryPath}.node`);
- }
-
- // We need the fallthrough so that in the end, we can fallback to the dynamic require.
- // This is for cases where precompiled binaries were not provided, but may have been compiled from source.
- if (platform === 'darwin') {
- if (arch === 'x64') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-darwin-x64-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-darwin-x64-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-darwin-x64-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-darwin-x64-127.node');
- }
- }
-
- if (arch === 'arm64') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-darwin-arm64-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-darwin-arm64-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-darwin-arm64-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-darwin-arm64-127.node');
- }
- }
- }
-
- if (platform === 'win32') {
- if (arch === 'x64') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-win32-x64-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-win32-x64-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-win32-x64-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-win32-x64-127.node');
- }
- }
- }
-
- if (platform === 'linux') {
- if (arch === 'x64') {
- if (stdlib === 'musl') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-musl-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-musl-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-musl-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-musl-127.node');
- }
- }
- if (stdlib === 'glibc') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-127.node');
- }
- }
- }
- if (arch === 'arm64') {
- if (stdlib === 'musl') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-127.node');
- }
- }
-
- if (stdlib === 'glibc') {
- if (abi === '93') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-93.node');
- }
- if (abi === '108') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-108.node');
- }
- if (abi === '115') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-115.node');
- }
- if (abi === '127') {
- return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-127.node');
- }
- }
- }
- }
-
- const built_from_source_path = resolve(esmCompatibleDirname, '..', `sentry_cpu_profiler-${identifier}`);
- return createdRequire(`${built_from_source_path}.node`);
-}
-
-const PrivateCpuProfilerBindings: PrivateV8CpuProfilerBindings = importCppBindingsModule();
-
-class Bindings implements V8CpuProfilerBindings {
- public startProfiling(name: string): void {
- if (!PrivateCpuProfilerBindings) {
- DEBUG_BUILD && logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.');
- return;
- }
-
- if (typeof PrivateCpuProfilerBindings.startProfiling !== 'function') {
- DEBUG_BUILD &&
- logger.log('[Profiling] Native startProfiling function is not available, ignoring call to startProfiling.');
- return;
- }
-
- return PrivateCpuProfilerBindings.startProfiling(name);
- }
-
- public stopProfiling(name: string, format: ProfileFormat.THREAD): RawThreadCpuProfile | null;
- public stopProfiling(name: string, format: ProfileFormat.CHUNK): RawChunkCpuProfile | null;
- public stopProfiling(
- name: string,
- format: ProfileFormat.CHUNK | ProfileFormat.THREAD,
- ): RawThreadCpuProfile | RawChunkCpuProfile | null {
- if (!PrivateCpuProfilerBindings) {
- DEBUG_BUILD &&
- logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.');
- return null;
- }
-
- if (typeof PrivateCpuProfilerBindings.stopProfiling !== 'function') {
- DEBUG_BUILD &&
- logger.log('[Profiling] Native stopProfiling function is not available, ignoring call to stopProfiling.');
- return null;
- }
-
- return PrivateCpuProfilerBindings.stopProfiling(
- name,
- format as unknown as any,
- threadId,
- !!GLOBAL_OBJ._sentryDebugIds,
- );
- }
-}
-
-const CpuProfilerBindings = new Bindings();
-
-export { PrivateCpuProfilerBindings };
-export { CpuProfilerBindings };
diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts
index ca9db531d1e9..5b455f72974d 100644
--- a/packages/profiling-node/src/integration.ts
+++ b/packages/profiling-node/src/integration.ts
@@ -1,5 +1,6 @@
/* eslint-disable max-lines */
+import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler';
import type { Event, IntegrationFn, Profile, ProfileChunk, ProfilingIntegration, Span } from '@sentry/core';
import {
LRUMap,
@@ -14,7 +15,6 @@ import {
uuid4,
} from '@sentry/core';
import type { NodeClient } from '@sentry/node';
-import { CpuProfilerBindings } from './cpu_profiler';
import { DEBUG_BUILD } from './debug-build';
import { NODE_MAJOR, NODE_VERSION } from './nodeVersion';
import { MAX_PROFILE_DURATION_MS, maybeProfileSpan, stopSpanProfile } from './spanProfileUtils';
@@ -120,8 +120,8 @@ function setupAutomatedSpanProfiling(client: NodeClient): void {
const profilesToAddToEnvelope: Profile[] = [];
for (const profiledTransaction of profiledTransactionEvents) {
- const profileContext = profiledTransaction.contexts?.['profile'];
- const profile_id = profileContext?.['profile_id'];
+ const profileContext = profiledTransaction.contexts?.profile;
+ const profile_id = profileContext?.profile_id;
if (!profile_id) {
throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context');
@@ -129,7 +129,7 @@ function setupAutomatedSpanProfiling(client: NodeClient): void {
// Remove the profile from the transaction context before sending, relay will take care of the rest.
if (profileContext) {
- delete profiledTransaction.contexts?.['profile'];
+ delete profiledTransaction.contexts?.profile;
}
const cpuProfile = takeFromProfileQueue(profile_id);
@@ -400,7 +400,7 @@ class ContinuousProfiler {
* Assigns thread_id and thread name context to a profiled event.
*/
private _assignThreadIdContext(event: Event): void {
- if (!event?.['contexts']?.['profile']) {
+ if (!event?.contexts?.profile) {
return;
}
@@ -410,10 +410,10 @@ class ContinuousProfiler {
// @ts-expect-error the trace fallback value is wrong, though it should never happen
// and in case it does, we dont want to override whatever was passed initially.
- event.contexts['trace'] = {
- ...(event.contexts?.['trace'] ?? {}),
+ event.contexts.trace = {
+ ...(event.contexts?.trace ?? {}),
data: {
- ...(event.contexts?.['trace']?.['data'] ?? {}),
+ ...(event.contexts?.trace?.data ?? {}),
['thread.id']: PROFILER_THREAD_ID_STRING,
['thread.name']: PROFILER_THREAD_NAME,
},
diff --git a/packages/profiling-node/src/spanProfileUtils.ts b/packages/profiling-node/src/spanProfileUtils.ts
index 1ee050ce22e5..342075bde890 100644
--- a/packages/profiling-node/src/spanProfileUtils.ts
+++ b/packages/profiling-node/src/spanProfileUtils.ts
@@ -1,7 +1,7 @@
+import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler';
import type { CustomSamplingContext, Span } from '@sentry/core';
import { logger, spanIsSampled, spanToJSON, uuid4 } from '@sentry/core';
import type { NodeClient } from '@sentry/node';
-import { CpuProfilerBindings } from './cpu_profiler';
import { DEBUG_BUILD } from './debug-build';
import type { RawThreadCpuProfile } from './types';
import { isValidSampleRate } from './utils';
diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts
index baf3370d6ce8..e6ab3803ebdd 100644
--- a/packages/profiling-node/src/utils.ts
+++ b/packages/profiling-node/src/utils.ts
@@ -1,7 +1,6 @@
import * as os from 'os';
import type {
Client,
- Context,
ContinuousThreadCpuProfile,
DebugImage,
DsnComponents,
@@ -14,6 +13,7 @@ import type {
ProfileChunkItem,
SdkInfo,
ThreadCpuProfile,
+ TransactionEvent,
} from '@sentry/core';
import {
createEnvelope,
@@ -99,7 +99,7 @@ export function createProfilingEvent(client: Client, profile: RawThreadCpuProfil
event_id: event.event_id ?? '',
transaction: event.transaction ?? '',
start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),
- trace_id: event.contexts?.['trace']?.['trace_id'] ?? '',
+ trace_id: event.contexts?.trace?.trace_id ?? '',
profile_id: profile.profile_id,
});
}
@@ -352,7 +352,7 @@ export function addProfilesToEnvelope(envelope: Envelope, profiles: Profile[]):
* @returns {Event[]}
*/
export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[] {
- const events: Event[] = [];
+ const events: TransactionEvent[] = [];
forEachEnvelopeItem(envelope, (item, type) => {
if (type !== 'transaction') {
@@ -361,18 +361,17 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[
// First item is the type, so we can skip it, everything else is an event
for (let j = 1; j < item.length; j++) {
- const event = item[j];
+ const event = item[j] as TransactionEvent;
if (!event) {
// Shouldn't happen, but lets be safe
continue;
}
- // @ts-expect-error profile_id is not part of the metadata type
- const profile_id = (event.contexts as Context)?.['profile']?.['profile_id'];
+ const profile_id = event.contexts?.profile?.profile_id;
if (event && profile_id) {
- events.push(item[j] as Event);
+ events.push(event);
}
}
});
diff --git a/packages/profiling-node/test/bindings.test.ts b/packages/profiling-node/test/bindings.test.ts
deleted file mode 100644
index 27361a87d941..000000000000
--- a/packages/profiling-node/test/bindings.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { platform } from 'os';
-// Contains unit tests for some of the C++ bindings. These functions
-// are exported on the private bindings object, so we can test them and
-// they should not be used outside of this file.
-import { PrivateCpuProfilerBindings } from '../src/cpu_profiler';
-
-const cases = [
- ['/Users/jonas/code/node_modules/@scope/package/file.js', '@scope.package:file'],
- ['/Users/jonas/code/node_modules/package/dir/file.js', 'package.dir:file'],
- ['/Users/jonas/code/node_modules/package/file.js', 'package:file'],
- ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'],
-
- // Preserves non .js extensions
- ['/Users/jonas/code/src/file.ts', 'Users.jonas.code.src:file.ts'],
- // No extension
- ['/Users/jonas/code/src/file', 'Users.jonas.code.src:file'],
- // Edge cases that shouldn't happen in practice, but try and handle them so we don't crash
- ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'],
- ['', ''],
-];
-
-describe('GetFrameModule', () => {
- it.each(
- platform() === 'win32'
- ? cases.map(([abs_path, expected]) => [abs_path ? `C:${abs_path.replace(/\//g, '\\')}` : '', expected])
- : cases,
- )('%s => %s', (abs_path: string, expected: string) => {
- expect(PrivateCpuProfilerBindings.getFrameModule(abs_path)).toBe(expected);
- });
-});
diff --git a/packages/profiling-node/test/cpu_profiler.test.ts b/packages/profiling-node/test/cpu_profiler.test.ts
deleted file mode 100644
index 9240ad636129..000000000000
--- a/packages/profiling-node/test/cpu_profiler.test.ts
+++ /dev/null
@@ -1,364 +0,0 @@
-import type { ContinuousThreadCpuProfile, ThreadCpuProfile } from '@sentry/core';
-import { CpuProfilerBindings, PrivateCpuProfilerBindings } from '../src/cpu_profiler';
-import type { RawThreadCpuProfile } from '../src/types';
-import { ProfileFormat } from '../src/types';
-
-// Required because we test a hypothetical long profile
-// and we cannot use advance timers as the c++ relies on
-// actual event loop ticks that we cannot advance from jest.
-jest.setTimeout(60_000);
-
-function fail(message: string): never {
- throw new Error(message);
-}
-
-const fibonacci = (n: number): number => {
- if (n <= 1) {
- return n;
- }
- return fibonacci(n - 1) + fibonacci(n - 2);
-};
-
-const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
-const profiled = async (name: string, fn: () => void) => {
- CpuProfilerBindings.startProfiling(name);
- await fn();
- return CpuProfilerBindings.stopProfiling(name, ProfileFormat.THREAD);
-};
-
-const assertValidSamplesAndStacks = (
- stacks: ThreadCpuProfile['stacks'],
- samples: ThreadCpuProfile['samples'] | ContinuousThreadCpuProfile['samples'],
-) => {
- expect(stacks.length).toBeGreaterThan(0);
- expect(samples.length).toBeGreaterThan(0);
- expect(stacks.length <= samples.length).toBe(true);
-
- for (const sample of samples) {
- if (sample.stack_id === undefined) {
- throw new Error(`Sample ${JSON.stringify(sample)} has not stack id associated`);
- }
- if (!stacks[sample.stack_id]) {
- throw new Error(`Failed to find stack for sample: ${JSON.stringify(sample)}`);
- }
- expect(stacks[sample.stack_id]).not.toBe(undefined);
- }
-
- for (const stack of stacks) {
- expect(stack).not.toBe(undefined);
- }
-};
-
-const isValidMeasurementValue = (v: any) => {
- if (isNaN(v)) return false;
- return typeof v === 'number' && v > 0;
-};
-
-const assertValidMeasurements = (measurement: RawThreadCpuProfile['measurements']['memory_footprint'] | undefined) => {
- if (!measurement) {
- throw new Error('Measurement is undefined');
- }
- expect(measurement).not.toBe(undefined);
- expect(typeof measurement.unit).toBe('string');
- expect(measurement.unit.length).toBeGreaterThan(0);
-
- for (let i = 0; i < measurement.values.length; i++) {
- expect(measurement?.values?.[i]?.elapsed_since_start_ns).toBeGreaterThan(0);
- expect(measurement?.values?.[i]?.value).toBeGreaterThan(0);
- }
-};
-
-describe('Private bindings', () => {
- it('does not crash if collect resources is false', async () => {
- PrivateCpuProfilerBindings.startProfiling!('profiled-program');
- await wait(100);
- expect(() => {
- const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false);
- if (!profile) throw new Error('No profile');
- }).not.toThrow();
- });
-
- it('throws if invalid format is supplied', async () => {
- PrivateCpuProfilerBindings.startProfiling!('profiled-program');
- await wait(100);
- expect(() => {
- const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', Number.MAX_SAFE_INTEGER, 0, false);
- if (!profile) throw new Error('No profile');
- }).toThrow('StopProfiling expects a valid format type as second argument.');
- });
-
- it('collects resources', async () => {
- PrivateCpuProfilerBindings.startProfiling!('profiled-program');
- await wait(100);
-
- const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, true);
- if (!profile) throw new Error('No profile');
-
- expect(profile.resources.length).toBeGreaterThan(0);
-
- expect(new Set(profile.resources).size).toBe(profile.resources.length);
-
- for (const resource of profile.resources) {
- expect(typeof resource).toBe('string');
- expect(resource).not.toBe(undefined);
- }
- });
-
- it('does not collect resources', async () => {
- PrivateCpuProfilerBindings.startProfiling!('profiled-program');
- await wait(100);
-
- const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false);
- if (!profile) throw new Error('No profile');
-
- expect(profile.resources.length).toBe(0);
- });
-});
-
-describe('Profiler bindings', () => {
- it('exports profiler binding methods', () => {
- expect(typeof CpuProfilerBindings['startProfiling']).toBe('function');
- expect(typeof CpuProfilerBindings['stopProfiling']).toBe('function');
- });
-
- it('profiles a program', async () => {
- const profile = await profiled('profiled-program', async () => {
- await wait(100);
- });
-
- if (!profile) fail('Profile is null');
-
- assertValidSamplesAndStacks(profile.stacks, profile.samples);
- });
-
- it('adds thread_id info', async () => {
- const profile = await profiled('profiled-program', async () => {
- await wait(100);
- });
-
- if (!profile) fail('Profile is null');
- const samples = profile.samples;
-
- if (!samples.length) {
- throw new Error('No samples');
- }
- for (const sample of samples) {
- expect(sample.thread_id).toBe('0');
- }
- });
-
- it('caps stack depth at 128', async () => {
- const recurseToDepth = async (depth: number): Promise => {
- if (depth === 0) {
- // Wait a bit to make sure stack gets sampled here
- await wait(1000);
- return 0;
- }
- const v = await recurseToDepth(depth - 1);
- return v;
- };
-
- const profile = await profiled('profiled-program', async () => {
- await recurseToDepth(256);
- });
-
- if (!profile) fail('Profile is null');
-
- for (const stack of profile.stacks) {
- expect(stack.length).toBeLessThanOrEqual(128);
- }
- });
-
- it('does not record two profiles when titles match', () => {
- CpuProfilerBindings.startProfiling('same-title');
- CpuProfilerBindings.startProfiling('same-title');
-
- const first = CpuProfilerBindings.stopProfiling('same-title', 0);
- const second = CpuProfilerBindings.stopProfiling('same-title', 0);
-
- expect(first).not.toBe(null);
- expect(second).toBe(null);
- });
-
- it('multiple calls with same title', () => {
- CpuProfilerBindings.startProfiling('same-title');
- expect(() => {
- CpuProfilerBindings.stopProfiling('same-title', 0);
- CpuProfilerBindings.stopProfiling('same-title', 0);
- }).not.toThrow();
- });
-
- it('does not crash if stopTransaction is called before startTransaction', () => {
- expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null);
- });
-
- it('does crash if name is invalid', () => {
- expect(() => CpuProfilerBindings.stopProfiling('', 0)).toThrow();
- // @ts-expect-error test invalid input
- expect(() => CpuProfilerBindings.stopProfiling(undefined)).toThrow();
- // @ts-expect-error test invalid input
- expect(() => CpuProfilerBindings.stopProfiling(null)).toThrow();
- // @ts-expect-error test invalid input
- expect(() => CpuProfilerBindings.stopProfiling({})).toThrow();
- });
-
- it('does not throw if stopTransaction is called before startTransaction', () => {
- expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null);
- expect(() => CpuProfilerBindings.stopProfiling('does not exist', 0)).not.toThrow();
- });
-
- it('compiles with eager logging by default', async () => {
- const profile = await profiled('profiled-program', async () => {
- await wait(100);
- });
-
- if (!profile) fail('Profile is null');
- expect(profile.profiler_logging_mode).toBe('eager');
- });
-
- it('chunk format type', async () => {
- const fn = async () => {
- await wait(1000);
- fibonacci(36);
- await wait(1000);
- };
-
- CpuProfilerBindings.startProfiling('non nullable stack');
- await fn();
- const profile = CpuProfilerBindings.stopProfiling('non nullable stack', ProfileFormat.CHUNK);
-
- if (!profile) fail('Profile is null');
-
- for (const sample of profile.samples) {
- if (!('timestamp' in sample)) {
- throw new Error(`Sample ${JSON.stringify(sample)} has no timestamp`);
- }
- expect(sample.timestamp).toBeDefined();
- // No older than a minute and not in the future. Timestamp is in seconds so convert to ms
- // as the constructor expects ms.
- expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeGreaterThan(Date.now() - 60 * 1e3);
- expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeLessThanOrEqual(Date.now());
- }
- });
-
- it('stacks are not null', async () => {
- const profile = await profiled('non nullable stack', async () => {
- await wait(1000);
- fibonacci(36);
- await wait(1000);
- });
-
- if (!profile) fail('Profile is null');
- assertValidSamplesAndStacks(profile.stacks, profile.samples);
- });
-
- it('samples at ~99hz', async () => {
- CpuProfilerBindings.startProfiling('profile');
- await wait(100);
- const profile = CpuProfilerBindings.stopProfiling('profile', 0);
-
- if (!profile) fail('Profile is null');
-
- // Exception for macos and windows - we seem to get way less samples there, but I'm not sure if that's due to poor
- // performance of the actions runner, machine or something else. This needs more investigation to determine
- // the cause of low sample count. https://github.com/actions/runner-images/issues/1336 seems relevant.
- if (process.platform === 'darwin' || process.platform === 'win32') {
- if (profile.samples.length < 2) {
- fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 2`);
- }
- } else {
- if (profile.samples.length < 6) {
- fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 6`);
- }
- }
- if (profile.samples.length > 15) {
- fail(`Too many samples on ${process.platform}, got ${profile.samples.length}`);
- }
- });
-
- it('collects memory footprint', async () => {
- CpuProfilerBindings.startProfiling('profile');
- await wait(1000);
- const profile = CpuProfilerBindings.stopProfiling('profile', 0);
-
- const heap_usage = profile?.measurements['memory_footprint'];
- if (!heap_usage) {
- throw new Error('memory_footprint is null');
- }
- expect(heap_usage.values.length).toBeGreaterThan(6);
- expect(heap_usage.values.length).toBeLessThanOrEqual(11);
- expect(heap_usage.unit).toBe('byte');
- expect(heap_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true);
- assertValidMeasurements(profile.measurements['memory_footprint']);
- });
-
- it('collects cpu usage', async () => {
- CpuProfilerBindings.startProfiling('profile');
- await wait(1000);
- const profile = CpuProfilerBindings.stopProfiling('profile', 0);
-
- const cpu_usage = profile?.measurements['cpu_usage'];
- if (!cpu_usage) {
- throw new Error('cpu_usage is null');
- }
- expect(cpu_usage.values.length).toBeGreaterThan(6);
- expect(cpu_usage.values.length).toBeLessThanOrEqual(11);
- expect(cpu_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true);
- expect(cpu_usage.unit).toBe('percent');
- assertValidMeasurements(profile.measurements['cpu_usage']);
- });
-
- it('does not overflow measurement buffer if profile runs longer than 30s', async () => {
- CpuProfilerBindings.startProfiling('profile');
- await wait(35000);
- const profile = CpuProfilerBindings.stopProfiling('profile', 0);
- expect(profile).not.toBe(null);
- expect(profile?.measurements?.['cpu_usage']?.values.length).toBeLessThanOrEqual(300);
- expect(profile?.measurements?.['memory_footprint']?.values.length).toBeLessThanOrEqual(300);
- });
-
- // eslint-disable-next-line @sentry-internal/sdk/no-skipped-tests
- it.skip('includes deopt reason', async () => {
- // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#52-the-object-being-iterated-is-not-a-simple-enumerable
- function iterateOverLargeHashTable() {
- const table: Record = {};
- for (let i = 0; i < 1e5; i++) {
- table[i] = i;
- }
- // eslint-disable-next-line
- for (const _ in table) {
- }
- }
-
- const profile = await profiled('profiled-program', async () => {
- iterateOverLargeHashTable();
- });
-
- // @ts-expect-error deopt reasons are disabled for now as we need to figure out the backend support
- const hasDeoptimizedFrame = profile.frames.some(f => f.deopt_reasons?.length > 0);
- expect(hasDeoptimizedFrame).toBe(true);
- });
-
- it('does not crash if the native startProfiling function is not available', async () => {
- const original = PrivateCpuProfilerBindings.startProfiling;
- PrivateCpuProfilerBindings.startProfiling = undefined;
-
- expect(() => {
- CpuProfilerBindings.startProfiling('profiled-program');
- }).not.toThrow();
-
- PrivateCpuProfilerBindings.startProfiling = original;
- });
-
- it('does not crash if the native stopProfiling function is not available', async () => {
- // eslint-disable-next-line @typescript-eslint/unbound-method
- const original = PrivateCpuProfilerBindings.stopProfiling;
- PrivateCpuProfilerBindings.stopProfiling = undefined;
-
- expect(() => {
- CpuProfilerBindings.stopProfiling('profiled-program', 0);
- }).not.toThrow();
-
- PrivateCpuProfilerBindings.stopProfiling = original;
- });
-});
diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts
index 9974eb6ebc64..758307d3fa34 100644
--- a/packages/profiling-node/test/spanProfileUtils.test.ts
+++ b/packages/profiling-node/test/spanProfileUtils.test.ts
@@ -1,11 +1,11 @@
import * as Sentry from '@sentry/node';
+import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler';
import { getMainCarrier } from '@sentry/core';
import { GLOBAL_OBJ, createEnvelope, logger } from '@sentry/core';
import type { ProfilingIntegration } from '@sentry/core';
import type { ProfileChunk, Transport } from '@sentry/core';
import type { NodeClientOptions } from '@sentry/node/build/types/types';
-import { CpuProfilerBindings } from '../src/cpu_profiler';
import { _nodeProfilingIntegration } from '../src/integration';
function makeClientWithHooks(): [Sentry.NodeClient, Transport] {
diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts
index 85f852052bac..13cbc6957d5c 100644
--- a/scripts/ci-unit-tests.ts
+++ b/scripts/ci-unit-tests.ts
@@ -6,7 +6,7 @@ const UNIT_TEST_ENV = process.env.UNIT_TEST_ENV as 'node' | 'browser' | undefine
const RUN_AFFECTED = process.argv.includes('--affected');
// These packages are tested separately in CI, so no need to run them here
-const DEFAULT_SKIP_PACKAGES = ['@sentry/profiling-node', '@sentry/bun', '@sentry/deno'];
+const DEFAULT_SKIP_PACKAGES = ['@sentry/bun', '@sentry/deno'];
// All other packages are run for multiple node versions
const BROWSER_TEST_PACKAGES = [
@@ -17,7 +17,6 @@ const BROWSER_TEST_PACKAGES = [
'@sentry/angular',
'@sentry/solid',
'@sentry/svelte',
- '@sentry/profiling-node',
'@sentry-internal/browser-utils',
'@sentry-internal/replay',
'@sentry-internal/replay-canvas',
diff --git a/yarn.lock b/yarn.lock
index 505d52f463d0..f47e402fd49f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6535,6 +6535,14 @@
"@angular-devkit/schematics" "14.2.13"
jsonc-parser "3.1.0"
+"@sentry-internal/node-cpu-profiler@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.0.0.tgz#76a0d363055876b91663769daee2d4b12321ba3b"
+ integrity sha512-0pZId+HY/AbNs1+CoCi8wogBWTrRv+DYeOgbevhekzMr5HYsA6PRY21NtHBXMbu0WcswFwaveDKR+sOW1EDHAA==
+ dependencies:
+ detect-libc "^2.0.2"
+ node-abi "^3.61.0"
+
"@sentry-internal/rrdom@2.31.0":
version "2.31.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.31.0.tgz#548773964167ec104d3cbb9d7a4b25103c091e06"
@@ -8113,11 +8121,6 @@
dependencies:
"@types/unist" "^2"
-"@types/node-abi@^3.0.3":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c"
- integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw==
-
"@types/node-cron@^3.0.11":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344"
@@ -11915,15 +11918,6 @@ cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
-clang-format@^1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.8.0.tgz#7779df1c5ce1bc8aac1b0b02b4e479191ef21d46"
- integrity sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw==
- dependencies:
- async "^3.2.3"
- glob "^7.0.0"
- resolve "^1.1.6"
-
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -12757,13 +12751,6 @@ cronstrue@^2.50.0:
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.50.0.tgz#eabba0f915f186765258b707b7a3950c663b5573"
integrity sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg==
-cross-env@^7.0.3:
- version "7.0.3"
- resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
- integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
- dependencies:
- cross-spawn "^7.0.1"
-
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -12775,7 +12762,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -22334,7 +22321,7 @@ node-gyp-build@^4.2.2, node-gyp-build@^4.3.0:
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055"
integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==
-node-gyp@^9.0.0, node-gyp@^9.4.1:
+node-gyp@^9.0.0:
version "9.4.1"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185"
integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==
@@ -27764,7 +27751,6 @@ stylus@0.59.0, stylus@^0.59.0:
sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills:
version "3.36.0"
- uid fd682f6129e507c00bb4e6319cc5d6b767e36061
resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061"
dependencies:
"@jridgewell/gen-mapping" "^0.3.2"
From 0963af0fad40edc368888924d11c857cd3dbf111 Mon Sep 17 00:00:00 2001
From: Luca Forstner
Date: Tue, 28 Jan 2025 22:52:09 +0100
Subject: [PATCH 45/59] fix(node): Add compatibility layer for Prisma v5
(#15169)
---
.../node-integration-tests/package.json | 6 +-
.../docker-compose.yml | 2 +-
.../suites/tracing/prisma-orm-v5/package.json | 22 ++++++
.../prisma/migrations/migration_lock.toml | 0
.../migrations/sentry_test/migration.sql | 0
.../prisma/schema.prisma | 1 +
.../suites/tracing/prisma-orm-v5/scenario.js | 52 ++++++++++++++
.../suites/tracing/prisma-orm-v5/test.ts | 69 +++++++++++++++++++
.../suites/tracing/prisma-orm-v5/yarn.lock | 58 ++++++++++++++++
.../tracing/prisma-orm-v6/docker-compose.yml | 13 ++++
.../package.json | 0
.../prisma/migrations/migration_lock.toml | 3 +
.../migrations/sentry_test/migration.sql | 12 ++++
.../prisma-orm-v6/prisma/schema.prisma | 15 ++++
.../{prisma-orm => prisma-orm-v6}/scenario.js | 0
.../{prisma-orm => prisma-orm-v6}/test.ts | 0
.../{prisma-orm => prisma-orm-v6}/yarn.lock | 0
.../node/src/integrations/tracing/prisma.ts | 59 ++++++++++++++--
.../prisma/vendor/v5-tracing-helper.ts | 41 +++++++++++
.../prisma/vendor/v6-tracing-helper.ts | 38 ++++++++++
yarn.lock | 5 --
21 files changed, 380 insertions(+), 16 deletions(-)
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/docker-compose.yml (81%)
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/migrations/migration_lock.toml (100%)
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/migrations/sentry_test/migration.sql (100%)
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/schema.prisma (90%)
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/package.json (100%)
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql
create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/scenario.js (100%)
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/test.ts (100%)
rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/yarn.lock (100%)
create mode 100644 packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts
create mode 100644 packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts
diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json
index 571397d12c8c..7b7fe3482200 100644
--- a/dev-packages/node-integration-tests/package.json
+++ b/dev-packages/node-integration-tests/package.json
@@ -16,11 +16,12 @@
"build:types": "tsc -p tsconfig.types.json",
"clean": "rimraf -g **/node_modules && run-p clean:script",
"clean:script": "node scripts/clean.js",
- "prisma:init": "cd suites/tracing/prisma-orm && yarn && yarn setup",
+ "prisma-v5:init": "cd suites/tracing/prisma-orm-v5 && yarn && yarn setup",
+ "prisma-v6:init": "cd suites/tracing/prisma-orm-v6 && yarn && yarn setup",
"lint": "eslint . --format stylish",
"fix": "eslint . --format stylish --fix",
"type-check": "tsc",
- "pretest": "run-s --silent prisma:init",
+ "pretest": "run-s --silent prisma-v5:init prisma-v6:init",
"test": "jest --config ./jest.config.js",
"test:watch": "yarn test --watch"
},
@@ -30,7 +31,6 @@
"@nestjs/common": "10.4.6",
"@nestjs/core": "10.4.6",
"@nestjs/platform-express": "10.4.6",
- "@prisma/client": "6.2.1",
"@sentry/aws-serverless": "9.0.0-alpha.0",
"@sentry/core": "9.0.0-alpha.0",
"@sentry/node": "9.0.0-alpha.0",
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml
similarity index 81%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml
index 45caa4bb3179..37d45547b537 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml
@@ -4,7 +4,7 @@ services:
db:
image: postgres:13
restart: always
- container_name: integration-tests-prisma
+ container_name: integration-tests-prisma-v5
ports:
- '5433:5432'
environment:
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json
new file mode 100644
index 000000000000..b8721038c83b
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "sentry-prisma-test",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "engines": {
+ "node": ">=18"
+ },
+ "scripts": {
+ "db-up": "docker compose up -d",
+ "generate": "prisma generate",
+ "migrate": "prisma migrate dev -n sentry-test",
+ "setup": "run-s --silent db-up generate migrate"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@prisma/client": "5.22.0",
+ "prisma": "5.22.0"
+ }
+}
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma
similarity index 90%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma
index 4363c97738ee..52682f1b6cf5 100644
--- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma
@@ -5,6 +5,7 @@ datasource db {
generator client {
provider = "prisma-client-js"
+ previewFeatures = ["tracing"]
}
model User {
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js
new file mode 100644
index 000000000000..767a6f27bdaa
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js
@@ -0,0 +1,52 @@
+const Sentry = require('@sentry/node');
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ tracesSampleRate: 1.0,
+ transport: loggingTransport,
+ integrations: [Sentry.prismaIntegration()],
+});
+
+const { randomBytes } = require('crypto');
+const { PrismaClient } = require('@prisma/client');
+
+// Stop the process from exiting before the transaction is sent
+setInterval(() => {}, 1000);
+
+async function run() {
+ const client = new PrismaClient();
+
+ await Sentry.startSpanManual(
+ {
+ name: 'Test Transaction',
+ op: 'transaction',
+ },
+ async span => {
+ await client.user.create({
+ data: {
+ name: 'Tilda',
+ email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`,
+ },
+ });
+
+ await client.user.findMany();
+
+ await client.user.deleteMany({
+ where: {
+ email: {
+ contains: 'sentry.io',
+ },
+ },
+ });
+
+ setTimeout(async () => {
+ span.end();
+ await client.$disconnect();
+ }, 500);
+ },
+ );
+}
+
+run();
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts
new file mode 100644
index 000000000000..0ece02f2f1cb
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts
@@ -0,0 +1,69 @@
+import { createRunner } from '../../../utils/runner';
+
+describe('Prisma ORM Tests', () => {
+ test('CJS - should instrument PostgreSQL queries from Prisma ORM', done => {
+ createRunner(__dirname, 'scenario.js')
+ .expect({
+ transaction: transaction => {
+ expect(transaction.transaction).toBe('Test Transaction');
+ const spans = transaction.spans || [];
+ expect(spans.length).toBeGreaterThanOrEqual(5);
+
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ data: {
+ method: 'create',
+ model: 'User',
+ name: 'User.create',
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ description: 'prisma:client:operation',
+ status: 'ok',
+ }),
+ );
+
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ data: {
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ description: 'prisma:client:serialize',
+ status: 'ok',
+ }),
+ );
+
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ data: {
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ description: 'prisma:client:connect',
+ status: 'ok',
+ }),
+ );
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ data: {
+ method: 'findMany',
+ model: 'User',
+ name: 'User.findMany',
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ description: 'prisma:client:operation',
+ status: 'ok',
+ }),
+ );
+ expect(spans).toContainEqual(
+ expect.objectContaining({
+ data: {
+ 'sentry.origin': 'auto.db.otel.prisma',
+ },
+ description: 'prisma:client:serialize',
+ status: 'ok',
+ }),
+ );
+ },
+ })
+ .start(done);
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock
new file mode 100644
index 000000000000..860aa032d6cc
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock
@@ -0,0 +1,58 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@prisma/client@5.22.0":
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802"
+ integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==
+
+"@prisma/debug@5.22.0":
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412"
+ integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==
+
+"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2":
+ version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4"
+ integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==
+
+"@prisma/engines@5.22.0":
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84"
+ integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==
+ dependencies:
+ "@prisma/debug" "5.22.0"
+ "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
+ "@prisma/fetch-engine" "5.22.0"
+ "@prisma/get-platform" "5.22.0"
+
+"@prisma/fetch-engine@5.22.0":
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e"
+ integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==
+ dependencies:
+ "@prisma/debug" "5.22.0"
+ "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2"
+ "@prisma/get-platform" "5.22.0"
+
+"@prisma/get-platform@5.22.0":
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd"
+ integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==
+ dependencies:
+ "@prisma/debug" "5.22.0"
+
+fsevents@2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+prisma@5.22.0:
+ version "5.22.0"
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197"
+ integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==
+ dependencies:
+ "@prisma/engines" "5.22.0"
+ optionalDependencies:
+ fsevents "2.3.3"
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml
new file mode 100644
index 000000000000..ddab7cb9c563
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3.9'
+
+services:
+ db:
+ image: postgres:13
+ restart: always
+ container_name: integration-tests-prisma-v6
+ ports:
+ - '5434:5432'
+ environment:
+ POSTGRES_USER: prisma
+ POSTGRES_PASSWORD: prisma
+ POSTGRES_DB: tests
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml
new file mode 100644
index 000000000000..fbffa92c2bb7
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql
new file mode 100644
index 000000000000..8619aaceb2b0
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql
@@ -0,0 +1,12 @@
+-- CreateTable
+CREATE TABLE "User" (
+ "id" SERIAL NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "email" TEXT NOT NULL,
+ "name" TEXT,
+
+ CONSTRAINT "User_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma
new file mode 100644
index 000000000000..71a4923afb8c
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma
@@ -0,0 +1,15 @@
+datasource db {
+ url = "postgresql://prisma:prisma@localhost:5434/tests"
+ provider = "postgresql"
+}
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now())
+ email String @unique
+ name String?
+}
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts
diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock
similarity index 100%
rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock
rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock
diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts
index ec9bc149410d..58516671c9a3 100644
--- a/packages/node/src/integrations/tracing/prisma.ts
+++ b/packages/node/src/integrations/tracing/prisma.ts
@@ -1,11 +1,61 @@
import type { Instrumentation } from '@opentelemetry/instrumentation';
// When importing CJS modules into an ESM module, we cannot import the named exports directly.
import * as prismaInstrumentation from '@prisma/instrumentation';
-import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
+import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, consoleSandbox, defineIntegration, spanToJSON } from '@sentry/core';
import { generateInstrumentOnce } from '../../otel/instrument';
+import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper';
+import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper';
const INTEGRATION_NAME = 'Prisma';
+const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
+ // @ts-expect-error We need to do the following for interop reasons
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
+
+type CompatibilityLayerTraceHelper = PrismaV5TracingHelper & PrismaV6TracingHelper;
+
+function isPrismaV6TracingHelper(helper: unknown): helper is PrismaV6TracingHelper {
+ return !!helper && typeof helper === 'object' && 'dispatchEngineSpans' in helper;
+}
+
+class SentryPrismaInteropInstrumentation extends EsmInteropPrismaInstrumentation {
+ public constructor() {
+ super();
+ }
+
+ public enable(): void {
+ super.enable();
+
+ // The PrismaIntegration (super class) defines a global variable `global["PRISMA_INSTRUMENTATION"]` when `enable()` is called. This global variable holds a "TracingHelper" which Prisma uses internally to create tracing data. It's their way of not depending on OTEL with their main package. The sucky thing is, prisma broke the interface of the tracing helper with the v6 major update. This means that if you use Prisma 5 with the v6 instrumentation (or vice versa) Prisma just blows up, because tries to call methods on the helper that no longer exist.
+ // Because we actually want to use the v6 instrumentation and not blow up in Prisma 5 user's faces, what we're doing here is backfilling the v5 method (`createEngineSpan`) with a noop so that no longer crashes when it attempts to call that function.
+ // We still won't fully emit all the spans, but this could potentially be implemented in the future.
+ const prismaInstrumentationObject = (globalThis as Record).PRISMA_INSTRUMENTATION;
+ const prismaTracingHelper =
+ prismaInstrumentationObject &&
+ typeof prismaInstrumentationObject === 'object' &&
+ 'helper' in prismaInstrumentationObject
+ ? prismaInstrumentationObject.helper
+ : undefined;
+
+ let emittedWarning = false;
+
+ if (isPrismaV6TracingHelper(prismaTracingHelper)) {
+ (prismaTracingHelper as CompatibilityLayerTraceHelper).createEngineSpan = () => {
+ consoleSandbox(() => {
+ if (!emittedWarning) {
+ emittedWarning = true;
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[Sentry] The Sentry SDK supports tracing with Prisma version 5 only with limited capabilities. For full tracing capabilities pass `prismaInstrumentation` for version 5 to the Sentry `prismaIntegration`. Read more: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/',
+ );
+ }
+ });
+ };
+ }
+ }
+}
+
export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>(
INTEGRATION_NAME,
options => {
@@ -14,12 +64,7 @@ export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?:
return options.prismaInstrumentation;
}
- const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation =
- // @ts-expect-error We need to do the following for interop reasons
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation;
-
- return new EsmInteropPrismaInstrumentation({});
+ return new SentryPrismaInteropInstrumentation();
},
);
diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts
new file mode 100644
index 000000000000..8823a8ca7728
--- /dev/null
+++ b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts
@@ -0,0 +1,41 @@
+// Vendored from https://github.com/prisma/prisma/blob/718358aa37975c18e5ea62f5b659fb47630b7609/packages/internals/src/tracing/types.ts#L1
+
+import type { Context, Span, SpanOptions } from '@opentelemetry/api';
+
+type V5SpanCallback = (span?: Span, context?: Context) => R;
+
+type V5ExtendedSpanOptions = SpanOptions & {
+ name: string;
+ internal?: boolean;
+ middleware?: boolean;
+ active?: boolean;
+ context?: Context;
+};
+
+type EngineSpanEvent = {
+ span: boolean;
+ spans: V5EngineSpan[];
+};
+
+type V5EngineSpanKind = 'client' | 'internal';
+
+type V5EngineSpan = {
+ span: boolean;
+ name: string;
+ trace_id: string;
+ span_id: string;
+ parent_span_id: string;
+ start_time: [number, number];
+ end_time: [number, number];
+ attributes?: Record;
+ links?: { trace_id: string; span_id: string }[];
+ kind: V5EngineSpanKind;
+};
+
+export interface PrismaV5TracingHelper {
+ isEnabled(): boolean;
+ getTraceParent(context?: Context): string;
+ createEngineSpan(engineSpanEvent: EngineSpanEvent): void;
+ getActiveContext(): Context | undefined;
+ runInChildSpan(nameOrOptions: string | V5ExtendedSpanOptions, callback: V5SpanCallback): R;
+}
diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts
new file mode 100644
index 000000000000..2ad1482a2e1a
--- /dev/null
+++ b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts
@@ -0,0 +1,38 @@
+// https://github.com/prisma/prisma/blob/d45607dfa10c4ef08cb8f79f18fa84ef33910150/packages/internals/src/tracing/types.ts#L1
+
+import type { Context, Span, SpanOptions } from '@opentelemetry/api';
+
+type V6SpanCallback = (span?: Span, context?: Context) => R;
+
+type V6ExtendedSpanOptions = SpanOptions & {
+ name: string;
+ internal?: boolean;
+ middleware?: boolean;
+ active?: boolean;
+ context?: Context;
+};
+
+type V6EngineSpanId = string;
+
+type V6HrTime = [number, number];
+
+type EngineSpanKind = 'client' | 'internal';
+
+type PrismaV6EngineSpan = {
+ id: V6EngineSpanId;
+ parentId: string | null;
+ name: string;
+ startTime: V6HrTime;
+ endTime: V6HrTime;
+ kind: EngineSpanKind;
+ attributes?: Record;
+ links?: V6EngineSpanId[];
+};
+
+export interface PrismaV6TracingHelper {
+ isEnabled(): boolean;
+ getTraceParent(context?: Context): string;
+ dispatchEngineSpans(spans: PrismaV6EngineSpan[]): void;
+ getActiveContext(): Context | undefined;
+ runInChildSpan(nameOrOptions: string | V6ExtendedSpanOptions, callback: V6SpanCallback