From 593d5b3389cf326ca41a6f1242d2a7ffb683e680 Mon Sep 17 00:00:00 2001
From: Rola Abuhasna
Date: Wed, 21 May 2025 10:05:10 +0200
Subject: [PATCH 01/26] test(react-router): Add and fix tests for pre rendered
routes (#16346)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It was confirmed that static pre-renders don't pick up Sentry trace meta
tags — this just adds a quick test.
More context:
https://linear.app/getsentry/issue/JS-392/rr7-evaluate-static-pre-rendering
P.S. Not a priority, but for some reason, I had to add a trailing / to
fetch the correct transactions and pass the tests. I’ll look into why
this is happening for transaction route names and whether this behavior
is expected in a later PR.
---------
Co-authored-by: Charly Gomez
---
.../react-router-7-framework/react-router.config.ts | 3 +--
.../tests/performance/pageload.client.test.ts | 9 ++++-----
.../tests/performance/trace-propagation.test.ts | 7 +++++++
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/react-router.config.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/react-router.config.ts
index 73b647e4eea6..bb1f96469dd2 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/react-router.config.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/react-router.config.ts
@@ -2,6 +2,5 @@ import type { Config } from '@react-router/dev/config';
export default {
ssr: true,
- // todo: check why this messes up client tracing in tests
- // prerender: ['/performance/static'],
+ prerender: ['/performance/static'],
} satisfies Config;
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts
index e283ea522c4a..b18ae44e0e71 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/pageload.client.test.ts
@@ -5,7 +5,7 @@ import { APP_NAME } from '../constants';
test.describe('client - pageload performance', () => {
test('should send pageload transaction', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/performance';
+ return transactionEvent.transaction === '/performance/';
});
await page.goto(`/performance`);
@@ -29,7 +29,7 @@ test.describe('client - pageload performance', () => {
spans: expect.any(Array),
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
- transaction: '/performance',
+ transaction: '/performance/',
type: 'transaction',
transaction_info: { source: 'url' },
measurements: expect.any(Object),
@@ -103,10 +103,9 @@ test.describe('client - pageload performance', () => {
});
});
- // todo: this page is currently not prerendered (see react-router.config.ts)
test('should send pageload transaction for prerendered pages', async ({ page }) => {
const txPromise = waitForTransaction(APP_NAME, async transactionEvent => {
- return transactionEvent.transaction === '/performance/static';
+ return transactionEvent.transaction === '/performance/static/';
});
await page.goto(`/performance/static`);
@@ -114,7 +113,7 @@ test.describe('client - pageload performance', () => {
const transaction = await txPromise;
expect(transaction).toMatchObject({
- transaction: '/performance/static',
+ transaction: '/performance/static/',
contexts: {
trace: {
span_id: expect.any(String),
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
index 6a9623171236..7562297b2d4d 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
@@ -33,4 +33,11 @@ test.describe('Trace propagation', () => {
expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id);
expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id);
});
+
+ test('should not have trace connection for prerendered pages', async ({ page }) => {
+ await page.goto('/performance/static');
+
+ const sentryTraceElement = await page.$('meta[name="sentry-trace"]');
+ expect(sentryTraceElement).toBeNull();
+ });
});
From 1eecb02e320f4ff52ceceb092b8361d78cfa42d6 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Wed, 21 May 2025 15:04:23 +0200
Subject: [PATCH 02/26] doc(node): Fix `ignoreIncomingRequestBody` JSDoc
(#16331)
This PR just fixes incorrect JSDoc where I missed a mix-up of incoming
and outgoing requests for the `ignoreIncomingRequestBody` option while
reviewing. Raised in
https://github.com/getsentry/sentry-docs/pull/13698/files#r2088760944
---
.../node/src/integrations/http/SentryHttpInstrumentation.ts | 4 ++--
packages/node/src/integrations/http/index.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts
index 03c1260a2e5a..ac82134fd07f 100644
--- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts
+++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts
@@ -77,8 +77,8 @@ export type SentryHttpInstrumentationOptions = InstrumentationConfig & {
* Do not capture the request body for incoming HTTP requests to URLs where the given callback returns `true`.
* This can be useful for long running requests where the body is not needed and we want to avoid capturing it.
*
- * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the outgoing request.
- * @param request Contains the {@type RequestOptions} object used to make the outgoing request.
+ * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the incoming request.
+ * @param request Contains the {@type RequestOptions} object used to make the incoming request.
*/
ignoreIncomingRequestBody?: (url: string, request: http.RequestOptions) => boolean;
diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts
index 72326e21e6f1..7c49b6a6670c 100644
--- a/packages/node/src/integrations/http/index.ts
+++ b/packages/node/src/integrations/http/index.ts
@@ -85,8 +85,8 @@ interface HttpOptions {
* Do not capture the request body for incoming HTTP requests to URLs where the given callback returns `true`.
* This can be useful for long running requests where the body is not needed and we want to avoid capturing it.
*
- * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the outgoing request.
- * @param request Contains the {@type RequestOptions} object used to make the outgoing request.
+ * @param url Contains the entire URL, including query string (if any), protocol, host, etc. of the incoming request.
+ * @param request Contains the {@type RequestOptions} object used to make the incoming request.
*/
ignoreIncomingRequestBody?: (url: string, request: RequestOptions) => boolean;
From 0a7053b4ae3e298e46244ba13f40de1fa8e1225b Mon Sep 17 00:00:00 2001
From: Charly Gomez
Date: Wed, 21 May 2025 16:36:13 +0200
Subject: [PATCH 03/26] test(remix): Pin shopify package versions for remix
(#16357)
---
.../e2e-tests/test-applications/remix-hydrogen/package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json b/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json
index 48f0becca3f7..6890e46da127 100644
--- a/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json
+++ b/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json
@@ -20,7 +20,7 @@
"@sentry/cloudflare": "latest || *",
"@sentry/remix": "latest || *",
"@sentry/vite-plugin": "^3.1.2",
- "@shopify/hydrogen": "^2025.1.0",
+ "@shopify/hydrogen": "2025.4.0",
"@shopify/remix-oxygen": "^2.0.10",
"graphql": "^16.6.0",
"graphql-tag": "^2.12.6",
@@ -34,7 +34,7 @@
"@remix-run/dev": "^2.15.2",
"@remix-run/eslint-config": "^2.15.2",
"@sentry-internal/test-utils": "link:../../../test-utils",
- "@shopify/cli": "^3.74.1",
+ "@shopify/cli": "3.74.1",
"@shopify/hydrogen-codegen": "^0.3.1",
"@shopify/mini-oxygen": "3.2.0",
"@shopify/oxygen-workers-types": "^4.1.2",
From 9a185eec3b22426fde16814f817c88e8789f0290 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Wed, 21 May 2025 16:37:06 +0200
Subject: [PATCH 04/26] fix(browser): Ensure logs are flushed when
sendClientReports=false (#16351)
This was tied together and could theoretically result in logs not being
flushed correctly, when users opt-out of client reports.
Noticed this while scanning over the browser client code :)
---
packages/browser/src/client.ts | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts
index 73cbd55d42db..c0841d161ed6 100644
--- a/packages/browser/src/client.ts
+++ b/packages/browser/src/client.ts
@@ -85,41 +85,41 @@ export class BrowserClient extends Client {
super(opts);
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const client = this;
- const { sendDefaultPii, _experiments } = client._options;
+ const { sendDefaultPii, sendClientReports, _experiments } = this._options;
const enableLogs = _experiments?.enableLogs;
- if (opts.sendClientReports && WINDOW.document) {
+ if (WINDOW.document && (sendClientReports || enableLogs)) {
WINDOW.document.addEventListener('visibilitychange', () => {
if (WINDOW.document.visibilityState === 'hidden') {
- this._flushOutcomes();
+ if (sendClientReports) {
+ this._flushOutcomes();
+ }
if (enableLogs) {
- _INTERNAL_flushLogsBuffer(client);
+ _INTERNAL_flushLogsBuffer(this);
}
}
});
}
if (enableLogs) {
- client.on('flush', () => {
- _INTERNAL_flushLogsBuffer(client);
+ this.on('flush', () => {
+ _INTERNAL_flushLogsBuffer(this);
});
- client.on('afterCaptureLog', () => {
- if (client._logFlushIdleTimeout) {
- clearTimeout(client._logFlushIdleTimeout);
+ this.on('afterCaptureLog', () => {
+ if (this._logFlushIdleTimeout) {
+ clearTimeout(this._logFlushIdleTimeout);
}
- client._logFlushIdleTimeout = setTimeout(() => {
- _INTERNAL_flushLogsBuffer(client);
+ this._logFlushIdleTimeout = setTimeout(() => {
+ _INTERNAL_flushLogsBuffer(this);
}, DEFAULT_FLUSH_INTERVAL);
});
}
if (sendDefaultPii) {
- client.on('postprocessEvent', addAutoIpAddressToUser);
- client.on('beforeSendSession', addAutoIpAddressToSession);
+ this.on('postprocessEvent', addAutoIpAddressToUser);
+ this.on('beforeSendSession', addAutoIpAddressToSession);
}
}
From b4bd21fde0054285d6123e0c4ec351a28885e9b3 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Wed, 21 May 2025 16:38:21 +0200
Subject: [PATCH 05/26] feat(node): Do not add HTTP & fetch span
instrumentation if tracing is disabled (#15730)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Part of https://github.com/getsentry/sentry-javascript/issues/15725,
this PR stops adding the `HttpInstrumentation` / `UndiciInstrumentation`
if tracing is not enabled.
I _think_ this should not really be breaking, as the integrations should
not do anything else, if tracing is disabled 🤔
This only skips the http instrumentation without spans on node 22+, as
only there our own diagnostics-channel based instrumentation can cover
everything we need.
---
packages/node/src/integrations/http/index.ts | 21 ++++++++++++++++---
.../node/src/integrations/node-fetch/index.ts | 8 ++++---
packages/node/test/integrations/http.test.ts | 15 +++++++++++--
3 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/packages/node/src/integrations/http/index.ts b/packages/node/src/integrations/http/index.ts
index 7c49b6a6670c..96e9b84315be 100644
--- a/packages/node/src/integrations/http/index.ts
+++ b/packages/node/src/integrations/http/index.ts
@@ -3,7 +3,8 @@ import { diag } from '@opentelemetry/api';
import type { HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import type { Span } from '@sentry/core';
-import { defineIntegration, getClient } from '@sentry/core';
+import { defineIntegration, getClient, hasSpansEnabled } from '@sentry/core';
+import { NODE_VERSION } from '../../nodeVersion';
import { generateInstrumentOnce } from '../../otel/instrument';
import type { NodeClient } from '../../sdk/client';
import type { HTTPModuleRequestIncomingMessage } from '../../transports/http-module';
@@ -159,8 +160,22 @@ export const instrumentOtelHttp = generateInstrumentOnce = {}): boolean {
// If `spans` is passed in, it takes precedence
- // Else, we by default emit spans, unless `skipOpenTelemetrySetup` is set to `true`
- return typeof options.spans === 'boolean' ? options.spans : !clientOptions.skipOpenTelemetrySetup;
+ // Else, we by default emit spans, unless `skipOpenTelemetrySetup` is set to `true` or spans are not enabled
+ if (typeof options.spans === 'boolean') {
+ return options.spans;
+ }
+
+ if (clientOptions.skipOpenTelemetrySetup) {
+ return false;
+ }
+
+ // IMPORTANT: We only disable span instrumentation when spans are not enabled _and_ we are on Node 22+,
+ // as otherwise the necessary diagnostics channel is not available yet
+ if (!hasSpansEnabled(clientOptions) && NODE_VERSION.major >= 22) {
+ return false;
+ }
+
+ return true;
}
/**
diff --git a/packages/node/src/integrations/node-fetch/index.ts b/packages/node/src/integrations/node-fetch/index.ts
index d62d1e7c88c2..e85ce34dfb35 100644
--- a/packages/node/src/integrations/node-fetch/index.ts
+++ b/packages/node/src/integrations/node-fetch/index.ts
@@ -1,7 +1,7 @@
import type { UndiciInstrumentationConfig } from '@opentelemetry/instrumentation-undici';
import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici';
import type { IntegrationFn } from '@sentry/core';
-import { defineIntegration, getClient, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
+import { defineIntegration, getClient, hasSpansEnabled, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { generateInstrumentOnce } from '../../otel/instrument';
import type { NodeClient } from '../../sdk/client';
import type { NodeClientOptions } from '../../types';
@@ -86,8 +86,10 @@ function getAbsoluteUrl(origin: string, path: string = '/'): string {
function _shouldInstrumentSpans(options: NodeFetchOptions, clientOptions: Partial = {}): boolean {
// If `spans` is passed in, it takes precedence
- // Else, we by default emit spans, unless `skipOpenTelemetrySetup` is set to `true`
- return typeof options.spans === 'boolean' ? options.spans : !clientOptions.skipOpenTelemetrySetup;
+ // Else, we by default emit spans, unless `skipOpenTelemetrySetup` is set to `true` or spans are not enabled
+ return typeof options.spans === 'boolean'
+ ? options.spans
+ : !clientOptions.skipOpenTelemetrySetup && hasSpansEnabled(clientOptions);
}
function getConfigWithDefaults(options: Partial = {}): UndiciInstrumentationConfig {
diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts
index 6d4bea24661f..89052a348ea4 100644
--- a/packages/node/test/integrations/http.test.ts
+++ b/packages/node/test/integrations/http.test.ts
@@ -1,19 +1,30 @@
import { describe, expect, it } from 'vitest';
import { _shouldInstrumentSpans } from '../../src/integrations/http';
+import { conditionalTest } from '../helpers/conditional';
describe('httpIntegration', () => {
describe('_shouldInstrumentSpans', () => {
it.each([
- [{}, {}, true],
[{ spans: true }, {}, true],
[{ spans: false }, {}, false],
[{ spans: true }, { skipOpenTelemetrySetup: true }, true],
[{ spans: false }, { skipOpenTelemetrySetup: true }, false],
[{}, { skipOpenTelemetrySetup: true }, false],
- [{}, { skipOpenTelemetrySetup: false }, true],
+ [{}, { tracesSampleRate: 0, skipOpenTelemetrySetup: true }, false],
+ [{}, { tracesSampleRate: 0 }, true],
])('returns the correct value for options=%j and clientOptions=%j', (options, clientOptions, expected) => {
const actual = _shouldInstrumentSpans(options, clientOptions);
expect(actual).toBe(expected);
});
+
+ conditionalTest({ min: 22 })('returns false without tracesSampleRate on Node >=22', () => {
+ const actual = _shouldInstrumentSpans({}, {});
+ expect(actual).toBe(false);
+ });
+
+ conditionalTest({ max: 21 })('returns true without tracesSampleRate on Node <22', () => {
+ const actual = _shouldInstrumentSpans({}, {});
+ expect(actual).toBe(true);
+ });
});
});
From 48f1a4f0c240d781bad3461bd26a683c5512e7ba Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Thu, 22 May 2025 09:13:51 +0200
Subject: [PATCH 06/26] fix(cloudflare): Capture exceptions thrown in hono
(#16355)
Our cloudflare SDK captures fetch exceptions in the catch block. But
hono never reaches [this
block](https://github.com/getsentry/sentry-javascript/blob/a874d763171289b5556973a4842f580d4bbb7ec0/packages/cloudflare/src/request.ts#L85-L90)
where we send exceptions.
hono processes errors with their `onError` function (or `errorHandler`)
and we need to capture the exception there.
This PR wraps the [`errorHandler` of
hono](https://github.com/honojs/hono/blob/bb7afaccfd5b6b514da356e069f27eb5ccfc0e3b/src/hono-base.ts#L36
).
Will create an E2E test in another PR.
---
packages/cloudflare/src/handler.ts | 21 ++++++
packages/cloudflare/test/handler.test.ts | 91 ++++++++++++++++++++++++
2 files changed, 112 insertions(+)
diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts
index 62956cff62cf..d3d1f80dbbd5 100644
--- a/packages/cloudflare/src/handler.ts
+++ b/packages/cloudflare/src/handler.ts
@@ -26,6 +26,7 @@ import { init } from './sdk';
* @param handler {ExportedHandler} The handler to wrap.
* @returns The wrapped handler.
*/
+// eslint-disable-next-line complexity
export function withSentry(
optionsCallback: (env: Env) => CloudflareOptions,
handler: ExportedHandler,
@@ -47,6 +48,26 @@ export function withSentry>) {
diff --git a/packages/cloudflare/test/handler.test.ts b/packages/cloudflare/test/handler.test.ts
index 6ae688f316f9..bced0fdbe277 100644
--- a/packages/cloudflare/test/handler.test.ts
+++ b/packages/cloudflare/test/handler.test.ts
@@ -7,6 +7,17 @@ import * as SentryCore from '@sentry/core';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { CloudflareClient } from '../src/client';
import { withSentry } from '../src/handler';
+import { markAsInstrumented } from '../src/instrument';
+
+// Custom type for hono-like apps (cloudflare handlers) that include errorHandler and onError
+type HonoLikeApp = ExportedHandler<
+ Env,
+ QueueHandlerMessage,
+ CfHostMetadata
+> & {
+ onError?: () => void;
+ errorHandler?: (err: Error) => Response;
+};
const MOCK_ENV = {
SENTRY_DSN: 'https://public@dsn.ingest.sentry.io/1337',
@@ -931,6 +942,86 @@ describe('withSentry', () => {
});
});
});
+
+ describe('hono errorHandler', () => {
+ test('captures errors handled by the errorHandler', async () => {
+ const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException');
+ const error = new Error('test hono error');
+
+ const honoApp = {
+ fetch(_request, _env, _context) {
+ return new Response('test');
+ },
+ onError() {}, // hono-like onError
+ errorHandler(err: Error) {
+ return new Response(`Error: ${err.message}`, { status: 500 });
+ },
+ } satisfies HonoLikeApp;
+
+ withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp);
+
+ // simulates hono's error handling
+ const errorHandlerResponse = honoApp.errorHandler?.(error);
+
+ expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
+ expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, {
+ mechanism: { handled: false, type: 'cloudflare' },
+ });
+ expect(errorHandlerResponse?.status).toBe(500);
+ });
+
+ test('preserves the original errorHandler functionality', async () => {
+ const originalErrorHandlerSpy = vi.fn().mockImplementation((err: Error) => {
+ return new Response(`Error: ${err.message}`, { status: 500 });
+ });
+
+ const error = new Error('test hono error');
+
+ const honoApp = {
+ fetch(_request, _env, _context) {
+ return new Response('test');
+ },
+ onError() {}, // hono-like onError
+ errorHandler: originalErrorHandlerSpy,
+ } satisfies HonoLikeApp;
+
+ withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp);
+
+ // Call the errorHandler directly to simulate Hono's error handling
+ const errorHandlerResponse = honoApp.errorHandler?.(error);
+
+ expect(originalErrorHandlerSpy).toHaveBeenCalledTimes(1);
+ expect(originalErrorHandlerSpy).toHaveBeenLastCalledWith(error);
+ expect(errorHandlerResponse?.status).toBe(500);
+ });
+
+ test('does not instrument an already instrumented errorHandler', async () => {
+ const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException');
+ const error = new Error('test hono error');
+
+ // Create a handler with an errorHandler that's already been instrumented
+ const originalErrorHandler = (err: Error) => {
+ return new Response(`Error: ${err.message}`, { status: 500 });
+ };
+
+ // Mark as instrumented before wrapping
+ markAsInstrumented(originalErrorHandler);
+
+ const honoApp = {
+ fetch(_request, _env, _context) {
+ return new Response('test');
+ },
+ onError() {}, // hono-like onError
+ errorHandler: originalErrorHandler,
+ } satisfies HonoLikeApp;
+
+ withSentry(env => ({ dsn: env.SENTRY_DSN }), honoApp);
+
+ // The errorHandler should not have been wrapped again
+ honoApp.errorHandler?.(error);
+ expect(captureExceptionSpy).not.toHaveBeenCalled();
+ });
+ });
});
function createMockExecutionContext(): ExecutionContext {
From 952373e43ba06f951dae87d7e9e06b4834e52e3b Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Fri, 23 May 2025 10:11:29 +0200
Subject: [PATCH 07/26] test(cloudflare): Add test for hono cloudflare (#16360)
Adding a test to check that the build and types work (just like with
cloudflare-wokers).
---
.../cloudflare-hono/package.json | 29 ++++++++
.../cloudflare-hono/src/env.d.ts | 6 ++
.../cloudflare-hono/src/index.ts | 34 +++++++++
.../cloudflare-hono/test/env.d.ts | 4 +
.../cloudflare-hono/test/index.test.ts | 74 +++++++++++++++++++
.../cloudflare-hono/test/tsconfig.json | 8 ++
.../cloudflare-hono/tsconfig.json | 16 ++++
.../cloudflare-hono/vitest.config.ts | 12 +++
.../cloudflare-hono/wrangler.toml | 7 ++
9 files changed, 190 insertions(+)
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts
create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json
new file mode 100644
index 000000000000..b9667aeef85f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "cloudflare-hono",
+ "scripts": {
+ "dev": "wrangler dev",
+ "build": "wrangler deploy --dry-run --var E2E_TEST_DSN=$E2E_TEST_DSN",
+ "test": "vitest",
+ "typecheck": "tsc --noEmit",
+ "cf-typegen": "wrangler types --env-interface CloudflareBindings",
+ "test:build": "pnpm install && pnpm build",
+ "//": "Just checking if it builds correctly and types don't break",
+ "test:assert": "pnpm typecheck"
+ },
+ "dependencies": {
+ "@sentry/cloudflare": "latest || *",
+ "hono": "4.7.10"
+ },
+ "devDependencies": {
+ "@cloudflare/vitest-pool-workers": "^0.8.31",
+ "@cloudflare/workers-types": "^4.20250521.0",
+ "vitest": "3.1.0",
+ "wrangler": "^4.16.0"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "sentryTest": {
+ "optional": true
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts
new file mode 100644
index 000000000000..0c9e04919e42
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/env.d.ts
@@ -0,0 +1,6 @@
+// Generated by Wrangler on Mon Jul 29 2024 21:44:31 GMT-0400 (Eastern Daylight Time)
+// by running `wrangler types`
+
+interface Env {
+ E2E_TEST_DSN: '';
+}
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts
new file mode 100644
index 000000000000..7cd667c72408
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/src/index.ts
@@ -0,0 +1,34 @@
+import { Hono } from 'hono';
+import * as Sentry from '@sentry/cloudflare';
+
+const app = new Hono();
+
+app.get('/', ctx => {
+ return ctx.json({ message: 'Welcome to Hono API' });
+});
+
+app.get('/hello/:name', ctx => {
+ const name = ctx.req.param('name');
+ return ctx.json({ message: `Hello, ${name}!` });
+});
+
+app.get('/error', () => {
+ throw new Error('This is a test error');
+});
+
+app.onError((err, ctx) => {
+ console.error(`Error occured: ${err.message}`);
+ return ctx.json({ error: err.message }, 500);
+});
+
+app.notFound(ctx => {
+ return ctx.json({ message: 'Not Found' }, 404);
+});
+
+export default Sentry.withSentry(
+ (env: Env) => ({
+ dsn: env?.E2E_TEST_DSN,
+ tracesSampleRate: 1.0,
+ }),
+ app,
+);
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts
new file mode 100644
index 000000000000..3b9f82b9628f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/env.d.ts
@@ -0,0 +1,4 @@
+declare module 'cloudflare:test' {
+ // ProvidedEnv controls the type of `import("cloudflare:test").env`
+ interface ProvidedEnv extends Env {}
+}
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts
new file mode 100644
index 000000000000..2ae93f9b1fd5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/index.test.ts
@@ -0,0 +1,74 @@
+import { describe, expect, it } from 'vitest';
+import app from '../src/index';
+import { SELF, createExecutionContext, env, waitOnExecutionContext } from 'cloudflare:test';
+
+describe('Hono app on Cloudflare Workers', () => {
+ describe('Unit Tests', () => {
+ it('should return welcome message', async () => {
+ const res = await app.request('/', {}, env);
+ expect(res.status).toBe(200);
+ const data = await res.json();
+ expect(data).toEqual({ message: 'Welcome to Hono API' });
+ });
+
+ it('should greet a user with their name', async () => {
+ const res = await app.request('/hello/tester', {}, env);
+ expect(res.status).toBe(200);
+ const data = await res.json();
+ expect(data).toEqual({ message: 'Hello, tester!' });
+ });
+
+ it('should handle errors with custom error handler', async () => {
+ const res = await app.request('/error', {}, env);
+ expect(res.status).toBe(500);
+ const data = await res.json();
+ expect(data).toHaveProperty('error', 'This is a test error');
+ });
+
+ it('should handle 404 with custom not found handler', async () => {
+ const res = await app.request('/non-existent-route', {}, env);
+ expect(res.status).toBe(404);
+ const data = await res.json();
+ expect(data).toEqual({ message: 'Not Found' });
+ });
+ });
+
+ // Integration test style with worker.fetch
+ describe('Integration Tests', () => {
+ it('should fetch the root endpoint', async () => {
+ // Create request and context
+ const request = new Request('http://localhost/');
+ const ctx = createExecutionContext();
+
+ const response = await app.fetch(request, env, ctx);
+
+ await waitOnExecutionContext(ctx);
+
+ expect(response.status).toBe(200);
+ const data = await response.json();
+ expect(data).toEqual({ message: 'Welcome to Hono API' });
+ });
+
+ it('should handle a parameter route', async () => {
+ // Create request and context
+ const request = new Request('http://localhost/hello/cloudflare');
+ const ctx = createExecutionContext();
+
+ const response = await app.fetch(request, env, ctx);
+
+ await waitOnExecutionContext(ctx);
+
+ expect(response.status).toBe(200);
+ const data = await response.json();
+ expect(data).toEqual({ message: 'Hello, cloudflare!' });
+ });
+
+ it('should handle errors gracefully', async () => {
+ const response = await SELF.fetch('http://localhost/error');
+
+ expect(response.status).toBe(500);
+ const data = await response.json();
+ expect(data).toHaveProperty('error', 'This is a test error');
+ });
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json
new file mode 100644
index 000000000000..bc019a7e2bfb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/test/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "types": ["@cloudflare/workers-types/experimental", "@cloudflare/vitest-pool-workers"]
+ },
+ "include": ["./**/*.ts", "../src/env.d.ts"],
+ "exclude": []
+}
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json b/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json
new file mode 100644
index 000000000000..e2025cec5039
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "lib": [
+ "ESNext"
+ ],
+ "jsx": "react-jsx",
+ "jsxImportSource": "hono/jsx"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["test", "node_modules"]
+}
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts b/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts
new file mode 100644
index 000000000000..4466287fbe5b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/vitest.config.ts
@@ -0,0 +1,12 @@
+import { defineWorkersProject } from '@cloudflare/vitest-pool-workers/config'
+
+export default defineWorkersProject(() => {
+ return {
+ test: {
+ globals: true,
+ poolOptions: {
+ workers: { wrangler: { configPath: './wrangler.toml' } },
+ },
+ },
+ }
+})
diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml b/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml
new file mode 100644
index 000000000000..9fdfb60c18b9
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/cloudflare-hono/wrangler.toml
@@ -0,0 +1,7 @@
+name = "cloudflare-hono"
+main = "src/index.ts"
+compatibility_date = "2023-10-30"
+compatibility_flags = ["nodejs_compat"]
+
+# [vars]
+# E2E_TEST_DSN = ""
From 3604a08a53d3127db1df9ea8b9d3df509a8c3f36 Mon Sep 17 00:00:00 2001
From: Tim Fish
Date: Fri, 23 May 2025 16:47:02 +0100
Subject: [PATCH 08/26] feat(core): Allow re-use of `captureLog` (#16352)
Co-authored-by: Abhijeet Prasad
---
packages/core/src/logs/exports.ts | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts
index a2451c6cc6c0..41f1155ee3e1 100644
--- a/packages/core/src/logs/exports.ts
+++ b/packages/core/src/logs/exports.ts
@@ -61,12 +61,25 @@ export function logAttributeToSerializedLogAttribute(value: unknown): Serialized
}
}
+function defaultCaptureSerializedLog(client: Client, serializedLog: SerializedLog): void {
+ const logBuffer = _INTERNAL_getLogBuffer(client);
+ if (logBuffer === undefined) {
+ GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [serializedLog]);
+ } else {
+ GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [...logBuffer, serializedLog]);
+ if (logBuffer.length >= MAX_LOG_BUFFER_SIZE) {
+ _INTERNAL_flushLogsBuffer(client, logBuffer);
+ }
+ }
+}
+
/**
* Captures a log event and sends it to Sentry.
*
* @param log - The log event to capture.
* @param scope - A scope. Uses the current scope if not provided.
* @param client - A client. Uses the current client if not provided.
+ * @param captureSerializedLog - A function to capture the serialized log.
*
* @experimental This method will experience breaking changes. This is not yet part of
* the stable Sentry SDK API and can be changed or removed without warning.
@@ -75,6 +88,7 @@ export function _INTERNAL_captureLog(
beforeLog: Log,
client: Client | undefined = getClient(),
scope = getCurrentScope(),
+ captureSerializedLog: (client: Client, log: SerializedLog) => void = defaultCaptureSerializedLog,
): void {
if (!client) {
DEBUG_BUILD && logger.warn('No client available to capture log.');
@@ -151,15 +165,7 @@ export function _INTERNAL_captureLog(
),
};
- const logBuffer = _INTERNAL_getLogBuffer(client);
- if (logBuffer === undefined) {
- GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [serializedLog]);
- } else {
- GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [...logBuffer, serializedLog]);
- if (logBuffer.length >= MAX_LOG_BUFFER_SIZE) {
- _INTERNAL_flushLogsBuffer(client, logBuffer);
- }
- }
+ captureSerializedLog(client, serializedLog);
client.emit('afterCaptureLog', log);
}
From 3d18c8e2f0bd6a6fe233a0ff6a450f9cb90cb901 Mon Sep 17 00:00:00 2001
From: Burak Yigit Kaya
Date: Sat, 24 May 2025 19:46:05 +0100
Subject: [PATCH 09/26] fix(node): Suppress Spotlight calls (#16380)
Uses the established `suppressTracing` wrapper to hide Spotlight calls
from OTEL too.
---
packages/node/src/integrations/spotlight.ts | 85 ++++++++-------------
1 file changed, 33 insertions(+), 52 deletions(-)
diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts
index 49a169076798..917fe073e194 100644
--- a/packages/node/src/integrations/spotlight.ts
+++ b/packages/node/src/integrations/spotlight.ts
@@ -1,6 +1,6 @@
import * as http from 'node:http';
import type { Client, Envelope, IntegrationFn } from '@sentry/core';
-import { defineIntegration, logger, serializeEnvelope } from '@sentry/core';
+import { defineIntegration, logger, serializeEnvelope, suppressTracing } from '@sentry/core';
type SpotlightConnectionOptions = {
/**
@@ -52,40 +52,40 @@ function connectToSpotlight(client: Client, options: Required {
+ const req = http.request(
+ {
+ method: 'POST',
+ path: spotlightUrl.pathname,
+ hostname: spotlightUrl.hostname,
+ port: spotlightUrl.port,
+ headers: {
+ 'Content-Type': 'application/x-sentry-envelope',
+ },
},
- },
- res => {
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
- // Reset failed requests counter on success
- failedRequests = 0;
- }
- res.on('data', () => {
- // Drain socket
- });
-
- res.on('end', () => {
- // Drain socket
- });
- res.setEncoding('utf8');
- },
- );
-
- req.on('error', () => {
- failedRequests++;
- logger.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar');
+ res => {
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
+ // Reset failed requests counter on success
+ failedRequests = 0;
+ }
+ res.on('data', () => {
+ // Drain socket
+ });
+
+ res.on('end', () => {
+ // Drain socket
+ });
+ res.setEncoding('utf8');
+ },
+ );
+
+ req.on('error', () => {
+ failedRequests++;
+ logger.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar');
+ });
+ req.write(serializedEnvelope);
+ req.end();
});
- req.write(serializedEnvelope);
- req.end();
});
}
@@ -97,22 +97,3 @@ function parseSidecarUrl(url: string): URL | undefined {
return undefined;
}
}
-
-type HttpRequestImpl = typeof http.request;
-type WrappedHttpRequest = HttpRequestImpl & { __sentry_original__: HttpRequestImpl };
-
-/**
- * We want to get an unpatched http request implementation to avoid capturing our own calls.
- */
-export function getNativeHttpRequest(): HttpRequestImpl {
- const { request } = http;
- if (isWrapped(request)) {
- return request.__sentry_original__;
- }
-
- return request;
-}
-
-function isWrapped(impl: HttpRequestImpl): impl is WrappedHttpRequest {
- return '__sentry_original__' in impl;
-}
From 72057b64864f0b9030266f28e465156424a06a94 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Mon, 26 May 2025 09:37:02 +0200
Subject: [PATCH 10/26] test(angular): Add Angular `20.0.0-rc.2` e2e test
(#16364)
adds an e2e test for Angular the latest Angular 20 RC -- to be promoted to Angular 20 stable once released
---
.github/workflows/build.yml | 6 +
.../angular-20/.editorconfig | 17 +
.../test-applications/angular-20/.gitignore | 44 +++
.../test-applications/angular-20/.npmrc | 2 +
.../test-applications/angular-20/README.md | 3 +
.../test-applications/angular-20/angular.json | 87 +++++
.../test-applications/angular-20/package.json | 50 +++
.../angular-20/playwright.config.mjs | 8 +
.../angular-20/public/favicon.ico | Bin 0 -> 15086 bytes
.../angular-20/src/app/app.component.ts | 12 +
.../angular-20/src/app/app.config.ts | 29 ++
.../angular-20/src/app/app.routes.ts | 42 +++
.../angular-20/src/app/cancel-guard.guard.ts | 5 +
.../src/app/cancel/cancel.components.ts | 8 +
.../component-tracking.components.ts | 21 ++
.../angular-20/src/app/home/home.component.ts | 26 ++
.../sample-component.components.ts | 12 +
.../angular-20/src/app/user/user.component.ts | 25 ++
.../angular-20/src/index.html | 13 +
.../test-applications/angular-20/src/main.ts | 15 +
.../angular-20/src/styles.css | 1 +
.../angular-20/start-event-proxy.mjs | 6 +
.../angular-20/tests/errors.test.ts | 65 ++++
.../angular-20/tests/performance.test.ts | 326 ++++++++++++++++++
.../angular-20/tsconfig.app.json | 11 +
.../angular-20/tsconfig.json | 27 ++
.../angular-20/tsconfig.spec.json | 10 +
27 files changed, 871 insertions(+)
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/.editorconfig
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/.gitignore
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/.npmrc
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/README.md
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/angular.json
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/package.json
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/playwright.config.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/public/favicon.ico
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/app.component.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/app.config.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/app.routes.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel-guard.guard.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel/cancel.components.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/component-tracking/component-tracking.components.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/home/home.component.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/sample-component/sample-component.components.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/app/user/user.component.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/index.html
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/main.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/src/styles.css
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/start-event-proxy.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/tests/errors.test.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/tests/performance.test.ts
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/tsconfig.app.json
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/tsconfig.json
create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/tsconfig.spec.json
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ae4095e304ab..9577eb15aff7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -886,7 +886,13 @@ jobs:
- uses: pnpm/action-setup@v4
with:
version: 9.4.0
+ - name: Set up Node for Angular 20
+ if: matrix.test-application == 'angular-20'
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.19.2'
- name: Set up Node
+ if: matrix.test-application != 'angular-20'
uses: actions/setup-node@v4
with:
node-version-file: 'dev-packages/e2e-tests/package.json'
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/.editorconfig b/dev-packages/e2e-tests/test-applications/angular-20/.editorconfig
new file mode 100644
index 000000000000..f166060da1cb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/.editorconfig
@@ -0,0 +1,17 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+ij_typescript_use_double_quotes = false
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/.gitignore b/dev-packages/e2e-tests/test-applications/angular-20/.gitignore
new file mode 100644
index 000000000000..315c644a53e8
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/.gitignore
@@ -0,0 +1,44 @@
+# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
+
+# Compiled output
+/dist
+/tmp
+/out-tsc
+/bazel-out
+
+# Node
+/node_modules
+npm-debug.log
+yarn-error.log
+
+# IDEs and editors
+.idea/
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# Miscellaneous
+/.angular/cache
+.sass-cache/
+/connect.lock
+/coverage
+/libpeerconnection.log
+testem.log
+/typings
+
+# System files
+.DS_Store
+Thumbs.db
+
+test-results
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/.npmrc b/dev-packages/e2e-tests/test-applications/angular-20/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/.npmrc
@@ -0,0 +1,2 @@
+@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/angular-20/README.md b/dev-packages/e2e-tests/test-applications/angular-20/README.md
new file mode 100644
index 000000000000..5798a982a95c
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/README.md
@@ -0,0 +1,3 @@
+# Angular 20
+
+E2E test app for Angular 20 and `@sentry/angular`.
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/angular.json b/dev-packages/e2e-tests/test-applications/angular-20/angular.json
new file mode 100644
index 000000000000..09939b0f9b23
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/angular.json
@@ -0,0 +1,87 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "angular-20": {
+ "projectType": "application",
+ "schematics": {},
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:application",
+ "options": {
+ "outputPath": "dist/angular-20",
+ "index": "src/index.html",
+ "browser": "src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "tsconfig.app.json",
+ "assets": [
+ {
+ "glob": "**/*",
+ "input": "public"
+ }
+ ],
+ "styles": ["src/styles.css"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kB",
+ "maximumError": "1MB"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "4kB",
+ "maximumError": "8kB"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "angular-20:build:production"
+ },
+ "development": {
+ "buildTarget": "angular-20:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n"
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "polyfills": ["zone.js", "zone.js/testing"],
+ "tsConfig": "tsconfig.spec.json",
+ "assets": [
+ {
+ "glob": "**/*",
+ "input": "public"
+ }
+ ],
+ "styles": ["src/styles.css"],
+ "scripts": []
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/package.json b/dev-packages/e2e-tests/test-applications/angular-20/package.json
new file mode 100644
index 000000000000..a43fcaf412b5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "angular-20",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "dev": "ng serve",
+ "proxy": "node start-event-proxy.mjs",
+ "preview": "http-server dist/angular-20/browser --port 8080",
+ "build": "ng build",
+ "watch": "ng build --watch --configuration development",
+ "test": "playwright test",
+ "test:build": "pnpm install && pnpm build",
+ "test:assert": "playwright test",
+ "clean": "npx rimraf .angular node_modules pnpm-lock.yaml dist"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^20.0.0-rc.2",
+ "@angular/common": "^20.0.0-rc.2",
+ "@angular/compiler": "^20.0.0-rc.2",
+ "@angular/core": "^20.0.0-rc.2",
+ "@angular/forms": "^20.0.0-rc.2",
+ "@angular/platform-browser": "^20.0.0-rc.2",
+ "@angular/platform-browser-dynamic": "^20.0.0-rc.2",
+ "@angular/router": "^20.0.0-rc.2",
+ "@sentry/angular": "* || latest",
+ "rxjs": "~7.8.0",
+ "tslib": "^2.3.0",
+ "zone.js": "~0.15.0"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "^20.0.0-rc.2",
+ "@angular/cli": "^20.0.0-rc.2",
+ "@angular/compiler-cli": "^20.0.0-rc.2",
+ "@playwright/test": "~1.50.0",
+ "@sentry-internal/test-utils": "link:../../../test-utils",
+ "@types/jasmine": "~5.1.0",
+ "http-server": "^14.1.1",
+ "jasmine-core": "~5.4.0",
+ "karma": "~6.4.0",
+ "karma-chrome-launcher": "~3.2.0",
+ "karma-coverage": "~2.2.0",
+ "karma-jasmine": "~5.1.0",
+ "karma-jasmine-html-reporter": "~2.1.0",
+ "typescript": "~5.8.3"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/angular-20/playwright.config.mjs
new file mode 100644
index 000000000000..0845325879c9
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/playwright.config.mjs
@@ -0,0 +1,8 @@
+import { getPlaywrightConfig } from '@sentry-internal/test-utils';
+
+const config = getPlaywrightConfig({
+ startCommand: `pnpm preview`,
+ port: 8080,
+});
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/public/favicon.ico b/dev-packages/e2e-tests/test-applications/angular-20/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6
GIT binary patch
literal 15086
zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA
zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~
zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r
zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@
z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f
zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q
zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l*
zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4
zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX
zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp|
z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB
ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v
z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f
z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9
znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp
z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep
zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky
z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3
zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK
zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=?
zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO
zKO?E0
zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l
z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4
z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG`
z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW
zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8
z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h
zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec
zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h
zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h
z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt
z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3
z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ
zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L
zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER
z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$
zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~
z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O
z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~
zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt
zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@
z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J
zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8
zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG
z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{
z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ
zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT
z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU
zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd
z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL`
zt?XK-iTEOWV}f>Pz3H-s*>W
z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW
Nr__JTFzvMr^FKp4S3v*(
literal 0
HcmV?d00001
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.component.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.component.ts
new file mode 100644
index 000000000000..e912fcc99b04
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [RouterOutlet],
+ template: ` `,
+})
+export class AppComponent {
+ title = 'angular-20';
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.config.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.config.ts
new file mode 100644
index 000000000000..f5cc30f3615b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.config.ts
@@ -0,0 +1,29 @@
+import {
+ ApplicationConfig,
+ ErrorHandler,
+ inject,
+ provideAppInitializer,
+ provideZoneChangeDetection,
+} from '@angular/core';
+import { Router, provideRouter } from '@angular/router';
+
+import { TraceService, createErrorHandler } from '@sentry/angular';
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ {
+ provide: ErrorHandler,
+ useValue: createErrorHandler(),
+ },
+ {
+ provide: TraceService,
+ deps: [Router],
+ },
+ provideAppInitializer(() => {
+ inject(TraceService);
+ }),
+ ],
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.routes.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.routes.ts
new file mode 100644
index 000000000000..24bf8b769051
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/app.routes.ts
@@ -0,0 +1,42 @@
+import { Routes } from '@angular/router';
+import { cancelGuard } from './cancel-guard.guard';
+import { CancelComponent } from './cancel/cancel.components';
+import { ComponentTrackingComponent } from './component-tracking/component-tracking.components';
+import { HomeComponent } from './home/home.component';
+import { UserComponent } from './user/user.component';
+
+export const routes: Routes = [
+ {
+ path: 'users/:id',
+ component: UserComponent,
+ },
+ {
+ path: 'home',
+ component: HomeComponent,
+ },
+ {
+ path: 'cancel',
+ component: CancelComponent,
+ canActivate: [cancelGuard],
+ },
+ {
+ path: 'component-tracking',
+ component: ComponentTrackingComponent,
+ },
+ {
+ path: 'redirect1',
+ redirectTo: '/redirect2',
+ },
+ {
+ path: 'redirect2',
+ redirectTo: '/redirect3',
+ },
+ {
+ path: 'redirect3',
+ redirectTo: '/users/456',
+ },
+ {
+ path: '**',
+ redirectTo: 'home',
+ },
+];
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel-guard.guard.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel-guard.guard.ts
new file mode 100644
index 000000000000..16ec4a2ab164
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel-guard.guard.ts
@@ -0,0 +1,5 @@
+import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
+
+export const cancelGuard: CanActivateFn = (_next: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
+ return false;
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel/cancel.components.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel/cancel.components.ts
new file mode 100644
index 000000000000..b6ee1876e035
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/cancel/cancel.components.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-cancel',
+ standalone: true,
+ template: `
`,
+})
+export class CancelComponent {}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/component-tracking/component-tracking.components.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/component-tracking/component-tracking.components.ts
new file mode 100644
index 000000000000..a82e5b1acce6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/component-tracking/component-tracking.components.ts
@@ -0,0 +1,21 @@
+import { AfterViewInit, Component, OnInit } from '@angular/core';
+import { TraceClass, TraceMethod, TraceModule } from '@sentry/angular';
+import { SampleComponent } from '../sample-component/sample-component.components';
+
+@Component({
+ selector: 'app-component-tracking',
+ standalone: true,
+ imports: [TraceModule, SampleComponent],
+ template: `
+
+
+ `,
+})
+@TraceClass({ name: 'ComponentTrackingComponent' })
+export class ComponentTrackingComponent implements OnInit, AfterViewInit {
+ @TraceMethod({ name: 'ngOnInit' })
+ ngOnInit() {}
+
+ @TraceMethod()
+ ngAfterViewInit() {}
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/home/home.component.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/home/home.component.ts
new file mode 100644
index 000000000000..033174fb0d8a
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/home/home.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-home',
+ standalone: true,
+ imports: [RouterLink],
+ template: `
+
+ Welcome to Sentry's Angular 20 E2E test app
+
+ Throw error
+
+ `,
+})
+export class HomeComponent {
+ throwError() {
+ throw new Error('Error thrown from Angular 20 E2E test app');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/sample-component/sample-component.components.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/sample-component/sample-component.components.ts
new file mode 100644
index 000000000000..da09425c7565
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/sample-component/sample-component.components.ts
@@ -0,0 +1,12 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-sample-component',
+ standalone: true,
+ template: `Component
`,
+})
+export class SampleComponent implements OnInit {
+ ngOnInit() {
+ console.log('SampleComponent');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/app/user/user.component.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/app/user/user.component.ts
new file mode 100644
index 000000000000..db02568d395f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/app/user/user.component.ts
@@ -0,0 +1,25 @@
+import { AsyncPipe } from '@angular/common';
+import { Component } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Observable, map } from 'rxjs';
+
+@Component({
+ selector: 'app-user',
+ standalone: true,
+ imports: [AsyncPipe],
+ template: `
+ Hello User {{ userId$ | async }}
+ Throw error
+ `,
+})
+export class UserComponent {
+ public userId$: Observable;
+
+ constructor(private route: ActivatedRoute) {
+ this.userId$ = this.route.paramMap.pipe(map(params => params.get('id') || 'UNKNOWN USER'));
+ }
+
+ throwError() {
+ throw new Error('Error thrown from user page');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/index.html b/dev-packages/e2e-tests/test-applications/angular-20/src/index.html
new file mode 100644
index 000000000000..0f546ff0114e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Angular 20
+
+
+
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/main.ts b/dev-packages/e2e-tests/test-applications/angular-20/src/main.ts
new file mode 100644
index 000000000000..a0b841afc333
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/main.ts
@@ -0,0 +1,15 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+import * as Sentry from '@sentry/angular';
+
+Sentry.init({
+ // Cannot use process.env here, so we hardcode the DSN
+ dsn: 'https://3b6c388182fb435097f41d181be2b2ba@o4504321058471936.ingest.sentry.io/4504321066008576',
+ tracesSampleRate: 1.0,
+ integrations: [Sentry.browserTracingIntegration({})],
+ tunnel: `http://localhost:3031/`, // proxy server
+});
+
+bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/src/styles.css b/dev-packages/e2e-tests/test-applications/angular-20/src/styles.css
new file mode 100644
index 000000000000..90d4ee0072ce
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/angular-20/start-event-proxy.mjs
new file mode 100644
index 000000000000..58559fb2f8b8
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/test-utils';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'angular-20',
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/angular-20/tests/errors.test.ts
new file mode 100644
index 000000000000..98ae26e195cc
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/tests/errors.test.ts
@@ -0,0 +1,65 @@
+import { expect, test } from '@playwright/test';
+import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';
+
+test('sends an error', async ({ page }) => {
+ const errorPromise = waitForError('angular-20', async errorEvent => {
+ return !errorEvent.type;
+ });
+
+ await page.goto(`/`);
+
+ await page.locator('#errorBtn').click();
+
+ const error = await errorPromise;
+
+ expect(error).toMatchObject({
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Error thrown from Angular 20 E2E test app',
+ mechanism: {
+ type: 'angular',
+ handled: false,
+ },
+ },
+ ],
+ },
+ transaction: '/home/',
+ });
+});
+
+test('assigns the correct transaction value after a navigation', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const errorPromise = waitForError('angular-20', async errorEvent => {
+ return !errorEvent.type;
+ });
+
+ await page.goto(`/`);
+ await pageloadTxnPromise;
+
+ await page.waitForTimeout(5000);
+
+ await page.locator('#navLink').click();
+
+ const [_, error] = await Promise.all([page.locator('#userErrorBtn').click(), errorPromise]);
+
+ expect(error).toMatchObject({
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Error thrown from user page',
+ mechanism: {
+ type: 'angular',
+ handled: false,
+ },
+ },
+ ],
+ },
+ transaction: '/users/:id/',
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/angular-20/tests/performance.test.ts
new file mode 100644
index 000000000000..f790cb10d180
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/tests/performance.test.ts
@@ -0,0 +1,326 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
+
+test('sends a pageload transaction with a parameterized URL', async ({ page }) => {
+ const transactionPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ await page.goto(`/`);
+
+ const rootSpan = await transactionPromise;
+
+ expect(rootSpan).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.angular',
+ },
+ },
+ transaction: '/home/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('sends a navigation transaction with a parameterized URL', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+ await pageloadTxnPromise;
+
+ await page.waitForTimeout(5000);
+
+ const [_, navigationTxn] = await Promise.all([page.locator('#navLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, pageloadTxn, navigationTxn] = await Promise.all([
+ page.locator('#navLink').click(),
+ pageloadTxnPromise,
+ navigationTxnPromise,
+ ]);
+
+ expect(pageloadTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.angular',
+ },
+ },
+ transaction: '/home/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('groups redirects within one navigation root span', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#redirectLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe('/redirect1');
+});
+
+test.describe('finish routing span', () => {
+ test('finishes routing span on navigation cancel', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#cancelLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/cancel',
+ transaction_info: {
+ source: 'url',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe('/cancel');
+ });
+
+ test('finishes routing span on navigation error', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#nonExistentLink').click(), navigationTxnPromise]);
+
+ const nonExistentRoute = '/non-existent';
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: nonExistentRoute,
+ transaction_info: {
+ source: 'url',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe(nonExistentRoute);
+ });
+});
+
+test.describe('TraceDirective', () => {
+ test('creates a child span with the component name as span name on ngOnInit', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const traceDirectiveSpans = navigationTxn.spans?.filter(
+ span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
+ );
+
+ expect(traceDirectiveSpans).toHaveLength(2);
+ expect(traceDirectiveSpans).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
+ },
+ description: '', // custom component name passed to trace directive
+ op: 'ui.angular.init',
+ origin: 'auto.ui.angular.trace_directive',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
+ },
+ description: '', // fallback selector name
+ op: 'ui.angular.init',
+ origin: 'auto.ui.angular.trace_directive',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ ]),
+ );
+ });
+});
+
+test.describe('TraceClass Decorator', () => {
+ test('adds init span for decorated class', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const classDecoratorSpan = navigationTxn.spans?.find(
+ span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_class_decorator',
+ );
+
+ expect(classDecoratorSpan).toBeDefined();
+ expect(classDecoratorSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator',
+ },
+ description: '',
+ op: 'ui.angular.init',
+ origin: 'auto.ui.angular.trace_class_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+});
+
+test.describe('TraceMethod Decorator', () => {
+ test('adds name to span description of decorated method `ngOnInit`', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const ngInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngOnInit');
+
+ expect(ngInitSpan).toBeDefined();
+ expect(ngInitSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngOnInit',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+ },
+ description: '',
+ op: 'ui.angular.ngOnInit',
+ origin: 'auto.ui.angular.trace_method_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+
+ test('adds fallback name to span description of decorated method `ngAfterViewInit`', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-20', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const ngAfterViewInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngAfterViewInit');
+
+ expect(ngAfterViewInitSpan).toBeDefined();
+ expect(ngAfterViewInitSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngAfterViewInit',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+ },
+ description: '',
+ op: 'ui.angular.ngAfterViewInit',
+ origin: 'auto.ui.angular.trace_method_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.app.json b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.app.json
new file mode 100644
index 000000000000..8886e903f8d0
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.app.json
@@ -0,0 +1,11 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"]
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.json b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.json
new file mode 100644
index 000000000000..5525117c6744
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.json
@@ -0,0 +1,27 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "outDir": "./dist/out-tsc",
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "experimentalDecorators": true,
+ "moduleResolution": "bundler",
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "ES2022"
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.spec.json b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.spec.json
new file mode 100644
index 000000000000..e00e30e6d4fb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-20/tsconfig.spec.json
@@ -0,0 +1,10 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": ["jasmine"]
+ },
+ "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
+}
From 349d7d0ed03a9e087f507c7292b80e3601bab767 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Mon, 26 May 2025 13:18:08 +0200
Subject: [PATCH 11/26] fix(browser): Move `browserTracingIntegration` code to
`setup` hook (#16386)
This PR removes the warning for multiple browserTracingIntegrations
being setup.
We realised this warning was actually just a symptom of us running a
bunch of the integration code in the function itself, not int `setup()`.
This lead to stuff running twice when users passed in a custom
integration in addition to a default one, because the logic is
basically:
```js
const defaultIntergations = [browserTracingIntegration()];
const usedDefinedIntegrations = [browserTracingIntegration()];
// at this point, the function was already executed twice, leading to side effects even without calling `init`!
```
Now, we move all the logic to setup/setupAfterAll, so this should be
safe. This means that the integration will be deduped anyhow (I added a
test to make sure this is the case), and we no longer need the warning.
fixes https://github.com/getsentry/sentry-javascript/issues/16369
---
.../multiple-integrations/init.js | 9 ---
.../multiple-integrations/test.ts | 21 ------
.../tracing/request/fetch-immediate/init.js | 14 ++++
.../tracing/request/fetch-immediate/test.ts | 40 ++++++++++
.../src/tracing/browserTracingIntegration.ts | 75 +++++++++----------
packages/core/test/lib/integration.test.ts | 31 +++++++-
.../react/src/reactrouterv6-compat-utils.tsx | 4 +-
7 files changed, 118 insertions(+), 76 deletions(-)
delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js
delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts
create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js
deleted file mode 100644
index 6d4dd43801b8..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as Sentry from '@sentry/browser';
-
-window.Sentry = Sentry;
-
-Sentry.init({
- dsn: 'https://public@dsn.ingest.sentry.io/1337',
- integrations: [Sentry.browserTracingIntegration(), Sentry.browserTracingIntegration()],
- tracesSampleRate: 1,
-});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts
deleted file mode 100644
index 1789bdf76c12..000000000000
--- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { expect } from '@playwright/test';
-import { sentryTest } from '../../../../utils/fixtures';
-import { shouldSkipTracingTest } from '../../../../utils/helpers';
-
-sentryTest('warns if multiple integrations are used', async ({ getLocalTestUrl, page }) => {
- if (shouldSkipTracingTest()) {
- sentryTest.skip();
- }
-
- const msgs: string[] = [];
-
- page.on('console', msg => {
- msgs.push(msg.text());
- });
-
- const url = await getLocalTestUrl({ testDir: __dirname });
-
- await page.goto(url);
-
- expect(msgs).toEqual(['Multiple browserTracingIntegration instances are not supported.']);
-});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js
new file mode 100644
index 000000000000..0a301839c169
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js
@@ -0,0 +1,14 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [Sentry.browserTracingIntegration()],
+ tracePropagationTargets: ['http://sentry-test-site.example'],
+ tracesSampleRate: 1,
+ autoSessionTracking: false,
+});
+
+// fetch directly after init
+fetch('http://sentry-test-site.example/0');
diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts
new file mode 100644
index 000000000000..7d11168acb1a
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts
@@ -0,0 +1,40 @@
+import { expect } from '@playwright/test';
+import { sentryTest } from '../../../../utils/fixtures';
+import {
+ envelopeRequestParser,
+ shouldSkipTracingTest,
+ waitForTransactionRequestOnUrl,
+} from '../../../../utils/helpers';
+
+sentryTest('should create spans for fetch requests called directly after init', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' }));
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+
+ const req = await waitForTransactionRequestOnUrl(page, url);
+ const tracingEvent = envelopeRequestParser(req);
+
+ const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client');
+
+ expect(requestSpans).toHaveLength(1);
+
+ expect(requestSpans![0]).toMatchObject({
+ description: 'GET http://sentry-test-site.example/0',
+ parent_span_id: tracingEvent.contexts?.trace?.span_id,
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ trace_id: tracingEvent.contexts?.trace?.trace_id,
+ data: {
+ 'http.method': 'GET',
+ 'http.url': 'http://sentry-test-site.example/0',
+ url: 'http://sentry-test-site.example/0',
+ 'server.address': 'sentry-test-site.example',
+ type: 'fetch',
+ },
+ });
+});
diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
index 0a4579f40774..d31fe41742f8 100644
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -3,7 +3,6 @@ import type { Client, IntegrationFn, Span, StartSpanOptions, TransactionSource,
import {
addNonEnumerableProperty,
browserPerformanceTimeOrigin,
- consoleSandbox,
generateTraceId,
getClient,
getCurrentScope,
@@ -233,8 +232,6 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
...defaultRequestInstrumentationOptions,
};
-let _hasBeenInitialized = false;
-
/**
* The Browser Tracing integration automatically instruments browser pageload/navigation
* actions as transactions, and captures requests, metrics and errors as spans.
@@ -245,14 +242,10 @@ let _hasBeenInitialized = false;
* We explicitly export the proper type here, as this has to be extended in some cases.
*/
export const browserTracingIntegration = ((_options: Partial = {}) => {
- if (_hasBeenInitialized) {
- consoleSandbox(() => {
- // eslint-disable-next-line no-console
- console.warn('Multiple browserTracingIntegration instances are not supported.');
- });
- }
-
- _hasBeenInitialized = true;
+ const latestRoute: RouteInfo = {
+ name: undefined,
+ source: undefined,
+ };
/**
* This is just a small wrapper that makes `document` optional.
@@ -260,8 +253,6 @@ export const browserTracingIntegration = ((_options: Partial void);
/** Create routing idle transaction. */
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions): void {
@@ -340,7 +307,9 @@ export const browserTracingIntegration = ((_options: Partial {
- _collectWebVitals();
+ // This will generally always be defined here, because it is set in `setup()` of the integration
+ // but technically, it is optional, so we guard here to be extra safe
+ _collectWebVitals?.();
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans });
setActiveIdleSpan(client, undefined);
@@ -378,8 +347,29 @@ export const browserTracingIntegration = ((_options: Partial {});
+
public constructor(name: string, tag?: string) {
this.name = name;
this.tag = tag;
}
-
- public setupOnce(): void {
- // noop
- }
}
type TestCase = [
@@ -74,6 +72,31 @@ describe('getIntegrationsToSetup', () => {
});
expect(integrations.map(i => i.name)).toEqual(expected);
});
+
+ test('it uses passed integration over default intergation', () => {
+ const integrationDefault = new MockIntegration('ChaseSquirrels');
+ const integration1 = new MockIntegration('ChaseSquirrels');
+
+ const integrations = getIntegrationsToSetup({
+ defaultIntegrations: [integrationDefault],
+ integrations: [integration1],
+ });
+
+ expect(integrations).toEqual([integration1]);
+ });
+
+ test('it uses last passed integration only', () => {
+ const integrationDefault = new MockIntegration('ChaseSquirrels');
+ const integration1 = new MockIntegration('ChaseSquirrels');
+ const integration2 = new MockIntegration('ChaseSquirrels');
+
+ const integrations = getIntegrationsToSetup({
+ defaultIntegrations: [integrationDefault],
+ integrations: [integration1, integration2],
+ });
+
+ expect(integrations).toEqual([integration2]);
+ });
});
describe('deduping', () => {
diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx
index 97109e191fcf..d9d0cbe45e8c 100644
--- a/packages/react/src/reactrouterv6-compat-utils.tsx
+++ b/packages/react/src/reactrouterv6-compat-utils.tsx
@@ -236,7 +236,9 @@ export function createReactRouterV6CompatibleTracingIntegration(
return {
...integration,
- setup() {
+ setup(client) {
+ integration.setup(client);
+
_useEffect = useEffect;
_useLocation = useLocation;
_useNavigationType = useNavigationType;
From 76b7f3a39d35acbd4c0c52eb1fd759fb12eb76a4 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Mon, 26 May 2025 13:19:50 +0200
Subject: [PATCH 12/26] feat(browser): Disable client when browser extension is
detected in `init()` (#16354)
Similar to https://github.com/getsentry/sentry-javascript/pull/16353,
this changes how default options for BrowserClient are handled. Instead
of building this in `init`, we now do (some) of it in BrowserClient.
Additionally, this also adjusts what we do if we detect a browser
extension: Instead of skipping the setup, we now just disable the
client. This streamlines this a bit and also ensures that we actually
always return a `Client` from init.
It also fixes the type for `BrowserOption` to actually allow to
configure `cdnBaseUrl`, which was actually only set for the
ClientOptions, oops.
The reason for this is to streamline the `init` code, making it easier
to extend/adjust it. Right now, there is a lot going on in different
places there. By moving as much as we can (and makes sense) to
`BrowserClient` this becomes a bit easier. While playing with different
ways to handle the browser extension stuff, I ended up landing on just
disabling the SDK in this scenario.
---
.../skip-init-browser-extension/test.ts | 7 +-
.../skip-init-chrome-extension/test.ts | 7 +-
packages/browser/src/client.ts | 40 ++++--
packages/browser/src/sdk.ts | 116 ++---------------
.../src/utils/detectBrowserExtension.ts | 65 ++++++++++
packages/browser/test/client.test.ts | 69 +++++++++-
packages/browser/test/sdk.test.ts | 120 ++----------------
7 files changed, 192 insertions(+), 232 deletions(-)
create mode 100644 packages/browser/src/utils/detectBrowserExtension.ts
diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts
index 5098b4aa6552..d949e5d2b19e 100644
--- a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts
+++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts
@@ -18,7 +18,12 @@ sentryTest(
return !!(window as any).Sentry.isInitialized();
});
- expect(isInitialized).toEqual(false);
+ const isEnabled = await page.evaluate(() => {
+ return !!(window as any).Sentry.getClient()?.getOptions().enabled;
+ });
+
+ expect(isInitialized).toEqual(true);
+ expect(isEnabled).toEqual(false);
if (hasDebugLogs()) {
expect(errorLogs.length).toEqual(1);
diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts
index 411fbd2f9db8..83996a83aad7 100644
--- a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts
+++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts
@@ -16,7 +16,12 @@ sentryTest('should not initialize when inside a Chrome browser extension', async
return !!(window as any).Sentry.isInitialized();
});
- expect(isInitialized).toEqual(false);
+ const isEnabled = await page.evaluate(() => {
+ return !!(window as any).Sentry.getClient()?.getOptions().enabled;
+ });
+
+ expect(isInitialized).toEqual(true);
+ expect(isEnabled).toEqual(false);
if (hasDebugLogs()) {
expect(errorLogs.length).toEqual(1);
diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts
index c0841d161ed6..5c50e53e708b 100644
--- a/packages/browser/src/client.ts
+++ b/packages/browser/src/client.ts
@@ -21,15 +21,24 @@ import { eventFromException, eventFromMessage } from './eventbuilder';
import { WINDOW } from './helpers';
import type { BrowserTransportOptions } from './transports/types';
+/**
+ * A magic string that build tooling can leverage in order to inject a release value into the SDK.
+ */
+declare const __SENTRY_RELEASE__: string | undefined;
+
const DEFAULT_FLUSH_INTERVAL = 5000;
+type BrowserSpecificOptions = BrowserClientReplayOptions &
+ BrowserClientProfilingOptions & {
+ /** If configured, this URL will be used as base URL for lazy loading integration. */
+ cdnBaseUrl?: string;
+ };
/**
* Configuration options for the Sentry Browser SDK.
* @see @sentry/core Options for more information.
*/
export type BrowserOptions = Options &
- BrowserClientReplayOptions &
- BrowserClientProfilingOptions & {
+ BrowserSpecificOptions & {
/**
* Important: Only set this option if you know what you are doing!
*
@@ -54,12 +63,7 @@ export type BrowserOptions = Options &
* Configuration options for the Sentry Browser SDK Client class
* @see BrowserClient for more information.
*/
-export type BrowserClientOptions = ClientOptions &
- BrowserClientReplayOptions &
- BrowserClientProfilingOptions & {
- /** If configured, this URL will be used as base URL for lazy loading integration. */
- cdnBaseUrl?: string;
- };
+export type BrowserClientOptions = ClientOptions & BrowserSpecificOptions;
/**
* The Sentry Browser SDK Client.
@@ -75,11 +79,7 @@ export class BrowserClient extends Client {
* @param options Configuration options for this SDK.
*/
public constructor(options: BrowserClientOptions) {
- const opts = {
- // We default this to true, as it is the safer scenario
- parentSpanIsAlwaysRootSpan: true,
- ...options,
- };
+ const opts = applyDefaultOptions(options);
const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource();
applySdkMetadata(opts, 'browser', ['browser'], sdkSource);
@@ -155,3 +155,17 @@ export class BrowserClient extends Client {
return super._prepareEvent(event, hint, currentScope, isolationScope);
}
}
+
+/** Exported only for tests. */
+export function applyDefaultOptions>(optionsArg: T): T {
+ return {
+ release:
+ typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value
+ ? __SENTRY_RELEASE__
+ : WINDOW.SENTRY_RELEASE?.id, // This supports the variable that sentry-webpack-plugin injects
+ sendClientReports: true,
+ // We default this to true, as it is the safer scenario
+ parentSpanIsAlwaysRootSpan: true,
+ ...optionsArg,
+ };
+}
diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts
index 56f3ace8f193..2101e4e36074 100644
--- a/packages/browser/src/sdk.ts
+++ b/packages/browser/src/sdk.ts
@@ -1,18 +1,14 @@
import type { Client, Integration, Options } from '@sentry/core';
import {
- consoleSandbox,
dedupeIntegration,
functionToStringIntegration,
getIntegrationsToSetup,
- getLocationHref,
inboundFiltersIntegration,
initAndBind,
stackParserFromStackParserOptions,
} from '@sentry/core';
import type { BrowserClientOptions, BrowserOptions } from './client';
import { BrowserClient } from './client';
-import { DEBUG_BUILD } from './debug-build';
-import { WINDOW } from './helpers';
import { breadcrumbsIntegration } from './integrations/breadcrumbs';
import { browserApiErrorsIntegration } from './integrations/browserapierrors';
import { browserSessionIntegration } from './integrations/browsersession';
@@ -21,22 +17,7 @@ import { httpContextIntegration } from './integrations/httpcontext';
import { linkedErrorsIntegration } from './integrations/linkederrors';
import { defaultStackParser } from './stack-parsers';
import { makeFetchTransport } from './transports/fetch';
-
-type ExtensionProperties = {
- chrome?: Runtime;
- browser?: Runtime;
- nw?: unknown;
-};
-type Runtime = {
- runtime?: {
- id?: string;
- };
-};
-
-/**
- * A magic string that build tooling can leverage in order to inject a release value into the SDK.
- */
-declare const __SENTRY_RELEASE__: string | undefined;
+import { checkAndWarnIfIsEmbeddedBrowserExtension } from './utils/detectBrowserExtension';
/** Get the default integrations for the browser SDK. */
export function getDefaultIntegrations(_options: Options): Integration[] {
@@ -59,40 +40,6 @@ export function getDefaultIntegrations(_options: Options): Integration[] {
];
}
-/** Exported only for tests. */
-export function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
- const defaultOptions: BrowserOptions = {
- defaultIntegrations: getDefaultIntegrations(optionsArg),
- release:
- typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value
- ? __SENTRY_RELEASE__
- : WINDOW.SENTRY_RELEASE?.id, // This supports the variable that sentry-webpack-plugin injects
- sendClientReports: true,
- };
-
- return {
- ...defaultOptions,
- ...dropTopLevelUndefinedKeys(optionsArg),
- };
-}
-
-/**
- * In contrast to the regular `dropUndefinedKeys` method,
- * this one does not deep-drop keys, but only on the top level.
- */
-function dropTopLevelUndefinedKeys(obj: T): Partial {
- const mutatetedObj: Partial = {};
-
- for (const k of Object.getOwnPropertyNames(obj)) {
- const key = k as keyof T;
- if (obj[key] !== undefined) {
- mutatetedObj[key] = obj[key];
- }
- }
-
- return mutatetedObj;
-}
-
/**
* The Sentry Browser SDK Client.
*
@@ -139,19 +86,21 @@ function dropTopLevelUndefinedKeys(obj: T): Partial {
*
* @see {@link BrowserOptions} for documentation on configuration options.
*/
-export function init(browserOptions: BrowserOptions = {}): Client | undefined {
- if (!browserOptions.skipBrowserExtensionCheck && _checkForBrowserExtension()) {
- return;
- }
+export function init(options: BrowserOptions = {}): Client | undefined {
+ const shouldDisableBecauseIsBrowserExtenstion =
+ !options.skipBrowserExtensionCheck && checkAndWarnIfIsEmbeddedBrowserExtension();
- const options = applyDefaultOptions(browserOptions);
const clientOptions: BrowserClientOptions = {
...options,
+ enabled: shouldDisableBecauseIsBrowserExtenstion ? false : options.enabled,
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
- integrations: getIntegrationsToSetup(options),
+ integrations: getIntegrationsToSetup({
+ integrations: options.integrations,
+ defaultIntegrations:
+ options.defaultIntegrations == null ? getDefaultIntegrations(options) : options.defaultIntegrations,
+ }),
transport: options.transport || makeFetchTransport,
};
-
return initAndBind(BrowserClient, clientOptions);
}
@@ -170,48 +119,3 @@ export function forceLoad(): void {
export function onLoad(callback: () => void): void {
callback();
}
-
-function _isEmbeddedBrowserExtension(): boolean {
- if (typeof WINDOW.window === 'undefined') {
- // No need to show the error if we're not in a browser window environment (e.g. service workers)
- return false;
- }
-
- const _window = WINDOW as typeof WINDOW & ExtensionProperties;
-
- // Running the SDK in NW.js, which appears like a browser extension but isn't, is also fine
- // see: https://github.com/getsentry/sentry-javascript/issues/12668
- if (_window.nw) {
- return false;
- }
-
- const extensionObject = _window['chrome'] || _window['browser'];
-
- if (!extensionObject?.runtime?.id) {
- return false;
- }
-
- const href = getLocationHref();
- const extensionProtocols = ['chrome-extension', 'moz-extension', 'ms-browser-extension', 'safari-web-extension'];
-
- // Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage
- const isDedicatedExtensionPage =
- WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}://`));
-
- return !isDedicatedExtensionPage;
-}
-
-function _checkForBrowserExtension(): true | void {
- if (_isEmbeddedBrowserExtension()) {
- if (DEBUG_BUILD) {
- consoleSandbox(() => {
- // eslint-disable-next-line no-console
- console.error(
- '[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
- );
- });
- }
-
- return true;
- }
-}
diff --git a/packages/browser/src/utils/detectBrowserExtension.ts b/packages/browser/src/utils/detectBrowserExtension.ts
new file mode 100644
index 000000000000..52e667ccecf2
--- /dev/null
+++ b/packages/browser/src/utils/detectBrowserExtension.ts
@@ -0,0 +1,65 @@
+import { consoleSandbox, getLocationHref } from '@sentry/core';
+import { DEBUG_BUILD } from '../debug-build';
+import { WINDOW } from '../helpers';
+
+type ExtensionRuntime = {
+ runtime?: {
+ id?: string;
+ };
+};
+type ExtensionProperties = {
+ chrome?: ExtensionRuntime;
+ browser?: ExtensionRuntime;
+ nw?: unknown;
+};
+
+/**
+ * Returns true if the SDK is running in an embedded browser extension.
+ * Stand-alone browser extensions (which do not share the same data as the main browser page) are fine.
+ */
+export function checkAndWarnIfIsEmbeddedBrowserExtension(): boolean {
+ if (_isEmbeddedBrowserExtension()) {
+ if (DEBUG_BUILD) {
+ consoleSandbox(() => {
+ // eslint-disable-next-line no-console
+ console.error(
+ '[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
+ );
+ });
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+function _isEmbeddedBrowserExtension(): boolean {
+ if (typeof WINDOW.window === 'undefined') {
+ // No need to show the error if we're not in a browser window environment (e.g. service workers)
+ return false;
+ }
+
+ const _window = WINDOW as typeof WINDOW & ExtensionProperties;
+
+ // Running the SDK in NW.js, which appears like a browser extension but isn't, is also fine
+ // see: https://github.com/getsentry/sentry-javascript/issues/12668
+ if (_window.nw) {
+ return false;
+ }
+
+ const extensionObject = _window['chrome'] || _window['browser'];
+
+ if (!extensionObject?.runtime?.id) {
+ return false;
+ }
+
+ const href = getLocationHref();
+ const extensionProtocols = ['chrome-extension', 'moz-extension', 'ms-browser-extension', 'safari-web-extension'];
+
+ // Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage
+ const isDedicatedExtensionPage =
+ WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}://`));
+
+ return !isDedicatedExtensionPage;
+}
diff --git a/packages/browser/test/client.test.ts b/packages/browser/test/client.test.ts
index a90f8cdbc388..c0f4e649501a 100644
--- a/packages/browser/test/client.test.ts
+++ b/packages/browser/test/client.test.ts
@@ -4,7 +4,7 @@
import * as sentryCore from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { BrowserClient } from '../src/client';
+import { applyDefaultOptions, BrowserClient } from '../src/client';
import { WINDOW } from '../src/helpers';
import { getDefaultBrowserClientOptions } from './helper/browser-client-options';
@@ -118,3 +118,70 @@ describe('BrowserClient', () => {
});
});
});
+
+describe('applyDefaultOptions', () => {
+ it('works with empty options', () => {
+ const options = {};
+ const actual = applyDefaultOptions(options);
+
+ expect(actual).toEqual({
+ release: undefined,
+ sendClientReports: true,
+ parentSpanIsAlwaysRootSpan: true,
+ });
+ });
+
+ it('works with options', () => {
+ const options = {
+ tracesSampleRate: 0.5,
+ release: '1.0.0',
+ };
+ const actual = applyDefaultOptions(options);
+
+ expect(actual).toEqual({
+ release: '1.0.0',
+ sendClientReports: true,
+ tracesSampleRate: 0.5,
+ parentSpanIsAlwaysRootSpan: true,
+ });
+ });
+
+ it('picks up release from WINDOW.SENTRY_RELEASE.id', () => {
+ const releaseBefore = WINDOW.SENTRY_RELEASE;
+
+ WINDOW.SENTRY_RELEASE = { id: '1.0.0' };
+ const options = {
+ tracesSampleRate: 0.5,
+ };
+ const actual = applyDefaultOptions(options);
+
+ expect(actual).toEqual({
+ release: '1.0.0',
+ sendClientReports: true,
+ tracesSampleRate: 0.5,
+ parentSpanIsAlwaysRootSpan: true,
+ });
+
+ WINDOW.SENTRY_RELEASE = releaseBefore;
+ });
+
+ it('passed in release takes precedence over WINDOW.SENTRY_RELEASE.id', () => {
+ const releaseBefore = WINDOW.SENTRY_RELEASE;
+
+ WINDOW.SENTRY_RELEASE = { id: '1.0.0' };
+ const options = {
+ release: '2.0.0',
+ tracesSampleRate: 0.5,
+ };
+ const actual = applyDefaultOptions(options);
+
+ expect(actual).toEqual({
+ release: '2.0.0',
+ sendClientReports: true,
+ tracesSampleRate: 0.5,
+ parentSpanIsAlwaysRootSpan: true,
+ });
+
+ WINDOW.SENTRY_RELEASE = releaseBefore;
+ });
+});
diff --git a/packages/browser/test/sdk.test.ts b/packages/browser/test/sdk.test.ts
index 342b008bfc18..b7972797182f 100644
--- a/packages/browser/test/sdk.test.ts
+++ b/packages/browser/test/sdk.test.ts
@@ -7,10 +7,10 @@ import type { Integration } from '@sentry/core';
import * as SentryCore from '@sentry/core';
import { createTransport, resolvedSyncPromise } from '@sentry/core';
import type { Mock } from 'vitest';
-import { afterAll, afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest';
+import { afterEach, describe, expect, it, test, vi } from 'vitest';
import type { BrowserOptions } from '../src';
import { WINDOW } from '../src';
-import { applyDefaultOptions, getDefaultIntegrations, init } from '../src/sdk';
+import { init } from '../src/sdk';
const PUBLIC_DSN = 'https://username@domain/123';
@@ -32,15 +32,11 @@ export class MockIntegration implements Integration {
}
describe('init', () => {
- beforeEach(() => {
- vi.clearAllMocks();
+ afterEach(() => {
+ vi.restoreAllMocks();
});
- afterAll(() => {
- vi.resetAllMocks();
- });
-
- test('installs default integrations', () => {
+ test('installs passed default integrations', () => {
const DEFAULT_INTEGRATIONS: Integration[] = [
new MockIntegration('MockIntegration 0.1'),
new MockIntegration('MockIntegration 0.2'),
@@ -134,7 +130,7 @@ describe('init', () => {
Object.defineProperty(WINDOW, 'browser', { value: undefined, writable: true });
Object.defineProperty(WINDOW, 'nw', { value: undefined, writable: true });
Object.defineProperty(WINDOW, 'window', { value: WINDOW, writable: true });
- vi.clearAllMocks();
+ vi.restoreAllMocks();
});
it('logs a browser extension error if executed inside a Chrome extension', () => {
@@ -151,8 +147,6 @@ describe('init', () => {
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
-
- consoleErrorSpy.mockRestore();
});
it('logs a browser extension error if executed inside a Firefox/Safari extension', () => {
@@ -166,8 +160,6 @@ describe('init', () => {
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
-
- consoleErrorSpy.mockRestore();
});
it.each(['chrome-extension', 'moz-extension', 'ms-browser-extension', 'safari-web-extension'])(
@@ -224,7 +216,7 @@ describe('init', () => {
consoleErrorSpy.mockRestore();
});
- it("doesn't return a client on initialization error", () => {
+ it('returns a disabled client on initialization error', () => {
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
Object.defineProperty(WINDOW, 'chrome', {
@@ -234,7 +226,9 @@ describe('init', () => {
const client = init(options);
- expect(client).toBeUndefined();
+ expect(client).toBeDefined();
+ expect(SentryCore.isEnabled()).toBe(false);
+ expect(client!['_isEnabled']()).toBe(false);
consoleErrorSpy.mockRestore();
});
@@ -245,97 +239,3 @@ describe('init', () => {
expect(client).not.toBeUndefined();
});
});
-
-describe('applyDefaultOptions', () => {
- test('it works with empty options', () => {
- const options = {};
- const actual = applyDefaultOptions(options);
-
- expect(actual).toEqual({
- defaultIntegrations: expect.any(Array),
- release: undefined,
- sendClientReports: true,
- });
-
- expect((actual.defaultIntegrations as { name: string }[]).map(i => i.name)).toEqual(
- getDefaultIntegrations(options).map(i => i.name),
- );
- });
-
- test('it works with options', () => {
- const options = {
- tracesSampleRate: 0.5,
- release: '1.0.0',
- };
- const actual = applyDefaultOptions(options);
-
- expect(actual).toEqual({
- defaultIntegrations: expect.any(Array),
- release: '1.0.0',
- sendClientReports: true,
- tracesSampleRate: 0.5,
- });
-
- expect((actual.defaultIntegrations as { name: string }[]).map(i => i.name)).toEqual(
- getDefaultIntegrations(options).map(i => i.name),
- );
- });
-
- test('it works with defaultIntegrations=false', () => {
- const options = {
- defaultIntegrations: false,
- } as const;
- const actual = applyDefaultOptions(options);
-
- expect(actual.defaultIntegrations).toStrictEqual(false);
- });
-
- test('it works with defaultIntegrations=[]', () => {
- const options = {
- defaultIntegrations: [],
- };
- const actual = applyDefaultOptions(options);
-
- expect(actual.defaultIntegrations).toEqual([]);
- });
-
- test('it works with tracesSampleRate=undefined', () => {
- const options = {
- tracesSampleRate: undefined,
- } as const;
- const actual = applyDefaultOptions(options);
-
- // Not defined, not even undefined
- expect('tracesSampleRate' in actual).toBe(false);
- });
-
- test('it works with tracesSampleRate=null', () => {
- const options = {
- tracesSampleRate: null,
- } as any;
- const actual = applyDefaultOptions(options);
-
- expect(actual.tracesSampleRate).toStrictEqual(null);
- });
-
- test('it works with tracesSampleRate=0', () => {
- const options = {
- tracesSampleRate: 0,
- } as const;
- const actual = applyDefaultOptions(options);
-
- expect(actual.tracesSampleRate).toStrictEqual(0);
- });
-
- test('it does not deep-drop undefined keys', () => {
- const options = {
- obj: {
- prop: undefined,
- },
- } as any;
- const actual = applyDefaultOptions(options) as any;
-
- expect('prop' in actual.obj).toBe(true);
- expect(actual.obj.prop).toStrictEqual(undefined);
- });
-});
From 2801d9b76a936d1d90e2f8fb81d800023f901ed8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 26 May 2025 18:33:23 +0200
Subject: [PATCH 13/26] feat(deps): bump @opentelemetry/semantic-conventions
from 1.32.0 to 1.34.0 (#16393)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps
[@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js)
from 1.32.0 to 1.34.0.
Release notes
Sourced from @opentelemetry/semantic-conventions
's
releases .
semconv/v1.34.0
1.34.0
:rocket: Features
feat: update semantic conventions to v1.34.0 #5703
@trentm
Semantic Conventions v1.34.0: changelog
| latest
docs
@opentelemetry/semantic-conventions
(stable) changes:
none
@opentelemetry/semantic-conventions/incubating
(unstable) changes: 12 added exports
Unstable changes in v1.34.0
ATTR_AWS_BEDROCK_GUARDRAIL_ID //
aws.bedrock.guardrail.id
ATTR_AWS_BEDROCK_KNOWLEDGE_BASE_ID // aws.bedrock.knowledge_base.id
ATTR_AWS_KINESIS_STREAM_NAME // aws.kinesis.stream_name
ATTR_AWS_LAMBDA_RESOURCE_MAPPING_ID // aws.lambda.resource_mapping.id
ATTR_AWS_SECRETSMANAGER_SECRET_ARN // aws.secretsmanager.secret.arn
ATTR_AWS_SNS_TOPIC_ARN // aws.sns.topic.arn
ATTR_AWS_SQS_QUEUE_URL // aws.sqs.queue.url
ATTR_AWS_STEP_FUNCTIONS_ACTIVITY_ARN // aws.step_functions.activity.arn
ATTR_AWS_STEP_FUNCTIONS_STATE_MACHINE_ARN //
aws.step_functions.state_machine.arn
ATTR_FEATURE_FLAG_RESULT_VALUE // feature_flag.result.value
ATTR_GEN_AI_CONVERSATION_ID // gen_ai.conversation.id
ATTR_GEN_AI_DATA_SOURCE_ID // gen_ai.data_source.id
semconv/v1.33.1
1.33.1
:boom: Breaking Changes
fix: Remove the subset of DB_SYSTEM_NAME_VALUE_*
exports that are unstable from the
@opentelemetry/semantic-conventions
entry point. #5690
Version 1.33.0 erroneously included all
DB_SYSTEM_NAME_VALUE_*
constants in the stable entry point.
Some of those enum values are not yet stable. They have been moved back
to the unstable
@opentelemetry/semantic-conventions/incubating
entry point.
See the PR description for a full list.
semconv/v1.33.0
1.33.0
:rocket: Features
... (truncated)
Commits
a2e0f2c
chore: prepare next release (#5705 )
019cb2c
feat(semantic-conventions): update semantic conventions to v1.34.0 (#5703 )
7a9b049
chore: enable tsconfig isolatedModules (#5697 )
6d0d33b
chore: prepare next release (#5700 )
280cd50
test(instrumentation-fetch): pin msw dep to avoid breakage in msw@2.8
(#5702 )
1c8ec3b
fix(semantic-conventions): Remove the subset of
DB_SYSTEM_NAME_VALUE_*
expo...
85f1bcc
chore(opentelemetry-instrumentation-http): Fix link to docs (#5699 )
f0ba942
chore(deps): update dependency @babel/core
to v7.27.1 (#5698 )
af9c024
chore(deps): update all patch versions (#5694 )
9dbd1e4
chore: prepare next release (#5696 )
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
dev-packages/opentelemetry-v2-tests/package.json | 2 +-
packages/nestjs/package.json | 2 +-
packages/nextjs/package.json | 2 +-
packages/node/package.json | 2 +-
packages/opentelemetry/package.json | 4 ++--
packages/react-router/package.json | 2 +-
packages/remix/package.json | 2 +-
packages/tanstackstart-react/package.json | 2 +-
packages/vercel-edge/package.json | 2 +-
yarn.lock | 8 ++++----
10 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/dev-packages/opentelemetry-v2-tests/package.json b/dev-packages/opentelemetry-v2-tests/package.json
index 80f43c01c982..216989ffb022 100644
--- a/dev-packages/opentelemetry-v2-tests/package.json
+++ b/dev-packages/opentelemetry-v2-tests/package.json
@@ -16,7 +16,7 @@
"@opentelemetry/core": "^2.0.0",
"@opentelemetry/instrumentation": "^0.200.0",
"@opentelemetry/sdk-trace-base": "^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.30.0"
+ "@opentelemetry/semantic-conventions": "^1.34.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json
index ca439183048c..e13dbd717cdc 100644
--- a/packages/nestjs/package.json
+++ b/packages/nestjs/package.json
@@ -48,7 +48,7 @@
"@opentelemetry/core": "^1.30.1",
"@opentelemetry/instrumentation": "0.57.2",
"@opentelemetry/instrumentation-nestjs-core": "0.44.1",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/core": "9.22.0",
"@sentry/node": "9.22.0"
},
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 28e85accecf2..2678530141b3 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -77,7 +77,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@rollup/plugin-commonjs": "28.0.1",
"@sentry-internal/browser-utils": "9.22.0",
"@sentry/core": "9.22.0",
diff --git a/packages/node/package.json b/packages/node/package.json
index 88572cc9d500..5898f2d33496 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -93,7 +93,7 @@
"@opentelemetry/instrumentation-undici": "0.10.1",
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-trace-base": "^1.30.1",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@prisma/instrumentation": "6.7.0",
"@sentry/core": "9.22.0",
"@sentry/opentelemetry": "9.22.0",
diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json
index b6b378d1d3da..b50d85981653 100644
--- a/packages/opentelemetry/package.json
+++ b/packages/opentelemetry/package.json
@@ -47,7 +47,7 @@
"@opentelemetry/core": "^1.30.1 || ^2.0.0",
"@opentelemetry/instrumentation": "^0.57.1 || ^0.200.0",
"@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.30.0"
+ "@opentelemetry/semantic-conventions": "^1.34.0"
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
@@ -55,7 +55,7 @@
"@opentelemetry/core": "^1.30.1",
"@opentelemetry/instrumentation": "^0.57.2",
"@opentelemetry/sdk-trace-base": "^1.30.1",
- "@opentelemetry/semantic-conventions": "^1.30.0"
+ "@opentelemetry/semantic-conventions": "^1.34.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index e488a2029acc..3ee797048354 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -37,7 +37,7 @@
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^1.30.1",
"@opentelemetry/instrumentation": "0.57.2",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/browser": "9.22.0",
"@sentry/cli": "^2.43.0",
"@sentry/core": "9.22.0",
diff --git a/packages/remix/package.json b/packages/remix/package.json
index be1bf30ad1d9..decdce331500 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -66,7 +66,7 @@
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@remix-run/router": "1.x",
"@sentry/cli": "^2.43.0",
"@sentry/core": "9.22.0",
diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json
index d8930ee14811..b95d4b34db00 100644
--- a/packages/tanstackstart-react/package.json
+++ b/packages/tanstackstart-react/package.json
@@ -51,7 +51,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry-internal/browser-utils": "9.22.0",
"@sentry/core": "9.22.0",
"@sentry/node": "9.22.0",
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
index 2af29ebfa807..88be3c33b114 100644
--- a/packages/vercel-edge/package.json
+++ b/packages/vercel-edge/package.json
@@ -47,7 +47,7 @@
"@opentelemetry/core": "^1.30.1",
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-trace-base": "^1.30.1",
- "@opentelemetry/semantic-conventions": "^1.28.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/opentelemetry": "9.22.0"
},
"scripts": {
diff --git a/yarn.lock b/yarn.lock
index 67dbf24aac88..862445d20e69 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5744,10 +5744,10 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6"
integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==
-"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.28.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0":
- version "1.32.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.32.0.tgz#a15e8f78f32388a7e4655e7f539570e40958ca3f"
- integrity sha512-s0OpmpQFSfMrmedAn9Lhg4KWJELHCU6uU9dtIJ28N8UGhf9Y55im5X8fEzwhwDwiSqN+ZPSNrDJF7ivf/AuRPQ==
+"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.34.0":
+ version "1.34.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz#8b6a46681b38a4d5947214033ac48128328c1738"
+ integrity sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==
"@opentelemetry/sql-common@^0.40.1":
version "0.40.1"
From 5c056278ee77d45740505e4337adf2f64fec60c4 Mon Sep 17 00:00:00 2001
From: Tim Fish
Date: Mon, 26 May 2025 19:08:37 +0200
Subject: [PATCH 14/26] feat(core): Export `_INTERNAL_captureSerializedLog`
(#16387)
I renamed `defaultCaptureSerializedLog` to
`_INTERNAL_captureSerializedLog` and exported it so it can be accessed
from other SDKs.
I will use this in the Electron SDK in the root main process to add logs
passed by IPC from other processes to the queue.
---
packages/core/src/index.ts | 2 +-
packages/core/src/logs/exports.ts | 13 +++++++++++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index a67f003aac56..986d18a972d2 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -119,7 +119,7 @@ export { trpcMiddleware } from './trpc';
export { wrapMcpServerWithSentry } from './mcp-server';
export { captureFeedback } from './feedback';
export type { ReportDialogOptions } from './report-dialog';
-export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer } from './logs/exports';
+export { _INTERNAL_captureLog, _INTERNAL_flushLogsBuffer, _INTERNAL_captureSerializedLog } from './logs/exports';
export { consoleLoggingIntegration } from './logs/console-integration';
export type { FeatureFlag } from './featureFlags';
diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts
index 41f1155ee3e1..9a738d503a80 100644
--- a/packages/core/src/logs/exports.ts
+++ b/packages/core/src/logs/exports.ts
@@ -61,7 +61,16 @@ export function logAttributeToSerializedLogAttribute(value: unknown): Serialized
}
}
-function defaultCaptureSerializedLog(client: Client, serializedLog: SerializedLog): void {
+/**
+ * Captures a serialized log event and adds it to the log buffer for the given client.
+ *
+ * @param client - A client. Uses the current client if not provided.
+ * @param serializedLog - The serialized log event to capture.
+ *
+ * @experimental This method will experience breaking changes. This is not yet part of
+ * the stable Sentry SDK API and can be changed or removed without warning.
+ */
+export function _INTERNAL_captureSerializedLog(client: Client, serializedLog: SerializedLog): void {
const logBuffer = _INTERNAL_getLogBuffer(client);
if (logBuffer === undefined) {
GLOBAL_OBJ._sentryClientToLogBufferMap?.set(client, [serializedLog]);
@@ -88,7 +97,7 @@ export function _INTERNAL_captureLog(
beforeLog: Log,
client: Client | undefined = getClient(),
scope = getCurrentScope(),
- captureSerializedLog: (client: Client, log: SerializedLog) => void = defaultCaptureSerializedLog,
+ captureSerializedLog: (client: Client, log: SerializedLog) => void = _INTERNAL_captureSerializedLog,
): void {
if (!client) {
DEBUG_BUILD && logger.warn('No client available to capture log.');
From e46c619474ddfbecf7c12c2a5630b5af70e66427 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Mon, 26 May 2025 19:10:48 +0200
Subject: [PATCH 15/26] fix(opentelemetry): Ensure `withScope` keeps span
active & `_getTraceInfoFromScope` works (#16385)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Also ensure that `withScope(scope, callback)` maintains the active span
from the passed in scope.
Fixes https://github.com/getsentry/sentry-javascript/issues/16361
The problem here was that we used the `_getSpanForScope` method in core
for this, but this only actually works for the core implementation, not
the node one 😬 So I rewrote this to use more general utilities, which
should work isomorphically. While doing this, I noticed an actual bug in
the `withScope` implementation in otel, where we did not actually keep
the active span correctly.
---
packages/core/src/client.ts | 19 ++++++++++---------
packages/core/test/lib/tracing/trace.test.ts | 13 +++++++++++++
.../opentelemetry/src/asyncContextStrategy.ts | 4 ++--
packages/opentelemetry/test/trace.test.ts | 15 +++++++++++++++
4 files changed, 40 insertions(+), 11 deletions(-)
diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts
index 0dda6c86fd26..d9abd8f5d0d2 100644
--- a/packages/core/src/client.ts
+++ b/packages/core/src/client.ts
@@ -1,7 +1,7 @@
/* eslint-disable max-lines */
import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
import { DEFAULT_ENVIRONMENT } from './constants';
-import { getCurrentScope, getIsolationScope, getTraceContextFromScope } from './currentScopes';
+import { getCurrentScope, getIsolationScope, getTraceContextFromScope, withScope } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import { createEventEnvelope, createSessionEnvelope } from './envelope';
import type { IntegrationIndex } from './integration';
@@ -36,8 +36,7 @@ import { getPossibleEventMessages } from './utils/eventUtils';
import { merge } from './utils/merge';
import { parseSampleRate } from './utils/parseSampleRate';
import { prepareEvent } from './utils/prepareEvent';
-import { _getSpanForScope } from './utils/spanOnScope';
-import { showSpanDropWarning, spanToTraceContext } from './utils/spanUtils';
+import { getActiveSpan, showSpanDropWarning, spanToTraceContext } from './utils/spanUtils';
import { convertSpanJsonToTransactionEvent, convertTransactionEventToSpanJson } from './utils/transactionEvent';
import { createClientReportEnvelope } from './utils-hoist/clientreport';
import { dsnToString, makeDsn } from './utils-hoist/dsn';
@@ -1325,10 +1324,12 @@ export function _getTraceInfoFromScope(
return [undefined, undefined];
}
- const span = _getSpanForScope(scope);
- const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope);
- const dynamicSamplingContext = span
- ? getDynamicSamplingContextFromSpan(span)
- : getDynamicSamplingContextFromScope(client, scope);
- return [dynamicSamplingContext, traceContext];
+ return withScope(scope, () => {
+ const span = getActiveSpan();
+ const traceContext = span ? spanToTraceContext(span) : getTraceContextFromScope(scope);
+ const dynamicSamplingContext = span
+ ? getDynamicSamplingContextFromSpan(span)
+ : getDynamicSamplingContextFromScope(client, scope);
+ return [dynamicSamplingContext, traceContext];
+ });
}
diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts
index 0e0d16fb6ec4..83f4b150a6d6 100644
--- a/packages/core/test/lib/tracing/trace.test.ts
+++ b/packages/core/test/lib/tracing/trace.test.ts
@@ -1781,6 +1781,19 @@ describe('getActiveSpan', () => {
const result = getActiveSpan();
expect(result).toBe(staticSpan);
});
+
+ it('handles active span when passing scopes to withScope', () => {
+ const [scope, span] = startSpan({ name: 'outer' }, span => {
+ return [getCurrentScope(), span];
+ });
+
+ const spanOnScope = withScope(scope, () => {
+ return getActiveSpan();
+ });
+
+ expect(spanOnScope).toBeDefined();
+ expect(spanOnScope).toBe(span);
+ });
});
describe('withActiveSpan()', () => {
diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts
index 695175bc3fa1..9f7b38d0b43d 100644
--- a/packages/opentelemetry/src/asyncContextStrategy.ts
+++ b/packages/opentelemetry/src/asyncContextStrategy.ts
@@ -8,7 +8,7 @@ import {
} from './constants';
import { continueTrace, startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
import type { CurrentScopes } from './types';
-import { getScopesFromContext } from './utils/contextData';
+import { getContextFromScope, getScopesFromContext } from './utils/contextData';
import { getActiveSpan } from './utils/getActiveSpan';
import { getTraceData } from './utils/getTraceData';
import { suppressTracing } from './utils/suppressTracing';
@@ -48,7 +48,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
}
function withSetScope(scope: Scope, callback: (scope: Scope) => T): T {
- const ctx = api.context.active();
+ const ctx = getContextFromScope(scope) || api.context.active();
// We depend on the otelContextManager to handle the context/hub
// We set the `SENTRY_FORK_SET_SCOPE_CONTEXT_KEY` context value, which is picked up by
diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts
index 74b37809f4ae..f9aed823a4a4 100644
--- a/packages/opentelemetry/test/trace.test.ts
+++ b/packages/opentelemetry/test/trace.test.ts
@@ -1342,6 +1342,21 @@ describe('trace', () => {
});
});
});
+
+ describe('scope passing', () => {
+ it('handles active span when passing scopes to withScope', () => {
+ const [scope, span] = startSpan({ name: 'outer' }, span => {
+ return [getCurrentScope(), span];
+ });
+
+ const spanOnScope = withScope(scope, () => {
+ return getActiveSpan();
+ });
+
+ expect(spanOnScope).toBeDefined();
+ expect(spanOnScope).toBe(span);
+ });
+ });
});
describe('trace (tracing disabled)', () => {
From c297bf99ae0f9f8bf0d62d686c0f7735e249bc1a Mon Sep 17 00:00:00 2001
From: "Benjamin E. Coe"
Date: Mon, 26 May 2025 13:46:34 -0400
Subject: [PATCH 16/26] feat(browser): option to ignore certain resource types
(#16389)
Allows specific resource spans to be ignored by passing an array of strings to ignoreResourceSpans. Example values include, resource.css, resource.script.
Co-authored-by: Lukas Stracke
---
.../resource-spans-ignored/assets/script.js | 3 ++
.../resource-spans-ignored/init.js | 14 ++++++
.../resource-spans-ignored/test.ts | 20 ++++++++
.../src/metrics/browserMetrics.ts | 25 +++++++++-
.../test/browser/browserMetrics.test.ts | 47 +++++++++++++++++++
.../src/tracing/browserTracingIntegration.ts | 11 ++++-
6 files changed, 117 insertions(+), 3 deletions(-)
create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/assets/script.js
create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js
create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/assets/script.js
new file mode 100644
index 000000000000..eab583b75a75
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/assets/script.js
@@ -0,0 +1,3 @@
+(() => {
+ // I do nothing.
+})();
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js
new file mode 100644
index 000000000000..70c0b30a03a5
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/init.js
@@ -0,0 +1,14 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [
+ Sentry.browserTracingIntegration({
+ ignoreResourceSpans: ['resource.script'],
+ idleTimeout: 9000,
+ }),
+ ],
+ tracesSampleRate: 1,
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts
new file mode 100644
index 000000000000..4bc9621f8395
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/resource-spans-ignored/test.ts
@@ -0,0 +1,20 @@
+import type { Route } from '@playwright/test';
+import { expect } from '@playwright/test';
+import type { Event } from '@sentry/core';
+import { sentryTest } from '../../../../utils/fixtures';
+import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
+
+sentryTest('should allow specific types of resource spans to be ignored.', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+
+ const eventData = await getFirstSentryEnvelopeRequest(page, url);
+ const allSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource.script'));
+
+ expect(allSpans?.length).toBe(0);
+});
diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts
index 71470a0d8706..d5ca039c65f0 100644
--- a/packages/browser-utils/src/metrics/browserMetrics.ts
+++ b/packages/browser-utils/src/metrics/browserMetrics.ts
@@ -300,6 +300,13 @@ interface AddPerformanceEntriesOptions {
* sent as a standalone span instead.
*/
recordClsOnPageloadSpan: boolean;
+
+ /**
+ * Resource spans with `op`s matching strings in the array will not be emitted.
+ *
+ * Default: []
+ */
+ ignoreResourceSpans: Array<'resouce.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;
}
/** Add performance related spans to a transaction */
@@ -355,7 +362,15 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
break;
}
case 'resource': {
- _addResourceSpans(span, entry as PerformanceResourceTiming, entry.name, startTime, duration, timeOrigin);
+ _addResourceSpans(
+ span,
+ entry as PerformanceResourceTiming,
+ entry.name,
+ startTime,
+ duration,
+ timeOrigin,
+ options.ignoreResourceSpans,
+ );
break;
}
// Ignore other entry types.
@@ -568,6 +583,7 @@ export function _addResourceSpans(
startTime: number,
duration: number,
timeOrigin: number,
+ ignoreResourceSpans?: Array,
): void {
// we already instrument based on fetch and xhr, so we don't need to
// duplicate spans here.
@@ -575,6 +591,11 @@ export function _addResourceSpans(
return;
}
+ const op = entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other';
+ if (ignoreResourceSpans?.includes(op)) {
+ return;
+ }
+
const parsedUrl = parseUrl(resourceUrl);
const attributes: SpanAttributes = {
@@ -616,7 +637,7 @@ export function _addResourceSpans(
startAndEndSpan(span, startTimestamp, endTimestamp, {
name: resourceUrl.replace(WINDOW.location.origin, ''),
- op: entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other',
+ op,
attributes,
});
}
diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts
index 99cf451f824e..87646a690f0e 100644
--- a/packages/browser-utils/test/browser/browserMetrics.test.ts
+++ b/packages/browser-utils/test/browser/browserMetrics.test.ts
@@ -271,6 +271,53 @@ describe('_addResourceSpans', () => {
}
});
+ it('allows resource spans to be ignored via ignoreResourceSpans', () => {
+ const spans: Span[] = [];
+ const ignoredResourceSpans = ['resource.other', 'resource.script'];
+
+ getClient()?.on('spanEnd', span => {
+ spans.push(span);
+ });
+
+ const table = [
+ {
+ initiatorType: undefined,
+ op: 'resource.other',
+ },
+ {
+ initiatorType: 'css',
+ op: 'resource.css',
+ },
+ {
+ initiatorType: 'css',
+ op: 'resource.css',
+ },
+ {
+ initiatorType: 'image',
+ op: 'resource.image',
+ },
+ {
+ initiatorType: 'script',
+ op: 'resource.script',
+ },
+ ];
+ for (const row of table) {
+ const { initiatorType } = row;
+ const entry = mockPerformanceResourceTiming({
+ initiatorType,
+ nextHopProtocol: 'http/1.1',
+ });
+ _addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465, ignoredResourceSpans);
+ }
+ expect(spans).toHaveLength(table.length - ignoredResourceSpans.length);
+ const spanOps = new Set(
+ spans.map(s => {
+ return spanToJSON(s).op;
+ }),
+ );
+ expect(spanOps).toEqual(new Set(['resource.css', 'resource.image']));
+ });
+
it('allows for enter size of 0', () => {
const spans: Span[] = [];
diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts
index d31fe41742f8..3f38bdb6a8be 100644
--- a/packages/browser/src/tracing/browserTracingIntegration.ts
+++ b/packages/browser/src/tracing/browserTracingIntegration.ts
@@ -144,6 +144,13 @@ export interface BrowserTracingOptions {
*/
enableHTTPTimings: boolean;
+ /**
+ * Resource spans with `op`s matching strings in the array will not be emitted.
+ *
+ * Default: []
+ */
+ ignoreResourceSpans: Array;
+
/**
* Link the currently started trace to a previous trace (e.g. a prior pageload, navigation or
* manually started span). When enabled, this option will allow you to navigate between traces
@@ -226,6 +233,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
enableLongTask: true,
enableLongAnimationFrame: true,
enableInp: true,
+ ignoreResourceSpans: [],
linkPreviousTrace: 'in-memory',
consistentTraceSampling: false,
_experiments: {},
@@ -268,6 +276,7 @@ export const browserTracingIntegration = ((_options: Partial
Date: Tue, 27 May 2025 08:00:29 +0100
Subject: [PATCH 17/26] fix(node): Don't warn about Spotlight on empty NODE_ENV
(#16381)
When running with `spotlight: true` I got a warning about not being in
development mode when `NODE_ENV` was not set. This typically indicates
dev mode so added a check for that.
---
packages/node/src/integrations/spotlight.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts
index 917fe073e194..4e36f3692fb0 100644
--- a/packages/node/src/integrations/spotlight.ts
+++ b/packages/node/src/integrations/spotlight.ts
@@ -20,7 +20,12 @@ const _spotlightIntegration = ((options: Partial = {
return {
name: INTEGRATION_NAME,
setup(client) {
- if (typeof process === 'object' && process.env && process.env.NODE_ENV !== 'development') {
+ if (
+ typeof process === 'object' &&
+ process.env &&
+ process.env.NODE_ENV &&
+ process.env.NODE_ENV !== 'development'
+ ) {
logger.warn("[Spotlight] It seems you're not in dev mode. Do you really want to have Spotlight enabled?");
}
connectToSpotlight(client, _options);
From 1a72ed3bc7c6763235673eba38ad6527c111bdfa Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 May 2025 09:39:46 +0200
Subject: [PATCH 18/26] feat(deps): bump @sentry/cli from 2.43.0 to 2.45.0
(#16395)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps [@sentry/cli](https://github.com/getsentry/sentry-cli) from 2.43.0
to 2.45.0.
Release notes
Sourced from @sentry/cli
's
releases .
2.45.0
New feature
Various fixes & improvements
2.44.0
Various fixes & improvements
2.43.1
Various fixes & improvements
Changelog
Sourced from @sentry/cli
's
changelog .
2.45.0
New feature
Various fixes & improvements
2.44.0
Various fixes & improvements
2.43.1
Various fixes & improvements
Commits
bed438e
meta: Update CHANGELOG.md
7839225
release: 2.45.0
47e426d
ref: Rename fixup_js_file_end
(#2475 )
f5ea48c
feat(sourcemaps): Multi-project sourcemaps upload (#2497 )
f540dc1
ref: Use slice instead of vec for assemble artifact request
8a63fec
ref: Separate LegacyUploadContext
for legacy uploads
b241c4a
feat: Remove organization and project info log (#2490 )
046ab9d
chore: update issue templates (#2493 )
4a0270f
build(deps): bump actions/create-github-app-token from 2.0.2 to 2.0.6
(#2485 )
1571be7
build(deps): bump github/codeql-action from 3.28.16 to 3.28.17
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
packages/react-router/package.json | 2 +-
packages/remix/package.json | 2 +-
yarn.lock | 88 +++++++++++++++---------------
3 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 3ee797048354..ac3872ff45c0 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -39,7 +39,7 @@
"@opentelemetry/instrumentation": "0.57.2",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/browser": "9.22.0",
- "@sentry/cli": "^2.43.0",
+ "@sentry/cli": "^2.45.0",
"@sentry/core": "9.22.0",
"@sentry/node": "9.22.0",
"@sentry/vite-plugin": "^3.2.4",
diff --git a/packages/remix/package.json b/packages/remix/package.json
index decdce331500..52c657ab1999 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -68,7 +68,7 @@
"@opentelemetry/instrumentation": "^0.57.2",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@remix-run/router": "1.x",
- "@sentry/cli": "^2.43.0",
+ "@sentry/cli": "^2.45.0",
"@sentry/core": "9.22.0",
"@sentry/node": "9.22.0",
"@sentry/opentelemetry": "9.22.0",
diff --git a/yarn.lock b/yarn.lock
index 862445d20e69..62182bda070f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6545,75 +6545,75 @@
resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.42.2.tgz#a32a4f226e717122b37d9969e8d4d0e14779f720"
integrity sha512-GtJSuxER7Vrp1IpxdUyRZzcckzMnb4N5KTW7sbTwUiwqARRo+wxS+gczYrS8tdgtmXs5XYhzhs+t4d52ITHMIg==
-"@sentry/cli-darwin@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.43.0.tgz#544caac85069a34d435b0723d8ac6bb6a530ab33"
- integrity sha512-0MYvRHJowXOMNY5W6XF4p9GQNH3LuQ+IHAQwVbZOsfwnEv8e20rf9BiPPzmJ9sIjZSWYR4yIqm6dBp6ABJFbGQ==
+"@sentry/cli-darwin@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.45.0.tgz#e3d6feae4fadcfdf91db9c7b9c4689a66d3d8d19"
+ integrity sha512-p4Uxfv/L2fQdP3/wYnKVVz9gzZJf/1Xp9D+6raax/3Bu5y87yHYUqcdt98y/VAXQD4ofp2QgmhGUVPofvQNZmg==
"@sentry/cli-linux-arm64@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.42.2.tgz#1c06c83ff21f51ec23acf5be3b1f8c7553bf86b1"
integrity sha512-BOxzI7sgEU5Dhq3o4SblFXdE9zScpz6EXc5Zwr1UDZvzgXZGosUtKVc7d1LmkrHP8Q2o18HcDWtF3WvJRb5Zpw==
-"@sentry/cli-linux-arm64@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.43.0.tgz#e110758c7cf6ced78d8b0b3c8c56802984b0fb98"
- integrity sha512-7URSaNjbEJQZyYJ33XK3pVKl6PU2oO9ETF6R/4Cz2FmU3fecACLKVldv7+OuNl9aspLZ62mnPMDvT732/Fp2Ug==
+"@sentry/cli-linux-arm64@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.45.0.tgz#384c8e17f7e7dc007d164033d0e7c75aa83a2e9b"
+ integrity sha512-gUcLoEjzg7AIc4QQGEZwRHri+EHf3Gcms9zAR1VHiNF3/C/jL4WeDPJF2YiWAQt6EtH84tHiyhw1Ab/R8XFClg==
"@sentry/cli-linux-arm@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.42.2.tgz#00cadc359ae3c051efb3e63873c033c61dbd1ca1"
integrity sha512-7udCw+YL9lwq+9eL3WLspvnuG+k5Icg92YE7zsteTzWLwgPVzaxeZD2f8hwhsu+wmL+jNqbpCRmktPteh3i2mg==
-"@sentry/cli-linux-arm@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.43.0.tgz#00d36a7347480b8b693cf0ed5cff270ad40045e7"
- integrity sha512-c2Fwb6HrFL1nbaGV4uRhHC1wEJPR+wfpKN5y06PgSNNbd10YrECAB3tqBHXC8CEmhuDyFR+ORGZ7VbswfCWEEQ==
+"@sentry/cli-linux-arm@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.45.0.tgz#b9d6f86f3934b4d9ced5b45a8158ff2ac2bdd25d"
+ integrity sha512-6sEskFLlFKJ+e0MOYgIclBTUX5jYMyYhHIxXahEkI/4vx6JO0uvpyRAkUJRpJkRh/lPog0FM+tbP3so+VxB2qQ==
"@sentry/cli-linux-i686@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.42.2.tgz#3b817b715dd806c20dfbffd539725ad8089c310a"
integrity sha512-Sw/dQp5ZPvKnq3/y7wIJyxTUJYPGoTX/YeMbDs8BzDlu9to2LWV3K3r7hE7W1Lpbaw4tSquUHiQjP5QHCOS7aQ==
-"@sentry/cli-linux-i686@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.43.0.tgz#83a31f47a13d8b3fae2716f86412fae2ffec5cf4"
- integrity sha512-bFo/tpMZeMJ275HPGmAENREchnBxhALOOpZAphSyalUu3pGZ+EETEtlSLrKyVNJo26Dye5W7GlrYUV9+rkyCtg==
+"@sentry/cli-linux-i686@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.45.0.tgz#39e22beb84cfa26e11bdc198364315fdfb4da4d5"
+ integrity sha512-VmmOaEAzSW23YdGNdy/+oQjCNAMY+HmOGA77A25/ep/9AV7PQB6FI7xO5Y1PVvlkxZFJ23e373njSsEeg4uDZw==
"@sentry/cli-linux-x64@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.42.2.tgz#ddf906bc3071cc79ce6e633eddcb76bb9068e688"
integrity sha512-mU4zUspAal6TIwlNLBV5oq6yYqiENnCWSxtSQVzWs0Jyq97wtqGNG9U+QrnwjJZ+ta/hvye9fvL2X25D/RxHQw==
-"@sentry/cli-linux-x64@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.43.0.tgz#dac1641c0c862d5d9f45c2b2c0d77b0281c53261"
- integrity sha512-EbAmKXUNU/Ii4pNGVRCepU6ks1M43wStMKx3pibrUTllrrCwqYKyPxRRdoFYySHkduwCxnoKZcLEg9vWZ3qS6A==
+"@sentry/cli-linux-x64@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.45.0.tgz#25cd3699297f9433835fb5edd42dad722c11f041"
+ integrity sha512-a0Oj68mrb25a0WjX/ShZ6AAd4PPiuLcgyzQr7bl2+DvYxIOajwkGbR+CZFEhOVZcfhTnixKy/qIXEzApEPHPQg==
-"@sentry/cli-win32-arm64@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.43.0.tgz#ec499fc49c380252984ae5cde6b59ca0dbad79ef"
- integrity sha512-KmJRCdQQGLSErJvrcGcN+yWo68m+5OdluhyJHsVYMOQknwu8YMOWLm12EIa+4t4GclDvwg5xcxLccCuiWMJUZw==
+"@sentry/cli-win32-arm64@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.45.0.tgz#50c7d29ea2169bdb4d98bbde81c5f7dac0dd3955"
+ integrity sha512-vn+CwS4p+52pQSLNPoi20ZOrQmv01ZgAmuMnjkh1oUZfTyBAwWLrAh6Cy4cztcN8DfL5dOWKQBo8DBKURE4ttg==
"@sentry/cli-win32-i686@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.42.2.tgz#9036085c7c6ce455ad45fda411c55ff39c06eb95"
integrity sha512-iHvFHPGqgJMNqXJoQpqttfsv2GI3cGodeTq4aoVLU/BT3+hXzbV0x1VpvvEhncJkDgDicJpFLM8sEPHb3b8abw==
-"@sentry/cli-win32-i686@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.43.0.tgz#bf686d387e91d7897cfbd96a681a52816f90fb0b"
- integrity sha512-ZWxZdOyZX7NJ/CTskzg+dJ2xTpobFLXVNMOMq0HiwdhqXP2zYYJzKnIt3mHNJYA40zYFODGSgxIamodjpB8BuA==
+"@sentry/cli-win32-i686@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.45.0.tgz#201075c4aec37a3e797160e0b468641245437f0c"
+ integrity sha512-8mMoDdlwxtcdNIMtteMK7dbi7054jak8wKSHJ5yzMw8UmWxC5thc/gXBc1uPduiaI56VjoJV+phWHBKCD+6I4w==
"@sentry/cli-win32-x64@2.42.2":
version "2.42.2"
resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.42.2.tgz#7d6464b63f32c9f97fff428f246b1f039b402233"
integrity sha512-vPPGHjYoaGmfrU7xhfFxG7qlTBacroz5NdT+0FmDn6692D8IvpNXl1K+eV3Kag44ipJBBeR8g1HRJyx/F/9ACw==
-"@sentry/cli-win32-x64@2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.43.0.tgz#9d5e7b9888c0e639d3e62e4dd7a58468103b6bce"
- integrity sha512-S/IRQYAziEnjpyROhnqzTqShDq3m8jcevXx+q5f49uQnFbfYcTgS1sdrEPqqao/K2boOWbffxYtTkvBiB/piQQ==
+"@sentry/cli-win32-x64@2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.45.0.tgz#2075e9e1ea3c3609e0fa1a758ca033e94e1c600f"
+ integrity sha512-ZvK9cIqFaq7vZ0jkHJ/xh5au6902Dr+AUxSk6L6vCL7JCe2p93KGL/4d8VFB5PD/P7Y9b+105G/e0QIFKzpeOw==
"@sentry/cli@2.42.2":
version "2.42.2"
@@ -6634,10 +6634,10 @@
"@sentry/cli-win32-i686" "2.42.2"
"@sentry/cli-win32-x64" "2.42.2"
-"@sentry/cli@^2.36.1", "@sentry/cli@^2.43.0":
- version "2.43.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.43.0.tgz#2b0dcb749a1529faeb2c239962307c5a2e33f817"
- integrity sha512-gBE3bkx+PBJxopTrzIJLX4xHe5S0w87q5frIveWKDZ5ulVIU6YWnVumay0y07RIEweUEj3IYva1qH6HG2abfiA==
+"@sentry/cli@^2.36.1", "@sentry/cli@^2.45.0":
+ version "2.45.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.45.0.tgz#35feed7a2fee54faf25daed73001a2a2a3143396"
+ integrity sha512-4sWu7zgzgHAjIxIjXUA/66qgeEf5ZOlloO+/JaGD5qXNSW0G7KMTR6iYjReNKMgdBCTH6bUUt9qiuA+Ex9Masw==
dependencies:
https-proxy-agent "^5.0.0"
node-fetch "^2.6.7"
@@ -6645,14 +6645,14 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
optionalDependencies:
- "@sentry/cli-darwin" "2.43.0"
- "@sentry/cli-linux-arm" "2.43.0"
- "@sentry/cli-linux-arm64" "2.43.0"
- "@sentry/cli-linux-i686" "2.43.0"
- "@sentry/cli-linux-x64" "2.43.0"
- "@sentry/cli-win32-arm64" "2.43.0"
- "@sentry/cli-win32-i686" "2.43.0"
- "@sentry/cli-win32-x64" "2.43.0"
+ "@sentry/cli-darwin" "2.45.0"
+ "@sentry/cli-linux-arm" "2.45.0"
+ "@sentry/cli-linux-arm64" "2.45.0"
+ "@sentry/cli-linux-i686" "2.45.0"
+ "@sentry/cli-linux-x64" "2.45.0"
+ "@sentry/cli-win32-arm64" "2.45.0"
+ "@sentry/cli-win32-i686" "2.45.0"
+ "@sentry/cli-win32-x64" "2.45.0"
"@sentry/rollup-plugin@3.4.0":
version "3.4.0"
From 33ef453e206d0ab6930566ed2d4daff237eee24f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 May 2025 09:40:28 +0200
Subject: [PATCH 19/26] feat(deps): bump @sentry/webpack-plugin from 3.3.1 to
3.5.0 (#16394)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps
[@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins)
from 3.3.1 to 3.5.0.
Release notes
Sourced from @sentry/webpack-plugin
's
releases .
3.5.0
feat(core): Add hook to customize source map file resolution (#732 )
fix(core): Avoid console output and telemetry init when plugins are
disabled (#741 )
Work in this release was contributed by @thecodewarrior
.
Thank you for your contribution!
3.4.0
fix: Replace existing debug ID comments (#730 )
feat: Expose bundler plugin primitives via
createSentryBuildPluginManager
(#714 )
Changelog
Sourced from @sentry/webpack-plugin
's
changelog .
3.5.0
feat(core): Add hook to customize source map file resolution (#732 )
fix(core): Avoid console output and telemetry init when plugins are
disabled (#741 )
Work in this release was contributed by @thecodewarrior
.
Thank you for your contribution!
3.4.0
fix: Replace existing debug ID comments (#730 )
feat: Expose bundler plugin primitives via
createSentryBuildPluginManager
(#714 )
Commits
20e59da
release: 3.5.0
bab95d9
meta: Add Changelog entry for 3.5.0 (#743 )
4dcccca
chore(deps-dev): bump rollup from 2.75.7 to 2.79.2 in
/packages/webpack-plugi...
7130a86
fix(core): Avoid console output and telemetry init when plugins are
disabled ...
3ba65eb
chore: Add contributor attribution to changelog (#738 )
b33725e
feat(core): Add hook to customize source map file resolution (#732 )
7b097fc
Merge branch 'release/3.4.0'
0e15c25
release: 3.4.0
30c15df
meta: Update changelog for 3.4.0 (#733 )
7bc20a7
fix: Replace existing debug ID comments (#730 )
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
packages/gatsby/package.json | 2 +-
packages/nextjs/package.json | 2 +-
yarn.lock | 40 ++++++++++++++++++------------------
3 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json
index e9da408c67b3..eb5c18783e24 100644
--- a/packages/gatsby/package.json
+++ b/packages/gatsby/package.json
@@ -47,7 +47,7 @@
"dependencies": {
"@sentry/core": "9.22.0",
"@sentry/react": "9.22.0",
- "@sentry/webpack-plugin": "3.3.1"
+ "@sentry/webpack-plugin": "3.5.0"
},
"peerDependencies": {
"gatsby": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 2678530141b3..a34c83f2f909 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -85,7 +85,7 @@
"@sentry/opentelemetry": "9.22.0",
"@sentry/react": "9.22.0",
"@sentry/vercel-edge": "9.22.0",
- "@sentry/webpack-plugin": "3.3.1",
+ "@sentry/webpack-plugin": "3.5.0",
"chalk": "3.0.0",
"resolve": "1.22.8",
"rollup": "4.35.0",
diff --git a/yarn.lock b/yarn.lock
index 62182bda070f..a8d0c62e5467 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6474,16 +6474,16 @@
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.2.4.tgz#c0877df6e5ce227bf51754bf27da2fa5227af847"
integrity sha512-yBzRn3GEUSv1RPtE4xB4LnuH74ZxtdoRJ5cmQ9i6mzlmGDxlrnKuvem5++AolZTE9oJqAD3Tx2rd1PqmpWnLoA==
-"@sentry/babel-plugin-component-annotate@3.3.1":
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.3.1.tgz#baecd89396cbb4659565a4e8efe7f0a71b19262a"
- integrity sha512-5GOxGT7lZN+I8A7Vp0rWY+726FDKEw8HnFiebe51rQrMbfGfCu2Aw9uSM0nT9OG6xhV6WvGccIcCszTPs4fUZQ==
-
"@sentry/babel-plugin-component-annotate@3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.4.0.tgz#f47a7652e16f84556df82cbc38f0004bca1335d1"
integrity sha512-tSzfc3aE7m0PM0Aj7HBDet5llH9AB9oc+tBQ8AvOqUSnWodLrNCuWeQszJ7mIBovD3figgCU3h0cvI6U5cDtsg==
+"@sentry/babel-plugin-component-annotate@3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz#1b0d01f903b725da876117d551610085c3dd21c7"
+ integrity sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==
+
"@sentry/bundler-plugin-core@2.22.6":
version "2.22.6"
resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d"
@@ -6512,13 +6512,13 @@
magic-string "0.30.8"
unplugin "1.0.1"
-"@sentry/bundler-plugin-core@3.3.1":
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.3.1.tgz#67c5017dc8a70f629c14e88420c6ede4e51c2047"
- integrity sha512-Dd6xaWb293j9otEJ1yJqG2Ra6zB49OPzMNdIkdP8wdY+S9UFQE5PyKTyredmPY7hqCc005OrUQZolIIo9Zl13A==
+"@sentry/bundler-plugin-core@3.4.0":
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.4.0.tgz#3a3459aba94cbeb093347f5730f15df25153fd0a"
+ integrity sha512-X1Q41AsQ6xcT6hB4wYmBDBukndKM/inT4IsR7pdKLi7ICpX2Qq6lisamBAEPCgEvnLpazSFguaiC0uiwMKAdqw==
dependencies:
"@babel/core" "^7.18.5"
- "@sentry/babel-plugin-component-annotate" "3.3.1"
+ "@sentry/babel-plugin-component-annotate" "3.4.0"
"@sentry/cli" "2.42.2"
dotenv "^16.3.1"
find-up "^5.0.0"
@@ -6526,13 +6526,13 @@
magic-string "0.30.8"
unplugin "1.0.1"
-"@sentry/bundler-plugin-core@3.4.0":
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.4.0.tgz#3a3459aba94cbeb093347f5730f15df25153fd0a"
- integrity sha512-X1Q41AsQ6xcT6hB4wYmBDBukndKM/inT4IsR7pdKLi7ICpX2Qq6lisamBAEPCgEvnLpazSFguaiC0uiwMKAdqw==
+"@sentry/bundler-plugin-core@3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz#b62af5be1b1a862e7062181655829c556c7d7c0b"
+ integrity sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ==
dependencies:
"@babel/core" "^7.18.5"
- "@sentry/babel-plugin-component-annotate" "3.4.0"
+ "@sentry/babel-plugin-component-annotate" "3.5.0"
"@sentry/cli" "2.42.2"
dotenv "^16.3.1"
find-up "^5.0.0"
@@ -6678,12 +6678,12 @@
"@sentry/bundler-plugin-core" "3.2.4"
unplugin "1.0.1"
-"@sentry/webpack-plugin@3.3.1":
- version "3.3.1"
- resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.3.1.tgz#b257e1cb5f939b68f5050e9c4ea040d7366a55de"
- integrity sha512-AFRnGNUnlIvq3M+ADdfWb+DIXWKK6yYEkVPAyOppkjO+cL/19gjXMdvAwv+CMFts28YCFKF8Kr3pamUiCmwodA==
+"@sentry/webpack-plugin@3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz#cde95534f1e945a4002d47465aeda01d382cd279"
+ integrity sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ==
dependencies:
- "@sentry/bundler-plugin-core" "3.3.1"
+ "@sentry/bundler-plugin-core" "3.5.0"
unplugin "1.0.1"
uuid "^9.0.0"
From 56137a861c3238ab776fcb4b97ba04bdf5b746bd Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 27 May 2025 09:41:33 +0200
Subject: [PATCH 20/26] feat(deps): bump @prisma/instrumentation from 6.7.0 to
6.8.2 (#16392)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps
[@prisma/instrumentation](https://github.com/prisma/prisma/tree/HEAD/packages/instrumentation)
from 6.7.0 to 6.8.2.
Release notes
Sourced from @prisma/instrumentation
's
releases .
6.8.2
Today, we are issuing the 6.8.2 patch release. It fully resolves an
issue with the prisma init
and prisma dev
commands for some Windows users who were still facing problems after the
previous incomplete fix in version 6.8.1.
Fixes:
6.8.1
Today, we are issuing the 6.8.1 patch release. It fixes an issue with
the prisma init
and prisma dev
commands on
Windows.
Fixes
6.8.0
Today, we are excited to share the 6.8.0
stable
release 🎉
🌟 Help us spread the word about Prisma by starring the
repo ☝️ or posting
on X about the release.
Highlights
Local development with Prisma Postgres via prisma dev
(Early Access)
In this release, we're releasing a way to develop against Prisma
Postgres locally — no Docker required!
To get started, run the new prisma dev
command:
npx prisma dev # starts a local Prisma Postgres
server
This command spins up a local Prisma Postgres instance and prints the
connection URL that you'll need to set as the url
of your
datasource
block to point to a local Prisma Postgres
instance. It looks similar to this:
datasource db {
provider = "postgresql"
url = "prisma+postgres://localhost:51213/?api_key=ey..."
}
You can then run migrations and execute queries against this local
Prisma Postgres instance as with any remote one. Note that you need to
keep the prisma dev
process running in order to interact
with the local Prisma Postgres instance.
📚 Learn more in the docs .
Native Deno support in prisma-client
generator
(Preview)
In this release, we're removing the deno
Preview feature
from the prisma-client-js
generator. If you want to use
Prisma ORM with Deno, you can now do so with the new prisma-client
generator:
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
</tr></table>
... (truncated)
Commits
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
packages/node/package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/node/package.json b/packages/node/package.json
index 5898f2d33496..12dccfc9555c 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -94,7 +94,7 @@
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.34.0",
- "@prisma/instrumentation": "6.7.0",
+ "@prisma/instrumentation": "6.8.2",
"@sentry/core": "9.22.0",
"@sentry/opentelemetry": "9.22.0",
"import-in-the-middle": "^1.13.1",
diff --git a/yarn.lock b/yarn.lock
index a8d0c62e5467..1b5d8e707ce3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5879,10 +5879,10 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73"
integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
-"@prisma/instrumentation@6.7.0":
- version "6.7.0"
- resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.7.0.tgz#5fd97be1f89e9d9268148424a812deaea491f80a"
- integrity sha512-3NuxWlbzYNevgPZbV0ktA2z6r0bfh0g22ONTxcK09a6+6MdIPjHsYx1Hnyu4yOq+j7LmupO5J69hhuOnuvj8oQ==
+"@prisma/instrumentation@6.8.2":
+ version "6.8.2"
+ resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.8.2.tgz#77a87a37f67ab35eaaf8ff629f889e9e11a465ac"
+ integrity sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q==
dependencies:
"@opentelemetry/instrumentation" "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0"
From bdbd54170b06b74bd231ee008d39d84095def308 Mon Sep 17 00:00:00 2001
From: Charly Gomez
Date: Wed, 28 May 2025 11:06:09 +0200
Subject: [PATCH 21/26] feat(nextjs): Include `static/chunks/main-*` files for
`widenClientFileUpload` (#16406)
---
packages/nextjs/src/config/webpackPluginOptions.ts | 8 ++++++--
.../test/config/webpack/webpackPluginOptions.test.ts | 4 +---
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts
index 335c7bea8976..641efd77524c 100644
--- a/packages/nextjs/src/config/webpackPluginOptions.ts
+++ b/packages/nextjs/src/config/webpackPluginOptions.ts
@@ -41,11 +41,15 @@ export function getWebpackPluginOptions(
);
}
- // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations.
+ // We want to include main-* files if widenClientFileUpload is true as they have proven to be useful
+ if (!sentryBuildOptions.widenClientFileUpload) {
+ sourcemapUploadIgnore.push(path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'));
+ }
+
+ // Always ignore framework, polyfills, and webpack files
sourcemapUploadIgnore.push(
path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework-*'),
path.posix.join(distDirAbsPath, 'static', 'chunks', 'framework.*'),
- path.posix.join(distDirAbsPath, 'static', 'chunks', 'main-*'),
path.posix.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'),
path.posix.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'),
);
diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
index 1dd0cfa95d5b..76ab58be9b64 100644
--- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
+++ b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts
@@ -153,9 +153,9 @@ describe('getWebpackPluginOptions()', () => {
expect(generatedPluginOptions.sourcemaps).toMatchObject({
assets: ['/my/project/dir/.next/static/chunks/pages/**', '/my/project/dir/.next/static/chunks/app/**'],
ignore: [
+ '/my/project/dir/.next/static/chunks/main-*',
'/my/project/dir/.next/static/chunks/framework-*',
'/my/project/dir/.next/static/chunks/framework.*',
- '/my/project/dir/.next/static/chunks/main-*',
'/my/project/dir/.next/static/chunks/polyfills-*',
'/my/project/dir/.next/static/chunks/webpack-*',
],
@@ -170,7 +170,6 @@ describe('getWebpackPluginOptions()', () => {
ignore: [
'/my/project/dir/.next/static/chunks/framework-*',
'/my/project/dir/.next/static/chunks/framework.*',
- '/my/project/dir/.next/static/chunks/main-*',
'/my/project/dir/.next/static/chunks/polyfills-*',
'/my/project/dir/.next/static/chunks/webpack-*',
],
@@ -197,7 +196,6 @@ describe('getWebpackPluginOptions()', () => {
ignore: [
'C:/my/windows/project/dir/.dist/v1/static/chunks/framework-*',
'C:/my/windows/project/dir/.dist/v1/static/chunks/framework.*',
- 'C:/my/windows/project/dir/.dist/v1/static/chunks/main-*',
'C:/my/windows/project/dir/.dist/v1/static/chunks/polyfills-*',
'C:/my/windows/project/dir/.dist/v1/static/chunks/webpack-*',
],
From 90f6619ce33b7bec43de41cc64df5603417e1aa7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ivan=20Pe=C5=A1i=C4=87?=
Date: Wed, 28 May 2025 11:26:45 +0200
Subject: [PATCH 22/26] feat(nuxt): Added support for nuxt layers (#16372)
Added ability to detect and import config files from the **active** nuxt
layer with strategy of the **most latest** layer taking precedence.
Included fallback behavior for good measure.
Rest of the config should be merged as it already is by c12 and defu.
Don't think this PR covers the git:repo based layers but those are
generally not full supported as well in Nuxt 3 ecosystem.
Added basic tests
Resolves
[#14345](https://github.com/getsentry/sentry-javascript/issues/14345)
---------
Co-authored-by: Ivan Pesic
---
packages/nuxt/src/module.ts | 4 +-
packages/nuxt/src/vite/utils.ts | 36 +++++++++++---
packages/nuxt/test/vite/utils.test.ts | 70 +++++++++++++++++++++++++++
3 files changed, 100 insertions(+), 10 deletions(-)
diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts
index a7e81a49d045..377508e860a2 100644
--- a/packages/nuxt/src/module.ts
+++ b/packages/nuxt/src/module.ts
@@ -35,7 +35,7 @@ export default defineNuxtModule({
const moduleDirResolver = createResolver(import.meta.url);
const buildDirResolver = createResolver(nuxt.options.buildDir);
- const clientConfigFile = findDefaultSdkInitFile('client');
+ const clientConfigFile = findDefaultSdkInitFile('client', nuxt);
if (clientConfigFile) {
// Inject the client-side Sentry config file with a side effect import
@@ -59,7 +59,7 @@ export default defineNuxtModule({
addPlugin({ src: moduleDirResolver.resolve('./runtime/plugins/sentry.client'), mode: 'client' });
}
- const serverConfigFile = findDefaultSdkInitFile('server');
+ const serverConfigFile = findDefaultSdkInitFile('server', nuxt);
if (serverConfigFile) {
addServerPlugin(moduleDirResolver.resolve('./runtime/plugins/sentry.server'));
diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts
index ea2db2bc21b8..f1ef1c9e4cf2 100644
--- a/packages/nuxt/src/vite/utils.ts
+++ b/packages/nuxt/src/vite/utils.ts
@@ -1,29 +1,49 @@
import { consoleSandbox } from '@sentry/core';
import * as fs from 'fs';
+import type { Nuxt } from 'nuxt/schema';
import * as path from 'path';
/**
* Find the default SDK init file for the given type (client or server).
* The sentry.server.config file is prioritized over the instrument.server file.
*/
-export function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
+export function findDefaultSdkInitFile(type: 'server' | 'client', nuxt?: Nuxt): string | undefined {
const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'];
- const cwd = process.cwd();
+ const relativePaths: string[] = [];
- const filePaths: string[] = [];
if (type === 'server') {
for (const ext of possibleFileExtensions) {
- // order is important here - we want to prioritize the server.config file
- filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`));
- filePaths.push(path.join(cwd, 'public', `instrument.${type}.${ext}`));
+ relativePaths.push(`sentry.${type}.config.${ext}`);
+ relativePaths.push(path.join('public', `instrument.${type}.${ext}`));
}
} else {
for (const ext of possibleFileExtensions) {
- filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`));
+ relativePaths.push(`sentry.${type}.config.${ext}`);
+ }
+ }
+
+ // Get layers from highest priority to lowest
+ const layers = [...(nuxt?.options._layers ?? [])].reverse();
+
+ for (const layer of layers) {
+ for (const relativePath of relativePaths) {
+ const fullPath = path.resolve(layer.cwd, relativePath);
+ if (fs.existsSync(fullPath)) {
+ return fullPath;
+ }
+ }
+ }
+
+ // As a fallback, also check CWD (left for pure compatibility)
+ const cwd = process.cwd();
+ for (const relativePath of relativePaths) {
+ const fullPath = path.resolve(cwd, relativePath);
+ if (fs.existsSync(fullPath)) {
+ return fullPath;
}
}
- return filePaths.find(filename => fs.existsSync(filename));
+ return undefined;
}
/**
diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts
index 7ffd7654549e..f19ec98b4b64 100644
--- a/packages/nuxt/test/vite/utils.test.ts
+++ b/packages/nuxt/test/vite/utils.test.ts
@@ -1,4 +1,5 @@
import * as fs from 'fs';
+import type { Nuxt } from 'nuxt/schema';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
constructFunctionReExport,
@@ -69,6 +70,75 @@ describe('findDefaultSdkInitFile', () => {
const result = findDefaultSdkInitFile('server');
expect(result).toMatch('packages/nuxt/sentry.server.config.js');
});
+
+ it('should return the latest layer config file path if client config exists', () => {
+ vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
+ return !(filePath instanceof URL) && filePath.includes('sentry.client.config.ts');
+ });
+
+ const nuxtMock = {
+ options: {
+ _layers: [
+ {
+ cwd: 'packages/nuxt/module',
+ },
+ {
+ cwd: 'packages/nuxt',
+ },
+ ],
+ },
+ } as Nuxt;
+
+ const result = findDefaultSdkInitFile('client', nuxtMock);
+ expect(result).toMatch('packages/nuxt/sentry.client.config.ts');
+ });
+
+ it('should return the latest layer config file path if server config exists', () => {
+ vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
+ return (
+ !(filePath instanceof URL) &&
+ (filePath.includes('sentry.server.config.ts') || filePath.includes('instrument.server.ts'))
+ );
+ });
+
+ const nuxtMock = {
+ options: {
+ _layers: [
+ {
+ cwd: 'packages/nuxt/module',
+ },
+ {
+ cwd: 'packages/nuxt',
+ },
+ ],
+ },
+ } as Nuxt;
+
+ const result = findDefaultSdkInitFile('server', nuxtMock);
+ expect(result).toMatch('packages/nuxt/sentry.server.config.ts');
+ });
+
+ it('should return the latest layer config file path if client config exists in former layer', () => {
+ vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
+ return !(filePath instanceof URL) && filePath.includes('nuxt/sentry.client.config.ts');
+ });
+
+ const nuxtMock = {
+ options: {
+ _layers: [
+ {
+ cwd: 'packages/nuxt/module',
+ },
+ {
+ cwd: 'packages/nuxt',
+ },
+ ],
+ },
+ } as Nuxt;
+
+ const result = findDefaultSdkInitFile('client', nuxtMock);
+ expect(result).toMatch('packages/nuxt/sentry.client.config.ts');
+ });
});
describe('getFilenameFromPath', () => {
From 5bbe3754dcf946948bb0bce18b0e00f22b14e7ba Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Wed, 28 May 2025 11:56:27 +0200
Subject: [PATCH 23/26] feat: Export `isEnabled` from all SDKs (#16405)
This was forgotten to re-export. We actually document this, but have not
exported it, oops.
---
packages/astro/src/index.server.ts | 1 +
packages/aws-serverless/src/index.ts | 1 +
packages/browser/src/exports.ts | 1 +
packages/bun/src/index.ts | 1 +
packages/cloudflare/src/index.ts | 1 +
packages/deno/src/index.ts | 1 +
packages/google-cloud-serverless/src/index.ts | 1 +
packages/node/src/index.ts | 1 +
packages/remix/src/cloudflare/index.ts | 1 +
packages/remix/src/server/index.ts | 1 +
packages/solidstart/src/server/index.ts | 1 +
packages/sveltekit/src/server/index.ts | 1 +
packages/sveltekit/src/worker/index.ts | 1 +
packages/vercel-edge/src/index.ts | 1 +
14 files changed, 14 insertions(+)
diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts
index 9e02765c66c3..29105cfb4b18 100644
--- a/packages/astro/src/index.server.ts
+++ b/packages/astro/src/index.server.ts
@@ -63,6 +63,7 @@ export {
eventFiltersIntegration,
initOpenTelemetry,
isInitialized,
+ isEnabled,
kafkaIntegration,
koaIntegration,
knexIntegration,
diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts
index 644d3345ec8f..942951c165da 100644
--- a/packages/aws-serverless/src/index.ts
+++ b/packages/aws-serverless/src/index.ts
@@ -14,6 +14,7 @@ export {
createTransport,
getClient,
isInitialized,
+ isEnabled,
generateInstrumentOnce,
getCurrentScope,
getGlobalScope,
diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts
index b454f29556c2..27e1ea34b8b7 100644
--- a/packages/browser/src/exports.ts
+++ b/packages/browser/src/exports.ts
@@ -32,6 +32,7 @@ export {
flush,
getClient,
isInitialized,
+ isEnabled,
getCurrentScope,
getIsolationScope,
getGlobalScope,
diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts
index b1ad295b1257..70104de6d7c3 100644
--- a/packages/bun/src/index.ts
+++ b/packages/bun/src/index.ts
@@ -33,6 +33,7 @@ export {
createTransport,
getClient,
isInitialized,
+ isEnabled,
generateInstrumentOnce,
getCurrentScope,
getGlobalScope,
diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts
index 64a6f57fccc2..6754ffd04f7c 100644
--- a/packages/cloudflare/src/index.ts
+++ b/packages/cloudflare/src/index.ts
@@ -33,6 +33,7 @@ export {
flush,
getClient,
isInitialized,
+ isEnabled,
getCurrentScope,
getGlobalScope,
getIsolationScope,
diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts
index f95d36771b24..f388de7cb5ee 100644
--- a/packages/deno/src/index.ts
+++ b/packages/deno/src/index.ts
@@ -33,6 +33,7 @@ export {
flush,
getClient,
isInitialized,
+ isEnabled,
getCurrentScope,
getGlobalScope,
getIsolationScope,
diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts
index 7376978a5226..f5d593312743 100644
--- a/packages/google-cloud-serverless/src/index.ts
+++ b/packages/google-cloud-serverless/src/index.ts
@@ -14,6 +14,7 @@ export {
createTransport,
getClient,
isInitialized,
+ isEnabled,
generateInstrumentOnce,
getCurrentScope,
getGlobalScope,
diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts
index 03705bda89ba..5a933002bc23 100644
--- a/packages/node/src/index.ts
+++ b/packages/node/src/index.ts
@@ -65,6 +65,7 @@ export {
export {
addBreadcrumb,
isInitialized,
+ isEnabled,
getGlobalScope,
lastEventId,
close,
diff --git a/packages/remix/src/cloudflare/index.ts b/packages/remix/src/cloudflare/index.ts
index f503adaf0711..3d3d17e1da27 100644
--- a/packages/remix/src/cloudflare/index.ts
+++ b/packages/remix/src/cloudflare/index.ts
@@ -55,6 +55,7 @@ export {
flush,
getClient,
isInitialized,
+ isEnabled,
getCurrentScope,
getGlobalScope,
getIsolationScope,
diff --git a/packages/remix/src/server/index.ts b/packages/remix/src/server/index.ts
index f90ff55eca6c..0aa30b3c93cc 100644
--- a/packages/remix/src/server/index.ts
+++ b/packages/remix/src/server/index.ts
@@ -52,6 +52,7 @@ export {
eventFiltersIntegration,
initOpenTelemetry,
isInitialized,
+ isEnabled,
knexIntegration,
kafkaIntegration,
koaIntegration,
diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts
index 06e97a20a96a..f11b9bb51077 100644
--- a/packages/solidstart/src/server/index.ts
+++ b/packages/solidstart/src/server/index.ts
@@ -55,6 +55,7 @@ export {
eventFiltersIntegration,
initOpenTelemetry,
isInitialized,
+ isEnabled,
knexIntegration,
kafkaIntegration,
koaIntegration,
diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts
index 5e49fa45fed3..8cc952bfb7e7 100644
--- a/packages/sveltekit/src/server/index.ts
+++ b/packages/sveltekit/src/server/index.ts
@@ -57,6 +57,7 @@ export {
eventFiltersIntegration,
initOpenTelemetry,
isInitialized,
+ isEnabled,
knexIntegration,
kafkaIntegration,
koaIntegration,
diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts
index 9fc8429e5864..3614922072ec 100644
--- a/packages/sveltekit/src/worker/index.ts
+++ b/packages/sveltekit/src/worker/index.ts
@@ -46,6 +46,7 @@ export {
// eslint-disable-next-line deprecation/deprecation
inboundFiltersIntegration,
isInitialized,
+ isEnabled,
lastEventId,
linkedErrorsIntegration,
requestDataIntegration,
diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts
index c98cf8ed253d..ff1231c8a1f8 100644
--- a/packages/vercel-edge/src/index.ts
+++ b/packages/vercel-edge/src/index.ts
@@ -33,6 +33,7 @@ export {
flush,
getClient,
isInitialized,
+ isEnabled,
getCurrentScope,
getGlobalScope,
getIsolationScope,
From a78868591c75954e4ccd2def9da6aa9561397c07 Mon Sep 17 00:00:00 2001
From: Daniel Griesser
Date: Wed, 28 May 2025 12:03:12 +0200
Subject: [PATCH 24/26] chore: Add external contributor to CHANGELOG.md
(#16408)
This PR adds the external contributor to the CHANGELOG.md file, so that
they are credited for their contribution. See #16372
Co-authored-by: s1gr1d <32902192+s1gr1d@users.noreply.github.com>
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ceb36dfdaffc..fd1a42eed45f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+Work in this release was contributed by @Xenossolitarius. Thank you for your contribution!
+
## 9.22.0
### Important changes
From 6e61f825abfff329f92ec30a97f3b49370683a2e Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Wed, 28 May 2025 14:28:48 +0200
Subject: [PATCH 25/26] fix(nuxt): Add `@sentry/nuxt` as external in Rollup
(#16407)
The Sentry Nuxt SDK emits the Sentry config file during the Rollup
build. Users add this file to their application which includes an import
from `@sentry/nuxt` and the init function. This file is then added as an
`.mjs` file in the build output.
However, when building the application, this `.mjs` file included a
bunch of OpenTelemetry imports and not only the init code as every
dependency from `@sentry/nuxt` was traced back and bundled into the
build output.
By adding `@sentry/nuxt` as external, the `.mjs` file really only
contains the content like in the `.ts` file.
**Could** fix
https://github.com/getsentry/sentry-javascript/issues/15204
---------
Co-authored-by: Andrei Borza
---
packages/nuxt/src/vite/addServerConfig.ts | 8 ++++
packages/nuxt/src/vite/utils.ts | 30 ++++++++++++
packages/nuxt/test/vite/utils.test.ts | 58 +++++++++++++++++++++++
3 files changed, 96 insertions(+)
diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts
index 771c534705cb..83e59d439ecf 100644
--- a/packages/nuxt/src/vite/addServerConfig.ts
+++ b/packages/nuxt/src/vite/addServerConfig.ts
@@ -8,6 +8,7 @@ import type { SentryNuxtModuleOptions } from '../common/types';
import {
constructFunctionReExport,
constructWrappedFunctionExportQuery,
+ getExternalOptionsWithSentryNuxt,
getFilenameFromNodeStartCommand,
QUERY_END_INDICATOR,
removeSentryQueryFromPath,
@@ -130,6 +131,13 @@ function injectServerConfigPlugin(nitro: Nitro, serverConfigFile: string, debug?
return {
name: 'rollup-plugin-inject-sentry-server-config',
+ options(opts) {
+ return {
+ ...opts,
+ external: getExternalOptionsWithSentryNuxt(opts.external),
+ };
+ },
+
buildStart() {
const configPath = createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`);
diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts
index f1ef1c9e4cf2..a0e88c892a25 100644
--- a/packages/nuxt/src/vite/utils.ts
+++ b/packages/nuxt/src/vite/utils.ts
@@ -2,6 +2,7 @@ import { consoleSandbox } from '@sentry/core';
import * as fs from 'fs';
import type { Nuxt } from 'nuxt/schema';
import * as path from 'path';
+import type { ExternalOption } from 'rollup';
/**
* Find the default SDK init file for the given type (client or server).
@@ -72,6 +73,35 @@ export function removeSentryQueryFromPath(url: string): string {
return url.replace(regex, '');
}
+/**
+ * Add @sentry/nuxt to the external options of the Rollup configuration to prevent Rollup bundling all dependencies
+ * that would result in adding imports from OpenTelemetry libraries etc. to the server build.
+ */
+export function getExternalOptionsWithSentryNuxt(previousExternal: ExternalOption | undefined): ExternalOption {
+ const sentryNuxt = /^@sentry\/nuxt$/;
+ let external: ExternalOption;
+
+ if (typeof previousExternal === 'function') {
+ external = new Proxy(previousExternal, {
+ apply(target, thisArg, args: [string, string | undefined, boolean]) {
+ const [source] = args;
+ if (sentryNuxt.test(source)) {
+ return true;
+ }
+ return Reflect.apply(target, thisArg, args);
+ },
+ });
+ } else if (Array.isArray(previousExternal)) {
+ external = [sentryNuxt, ...previousExternal];
+ } else if (previousExternal) {
+ external = [sentryNuxt, previousExternal];
+ } else {
+ external = sentryNuxt;
+ }
+
+ return external;
+}
+
/**
* Extracts and sanitizes function re-export and function wrap query parameters from a query string.
* If it is a default export, it is not considered for re-exporting.
diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts
index f19ec98b4b64..9419c5f0a545 100644
--- a/packages/nuxt/test/vite/utils.test.ts
+++ b/packages/nuxt/test/vite/utils.test.ts
@@ -6,6 +6,7 @@ import {
constructWrappedFunctionExportQuery,
extractFunctionReexportQueryParameters,
findDefaultSdkInitFile,
+ getExternalOptionsWithSentryNuxt,
getFilenameFromNodeStartCommand,
QUERY_END_INDICATOR,
removeSentryQueryFromPath,
@@ -366,3 +367,60 @@ export { foo_sentryWrapped as foo };
expect(result).toBe('');
});
});
+
+describe('getExternalOptionsWithSentryNuxt', () => {
+ it('should return sentryExternals when previousExternal is undefined', () => {
+ const result = getExternalOptionsWithSentryNuxt(undefined);
+ expect(result).toEqual(/^@sentry\/nuxt$/);
+ });
+
+ it('should merge sentryExternals with array previousExternal', () => {
+ const previousExternal = [/vue/, 'react'];
+ const result = getExternalOptionsWithSentryNuxt(previousExternal);
+ expect(result).toEqual([/^@sentry\/nuxt$/, /vue/, 'react']);
+ });
+
+ it('should create array with sentryExternals and non-array previousExternal', () => {
+ const previousExternal = 'vue';
+ const result = getExternalOptionsWithSentryNuxt(previousExternal);
+ expect(result).toEqual([/^@sentry\/nuxt$/, 'vue']);
+ });
+
+ it('should create a proxy when previousExternal is a function', () => {
+ const mockExternalFn = vi.fn().mockReturnValue(false);
+ const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
+
+ expect(typeof result).toBe('function');
+ expect(result).toBeInstanceOf(Function);
+ });
+
+ it('should return true from proxied function when source is @sentry/nuxt', () => {
+ const mockExternalFn = vi.fn().mockReturnValue(false);
+ const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
+
+ // @ts-expect-error - result is a function
+ const output = result('@sentry/nuxt', undefined, false);
+ expect(output).toBe(true);
+ expect(mockExternalFn).not.toHaveBeenCalled();
+ });
+
+ it('should return false from proxied function and call function when source just includes @sentry/nuxt', () => {
+ const mockExternalFn = vi.fn().mockReturnValue(false);
+ const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
+
+ // @ts-expect-error - result is a function
+ const output = result('@sentry/nuxt/dist/index.js', undefined, false);
+ expect(output).toBe(false);
+ expect(mockExternalFn).toHaveBeenCalledWith('@sentry/nuxt/dist/index.js', undefined, false);
+ });
+
+ it('should call original function when source does not include @sentry/nuxt', () => {
+ const mockExternalFn = vi.fn().mockReturnValue(false);
+ const result = getExternalOptionsWithSentryNuxt(mockExternalFn);
+
+ // @ts-expect-error - result is a function
+ const output = result('vue', undefined, false);
+ expect(output).toBe(false);
+ expect(mockExternalFn).toHaveBeenCalledWith('vue', undefined, false);
+ });
+});
From 5eca4272b26178be98229a9040b655c309ea696a Mon Sep 17 00:00:00 2001
From: Andrei Borza
Date: Wed, 28 May 2025 14:33:29 +0200
Subject: [PATCH 26/26] meta(changelog): Update changelog for 9.23.0
---
CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd1a42eed45f..1adce32abab6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,43 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 9.23.0
+
+### Important changes
+
+- **feat(browser): option to ignore certain resource types ([#16389](https://github.com/getsentry/sentry-javascript/pull/16389))**
+
+Adds an option to opt out of certain `resource.*` spans via `ignoreResourceSpans`.
+
+For example, to opt out of `resource.script` spans:
+
+```js
+Sentry.browserTracingIntegration({
+ ignoreResourceSpans: ['resource.script'],
+}),
+```
+
+### Other changes
+
+- feat: Export `isEnabled` from all SDKs ([#16405](https://github.com/getsentry/sentry-javascript/pull/16405))
+- feat(browser): Disable client when browser extension is detected in `init()` ([#16354](https://github.com/getsentry/sentry-javascript/pull/16354))
+- feat(core): Allow re-use of `captureLog` ([#16352](https://github.com/getsentry/sentry-javascript/pull/16352))
+- feat(core): Export `_INTERNAL_captureSerializedLog` ([#16387](https://github.com/getsentry/sentry-javascript/pull/16387))
+- feat(deps): bump @opentelemetry/semantic-conventions from 1.32.0 to 1.34.0 ([#16393](https://github.com/getsentry/sentry-javascript/pull/16393))
+- feat(deps): bump @prisma/instrumentation from 6.7.0 to 6.8.2 ([#16392](https://github.com/getsentry/sentry-javascript/pull/16392))
+- feat(deps): bump @sentry/cli from 2.43.0 to 2.45.0 ([#16395](https://github.com/getsentry/sentry-javascript/pull/16395))
+- feat(deps): bump @sentry/webpack-plugin from 3.3.1 to 3.5.0 ([#16394](https://github.com/getsentry/sentry-javascript/pull/16394))
+- feat(nextjs): Include `static/chunks/main-*` files for `widenClientFileUpload` ([#16406](https://github.com/getsentry/sentry-javascript/pull/16406))
+- feat(node): Do not add HTTP & fetch span instrumentation if tracing is disabled ([#15730](https://github.com/getsentry/sentry-javascript/pull/15730))
+- feat(nuxt): Added support for nuxt layers ([#16372](https://github.com/getsentry/sentry-javascript/pull/16372))
+- fix(browser): Ensure logs are flushed when sendClientReports=false ([#16351](https://github.com/getsentry/sentry-javascript/pull/16351))
+- fix(browser): Move `browserTracingIntegration` code to `setup` hook ([#16386](https://github.com/getsentry/sentry-javascript/pull/16386))
+- fix(cloudflare): Capture exceptions thrown in hono ([#16355](https://github.com/getsentry/sentry-javascript/pull/16355))
+- fix(node): Don't warn about Spotlight on empty NODE_ENV ([#16381](https://github.com/getsentry/sentry-javascript/pull/16381))
+- fix(node): Suppress Spotlight calls ([#16380](https://github.com/getsentry/sentry-javascript/pull/16380))
+- fix(nuxt): Add `@sentry/nuxt` as external in Rollup ([#16407](https://github.com/getsentry/sentry-javascript/pull/16407))
+- fix(opentelemetry): Ensure `withScope` keeps span active & `_getTraceInfoFromScope` works ([#16385](https://github.com/getsentry/sentry-javascript/pull/16385))
+
Work in this release was contributed by @Xenossolitarius. Thank you for your contribution!
## 9.22.0