From 87cd836e45133c0523627bb17f49b106eeb8ee3f Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Thu, 17 Jul 2025 13:41:44 +0200
Subject: [PATCH 01/86] test(astro-5): Add test for current parametrized routes
(#17054)
Adds a test case with a client, server and server API request which
checks:
- that everything is correctly connected
- that everything has parametrized routes (the current state of it -
some are not yet parametrized)
This is a visual representation of the test case (with a different
param):
Part of https://github.com/getsentry/sentry-javascript/issues/16686
---
.../src/pages/api/user/[userId].json.js | 8 +
.../astro-5/src/pages/index.astro | 1 +
.../src/pages/user-page/[userId].astro | 17 +++
.../astro-5/tests/tracing.dynamic.test.ts | 144 ++++++++++++++++++
4 files changed, 170 insertions(+)
create mode 100644 dev-packages/e2e-tests/test-applications/astro-5/src/pages/api/user/[userId].json.js
create mode 100644 dev-packages/e2e-tests/test-applications/astro-5/src/pages/user-page/[userId].astro
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/api/user/[userId].json.js b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/api/user/[userId].json.js
new file mode 100644
index 000000000000..481c8979dc89
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/api/user/[userId].json.js
@@ -0,0 +1,8 @@
+export function GET({ params }) {
+ return new Response(
+ JSON.stringify({
+ greeting: `Hello ${params.userId}`,
+ userId: params.userId,
+ }),
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro
index 457d94f43457..61bf20bfe31e 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/index.astro
@@ -16,6 +16,7 @@ import Layout from '../layouts/Layout.astro';
SSR pageStatic PageServer Island
+ Test Parametrized Routes
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/user-page/[userId].astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/user-page/[userId].astro
new file mode 100644
index 000000000000..e35bd3a34d97
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/user-page/[userId].astro
@@ -0,0 +1,17 @@
+---
+import Layout from '../../layouts/Layout.astro';
+
+export const prerender = false;
+
+const { userId } = Astro.params;
+
+const response = await fetch(Astro.url.origin + `/api/user/${userId}.json`)
+const data = await response.json();
+
+---
+
+
+
{data.greeting}
+
+
data: {JSON.stringify(data)}
+
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
index eb70f7362e63..36f32cd5dc0e 100644
--- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
+++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts
@@ -119,3 +119,147 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
});
});
});
+
+test.describe('nested SSR routes (client, server, server request)', () => {
+ /** The user-page route fetches from an endpoint and creates a deeply nested span structure:
+ * pageload — /user-page/myUsername123
+ * ├── browser.** — multiple browser spans
+ * └── browser.request — /user-page/myUsername123
+ * └── http.server — GET /user-page/[userId] (SSR page request)
+ * └── http.client — GET /api/user/myUsername123.json (executing fetch call from SSR page - span)
+ * └── http.server — GET /api/user/myUsername123.json (server request)
+ */
+ test('sends connected server and client pageload and request spans with the same trace id', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('/user-page/') ?? false;
+ });
+
+ const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false;
+ });
+
+ const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false;
+ });
+
+ await page.goto('/user-page/myUsername123');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+ const serverPageRequestTxn = await serverPageRequestTxnPromise;
+ const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise;
+ const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find(
+ span => span.op === 'http.client' && span.description?.includes('/api/user/'),
+ );
+
+ const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id;
+
+ // Verify all spans have the same trace ID
+ expect(clientPageloadTraceId).toEqual(serverPageRequestTxn.contexts?.trace?.trace_id);
+ expect(clientPageloadTraceId).toEqual(serverHTTPServerRequestTxn.contexts?.trace?.trace_id);
+ expect(clientPageloadTraceId).toEqual(serverRequestHTTPClientSpan?.trace_id);
+
+ // serverPageRequest has no parent (root span)
+ expect(serverPageRequestTxn.contexts?.trace?.parent_span_id).toBeUndefined();
+
+ // clientPageload's parent and serverRequestHTTPClient's parent is serverPageRequest
+ const serverPageRequestSpanId = serverPageRequestTxn.contexts?.trace?.span_id;
+ expect(clientPageloadTxn.contexts?.trace?.parent_span_id).toEqual(serverPageRequestSpanId);
+ expect(serverRequestHTTPClientSpan?.parent_span_id).toEqual(serverPageRequestSpanId);
+
+ // serverHTTPServerRequest's parent is serverRequestHTTPClient
+ expect(serverHTTPServerRequestTxn.contexts?.trace?.parent_span_id).toEqual(serverRequestHTTPClientSpan?.span_id);
+ });
+
+ test('sends parametrized pageload, server and API request transaction names', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('/user-page/') ?? false;
+ });
+
+ const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false;
+ });
+
+ const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false;
+ });
+
+ await page.goto('/user-page/myUsername123');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+ const serverPageRequestTxn = await serverPageRequestTxnPromise;
+ const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise;
+
+ const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find(
+ span => span.op === 'http.client' && span.description?.includes('/api/user/'),
+ );
+
+ // Client pageload transaction - actual URL with pageload operation
+ expect(clientPageloadTxn).toMatchObject({
+ transaction: '/user-page/myUsername123', // todo: parametrize to '/user-page/[userId]'
+ transaction_info: { source: 'url' },
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.browser',
+ data: {
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.browser',
+ 'sentry.source': 'url',
+ },
+ },
+ },
+ });
+
+ // Server page request transaction - parametrized transaction name with actual URL in data
+ expect(serverPageRequestTxn).toMatchObject({
+ transaction: 'GET /user-page/[userId]',
+ transaction_info: { source: 'route' },
+ contexts: {
+ trace: {
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ data: {
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.source': 'route',
+ url: expect.stringContaining('/user-page/myUsername123'),
+ },
+ },
+ },
+ request: { url: expect.stringContaining('/user-page/myUsername123') },
+ });
+
+ // HTTP client span - actual API URL with client operation
+ expect(serverRequestHTTPClientSpan).toMatchObject({
+ op: 'http.client',
+ origin: 'auto.http.otel.node_fetch',
+ description: 'GET http://localhost:3030/api/user/myUsername123.json', // todo: parametrize (this is just a span though - no transaction)
+ data: {
+ 'sentry.op': 'http.client',
+ 'sentry.origin': 'auto.http.otel.node_fetch',
+ 'url.full': expect.stringContaining('/api/user/myUsername123.json'),
+ 'url.path': '/api/user/myUsername123.json',
+ url: expect.stringContaining('/api/user/myUsername123.json'),
+ },
+ });
+
+ // Server HTTP request transaction - should be parametrized (todo: currently not parametrized)
+ expect(serverHTTPServerRequestTxn).toMatchObject({
+ transaction: 'GET /api/user/myUsername123.json', // todo: should be parametrized to 'GET /api/user/[userId].json'
+ transaction_info: { source: 'route' },
+ contexts: {
+ trace: {
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ data: {
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.source': 'route',
+ url: expect.stringContaining('/api/user/myUsername123.json'),
+ },
+ },
+ },
+ request: { url: expect.stringContaining('/api/user/myUsername123.json') },
+ });
+ });
+});
From 64738db597d8222b321d1bc2c8b0345c85934449 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Thu, 17 Jul 2025 14:18:30 +0200
Subject: [PATCH 02/86] ref(profiling): Improve generic profiling type (#17056)
This allows us to avoid `any` here, and makes more sense overall IMHO.
---
packages/core/src/profiling.ts | 8 ++++----
packages/core/src/types-hoist/profiling.ts | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/core/src/profiling.ts b/packages/core/src/profiling.ts
index b49a30070683..407c4a07c53c 100644
--- a/packages/core/src/profiling.ts
+++ b/packages/core/src/profiling.ts
@@ -4,8 +4,8 @@ import type { Profiler, ProfilingIntegration } from './types-hoist/profiling';
import { debug } from './utils/debug-logger';
function isProfilingIntegrationWithProfiler(
- integration: ProfilingIntegration | undefined,
-): integration is ProfilingIntegration {
+ integration: ProfilingIntegration | undefined,
+): integration is ProfilingIntegration {
return (
!!integration &&
typeof integration['_profiler'] !== 'undefined' &&
@@ -25,7 +25,7 @@ function startProfiler(): void {
return;
}
- const integration = client.getIntegrationByName>('ProfilingIntegration');
+ const integration = client.getIntegrationByName('ProfilingIntegration');
if (!integration) {
DEBUG_BUILD && debug.warn('ProfilingIntegration is not available');
@@ -51,7 +51,7 @@ function stopProfiler(): void {
return;
}
- const integration = client.getIntegrationByName>('ProfilingIntegration');
+ const integration = client.getIntegrationByName('ProfilingIntegration');
if (!integration) {
DEBUG_BUILD && debug.warn('ProfilingIntegration is not available');
return;
diff --git a/packages/core/src/types-hoist/profiling.ts b/packages/core/src/types-hoist/profiling.ts
index 2c0c439450bb..2c369c0d5e57 100644
--- a/packages/core/src/types-hoist/profiling.ts
+++ b/packages/core/src/types-hoist/profiling.ts
@@ -9,7 +9,7 @@ export interface ContinuousProfiler {
stop(): void;
}
-export interface ProfilingIntegration extends Integration {
+export interface ProfilingIntegration extends Integration {
_profiler: ContinuousProfiler;
}
From ad5e7c3e2a51e0ad04ecff443b6ed523fbc812f6 Mon Sep 17 00:00:00 2001
From: Francesco Gringl-Novy
Date: Thu, 17 Jul 2025 14:37:57 +0200
Subject: [PATCH 03/86] ref: Remove `@typescript-eslint/no-empty-function`
eslint rule (#17057)
This is IMHO not super helpful, we constantly disable it or add a noop
comment for no gain.
---
packages/cloudflare/src/opentelemetry/tracer.ts | 3 +--
packages/core/src/tracing/sentryNonRecordingSpan.ts | 1 -
packages/core/src/utils/handleCallbackErrors.ts | 7 +------
packages/eslint-config-sdk/src/base.js | 4 +++-
packages/feedback/src/modal/integration.tsx | 1 -
packages/feedback/src/screenshot/integration.ts | 1 -
packages/sveltekit/src/vite/svelteConfig.ts | 3 ---
7 files changed, 5 insertions(+), 15 deletions(-)
diff --git a/packages/cloudflare/src/opentelemetry/tracer.ts b/packages/cloudflare/src/opentelemetry/tracer.ts
index 94dc917c5070..a180346f7cce 100644
--- a/packages/cloudflare/src/opentelemetry/tracer.ts
+++ b/packages/cloudflare/src/opentelemetry/tracer.ts
@@ -71,8 +71,7 @@ class SentryCloudflareTracer implements Tracer {
? context
: typeof fn === 'function'
? fn
- : // eslint-disable-next-line @typescript-eslint/no-empty-function
- () => {}
+ : () => {}
) as F;
// In OTEL the semantic matches `startSpanManual` because spans are not auto-ended
diff --git a/packages/core/src/tracing/sentryNonRecordingSpan.ts b/packages/core/src/tracing/sentryNonRecordingSpan.ts
index 69d1aa2a85ba..2f65e0eb8c08 100644
--- a/packages/core/src/tracing/sentryNonRecordingSpan.ts
+++ b/packages/core/src/tracing/sentryNonRecordingSpan.ts
@@ -32,7 +32,6 @@ export class SentryNonRecordingSpan implements Span {
}
/** @inheritdoc */
- // eslint-disable-next-line @typescript-eslint/no-empty-function
public end(_timestamp?: SpanTimeInput): void {}
/** @inheritdoc */
diff --git a/packages/core/src/utils/handleCallbackErrors.ts b/packages/core/src/utils/handleCallbackErrors.ts
index cf4d29766445..5675638e18f2 100644
--- a/packages/core/src/utils/handleCallbackErrors.ts
+++ b/packages/core/src/utils/handleCallbackErrors.ts
@@ -14,12 +14,7 @@ import { isThenable } from '../utils/is';
export function handleCallbackErrors<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Fn extends () => any,
->(
- fn: Fn,
- onError: (error: unknown) => void,
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- onFinally: () => void = () => {},
-): ReturnType {
+>(fn: Fn, onError: (error: unknown) => void, onFinally: () => void = () => {}): ReturnType {
let maybePromiseResult: ReturnType;
try {
maybePromiseResult = fn();
diff --git a/packages/eslint-config-sdk/src/base.js b/packages/eslint-config-sdk/src/base.js
index fade0633fc98..52334507ac8b 100644
--- a/packages/eslint-config-sdk/src/base.js
+++ b/packages/eslint-config-sdk/src/base.js
@@ -107,6 +107,9 @@ module.exports = {
// Be explicit about class member accessibility (public, private, protected). Turned off
// on tests for ease of use.
'@typescript-eslint/explicit-member-accessibility': ['error'],
+
+ // We do not care about empty functions
+ '@typescript-eslint/no-empty-function': 'off',
},
},
{
@@ -178,7 +181,6 @@ module.exports = {
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
- '@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@sentry-internal/sdk/no-focused-tests': 'error',
'@sentry-internal/sdk/no-skipped-tests': 'error',
diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx
index dd2c341760a7..5bdf4362509d 100644
--- a/packages/feedback/src/modal/integration.tsx
+++ b/packages/feedback/src/modal/integration.tsx
@@ -22,7 +22,6 @@ function getUser(): User | undefined {
export const feedbackModalIntegration = ((): FeedbackModalIntegration => {
return {
name: 'FeedbackModal',
- // eslint-disable-next-line @typescript-eslint/no-empty-function
setupOnce() {},
createDialog: ({ options, screenshotIntegration, sendFeedback, shadow }) => {
const shadowRoot = shadow as unknown as ShadowRoot;
diff --git a/packages/feedback/src/screenshot/integration.ts b/packages/feedback/src/screenshot/integration.ts
index 68c8c8535f8e..457a93935843 100644
--- a/packages/feedback/src/screenshot/integration.ts
+++ b/packages/feedback/src/screenshot/integration.ts
@@ -7,7 +7,6 @@ import { ScreenshotEditorFactory } from './components/ScreenshotEditor';
export const feedbackScreenshotIntegration = ((): FeedbackScreenshotIntegration => {
return {
name: 'FeedbackScreenshot',
- // eslint-disable-next-line @typescript-eslint/no-empty-function
setupOnce() {},
createInput: ({ h, hooks, dialog, options }) => {
const outputBuffer = DOCUMENT.createElement('canvas');
diff --git a/packages/sveltekit/src/vite/svelteConfig.ts b/packages/sveltekit/src/vite/svelteConfig.ts
index ed0c9ec6f801..34874bfd2f97 100644
--- a/packages/sveltekit/src/vite/svelteConfig.ts
+++ b/packages/sveltekit/src/vite/svelteConfig.ts
@@ -86,13 +86,10 @@ async function getNodeAdapterOutputDir(svelteConfig: Config): Promise {
},
// @ts-expect-error - No need to implement the other methods
log: {
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- this should be a noop
minor() {},
},
getBuildDirectory: () => '',
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- this should be a noop
rimraf: () => {},
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- this should be a noop
mkdirp: () => {},
config: {
From 53101125bf91618250729ee26faf5ca8ea6e3a33 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Thu, 17 Jul 2025 17:17:35 +0200
Subject: [PATCH 04/86] feat!: Bump to OpenTelemetry v2 (#16872)
This PR
* bumps all OpenTelemetry packages to v2 and equivalent (i.e. `0.202.0`
for experimental and latest versions for all instrumentations).
* removes our vendored express v5 instrumentation in favor of the
OpenTelemetry instrumentation
* adds polyfills for node utils to vercel-edge
* removes the `@sentry/opentelemetry` OpenTelemetry v2 test dev-package
as this is now covered by the package itself
**Note**: Once this PR is merged, the `develop` branch officially
becomes the `v10` branch and any releases for v9 have to happen on the
`v9` branch.
---
.size-limit.js | 11 +-
.../node-core-express-otel-v2/package.json | 6 +-
.../tests/server.test.ts | 2 -
.../tests/server.test.ts | 2 -
.../tests/server.test.ts | 2 -
.../node-express/tests/transactions.test.ts | 7 +-
.../node-otel-custom-sampler/package.json | 2 +-
.../src/instrument.ts | 3 +-
.../tests/sampling.test.ts | 2 -
.../node-otel-sdk-node/package.json | 4 +-
.../tests/transactions.test.ts | 7 +-
.../node-otel-without-tracing/package.json | 10 +-
.../src/instrument.ts | 18 +-
.../test-applications/node-otel/package.json | 4 +-
.../node-otel/tests/transactions.test.ts | 7 +-
.../performance/trace-propagation.test.ts | 6 +-
.../performance/trace-propagation.test.ts | 6 +-
.../performance/trace-propagation.test.ts | 6 +-
.../node-core-integration-tests/package.json | 12 +-
.../aws-serverless/aws-integration/s3/test.ts | 2 +-
.../suites/express-v5/tracing/test.ts | 16 +
.../suites/express/tracing/test.ts | 19 +-
.../suites/tracing/kafkajs/test.ts | 8 +-
.../opentelemetry-v2-tests/.eslintrc.js | 6 -
dev-packages/opentelemetry-v2-tests/README.md | 19 -
.../opentelemetry-v2-tests/package.json | 26 -
.../test/asyncContextStrategy.test.ts | 442 ----
.../test/custom/client.test.ts | 19 -
.../test/helpers/TestClient.ts | 48 -
.../test/helpers/initOtel.ts | 81 -
.../test/helpers/mockSdkInit.ts | 81 -
.../test/integration/breadcrumbs.test.ts | 357 ---
.../test/integration/scope.test.ts | 387 ----
.../test/integration/transactions.test.ts | 724 ------
.../test/propagator.test.ts | 670 ------
.../test/sampler.test.ts | 141 --
.../test/spanExporter.test.ts | 169 --
.../opentelemetry-v2-tests/test/trace.test.ts | 1935 -----------------
.../opentelemetry-v2-tests/test/tsconfig.json | 3 -
.../test/utils/getActiveSpan.test.ts | 155 --
.../test/utils/getRequestSpanData.test.ts | 80 -
.../test/utils/getSpanKind.test.ts | 11 -
.../test/utils/getTraceData.test.ts | 94 -
.../test/utils/groupSpansWithParents.test.ts | 174 --
.../test/utils/mapStatus.test.ts | 130 --
.../test/utils/parseSpanDescription.test.ts | 690 ------
.../test/utils/setupCheck.test.ts | 44 -
.../test/utils/setupEventContextTrace.test.ts | 108 -
.../test/utils/spanToJSON.test.ts | 78 -
.../test/utils/spanTypes.test.ts | 80 -
.../opentelemetry-v2-tests/tsconfig.json | 8 -
.../opentelemetry-v2-tests/tsconfig.test.json | 12 -
.../opentelemetry-v2-tests/vite.config.ts | 11 -
package.json | 3 +-
packages/aws-serverless/package.json | 6 +-
packages/nestjs/package.json | 6 +-
.../sentry-nest-event-instrumentation.ts | 2 +-
.../sentry-nest-instrumentation.ts | 2 +-
packages/node-core/package.json | 10 +-
.../node-core/test/helpers/mockSdkInit.ts | 42 +-
.../test/integration/transactions.test.ts | 12 +-
packages/node/package.json | 54 +-
.../express-v5/enums/AttributeNames.ts | 19 -
.../express-v5/enums/ExpressLayerType.ts | 20 -
.../tracing/express-v5/instrumentation.ts | 326 ---
.../tracing/express-v5/internal-types.ts | 63 -
.../integrations/tracing/express-v5/types.ts | 65 -
.../integrations/tracing/express-v5/utils.ts | 191 --
.../node/src/integrations/tracing/express.ts | 12 -
.../node/src/integrations/tracing/index.ts | 3 +-
.../node/src/integrations/tracing/redis.ts | 6 +-
packages/node/src/sdk/initOtel.ts | 16 +-
packages/node/test/helpers/mockSdkInit.ts | 26 +-
.../test/integration/transactions.test.ts | 12 +-
packages/opentelemetry/package.json | 6 +-
.../test/asyncContextStrategy.test.ts | 2 +-
.../opentelemetry/test/helpers/initOtel.ts | 26 +-
.../opentelemetry}/test/helpers/isSpan.ts | 0
.../opentelemetry/test/helpers/mockSdkInit.ts | 15 +
.../test/integration/transactions.test.ts | 44 +-
packages/opentelemetry/test/sampler.test.ts | 12 +-
packages/opentelemetry/test/trace.test.ts | 17 +-
.../test/utils/getActiveSpan.test.ts | 4 +-
.../test/utils/getRequestSpanData.test.ts | 2 +-
.../test/utils/groupSpansWithParents.test.ts | 2 +-
.../test/utils/mapStatus.test.ts | 2 +-
.../test/utils/setupCheck.test.ts | 8 +-
.../test/utils/setupEventContextTrace.test.ts | 2 +-
.../test/utils/spanToJSON.test.ts | 2 +-
packages/react-router/package.json | 4 +-
packages/remix/package.json | 2 +-
packages/vercel-edge/package.json | 12 +-
packages/vercel-edge/rollup.npm.config.mjs | 23 +-
packages/vercel-edge/src/client.ts | 5 +-
packages/vercel-edge/src/sdk.ts | 16 +-
yarn.lock | 474 ++--
96 files changed, 521 insertions(+), 8000 deletions(-)
delete mode 100644 dev-packages/opentelemetry-v2-tests/.eslintrc.js
delete mode 100644 dev-packages/opentelemetry-v2-tests/README.md
delete mode 100644 dev-packages/opentelemetry-v2-tests/package.json
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/propagator.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/sampler.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/trace.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/tsconfig.json
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts
delete mode 100644 dev-packages/opentelemetry-v2-tests/tsconfig.json
delete mode 100644 dev-packages/opentelemetry-v2-tests/tsconfig.test.json
delete mode 100644 dev-packages/opentelemetry-v2-tests/vite.config.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/instrumentation.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/internal-types.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/types.ts
delete mode 100644 packages/node/src/integrations/tracing/express-v5/utils.ts
rename {dev-packages/opentelemetry-v2-tests => packages/opentelemetry}/test/helpers/isSpan.ts (100%)
diff --git a/.size-limit.js b/.size-limit.js
index 685b40b00fbe..13b963bacd8d 100644
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -217,6 +217,15 @@ module.exports = [
gzip: true,
limit: '41 KB',
},
+ // Node-Core SDK (ESM)
+ {
+ name: '@sentry/node-core',
+ path: 'packages/node-core/build/esm/index.js',
+ import: createImport('init'),
+ ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
+ gzip: true,
+ limit: '116 KB',
+ },
// Node SDK (ESM)
{
name: '@sentry/node',
@@ -224,7 +233,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
- limit: '170 KB',
+ limit: '144 KB',
},
{
name: '@sentry/node - without tracing',
diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json
index e7f854cb7943..cab490640de9 100644
--- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json
@@ -16,11 +16,11 @@
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^2.0.0",
"@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.202.0",
- "@opentelemetry/instrumentation-http": "^0.202.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/instrumentation-http": "^0.203.0",
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-trace-node": "^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.30.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
"@types/express": "^4.17.21",
"@types/node": "^18.19.1",
"express": "^4.21.2",
diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts
index 75e718016a90..a403a23bebda 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts
@@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'query',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
@@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'expressInit',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts
index a1b596072a6a..d919c75ea61b 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts
@@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'query',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
@@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'expressInit',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts
index 2f3208a33bbb..6a4283a9caa9 100644
--- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts
@@ -65,7 +65,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'query',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
@@ -84,7 +83,6 @@ test('Should record a transaction for route with parameters', async ({ request }
data: {
'express.name': 'expressInit',
'express.type': 'middleware',
- 'http.route': '/',
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
},
diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts
index 7f9b18b4cc50..4d3ee36c9778 100644
--- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts
@@ -66,7 +66,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -85,7 +84,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -144,7 +142,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -163,7 +160,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -192,8 +188,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
- status: 'ok',
+ status: 'unknown_error',
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ measurements: {},
});
});
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json
index c35bcef4da90..92770106970e 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/sdk-trace-node": "^1.25.1",
+ "@opentelemetry/sdk-trace-node": "^2.0.0",
"@sentry/node": "latest || *",
"@sentry/opentelemetry": "latest || *",
"@types/express": "4.17.17",
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts
index d0aed916864b..de09f0965baa 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts
+++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/src/instrument.ts
@@ -19,10 +19,9 @@ Sentry.init({
const provider = new NodeTracerProvider({
sampler: new CustomSampler(),
+ spanProcessors: [new SentrySpanProcessor()],
});
-provider.addSpanProcessor(new SentrySpanProcessor());
-
provider.register({
propagator: new SentryPropagator(),
contextManager: new Sentry.SentryContextManager(),
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts
index c3e40d06d6b0..5ca9077634d2 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/tests/sampling.test.ts
@@ -50,7 +50,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -69,7 +68,6 @@ test('Sends a sampled API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json
index f5013b83598a..d26dc2db5843 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json
@@ -11,8 +11,8 @@
"test:assert": "pnpm test"
},
"dependencies": {
- "@opentelemetry/sdk-node": "0.52.1",
- "@opentelemetry/exporter-trace-otlp-http": "0.52.1",
+ "@opentelemetry/sdk-node": "0.203.0",
+ "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
"@sentry/node": "latest || *",
"@types/express": "4.17.17",
"@types/node": "^18.19.1",
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts
index 0f978f72cf57..3e12007c0d75 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts
@@ -74,7 +74,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -93,7 +92,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -152,7 +150,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -171,7 +168,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -200,8 +196,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
- status: 'ok',
+ status: 'unknown_error',
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ measurements: {},
});
});
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json
index 4e83198da45c..11b5509fb637 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json
@@ -11,11 +11,11 @@
"test:assert": "pnpm test"
},
"dependencies": {
- "@opentelemetry/sdk-trace-node": "1.26.0",
- "@opentelemetry/exporter-trace-otlp-http": "0.53.0",
- "@opentelemetry/instrumentation-undici": "0.6.0",
- "@opentelemetry/instrumentation-http": "0.53.0",
- "@opentelemetry/instrumentation": "0.53.0",
+ "@opentelemetry/sdk-trace-node": "2.0.0",
+ "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
+ "@opentelemetry/instrumentation-undici": "0.13.2",
+ "@opentelemetry/instrumentation-http": "0.203.0",
+ "@opentelemetry/instrumentation": "0.203.0",
"@sentry/node": "latest || *",
"@types/express": "4.17.17",
"@types/node": "^18.19.1",
diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts
index ebabd499fee5..b833af11ef83 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts
+++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts
@@ -20,15 +20,15 @@ Sentry.init({
});
// Create and configure NodeTracerProvider
-const provider = new NodeTracerProvider({});
-
-provider.addSpanProcessor(
- new BatchSpanProcessor(
- new OTLPTraceExporter({
- url: 'http://localhost:3032/',
- }),
- ),
-);
+const provider = new NodeTracerProvider({
+ spanProcessors: [
+ new BatchSpanProcessor(
+ new OTLPTraceExporter({
+ url: 'http://localhost:3032/',
+ }),
+ ),
+ ],
+});
// Initialize the provider
provider.register({
diff --git a/dev-packages/e2e-tests/test-applications/node-otel/package.json b/dev-packages/e2e-tests/test-applications/node-otel/package.json
index af285767a655..1a554ece3bf7 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel/package.json
+++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json
@@ -11,8 +11,8 @@
"test:assert": "pnpm test"
},
"dependencies": {
- "@opentelemetry/sdk-node": "0.52.1",
- "@opentelemetry/exporter-trace-otlp-http": "0.52.1",
+ "@opentelemetry/sdk-node": "0.203.0",
+ "@opentelemetry/exporter-trace-otlp-http": "0.203.0",
"@sentry/node": "latest || *",
"@types/express": "4.17.17",
"@types/node": "^18.19.1",
diff --git a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts
index de68adf681b7..c6abde474439 100644
--- a/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/node-otel/tests/transactions.test.ts
@@ -74,7 +74,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -93,7 +92,6 @@ test('Sends an API route transaction', async ({ baseURL }) => {
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -152,7 +150,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'query',
'express.type': 'middleware',
},
@@ -171,7 +168,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
data: {
'sentry.origin': 'auto.http.otel.express',
'sentry.op': 'middleware.express',
- 'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware',
},
@@ -200,8 +196,9 @@ test('Sends an API route transaction for an errored route', async ({ baseURL })
parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
span_id: expect.stringMatching(/[a-f0-9]{16}/),
start_timestamp: expect.any(Number),
- status: 'ok',
+ status: 'unknown_error',
timestamp: expect.any(Number),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ measurements: {},
});
});
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts
index 7562297b2d4d..e9b2c9409154 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-custom/tests/performance/trace-propagation.test.ts
@@ -31,7 +31,11 @@ test.describe('Trace propagation', () => {
const clientTx = await clientTxPromise;
expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id);
- expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id);
+
+ const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express');
+
+ expect(requestHandlerSpan).toBeDefined();
+ expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id);
});
test('should not have trace connection for prerendered pages', async ({ page }) => {
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts
index 7562297b2d4d..e9b2c9409154 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-node-20-18/tests/performance/trace-propagation.test.ts
@@ -31,7 +31,11 @@ test.describe('Trace propagation', () => {
const clientTx = await clientTxPromise;
expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id);
- expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id);
+
+ const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express');
+
+ expect(requestHandlerSpan).toBeDefined();
+ expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id);
});
test('should not have trace connection for prerendered pages', async ({ page }) => {
diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
index 7562297b2d4d..e9b2c9409154 100644
--- a/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
+++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/tests/performance/trace-propagation.test.ts
@@ -31,7 +31,11 @@ test.describe('Trace propagation', () => {
const clientTx = await clientTxPromise;
expect(clientTx.contexts?.trace?.trace_id).toEqual(serverTx.contexts?.trace?.trace_id);
- expect(clientTx.contexts?.trace?.parent_span_id).toBe(serverTx.contexts?.trace?.span_id);
+
+ const requestHandlerSpan = serverTx.spans?.find(span => span.op === 'request_handler.express');
+
+ expect(requestHandlerSpan).toBeDefined();
+ expect(clientTx.contexts?.trace?.parent_span_id).toBe(requestHandlerSpan?.span_id);
});
test('should not have trace connection for prerendered pages', async ({ page }) => {
diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json
index 1aa77efcf44c..b27f7db9584e 100644
--- a/dev-packages/node-core-integration-tests/package.json
+++ b/dev-packages/node-core-integration-tests/package.json
@@ -27,12 +27,12 @@
"@nestjs/core": "10.4.6",
"@nestjs/platform-express": "10.4.6",
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/instrumentation-http": "0.57.2",
- "@opentelemetry/resources": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1",
+ "@opentelemetry/context-async-hooks": "^2.0.0",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/instrumentation-http": "0.203.0",
+ "@opentelemetry/resources": "^2.0.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/core": "9.40.0",
"@sentry/node-core": "9.40.0",
diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts
index b131eb421b61..a6fdc2ce88af 100644
--- a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts
+++ b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration/s3/test.ts
@@ -14,7 +14,7 @@ const EXPECTED_TRANSCATION = {
'rpc.system': 'aws-api',
'rpc.method': 'PutObject',
'rpc.service': 'S3',
- 'aws.region': 'us-east-1',
+ 'cloud.region': 'us-east-1',
'aws.s3.bucket': 'ot-demo-test',
'otel.kind': 'CLIENT',
}),
diff --git a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts
index 5ed3878572b8..b8be6a003fda 100644
--- a/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts
+++ b/dev-packages/node-integration-tests/suites/express-v5/tracing/test.ts
@@ -82,6 +82,22 @@ describe('express v5 tracing', () => {
.expect({
transaction: {
transaction: 'GET /',
+ contexts: {
+ trace: {
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ data: {
+ 'http.response.status_code': 200,
+ url: expect.stringMatching(/\/$/),
+ 'http.method': 'GET',
+ 'http.url': expect.stringMatching(/\/$/),
+ 'http.route': '/',
+ 'http.target': '/',
+ },
+ op: 'http.server',
+ status: 'ok',
+ },
+ },
},
})
.start();
diff --git a/dev-packages/node-integration-tests/suites/express/tracing/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/test.ts
index f5a772dbf096..4476c76a3933 100644
--- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts
+++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts
@@ -83,6 +83,22 @@ describe('express tracing', () => {
.expect({
transaction: {
transaction: 'GET /',
+ contexts: {
+ trace: {
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ data: {
+ 'http.response.status_code': 200,
+ url: expect.stringMatching(/\/$/),
+ 'http.method': 'GET',
+ 'http.url': expect.stringMatching(/\/$/),
+ 'http.route': '/',
+ 'http.target': '/',
+ },
+ op: 'http.server',
+ status: 'ok',
+ },
+ },
},
})
.start();
@@ -327,8 +343,7 @@ describe('express tracing', () => {
const runner = createRunner()
.expect({
transaction: {
- // TODO(v10): This is incorrect on OpenTelemetry v1 but can be fixed in v2
- transaction: `GET ${status_code === 404 ? '/' : url}`,
+ transaction: `GET ${url}`,
contexts: {
trace: {
span_id: expect.stringMatching(/[a-f0-9]{16}/),
diff --git a/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts
index 6e03921c4b73..176d947e1ecf 100644
--- a/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts
+++ b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts
@@ -15,14 +15,14 @@ describe('kafkajs', () => {
})
.expect({
transaction: {
- transaction: 'test-topic',
+ transaction: 'send test-topic',
contexts: {
trace: expect.objectContaining({
op: 'message',
status: 'ok',
data: expect.objectContaining({
'messaging.system': 'kafka',
- 'messaging.destination': 'test-topic',
+ 'messaging.destination.name': 'test-topic',
'otel.kind': 'PRODUCER',
'sentry.op': 'message',
'sentry.origin': 'auto.kafkajs.otel.producer',
@@ -33,14 +33,14 @@ describe('kafkajs', () => {
})
.expect({
transaction: {
- transaction: 'test-topic',
+ transaction: 'process test-topic',
contexts: {
trace: expect.objectContaining({
op: 'message',
status: 'ok',
data: expect.objectContaining({
'messaging.system': 'kafka',
- 'messaging.destination': 'test-topic',
+ 'messaging.destination.name': 'test-topic',
'otel.kind': 'CONSUMER',
'sentry.op': 'message',
'sentry.origin': 'auto.kafkajs.otel.consumer',
diff --git a/dev-packages/opentelemetry-v2-tests/.eslintrc.js b/dev-packages/opentelemetry-v2-tests/.eslintrc.js
deleted file mode 100644
index fdb9952bae52..000000000000
--- a/dev-packages/opentelemetry-v2-tests/.eslintrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- env: {
- node: true,
- },
- extends: ['../../.eslintrc.js'],
-};
diff --git a/dev-packages/opentelemetry-v2-tests/README.md b/dev-packages/opentelemetry-v2-tests/README.md
deleted file mode 100644
index e5ae255c830c..000000000000
--- a/dev-packages/opentelemetry-v2-tests/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# OpenTelemetry v2 Tests
-
-This package contains tests for `@sentry/opentelemetry` when using OpenTelemetry v2. It is used to ensure compatibility with OpenTelemetry v2 APIs.
-
-## Running Tests
-
-To run the tests:
-
-```bash
-yarn test
-```
-
-## Structure
-
-The tests are copied from `packages/opentelemetry/test` with adjusted imports to work with OpenTelemetry v2 dependencies. The main differences are:
-
-1. Uses OpenTelemetry v2 as devDependencies
-2. Imports from `@sentry/opentelemetry` instead of relative paths
-3. Tests the same functionality but with v2 APIs
diff --git a/dev-packages/opentelemetry-v2-tests/package.json b/dev-packages/opentelemetry-v2-tests/package.json
deleted file mode 100644
index 5d091acc5674..000000000000
--- a/dev-packages/opentelemetry-v2-tests/package.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "name": "@sentry-internal/opentelemetry-v2-tests",
- "version": "9.40.0",
- "private": true,
- "description": "Tests for @sentry/opentelemetry with OpenTelemetry v2",
- "engines": {
- "node": ">=18"
- },
- "scripts": {
- "lint": "eslint . --format stylish",
- "fix": "eslint . --format stylish --fix",
- "test": "vitest run",
- "test:watch": "vitest --watch"
- },
- "devDependencies": {
- "@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^2.0.0",
- "@opentelemetry/core": "^2.0.0",
- "@opentelemetry/instrumentation": "^0.203.0",
- "@opentelemetry/sdk-trace-base": "^2.0.0",
- "@opentelemetry/semantic-conventions": "^1.34.0"
- },
- "volta": {
- "extends": "../../package.json"
- }
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts b/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts
deleted file mode 100644
index 0df183362633..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/asyncContextStrategy.test.ts
+++ /dev/null
@@ -1,442 +0,0 @@
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import type { Scope } from '@sentry/core';
-import {
- getCurrentScope,
- getIsolationScope,
- Scope as ScopeClass,
- setAsyncContextStrategy,
- withIsolationScope,
- withScope,
-} from '@sentry/core';
-import { afterAll, afterEach, beforeEach, describe, expect, it, test } from 'vitest';
-import { setOpenTelemetryContextAsyncContextStrategy } from '../../../packages/opentelemetry/src/asyncContextStrategy';
-import { setupOtel } from './helpers/initOtel';
-import { cleanupOtel } from './helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from './helpers/TestClient';
-
-describe('asyncContextStrategy', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- getCurrentScope().clear();
- getIsolationScope().clear();
-
- const options = getDefaultTestClientOptions();
- const client = new TestClient(options);
- [provider] = setupOtel(client);
- setOpenTelemetryContextAsyncContextStrategy();
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- afterAll(() => {
- // clear the strategy
- setAsyncContextStrategy(undefined);
- });
-
- test('scope inheritance', () => {
- const initialScope = getCurrentScope();
- const initialIsolationScope = getIsolationScope();
-
- initialScope.setExtra('a', 'a');
- initialIsolationScope.setExtra('aa', 'aa');
-
- withIsolationScope(() => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- scope1.setExtra('b', 'b');
- isolationScope1.setExtra('bb', 'bb');
-
- withScope(() => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- scope2.setExtra('c', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b: 'b',
- c: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb: 'bb',
- });
- });
- });
- });
-
- test('async scope inheritance', async () => {
- const initialScope = getCurrentScope();
- const initialIsolationScope = getIsolationScope();
-
- async function asyncSetExtra(scope: Scope, key: string, value: string): Promise {
- await new Promise(resolve => setTimeout(resolve, 1));
- scope.setExtra(key, value);
- }
-
- initialScope.setExtra('a', 'a');
- initialIsolationScope.setExtra('aa', 'aa');
-
- await withIsolationScope(async () => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- await asyncSetExtra(scope1, 'b', 'b');
- await asyncSetExtra(isolationScope1, 'bb', 'bb');
-
- await withScope(async () => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- await asyncSetExtra(scope2, 'c', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b: 'b',
- c: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb: 'bb',
- });
- });
- });
- });
-
- test('concurrent scope contexts', () => {
- const initialScope = getCurrentScope();
- const initialIsolationScope = getIsolationScope();
-
- initialScope.setExtra('a', 'a');
- initialIsolationScope.setExtra('aa', 'aa');
-
- withIsolationScope(() => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- scope1.setExtra('b', 'b');
- isolationScope1.setExtra('bb', 'bb');
-
- withScope(() => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- scope2.setExtra('c', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b: 'b',
- c: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb: 'bb',
- });
- });
- });
-
- withIsolationScope(() => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- scope1.setExtra('b2', 'b');
- isolationScope1.setExtra('bb2', 'bb');
-
- withScope(() => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- scope2.setExtra('c2', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b2: 'b',
- c2: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb2: 'bb',
- });
- });
- });
- });
-
- test('concurrent async scope contexts', async () => {
- const initialScope = getCurrentScope();
- const initialIsolationScope = getIsolationScope();
-
- async function asyncSetExtra(scope: Scope, key: string, value: string): Promise {
- await new Promise(resolve => setTimeout(resolve, 1));
- scope.setExtra(key, value);
- }
-
- initialScope.setExtra('a', 'a');
- initialIsolationScope.setExtra('aa', 'aa');
-
- await withIsolationScope(async () => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- await asyncSetExtra(scope1, 'b', 'b');
- await asyncSetExtra(isolationScope1, 'bb', 'bb');
-
- await withScope(async () => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- await asyncSetExtra(scope2, 'c', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b: 'b',
- c: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb: 'bb',
- });
- });
- });
-
- await withIsolationScope(async () => {
- const scope1 = getCurrentScope();
- const isolationScope1 = getIsolationScope();
-
- expect(scope1).not.toBe(initialScope);
- expect(isolationScope1).not.toBe(initialIsolationScope);
-
- expect(scope1.getScopeData()).toEqual(initialScope.getScopeData());
- expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData());
-
- scope1.setExtra('b2', 'b');
- isolationScope1.setExtra('bb2', 'bb');
-
- await withScope(async () => {
- const scope2 = getCurrentScope();
- const isolationScope2 = getIsolationScope();
-
- expect(scope2).not.toBe(scope1);
- expect(isolationScope2).toBe(isolationScope1);
-
- expect(scope2.getScopeData()).toEqual(scope1.getScopeData());
-
- scope2.setExtra('c2', 'c');
-
- expect(scope2.getScopeData().extra).toEqual({
- a: 'a',
- b2: 'b',
- c2: 'c',
- });
-
- expect(isolationScope2.getScopeData().extra).toEqual({
- aa: 'aa',
- bb2: 'bb',
- });
- });
- });
- });
-
- describe('withScope()', () => {
- it('will make the passed scope the active scope within the callback', () =>
- new Promise(done => {
- withScope(scope => {
- expect(getCurrentScope()).toBe(scope);
- done();
- });
- }));
-
- it('will pass a scope that is different from the current active isolation scope', () =>
- new Promise(done => {
- withScope(scope => {
- expect(getIsolationScope()).not.toBe(scope);
- done();
- });
- }));
-
- it('will always make the inner most passed scope the current scope when nesting calls', () =>
- new Promise(done => {
- withIsolationScope(_scope1 => {
- withIsolationScope(scope2 => {
- expect(getIsolationScope()).toBe(scope2);
- done();
- });
- });
- }));
-
- it('forks the scope when not passing any scope', () =>
- new Promise(done => {
- const initialScope = getCurrentScope();
- initialScope.setTag('aa', 'aa');
-
- withScope(scope => {
- expect(getCurrentScope()).toBe(scope);
- scope.setTag('bb', 'bb');
- expect(scope).not.toBe(initialScope);
- expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' });
- done();
- });
- }));
-
- it('forks the scope when passing undefined', () =>
- new Promise(done => {
- const initialScope = getCurrentScope();
- initialScope.setTag('aa', 'aa');
-
- withScope(undefined, scope => {
- expect(getCurrentScope()).toBe(scope);
- scope.setTag('bb', 'bb');
- expect(scope).not.toBe(initialScope);
- expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' });
- done();
- });
- }));
-
- it('sets the passed in scope as active scope', () =>
- new Promise(done => {
- const initialScope = getCurrentScope();
- initialScope.setTag('aa', 'aa');
-
- const customScope = new ScopeClass();
-
- withScope(customScope, scope => {
- expect(getCurrentScope()).toBe(customScope);
- expect(scope).toBe(customScope);
- done();
- });
- }));
- });
-
- describe('withIsolationScope()', () => {
- it('will make the passed isolation scope the active isolation scope within the callback', () =>
- new Promise(done => {
- withIsolationScope(scope => {
- expect(getIsolationScope()).toBe(scope);
- done();
- });
- }));
-
- it('will pass an isolation scope that is different from the current active scope', () =>
- new Promise(done => {
- withIsolationScope(scope => {
- expect(getCurrentScope()).not.toBe(scope);
- done();
- });
- }));
-
- it('will always make the inner most passed scope the current scope when nesting calls', () =>
- new Promise(done => {
- withIsolationScope(_scope1 => {
- withIsolationScope(scope2 => {
- expect(getIsolationScope()).toBe(scope2);
- done();
- });
- });
- }));
-
- it('forks the isolation scope when not passing any isolation scope', () =>
- new Promise(done => {
- const initialScope = getIsolationScope();
- initialScope.setTag('aa', 'aa');
-
- withIsolationScope(scope => {
- expect(getIsolationScope()).toBe(scope);
- scope.setTag('bb', 'bb');
- expect(scope).not.toBe(initialScope);
- expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' });
- done();
- });
- }));
-
- it('forks the isolation scope when passing undefined', () =>
- new Promise(done => {
- const initialScope = getIsolationScope();
- initialScope.setTag('aa', 'aa');
-
- withIsolationScope(undefined, scope => {
- expect(getIsolationScope()).toBe(scope);
- scope.setTag('bb', 'bb');
- expect(scope).not.toBe(initialScope);
- expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' });
- done();
- });
- }));
-
- it('sets the passed in isolation scope as active isolation scope', () =>
- new Promise(done => {
- const initialScope = getIsolationScope();
- initialScope.setTag('aa', 'aa');
-
- const customScope = new ScopeClass();
-
- withIsolationScope(customScope, scope => {
- expect(getIsolationScope()).toBe(customScope);
- expect(scope).toBe(customScope);
- done();
- });
- }));
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts b/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts
deleted file mode 100644
index b39f45d4919e..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/custom/client.test.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { ProxyTracer } from '@opentelemetry/api';
-import { describe, expect, it } from 'vitest';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('OpenTelemetryClient', () => {
- it('exposes a tracer', () => {
- const options = getDefaultTestClientOptions();
- const client = new TestClient(options);
-
- const tracer = client.tracer;
- expect(tracer).toBeDefined();
- expect(tracer).toBeInstanceOf(ProxyTracer);
-
- // Ensure we always get the same tracer instance
- const tracer2 = client.tracer;
-
- expect(tracer2).toBe(tracer);
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts
deleted file mode 100644
index f67cc361d73e..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/helpers/TestClient.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { ClientOptions, Event, Options, SeverityLevel } from '@sentry/core';
-import { Client, createTransport, getCurrentScope, resolvedSyncPromise } from '@sentry/core';
-import { wrapClientClass } from '../../../../packages/opentelemetry/src/custom/client';
-import type { OpenTelemetryClient } from '../../../../packages/opentelemetry/src/types';
-
-class BaseTestClient extends Client {
- public constructor(options: ClientOptions) {
- super(options);
- }
-
- public eventFromException(exception: any): PromiseLike {
- return resolvedSyncPromise({
- exception: {
- values: [
- {
- type: exception.name,
- value: exception.message,
- },
- ],
- },
- });
- }
-
- public eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike {
- return resolvedSyncPromise({ message, level });
- }
-}
-
-export const TestClient = wrapClientClass(BaseTestClient);
-
-export type TestClientInterface = Client & OpenTelemetryClient;
-
-export function init(options: Partial = {}): void {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1, ...options }));
-
- // The client is on the current scope, from where it generally is inherited
- getCurrentScope().setClient(client);
- client.init();
-}
-
-export function getDefaultTestClientOptions(options: Partial = {}): ClientOptions {
- return {
- integrations: [],
- transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})),
- stackParser: () => [],
- ...options,
- } as ClientOptions;
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts
deleted file mode 100644
index b45e49e28d79..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/helpers/initOtel.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api';
-import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
-import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
-import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import {
- ATTR_SERVICE_NAME,
- ATTR_SERVICE_VERSION,
- SEMRESATTRS_SERVICE_NAMESPACE,
-} from '@opentelemetry/semantic-conventions';
-import { debug as debugLogger, getClient, SDK_VERSION } from '@sentry/core';
-import { wrapContextManagerClass } from '../../../../packages/opentelemetry/src/contextManager';
-import { DEBUG_BUILD } from '../../../../packages/opentelemetry/src/debug-build';
-import { SentryPropagator } from '../../../../packages/opentelemetry/src/propagator';
-import { SentrySampler } from '../../../../packages/opentelemetry/src/sampler';
-import { setupEventContextTrace } from '../../../../packages/opentelemetry/src/setupEventContextTrace';
-import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor';
-import { enhanceDscWithOpenTelemetryRootSpanName } from '../../../../packages/opentelemetry/src/utils/enhanceDscWithOpenTelemetryRootSpanName';
-import type { TestClientInterface } from './TestClient';
-
-/**
- * Initialize OpenTelemetry for Node.
- */
-export function initOtel(): void {
- const client = getClient();
-
- if (!client) {
- DEBUG_BUILD &&
- debugLogger.warn(
- 'No client available, skipping OpenTelemetry setup. This probably means that `Sentry.init()` was not called before `initOtel()`.',
- );
- return;
- }
-
- if (client.getOptions().debug) {
- diag.setLogger(
- {
- error: debugLogger.error,
- warn: debugLogger.warn,
- info: debugLogger.log,
- debug: debugLogger.log,
- verbose: debugLogger.log,
- },
- DiagLogLevel.DEBUG,
- );
- }
-
- setupEventContextTrace(client);
- enhanceDscWithOpenTelemetryRootSpanName(client);
-
- const [provider, spanProcessor] = setupOtel(client);
- client.traceProvider = provider;
- client.spanProcessor = spanProcessor;
-}
-
-/** Just exported for tests. */
-export function setupOtel(client: TestClientInterface): [BasicTracerProvider, SentrySpanProcessor] {
- const spanProcessor = new SentrySpanProcessor();
- // Create and configure NodeTracerProvider
- const provider = new BasicTracerProvider({
- sampler: new SentrySampler(client),
- resource: defaultResource().merge(
- resourceFromAttributes({
- [ATTR_SERVICE_NAME]: 'opentelemetry-test',
- // eslint-disable-next-line deprecation/deprecation
- [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
- [ATTR_SERVICE_VERSION]: SDK_VERSION,
- }),
- ),
- forceFlushTimeoutMillis: 500,
- spanProcessors: [spanProcessor],
- });
-
- // We use a custom context manager to keep context in sync with sentry scope
- const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager);
-
- trace.setGlobalTracerProvider(provider);
- propagation.setGlobalPropagator(new SentryPropagator());
- context.setGlobalContextManager(new SentryContextManager());
-
- return [provider, spanProcessor];
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts b/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts
deleted file mode 100644
index 12372f60ea85..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/helpers/mockSdkInit.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api';
-import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import type { ClientOptions, Options } from '@sentry/core';
-import { flush, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
-import { setOpenTelemetryContextAsyncContextStrategy } from '../../../../packages/opentelemetry/src/asyncContextStrategy';
-import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor';
-import type { OpenTelemetryClient } from '../../../../packages/opentelemetry/src/types';
-import { clearOpenTelemetrySetupCheck } from '../../../../packages/opentelemetry/src/utils/setupCheck';
-import { initOtel } from './initOtel';
-import { init as initTestClient } from './TestClient';
-
-const PUBLIC_DSN = 'https://username@domain/123';
-
-/**
- * Initialize Sentry for Node.
- */
-function init(options: Partial | undefined = {}): void {
- setOpenTelemetryContextAsyncContextStrategy();
- initTestClient(options);
- initOtel();
-}
-
-function resetGlobals(): void {
- getCurrentScope().clear();
- getCurrentScope().setClient(undefined);
- getIsolationScope().clear();
- getGlobalScope().clear();
- delete (global as any).__SENTRY__;
-}
-
-export function mockSdkInit(options?: Partial) {
- resetGlobals();
-
- init({ dsn: PUBLIC_DSN, ...options });
-}
-
-export async function cleanupOtel(_provider?: BasicTracerProvider): Promise {
- clearOpenTelemetrySetupCheck();
-
- const provider = getProvider(_provider);
-
- if (provider) {
- await provider.forceFlush();
- await provider.shutdown();
- }
-
- // Disable all globally registered APIs
- trace.disable();
- context.disable();
- propagation.disable();
-
- await flush();
-}
-
-export function getSpanProcessor(): SentrySpanProcessor | undefined {
- const client = getClient();
- if (!client) {
- return undefined;
- }
-
- const spanProcessor = client.spanProcessor;
- if (spanProcessor instanceof SentrySpanProcessor) {
- return spanProcessor;
- }
-
- return undefined;
-}
-
-export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined {
- let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider();
-
- if (provider instanceof ProxyTracerProvider) {
- provider = provider.getDelegate();
- }
-
- if (!(provider instanceof BasicTracerProvider)) {
- return undefined;
- }
-
- return provider;
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts
deleted file mode 100644
index 800c2dbbeba1..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/integration/breadcrumbs.test.ts
+++ /dev/null
@@ -1,357 +0,0 @@
-import { addBreadcrumb, captureException, getClient, withIsolationScope, withScope } from '@sentry/core';
-import { afterEach, describe, expect, it, vi } from 'vitest';
-import { startSpan } from '../../../../packages/opentelemetry/src/trace';
-import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
-import type { TestClientInterface } from '../helpers/TestClient';
-
-describe('Integration | breadcrumbs', () => {
- const beforeSendTransaction = vi.fn(() => null);
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- describe('without tracing', () => {
- it('correctly adds & retrieves breadcrumbs', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb });
-
- const client = getClient() as TestClientInterface;
-
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
- addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } });
- addBreadcrumb({ timestamp: 123455, message: 'test3' });
-
- const error = new Error('test');
- captureException(error);
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(3);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test1', timestamp: 123456 },
- { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 },
- { message: 'test3', timestamp: 123455 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-
- it('handles parallel isolation scopes', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- addBreadcrumb({ timestamp: 123456, message: 'test0' });
-
- withIsolationScope(() => {
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
- });
-
- withIsolationScope(() => {
- addBreadcrumb({ timestamp: 123456, message: 'test2' });
- captureException(error);
- });
-
- withIsolationScope(() => {
- addBreadcrumb({ timestamp: 123456, message: 'test3' });
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(4);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test0', timestamp: 123456 },
- { message: 'test2', timestamp: 123456 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
- });
-
- it('correctly adds & retrieves breadcrumbs', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- startSpan({ name: 'test' }, () => {
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
-
- startSpan({ name: 'inner1' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } });
- });
-
- startSpan({ name: 'inner2' }, () => {
- addBreadcrumb({ timestamp: 123455, message: 'test3' });
- });
-
- captureException(error);
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(3);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test1', timestamp: 123456 },
- { data: { nested: 'yes' }, message: 'test2', timestamp: 123457 },
- { message: 'test3', timestamp: 123455 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-
- it('correctly adds & retrieves breadcrumbs for the current isolation scope only', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- withIsolationScope(() => {
- startSpan({ name: 'test1' }, () => {
- addBreadcrumb({ timestamp: 123456, message: 'test1-a' });
-
- startSpan({ name: 'inner1' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test1-b' });
- });
- });
- });
-
- withIsolationScope(() => {
- startSpan({ name: 'test2' }, () => {
- addBreadcrumb({ timestamp: 123456, message: 'test2-a' });
-
- startSpan({ name: 'inner2' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2-b' });
- });
-
- captureException(error);
- });
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(4);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test2-a', timestamp: 123456 },
- { message: 'test2-b', timestamp: 123457 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-
- it('ignores scopes inside of root span', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- startSpan({ name: 'test1' }, () => {
- withScope(() => {
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
- });
- startSpan({ name: 'inner1' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2' });
- });
-
- captureException(error);
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(2);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test1', timestamp: 123456 },
- { message: 'test2', timestamp: 123457 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-
- it('handles deep nesting of scopes', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- startSpan({ name: 'test1' }, () => {
- withScope(() => {
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
- });
- startSpan({ name: 'inner1' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2' });
-
- startSpan({ name: 'inner2' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test3' });
-
- startSpan({ name: 'inner3' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test4' });
-
- captureException(error);
-
- startSpan({ name: 'inner4' }, () => {
- addBreadcrumb({ timestamp: 123457, message: 'test5' });
- });
-
- addBreadcrumb({ timestamp: 123457, message: 'test6' });
- });
- });
- });
-
- addBreadcrumb({ timestamp: 123456, message: 'test99' });
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test1', timestamp: 123456 },
- { message: 'test2', timestamp: 123457 },
- { message: 'test3', timestamp: 123457 },
- { message: 'test4', timestamp: 123457 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-
- it('correctly adds & retrieves breadcrumbs in async isolation scopes', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb);
-
- mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, tracesSampleRate: 1 });
-
- const client = getClient() as TestClientInterface;
-
- const error = new Error('test');
-
- const promise1 = withIsolationScope(() => {
- return startSpan({ name: 'test' }, async () => {
- addBreadcrumb({ timestamp: 123456, message: 'test1' });
-
- await startSpan({ name: 'inner1' }, async () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2' });
- });
-
- await startSpan({ name: 'inner2' }, async () => {
- addBreadcrumb({ timestamp: 123455, message: 'test3' });
- });
-
- await new Promise(resolve => setTimeout(resolve, 10));
-
- captureException(error);
- });
- });
-
- const promise2 = withIsolationScope(() => {
- return startSpan({ name: 'test-b' }, async () => {
- addBreadcrumb({ timestamp: 123456, message: 'test1-b' });
-
- await startSpan({ name: 'inner1' }, async () => {
- addBreadcrumb({ timestamp: 123457, message: 'test2-b' });
- });
-
- await startSpan({ name: 'inner2' }, async () => {
- addBreadcrumb({ timestamp: 123455, message: 'test3-b' });
- });
- });
- });
-
- await Promise.all([promise1, promise2]);
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeBreadcrumb).toHaveBeenCalledTimes(6);
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test1', timestamp: 123456 },
- { message: 'test2', timestamp: 123457 },
- { message: 'test3', timestamp: 123455 },
- ],
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts
deleted file mode 100644
index 3e237b749d5e..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/integration/scope.test.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-import {
- captureException,
- getCapturedScopesOnSpan,
- getClient,
- getCurrentScope,
- getIsolationScope,
- setTag,
- withIsolationScope,
- withScope,
-} from '@sentry/core';
-import { afterEach, describe, expect, it, vi } from 'vitest';
-import { startSpan } from '../../../../packages/opentelemetry/src/trace';
-import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
-import type { TestClientInterface } from '../helpers/TestClient';
-
-describe('Integration | Scope', () => {
- afterEach(async () => {
- await cleanupOtel();
- });
-
- describe.each([
- ['with tracing', true],
- ['without tracing', false],
- ])('%s', (_name, tracingEnabled) => {
- it('correctly syncs OTEL context & Sentry hub/scope', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeSendTransaction = vi.fn(() => null);
-
- mockSdkInit({
- tracesSampleRate: tracingEnabled ? 1 : 0,
- beforeSend,
- beforeSendTransaction,
- });
-
- const client = getClient() as TestClientInterface;
-
- const rootScope = getCurrentScope();
-
- const error = new Error('test error');
- let spanId: string | undefined;
- let traceId: string | undefined;
-
- rootScope.setTag('tag1', 'val1');
-
- withScope(scope1 => {
- scope1.setTag('tag2', 'val2');
-
- withScope(scope2b => {
- scope2b.setTag('tag3-b', 'val3-b');
- });
-
- withScope(scope2 => {
- scope2.setTag('tag3', 'val3');
-
- startSpan({ name: 'outer' }, span => {
- expect(getCapturedScopesOnSpan(span).scope).toBe(tracingEnabled ? scope2 : undefined);
-
- spanId = span.spanContext().spanId;
- traceId = span.spanContext().traceId;
-
- setTag('tag4', 'val4');
-
- captureException(error);
- });
- });
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
-
- if (spanId) {
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: {
- trace: {
- span_id: spanId,
- trace_id: traceId,
- },
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
- }
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- tags: {
- tag1: 'val1',
- tag2: 'val2',
- tag3: 'val3',
- tag4: 'val4',
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- },
- );
-
- if (tracingEnabled) {
- expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
- // Note: Scope for transaction is taken at `start` time, not `finish` time
- expect(beforeSendTransaction).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- data: {
- 'sentry.origin': 'manual',
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- },
- span_id: spanId,
- status: 'ok',
- trace_id: traceId,
- origin: 'manual',
- },
- }),
- spans: [],
- start_timestamp: expect.any(Number),
- tags: {
- tag1: 'val1',
- tag2: 'val2',
- tag3: 'val3',
- tag4: 'val4',
- },
- timestamp: expect.any(Number),
- transaction_info: { source: 'custom' },
- type: 'transaction',
- }),
- {
- event_id: expect.any(String),
- },
- );
- }
- });
-
- it('isolates parallel scopes', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeSendTransaction = vi.fn(() => null);
-
- mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction });
-
- const client = getClient() as TestClientInterface;
- const rootScope = getCurrentScope();
-
- const error1 = new Error('test error 1');
- const error2 = new Error('test error 2');
- let spanId1: string | undefined;
- let spanId2: string | undefined;
- let traceId1: string | undefined;
- let traceId2: string | undefined;
-
- rootScope.setTag('tag1', 'val1');
-
- const initialIsolationScope = getIsolationScope();
-
- withScope(scope1 => {
- scope1.setTag('tag2', 'val2a');
-
- expect(getIsolationScope()).toBe(initialIsolationScope);
-
- withScope(scope2 => {
- scope2.setTag('tag3', 'val3a');
-
- startSpan({ name: 'outer' }, span => {
- expect(getIsolationScope()).toBe(initialIsolationScope);
-
- spanId1 = span.spanContext().spanId;
- traceId1 = span.spanContext().traceId;
-
- setTag('tag4', 'val4a');
-
- captureException(error1);
- });
- });
- });
-
- withScope(scope1 => {
- scope1.setTag('tag2', 'val2b');
-
- expect(getIsolationScope()).toBe(initialIsolationScope);
-
- withScope(scope2 => {
- scope2.setTag('tag3', 'val3b');
-
- startSpan({ name: 'outer' }, span => {
- expect(getIsolationScope()).toBe(initialIsolationScope);
-
- spanId2 = span.spanContext().spanId;
- traceId2 = span.spanContext().traceId;
-
- setTag('tag4', 'val4b');
-
- captureException(error2);
- });
- });
- });
-
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(2);
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: spanId1
- ? {
- span_id: spanId1,
- trace_id: traceId1,
- }
- : expect.any(Object),
- }),
- tags: {
- tag1: 'val1',
- tag2: 'val2a',
- tag3: 'val3a',
- tag4: 'val4a',
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error1,
- syntheticException: expect.any(Error),
- },
- );
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: spanId2
- ? {
- span_id: spanId2,
- trace_id: traceId2,
- }
- : expect.any(Object),
- }),
- tags: {
- tag1: 'val1',
- tag2: 'val2b',
- tag3: 'val3b',
- tag4: 'val4b',
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error2,
- syntheticException: expect.any(Error),
- },
- );
-
- if (tracingEnabled) {
- expect(beforeSendTransaction).toHaveBeenCalledTimes(2);
- }
- });
-
- it('isolates parallel isolation scopes', async () => {
- const beforeSend = vi.fn(() => null);
- const beforeSendTransaction = vi.fn(() => null);
-
- mockSdkInit({ tracesSampleRate: tracingEnabled ? 1 : 0, beforeSend, beforeSendTransaction });
-
- const client = getClient() as TestClientInterface;
- const rootScope = getCurrentScope();
-
- const error1 = new Error('test error 1');
- const error2 = new Error('test error 2');
- let spanId1: string | undefined;
- let spanId2: string | undefined;
- let traceId1: string | undefined;
- let traceId2: string | undefined;
-
- rootScope.setTag('tag1', 'val1');
-
- const initialIsolationScope = getIsolationScope();
- initialIsolationScope.setTag('isolationTag1', 'val1');
-
- withIsolationScope(scope1 => {
- scope1.setTag('tag2', 'val2a');
-
- expect(getIsolationScope()).not.toBe(initialIsolationScope);
- getIsolationScope().setTag('isolationTag2', 'val2');
-
- withScope(scope2 => {
- scope2.setTag('tag3', 'val3a');
-
- startSpan({ name: 'outer' }, span => {
- expect(getIsolationScope()).not.toBe(initialIsolationScope);
-
- spanId1 = span.spanContext().spanId;
- traceId1 = span.spanContext().traceId;
-
- setTag('tag4', 'val4a');
-
- captureException(error1);
- });
- });
- });
-
- withIsolationScope(scope1 => {
- scope1.setTag('tag2', 'val2b');
-
- expect(getIsolationScope()).not.toBe(initialIsolationScope);
- getIsolationScope().setTag('isolationTag2', 'val2b');
-
- withScope(scope2 => {
- scope2.setTag('tag3', 'val3b');
-
- startSpan({ name: 'outer' }, span => {
- expect(getIsolationScope()).not.toBe(initialIsolationScope);
-
- spanId2 = span.spanContext().spanId;
- traceId2 = span.spanContext().traceId;
-
- setTag('tag4', 'val4b');
-
- captureException(error2);
- });
- });
- });
-
- await client.flush();
-
- expect(spanId1).toBeDefined();
- expect(spanId2).toBeDefined();
- expect(traceId1).toBeDefined();
- expect(traceId2).toBeDefined();
-
- expect(beforeSend).toHaveBeenCalledTimes(2);
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: spanId1,
- trace_id: traceId1,
- },
- }),
- tags: {
- tag1: 'val1',
- tag2: 'val2a',
- tag3: 'val3a',
- tag4: 'val4a',
- isolationTag1: 'val1',
- isolationTag2: 'val2',
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error1,
- syntheticException: expect.any(Error),
- },
- );
-
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: spanId2,
- trace_id: traceId2,
- },
- }),
- tags: {
- tag1: 'val1',
- tag2: 'val2b',
- tag3: 'val3b',
- tag4: 'val4b',
- isolationTag1: 'val1',
- isolationTag2: 'val2b',
- },
- }),
- {
- event_id: expect.any(String),
- originalException: error2,
- syntheticException: expect.any(Error),
- },
- );
-
- if (tracingEnabled) {
- expect(beforeSendTransaction).toHaveBeenCalledTimes(2);
- }
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts b/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts
deleted file mode 100644
index 4c9909e09d9f..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/integration/transactions.test.ts
+++ /dev/null
@@ -1,724 +0,0 @@
-import type { SpanContext } from '@opentelemetry/api';
-import { context, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api';
-import { TraceState } from '@opentelemetry/core';
-import type { Event, TransactionEvent } from '@sentry/core';
-import {
- addBreadcrumb,
- debug,
- getClient,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
- setTag,
- startSpanManual,
- withIsolationScope,
-} from '@sentry/core';
-import { afterEach, describe, expect, it, vi } from 'vitest';
-import { SENTRY_TRACE_STATE_DSC } from '../../../../packages/opentelemetry/src/constants';
-import { startInactiveSpan, startSpan } from '../../../../packages/opentelemetry/src/trace';
-import { makeTraceState } from '../../../../packages/opentelemetry/src/utils/makeTraceState';
-import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit';
-import type { TestClientInterface } from '../helpers/TestClient';
-
-describe('Integration | Transactions', () => {
- afterEach(async () => {
- vi.restoreAllMocks();
- vi.useRealTimers();
- await cleanupOtel();
- });
-
- it('correctly creates transaction & spans', async () => {
- const transactions: TransactionEvent[] = [];
- const beforeSendTransaction = vi.fn(event => {
- transactions.push(event);
- return null;
- });
-
- mockSdkInit({
- tracesSampleRate: 1,
- beforeSendTransaction,
- release: '8.0.0',
- });
-
- const client = getClient() as TestClientInterface;
-
- addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 });
- setTag('outer.tag', 'test value');
-
- startSpan(
- {
- op: 'test op',
- name: 'test name',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
- },
- },
- span => {
- addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 });
-
- span.setAttributes({
- 'test.outer': 'test value',
- });
-
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
-
- setTag('test.tag', 'test value');
-
- startSpan({ name: 'inner span 2' }, innerSpan => {
- addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 });
-
- innerSpan.setAttributes({
- 'test.inner': 'test value',
- });
- });
- },
- );
-
- await client.flush();
-
- expect(transactions).toHaveLength(1);
- const transaction = transactions[0]!;
-
- expect(transaction.breadcrumbs).toEqual([
- { message: 'test breadcrumb 1', timestamp: 123456 },
- { message: 'test breadcrumb 2', timestamp: 123456 },
- { message: 'test breadcrumb 3', timestamp: 123456 },
- ]);
-
- expect(transaction.contexts?.otel).toEqual({
- resource: {
- 'service.name': 'opentelemetry-test',
- 'service.namespace': 'sentry',
- 'service.version': expect.any(String),
- 'telemetry.sdk.language': 'nodejs',
- 'telemetry.sdk.name': 'opentelemetry',
- 'telemetry.sdk.version': expect.any(String),
- },
- });
-
- expect(transaction.contexts?.trace).toEqual({
- data: {
- 'sentry.op': 'test op',
- 'sentry.origin': 'auto.test',
- 'sentry.source': 'task',
- 'sentry.sample_rate': 1,
- 'test.outer': 'test value',
- },
- op: 'test op',
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- status: 'ok',
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'auto.test',
- });
-
- expect(transaction.sdkProcessingMetadata?.sampleRate).toEqual(1);
- expect(transaction.sdkProcessingMetadata?.dynamicSamplingContext).toEqual({
- environment: 'production',
- public_key: expect.any(String),
- sample_rate: '1',
- sampled: 'true',
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- transaction: 'test name',
- release: '8.0.0',
- sample_rand: expect.any(String),
- });
-
- expect(transaction.environment).toEqual('production');
- expect(transaction.event_id).toEqual(expect.any(String));
- expect(transaction.start_timestamp).toEqual(expect.any(Number));
- expect(transaction.timestamp).toEqual(expect.any(Number));
- expect(transaction.transaction).toEqual('test name');
-
- expect(transaction.tags).toEqual({
- 'outer.tag': 'test value',
- 'test.tag': 'test value',
- });
- expect(transaction.transaction_info).toEqual({ source: 'task' });
- expect(transaction.type).toEqual('transaction');
-
- expect(transaction.spans).toHaveLength(2);
- const spans = transaction.spans || [];
-
- // note: Currently, spans do not have any context/span added to them
- // This is the same behavior as for the "regular" SDKs
- expect(spans).toEqual([
- {
- data: {
- 'sentry.origin': 'manual',
- },
- description: 'inner span 1',
- origin: 'manual',
- parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- start_timestamp: expect.any(Number),
- status: 'ok',
- timestamp: expect.any(Number),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- },
- {
- data: {
- 'test.inner': 'test value',
- 'sentry.origin': 'manual',
- },
- description: 'inner span 2',
- origin: 'manual',
- parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- start_timestamp: expect.any(Number),
- status: 'ok',
- timestamp: expect.any(Number),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- },
- ]);
- });
-
- it('correctly creates concurrent transaction & spans', async () => {
- const beforeSendTransaction = vi.fn(() => null);
-
- mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
-
- const client = getClient() as TestClientInterface;
-
- addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 });
-
- withIsolationScope(() => {
- startSpan(
- {
- op: 'test op',
- name: 'test name',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- },
- },
- span => {
- addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 });
-
- span.setAttributes({
- 'test.outer': 'test value',
- });
-
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
-
- setTag('test.tag', 'test value');
-
- startSpan({ name: 'inner span 2' }, innerSpan => {
- addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 });
-
- innerSpan.setAttributes({
- 'test.inner': 'test value',
- });
- });
- },
- );
- });
-
- withIsolationScope(() => {
- startSpan({ op: 'test op b', name: 'test name b' }, span => {
- addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 });
-
- span.setAttributes({
- 'test.outer': 'test value b',
- });
-
- const subSpan = startInactiveSpan({ name: 'inner span 1b' });
- subSpan.end();
-
- setTag('test.tag', 'test value b');
-
- startSpan({ name: 'inner span 2b' }, innerSpan => {
- addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 });
-
- innerSpan.setAttributes({
- 'test.inner': 'test value b',
- });
- });
- });
- });
-
- await client.flush();
-
- expect(beforeSendTransaction).toHaveBeenCalledTimes(2);
- expect(beforeSendTransaction).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test breadcrumb 1', timestamp: 123456 },
- { message: 'test breadcrumb 2', timestamp: 123456 },
- { message: 'test breadcrumb 3', timestamp: 123456 },
- ],
- contexts: expect.objectContaining({
- trace: {
- data: {
- 'sentry.op': 'test op',
- 'sentry.origin': 'auto.test',
- 'sentry.source': 'task',
- 'test.outer': 'test value',
- 'sentry.sample_rate': 1,
- },
- op: 'test op',
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- status: 'ok',
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'auto.test',
- },
- }),
- spans: [expect.any(Object), expect.any(Object)],
- start_timestamp: expect.any(Number),
- tags: {
- 'test.tag': 'test value',
- },
- timestamp: expect.any(Number),
- transaction: 'test name',
- transaction_info: { source: 'task' },
- type: 'transaction',
- }),
- {
- event_id: expect.any(String),
- },
- );
-
- expect(beforeSendTransaction).toHaveBeenCalledWith(
- expect.objectContaining({
- breadcrumbs: [
- { message: 'test breadcrumb 1', timestamp: 123456 },
- { message: 'test breadcrumb 2b', timestamp: 123456 },
- { message: 'test breadcrumb 3b', timestamp: 123456 },
- ],
- contexts: expect.objectContaining({
- trace: {
- data: {
- 'sentry.op': 'test op b',
- 'sentry.origin': 'manual',
- 'sentry.source': 'custom',
- 'test.outer': 'test value b',
- 'sentry.sample_rate': 1,
- },
- op: 'test op b',
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- status: 'ok',
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'manual',
- },
- }),
- spans: [expect.any(Object), expect.any(Object)],
- start_timestamp: expect.any(Number),
- tags: {
- 'test.tag': 'test value b',
- },
- timestamp: expect.any(Number),
- transaction: 'test name b',
- transaction_info: { source: 'custom' },
- type: 'transaction',
- }),
- {
- event_id: expect.any(String),
- },
- );
- });
-
- it('correctly creates transaction & spans with a trace header data', async () => {
- const beforeSendTransaction = vi.fn(() => null);
-
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
- const parentSpanId = '6e0c63257de34c92';
-
- const traceState = makeTraceState({
- dsc: undefined,
- sampled: true,
- });
-
- const spanContext: SpanContext = {
- traceId,
- spanId: parentSpanId,
- isRemote: true,
- traceFlags: TraceFlags.SAMPLED,
- traceState,
- };
-
- mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
-
- const client = getClient() as TestClientInterface;
-
- // We simulate the correct context we'd normally get from the SentryPropagator
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- startSpan(
- {
- op: 'test op',
- name: 'test name',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- },
- },
- () => {
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
- startSpan({ name: 'inner span 2' }, () => {});
- },
- );
- });
-
- await client.flush();
-
- expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
- expect(beforeSendTransaction).toHaveBeenLastCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- data: {
- 'sentry.op': 'test op',
- 'sentry.origin': 'auto.test',
- 'sentry.source': 'task',
- },
- op: 'test op',
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- parent_span_id: parentSpanId,
- status: 'ok',
- trace_id: traceId,
- origin: 'auto.test',
- },
- }),
- // spans are circular (they have a reference to the transaction), which leads to jest choking on this
- // instead we compare them in detail below
- spans: [expect.any(Object), expect.any(Object)],
- start_timestamp: expect.any(Number),
- timestamp: expect.any(Number),
- transaction: 'test name',
- transaction_info: { source: 'task' },
- type: 'transaction',
- }),
- {
- event_id: expect.any(String),
- },
- );
-
- // Checking the spans here, as they are circular to the transaction...
- const runArgs = beforeSendTransaction.mock.calls[0] as unknown as [TransactionEvent, unknown];
- const spans = runArgs[0].spans || [];
-
- // note: Currently, spans do not have any context/span added to them
- // This is the same behavior as for the "regular" SDKs
- expect(spans).toEqual([
- {
- data: {
- 'sentry.origin': 'manual',
- },
- description: 'inner span 1',
- origin: 'manual',
- parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- start_timestamp: expect.any(Number),
- status: 'ok',
- timestamp: expect.any(Number),
- trace_id: traceId,
- },
- {
- data: {
- 'sentry.origin': 'manual',
- },
- description: 'inner span 2',
- origin: 'manual',
- parent_span_id: expect.stringMatching(/[a-f0-9]{16}/),
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- start_timestamp: expect.any(Number),
- status: 'ok',
- timestamp: expect.any(Number),
- trace_id: traceId,
- },
- ]);
- });
-
- it('cleans up spans that are not flushed for over 5 mins', async () => {
- const beforeSendTransaction = vi.fn(() => null);
-
- const now = Date.now();
- vi.useFakeTimers();
- vi.setSystemTime(now);
-
- const logs: unknown[] = [];
- vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg));
-
- mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
-
- const spanProcessor = getSpanProcessor();
-
- const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
-
- if (!exporter) {
- throw new Error('No exporter found, aborting test...');
- }
-
- void startSpan({ name: 'test name' }, async () => {
- startInactiveSpan({ name: 'inner span 1' }).end();
- startInactiveSpan({ name: 'inner span 2' }).end();
-
- // Pretend this is pending for 10 minutes
- await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000));
- });
-
- // Child-spans have been added to the exporter, but they are pending since they are waiting for their parent
- const finishedSpans1 = [];
- exporter['_finishedSpanBuckets'].forEach(bucket => {
- if (bucket) {
- finishedSpans1.push(...bucket.spans);
- }
- });
- expect(finishedSpans1.length).toBe(2);
- expect(beforeSendTransaction).toHaveBeenCalledTimes(0);
-
- // Now wait for 5 mins
- vi.advanceTimersByTime(5 * 60 * 1_000 + 1);
-
- // Adding another span will trigger the cleanup
- startSpan({ name: 'other span' }, () => {});
-
- vi.advanceTimersByTime(1);
-
- // Old spans have been cleared away
- const finishedSpans2 = [];
- exporter['_finishedSpanBuckets'].forEach(bucket => {
- if (bucket) {
- finishedSpans2.push(...bucket.spans);
- }
- });
- expect(finishedSpans2.length).toBe(0);
-
- // Called once for the 'other span'
- expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
-
- expect(logs).toEqual(
- expect.arrayContaining([
- 'SpanExporter dropped 2 spans because they were pending for more than 300 seconds.',
- 'SpanExporter exported 1 spans, 0 spans are waiting for their parent spans to finish',
- ]),
- );
- });
-
- it('includes child spans that are finished in the same tick but after their parent span', async () => {
- const now = Date.now();
- vi.useFakeTimers();
- vi.setSystemTime(now);
-
- const logs: unknown[] = [];
- vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg));
-
- const transactions: Event[] = [];
-
- mockSdkInit({
- tracesSampleRate: 1,
- beforeSendTransaction: event => {
- transactions.push(event);
- return null;
- },
- });
-
- const spanProcessor = getSpanProcessor();
-
- const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
-
- if (!exporter) {
- throw new Error('No exporter found, aborting test...');
- }
-
- startSpanManual({ name: 'test name' }, async span => {
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
-
- const subSpan2 = startInactiveSpan({ name: 'inner span 2' });
-
- span.end();
- subSpan2.end();
- });
-
- vi.advanceTimersByTime(1);
-
- expect(transactions).toHaveLength(1);
- expect(transactions[0]?.spans).toHaveLength(2);
-
- // No spans are pending
- const finishedSpans = [];
- exporter['_finishedSpanBuckets'].forEach(bucket => {
- if (bucket) {
- finishedSpans.push(...bucket.spans);
- }
- });
- expect(finishedSpans.length).toBe(0);
- });
-
- it('collects child spans that are finished within 5 minutes their parent span has been sent', async () => {
- const timeout = 5 * 60 * 1000;
- const now = Date.now();
- vi.useFakeTimers();
- vi.setSystemTime(now);
-
- const logs: unknown[] = [];
- vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg));
-
- const transactions: Event[] = [];
-
- mockSdkInit({
- tracesSampleRate: 1,
- beforeSendTransaction: event => {
- transactions.push(event);
- return null;
- },
- });
-
- const spanProcessor = getSpanProcessor();
-
- const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
-
- if (!exporter) {
- throw new Error('No exporter found, aborting test...');
- }
-
- startSpanManual({ name: 'test name' }, async span => {
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
-
- const subSpan2 = startInactiveSpan({ name: 'inner span 2' });
-
- span.end();
-
- setTimeout(() => {
- subSpan2.end();
- }, timeout - 2);
- });
-
- vi.advanceTimersByTime(timeout - 1);
-
- expect(transactions).toHaveLength(2);
- expect(transactions[0]?.spans).toHaveLength(1);
-
- const finishedSpans: any = exporter['_finishedSpanBuckets'].flatMap(bucket =>
- bucket ? Array.from(bucket.spans) : [],
- );
- expect(finishedSpans.length).toBe(0);
- });
-
- it('discards child spans that are finished after 5 minutes their parent span has been sent', async () => {
- const timeout = 5 * 60 * 1000;
- const now = Date.now();
- vi.useFakeTimers();
- vi.setSystemTime(now);
-
- const logs: unknown[] = [];
- vi.spyOn(debug, 'log').mockImplementation(msg => logs.push(msg));
-
- const transactions: Event[] = [];
-
- mockSdkInit({
- tracesSampleRate: 1,
- beforeSendTransaction: event => {
- transactions.push(event);
- return null;
- },
- });
-
- const spanProcessor = getSpanProcessor();
-
- const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
-
- if (!exporter) {
- throw new Error('No exporter found, aborting test...');
- }
-
- startSpanManual({ name: 'test name' }, async span => {
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
- subSpan.end();
-
- const subSpan2 = startInactiveSpan({ name: 'inner span 2' });
-
- span.end();
-
- setTimeout(() => {
- subSpan2.end();
- }, timeout + 1);
- });
-
- vi.advanceTimersByTime(timeout + 2);
-
- expect(transactions).toHaveLength(1);
- expect(transactions[0]?.spans).toHaveLength(1);
-
- // subSpan2 is pending (and will eventually be cleaned up)
- const finishedSpans: any = [];
- exporter['_finishedSpanBuckets'].forEach(bucket => {
- if (bucket) {
- finishedSpans.push(...bucket.spans);
- }
- });
- expect(finishedSpans.length).toBe(1);
- expect(finishedSpans[0]?.name).toBe('inner span 2');
- });
-
- it('uses & inherits DSC on span trace state', async () => {
- const transactionEvents: Event[] = [];
- const beforeSendTransaction = vi.fn(event => {
- transactionEvents.push(event);
- return null;
- });
-
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
- const parentSpanId = '6e0c63257de34c92';
-
- const dscString = `sentry-transaction=other-transaction,sentry-environment=other,sentry-release=8.0.0,sentry-public_key=public,sentry-trace_id=${traceId},sentry-sampled=true`;
-
- const spanContext: SpanContext = {
- traceId,
- spanId: parentSpanId,
- isRemote: true,
- traceFlags: TraceFlags.SAMPLED,
- traceState: new TraceState().set(SENTRY_TRACE_STATE_DSC, dscString),
- };
-
- mockSdkInit({
- tracesSampleRate: 1,
- beforeSendTransaction,
- release: '7.0.0',
- });
-
- const client = getClient() as TestClientInterface;
-
- // We simulate the correct context we'd normally get from the SentryPropagator
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- startSpan(
- {
- op: 'test op',
- name: 'test name',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- },
- },
- span => {
- expect(span.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString);
-
- const subSpan = startInactiveSpan({ name: 'inner span 1' });
-
- expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString);
-
- subSpan.end();
-
- startSpan({ name: 'inner span 2' }, subSpan => {
- expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString);
- });
- },
- );
- });
-
- await client.flush();
-
- expect(transactionEvents).toHaveLength(1);
- expect(transactionEvents[0]?.sdkProcessingMetadata?.dynamicSamplingContext).toEqual({
- environment: 'other',
- public_key: 'public',
- release: '8.0.0',
- sampled: 'true',
- trace_id: traceId,
- transaction: 'other-transaction',
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts b/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts
deleted file mode 100644
index 8e3f85b38250..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/propagator.test.ts
+++ /dev/null
@@ -1,670 +0,0 @@
-import {
- context,
- defaultTextMapGetter,
- defaultTextMapSetter,
- propagation,
- ROOT_CONTEXT,
- trace,
- TraceFlags,
-} from '@opentelemetry/api';
-import { suppressTracing } from '@opentelemetry/core';
-import { getCurrentScope, withScope } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import {
- SENTRY_BAGGAGE_HEADER,
- SENTRY_SCOPES_CONTEXT_KEY,
- SENTRY_TRACE_HEADER,
-} from '../../../packages/opentelemetry/src/constants';
-import { SentryPropagator } from '../../../packages/opentelemetry/src/propagator';
-import { getSamplingDecision } from '../../../packages/opentelemetry/src/utils/getSamplingDecision';
-import { makeTraceState } from '../../../packages/opentelemetry/src/utils/makeTraceState';
-import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit';
-
-describe('SentryPropagator', () => {
- const propagator = new SentryPropagator();
- let carrier: { [key: string]: unknown };
-
- beforeEach(() => {
- carrier = {};
- mockSdkInit({
- environment: 'production',
- release: '1.0.0',
- tracesSampleRate: 1,
- dsn: 'https://abc@domain/123',
- });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('returns fields set', () => {
- expect(propagator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]);
- });
-
- describe('inject', () => {
- describe('without active local span', () => {
- it('uses scope propagation context without DSC if no span is found', () => {
- withScope(scope => {
- scope.setPropagationContext({
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- parentSpanId: '6e0c63257de34c93',
- sampled: true,
- sampleRand: Math.random(),
- });
-
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
-
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- ].sort(),
- );
- expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/);
- });
- });
-
- it('uses scope propagation context with DSC if no span is found', () => {
- withScope(scope => {
- scope.setPropagationContext({
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- parentSpanId: '6e0c63257de34c93',
- sampled: true,
- sampleRand: Math.random(),
- dsc: {
- transaction: 'sampled-transaction',
- sampled: 'false',
- trace_id: 'dsc_trace_id',
- public_key: 'dsc_public_key',
- environment: 'dsc_environment',
- release: 'dsc_release',
- sample_rate: '0.5',
- replay_id: 'dsc_replay_id',
- },
- });
-
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
-
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'sentry-environment=dsc_environment',
- 'sentry-release=dsc_release',
- 'sentry-public_key=dsc_public_key',
- 'sentry-trace_id=dsc_trace_id',
- 'sentry-transaction=sampled-transaction',
- 'sentry-sampled=false',
- 'sentry-sample_rate=0.5',
- 'sentry-replay_id=dsc_replay_id',
- ].sort(),
- );
- expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-1/);
- });
- });
-
- it('uses propagation data from current scope if no scope & span is found', () => {
- const scope = getCurrentScope();
- const traceId = scope.getPropagationContext().traceId;
-
- const ctx = trace.deleteSpan(ROOT_CONTEXT).deleteValue(SENTRY_SCOPES_CONTEXT_KEY);
- propagator.inject(ctx, carrier, defaultTextMapSetter);
-
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual([
- 'sentry-environment=production',
- 'sentry-public_key=abc',
- 'sentry-release=1.0.0',
- `sentry-trace_id=${traceId}`,
- ]);
- expect(carrier[SENTRY_TRACE_HEADER]).toMatch(traceId);
- });
- });
-
- describe('with active span', () => {
- it.each([
- [
- 'continues a remote trace without dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- isRemote: true,
- },
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-sampled=true',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-transaction=test',
- expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/),
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1',
- true,
- ],
- [
- 'continues a remote trace with dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- isRemote: true,
- traceState: makeTraceState({
- dsc: {
- transaction: 'sampled-transaction',
- sampled: 'true',
- trace_id: 'dsc_trace_id',
- public_key: 'dsc_public_key',
- environment: 'dsc_environment',
- release: 'dsc_release',
- sample_rate: '0.5',
- replay_id: 'dsc_replay_id',
- },
- }),
- },
- [
- 'sentry-environment=dsc_environment',
- 'sentry-release=dsc_release',
- 'sentry-public_key=dsc_public_key',
- 'sentry-trace_id=dsc_trace_id',
- 'sentry-transaction=sampled-transaction',
- 'sentry-sampled=true',
- 'sentry-sample_rate=0.5',
- 'sentry-replay_id=dsc_replay_id',
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1',
- true,
- ],
- [
- 'continues an unsampled remote trace without dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- },
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-sampled=true',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-transaction=test',
- expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/),
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1',
- undefined,
- ],
- [
- 'continues an unsampled remote trace with sampled trace state & without dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- traceState: makeTraceState({
- sampled: false,
- }),
- },
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-sampled=false',
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0',
- false,
- ],
- [
- 'continues an unsampled remote trace with dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- traceState: makeTraceState({
- dsc: {
- transaction: 'sampled-transaction',
- sampled: 'false',
- trace_id: 'dsc_trace_id',
- public_key: 'dsc_public_key',
- environment: 'dsc_environment',
- release: 'dsc_release',
- sample_rate: '0.5',
- replay_id: 'dsc_replay_id',
- },
- }),
- },
- [
- 'sentry-environment=dsc_environment',
- 'sentry-release=dsc_release',
- 'sentry-public_key=dsc_public_key',
- 'sentry-trace_id=dsc_trace_id',
- 'sentry-transaction=sampled-transaction',
- 'sentry-sampled=false',
- 'sentry-sample_rate=0.5',
- 'sentry-replay_id=dsc_replay_id',
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0',
- false,
- ],
- [
- 'continues an unsampled remote trace with dsc & sampled trace state',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- traceState: makeTraceState({
- sampled: false,
- dsc: {
- transaction: 'sampled-transaction',
- trace_id: 'dsc_trace_id',
- public_key: 'dsc_public_key',
- environment: 'dsc_environment',
- release: 'dsc_release',
- sample_rate: '0.5',
- replay_id: 'dsc_replay_id',
- },
- }),
- },
- [
- 'sentry-environment=dsc_environment',
- 'sentry-release=dsc_release',
- 'sentry-public_key=dsc_public_key',
- 'sentry-trace_id=dsc_trace_id',
- 'sentry-transaction=sampled-transaction',
- 'sentry-sample_rate=0.5',
- 'sentry-replay_id=dsc_replay_id',
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0',
- false,
- ],
- [
- 'starts a new trace without existing dsc',
- {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- },
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-sampled=true',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- ],
- 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1',
- true,
- ],
- ])('%s', (_name, spanContext, baggage, sentryTrace, samplingDecision) => {
- expect(getSamplingDecision(spanContext)).toBe(samplingDecision);
-
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- trace.getTracer('test').startActiveSpan('test', span => {
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
- baggage.forEach(baggageItem => {
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toContainEqual(baggageItem);
- });
- expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace.replace('{{spanId}}', span.spanContext().spanId));
- });
- });
- });
-
- it('uses local span over propagation context', () => {
- context.with(
- trace.setSpanContext(ROOT_CONTEXT, {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- isRemote: true,
- }),
- () => {
- trace.getTracer('test').startActiveSpan('test', span => {
- withScope(scope => {
- scope.setPropagationContext({
- traceId: 'TRACE_ID',
- parentSpanId: 'PARENT_SPAN_ID',
- sampled: true,
- sampleRand: Math.random(),
- });
-
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
-
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-sampled=true',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-transaction=test',
- expect.stringMatching(/sentry-sample_rand=0\.[0-9]+/),
- ].forEach(item => {
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toContainEqual(item);
- });
- expect(carrier[SENTRY_TRACE_HEADER]).toBe(
- `d4cda95b652f4a1592b449d5929fda1b-${span.spanContext().spanId}-1`,
- );
- });
- });
- },
- );
- });
-
- it('uses remote span with deferred sampling decision over propagation context', () => {
- const carrier: Record = {};
- context.with(
- trace.setSpanContext(ROOT_CONTEXT, {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- }),
- () => {
- withScope(scope => {
- scope.setPropagationContext({
- traceId: 'TRACE_ID',
- parentSpanId: 'PARENT_SPAN_ID',
- sampled: true,
- sampleRand: Math.random(),
- });
-
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
-
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- ].sort(),
- );
- // Used spanId is a random ID, not from the remote span
- expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}/);
- expect(carrier[SENTRY_TRACE_HEADER]).not.toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92');
- });
- },
- );
- });
-
- it('uses remote span over propagation context', () => {
- const carrier: Record = {};
- context.with(
- trace.setSpanContext(ROOT_CONTEXT, {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- isRemote: true,
- traceState: makeTraceState({ sampled: false }),
- }),
- () => {
- withScope(scope => {
- scope.setPropagationContext({
- traceId: 'TRACE_ID',
- parentSpanId: 'PARENT_SPAN_ID',
- sampled: true,
- sampleRand: Math.random(),
- });
-
- propagator.inject(context.active(), carrier, defaultTextMapSetter);
-
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-public_key=abc',
- 'sentry-sampled=false',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- ].sort(),
- );
- // Used spanId is a random ID, not from the remote span
- expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/d4cda95b652f4a1592b449d5929fda1b-[a-f0-9]{16}-0/);
- expect(carrier[SENTRY_TRACE_HEADER]).not.toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0');
- });
- },
- );
- });
- });
-
- it('should include existing baggage', () => {
- const spanContext = {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- };
- const context = trace.setSpanContext(ROOT_CONTEXT, spanContext);
- const baggage = propagation.createBaggage({ foo: { value: 'bar' } });
- propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'foo=bar',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-public_key=abc',
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-sampled=true',
- ].sort(),
- );
- });
-
- it('should include existing baggage header', () => {
- const spanContext = {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- };
-
- const carrier = {
- other: 'header',
- baggage: 'foo=bar,other=yes',
- };
- const context = trace.setSpanContext(ROOT_CONTEXT, spanContext);
- const baggage = propagation.createBaggage();
- propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'foo=bar',
- 'other=yes',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-public_key=abc',
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-sampled=true',
- ].sort(),
- );
- });
-
- it('should include existing baggage array header', () => {
- const spanContext = {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- };
-
- const carrier = {
- other: 'header',
- baggage: ['foo=bar,other=yes', 'other2=no'],
- };
- const context = trace.setSpanContext(ROOT_CONTEXT, spanContext);
- const baggage = propagation.createBaggage();
- propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'foo=bar',
- 'other=yes',
- 'other2=no',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-public_key=abc',
- 'sentry-environment=production',
- 'sentry-release=1.0.0',
- 'sentry-sampled=true',
- ].sort(),
- );
- });
-
- it('should overwrite existing sentry baggage header', () => {
- const spanContext = {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- };
-
- const carrier = {
- baggage: 'foo=bar,other=yes,sentry-release=9.9.9,sentry-other=yes',
- };
- const context = trace.setSpanContext(ROOT_CONTEXT, spanContext);
- const baggage = propagation.createBaggage();
- propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
- expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(
- [
- 'foo=bar',
- 'other=yes',
- 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b',
- 'sentry-public_key=abc',
- 'sentry-environment=production',
- 'sentry-other=yes',
- 'sentry-release=1.0.0',
- 'sentry-sampled=true',
- ].sort(),
- );
- });
-
- it('should create baggage without propagation context', () => {
- const scope = getCurrentScope();
- const traceId = scope.getPropagationContext().traceId;
-
- const context = ROOT_CONTEXT;
- const baggage = propagation.createBaggage({ foo: { value: 'bar' } });
- propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter);
- expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(
- `foo=bar,sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=${traceId}`,
- );
- });
-
- it('should NOT set baggage and sentry-trace header if instrumentation is suppressed', () => {
- const spanContext = {
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- };
-
- const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext));
- propagator.inject(context, carrier, defaultTextMapSetter);
- expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined);
- expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined);
- });
- });
-
- describe('extract', () => {
- it('sets data from sentry trace header on span context', () => {
- const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1';
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual({
- isRemote: true,
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- traceState: makeTraceState({}),
- });
- expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true);
- });
-
- it('sets data from negative sampled sentry trace header on span context', () => {
- const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0';
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual({
- isRemote: true,
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- traceState: makeTraceState({ sampled: false }),
- });
- expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(false);
- });
-
- it('sets data from not sampled sentry trace header on span context', () => {
- const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92';
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual({
- isRemote: true,
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.NONE,
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- traceState: makeTraceState({}),
- });
- expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(undefined);
- });
-
- it('handles undefined sentry trace header', () => {
- const sentryTraceHeader = undefined;
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual(undefined);
- expect(getCurrentScope().getPropagationContext()).toEqual({
- traceId: expect.stringMatching(/[a-f0-9]{32}/),
- sampleRand: expect.any(Number),
- });
- });
-
- it('sets data from baggage header on span context', () => {
- const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1';
- const baggage =
- 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction,sentry-sample_rand=0.123';
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- carrier[SENTRY_BAGGAGE_HEADER] = baggage;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual({
- isRemote: true,
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- traceState: makeTraceState({
- dsc: {
- environment: 'production',
- release: '1.0.0',
- public_key: 'abc',
- trace_id: 'd4cda95b652f4a1592b449d5929fda1b',
- transaction: 'dsc-transaction',
- sample_rand: '0.123',
- },
- }),
- });
- expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true);
- });
-
- it('handles empty dsc baggage header', () => {
- const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1';
- const baggage = '';
- carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader;
- carrier[SENTRY_BAGGAGE_HEADER] = baggage;
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual({
- isRemote: true,
- spanId: '6e0c63257de34c92',
- traceFlags: TraceFlags.SAMPLED,
- traceId: 'd4cda95b652f4a1592b449d5929fda1b',
- traceState: makeTraceState({}),
- });
- expect(getSamplingDecision(trace.getSpanContext(context)!)).toBe(true);
- });
-
- it('handles when sentry-trace is an empty array', () => {
- carrier[SENTRY_TRACE_HEADER] = [];
- const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
- expect(trace.getSpanContext(context)).toEqual(undefined);
- expect(getCurrentScope().getPropagationContext()).toEqual({
- traceId: expect.stringMatching(/[a-f0-9]{32}/),
- sampleRand: expect.any(Number),
- });
- });
- });
-});
-
-function baggageToArray(baggage: unknown): string[] {
- return typeof baggage === 'string' ? baggage.split(',').sort() : [];
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts b/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts
deleted file mode 100644
index 86cf7b135f97..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/sampler.test.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { context, SpanKind, trace } from '@opentelemetry/api';
-import { TraceState } from '@opentelemetry/core';
-import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
-import { ATTR_HTTP_REQUEST_METHOD } from '@opentelemetry/semantic-conventions';
-import { generateSpanId, generateTraceId } from '@sentry/core';
-import { afterEach, describe, expect, it, vi } from 'vitest';
-import { SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING } from '../../../packages/opentelemetry/src/constants';
-import { SentrySampler } from '../../../packages/opentelemetry/src/sampler';
-import { cleanupOtel } from './helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from './helpers/TestClient';
-
-describe('SentrySampler', () => {
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('works with tracesSampleRate=0', () => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 }));
- const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent');
- const sampler = new SentrySampler(client);
-
- const ctx = context.active();
- const traceId = generateTraceId();
- const spanName = 'test';
- const spanKind = SpanKind.INTERNAL;
- const spanAttributes = {};
- const links = undefined;
-
- const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual(
- expect.objectContaining({
- decision: SamplingDecision.NOT_RECORD,
- attributes: { 'sentry.sample_rate': 0 },
- }),
- );
- expect(actual.traceState?.get('sentry.sampled_not_recording')).toBe('1');
- expect(actual.traceState?.get('sentry.sample_rand')).toEqual(expect.any(String));
- expect(spyOnDroppedEvent).toHaveBeenCalledTimes(1);
- expect(spyOnDroppedEvent).toHaveBeenCalledWith('sample_rate', 'transaction');
-
- spyOnDroppedEvent.mockReset();
- });
-
- it('works with tracesSampleRate=0 & for a child span', () => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 }));
- const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent');
- const sampler = new SentrySampler(client);
-
- const traceId = generateTraceId();
- const ctx = trace.setSpanContext(context.active(), {
- spanId: generateSpanId(),
- traceId,
- traceFlags: 0,
- traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'),
- });
- const spanName = 'test';
- const spanKind = SpanKind.INTERNAL;
- const spanAttributes = {};
- const links = undefined;
-
- const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual({
- decision: SamplingDecision.NOT_RECORD,
- traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'),
- });
- expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
-
- spyOnDroppedEvent.mockReset();
- });
-
- it('works with tracesSampleRate=1', () => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent');
- const sampler = new SentrySampler(client);
-
- const ctx = context.active();
- const traceId = generateTraceId();
- const spanName = 'test';
- const spanKind = SpanKind.INTERNAL;
- const spanAttributes = {};
- const links = undefined;
-
- const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual(
- expect.objectContaining({
- decision: SamplingDecision.RECORD_AND_SAMPLED,
- attributes: { 'sentry.sample_rate': 1 },
- }),
- );
- expect(actual.traceState?.constructor.name).toBe('TraceState');
- expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
-
- spyOnDroppedEvent.mockReset();
- });
-
- it('works with traceSampleRate=undefined', () => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: undefined }));
- const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent');
- const sampler = new SentrySampler(client);
-
- const ctx = context.active();
- const traceId = generateTraceId();
- const spanName = 'test';
- const spanKind = SpanKind.INTERNAL;
- const spanAttributes = {};
- const links = undefined;
-
- const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual({
- decision: SamplingDecision.NOT_RECORD,
- traceState: new TraceState(),
- });
- expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
-
- spyOnDroppedEvent.mockReset();
- });
-
- it('ignores local http client root spans', () => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 }));
- const spyOnDroppedEvent = vi.spyOn(client, 'recordDroppedEvent');
- const sampler = new SentrySampler(client);
-
- const ctx = context.active();
- const traceId = generateTraceId();
- const spanName = 'test';
- const spanKind = SpanKind.CLIENT;
- const spanAttributes = {
- [ATTR_HTTP_REQUEST_METHOD]: 'GET',
- };
- const links = undefined;
-
- const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual({
- decision: SamplingDecision.NOT_RECORD,
- traceState: new TraceState(),
- });
- expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
-
- spyOnDroppedEvent.mockReset();
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts b/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts
deleted file mode 100644
index 5a1782c89e7b..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/spanExporter.test.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import { ATTR_HTTP_RESPONSE_STATUS_CODE } from '@opentelemetry/semantic-conventions';
-import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, startInactiveSpan, startSpanManual } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { createTransactionForOtelSpan } from '../../../packages/opentelemetry/src/spanExporter';
-import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit';
-
-describe('createTransactionForOtelSpan', () => {
- beforeEach(() => {
- mockSdkInit({
- tracesSampleRate: 1,
- });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('works with a basic span', () => {
- const span = startInactiveSpan({ name: 'test', startTime: 1733821670000 });
- span.end(1733821672000);
-
- const event = createTransactionForOtelSpan(span as any);
- // we do not care about this here
- delete event.sdkProcessingMetadata;
-
- expect(event).toEqual({
- contexts: {
- trace: {
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- data: {
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- 'sentry.origin': 'manual',
- },
- origin: 'manual',
- status: 'ok',
- },
- otel: {
- resource: {
- 'service.name': 'opentelemetry-test',
- 'telemetry.sdk.language': 'nodejs',
- 'telemetry.sdk.name': 'opentelemetry',
- 'telemetry.sdk.version': expect.any(String),
- 'service.namespace': 'sentry',
- 'service.version': SDK_VERSION,
- },
- },
- },
- spans: [],
- start_timestamp: 1733821670,
- timestamp: 1733821672,
- transaction: 'test',
- type: 'transaction',
- transaction_info: { source: 'custom' },
- });
- });
-
- it('works with a http.server span', () => {
- const span = startInactiveSpan({
- name: 'test',
- startTime: 1733821670000,
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server',
- [ATTR_HTTP_RESPONSE_STATUS_CODE]: 200,
- },
- });
- span.end(1733821672000);
-
- const event = createTransactionForOtelSpan(span as any);
- // we do not care about this here
- delete event.sdkProcessingMetadata;
-
- expect(event).toEqual({
- contexts: {
- trace: {
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- data: {
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- 'sentry.origin': 'manual',
- 'sentry.op': 'http.server',
- 'http.response.status_code': 200,
- },
- origin: 'manual',
- status: 'ok',
- op: 'http.server',
- },
- otel: {
- resource: {
- 'service.name': 'opentelemetry-test',
- 'telemetry.sdk.language': 'nodejs',
- 'telemetry.sdk.name': 'opentelemetry',
- 'telemetry.sdk.version': expect.any(String),
- 'service.namespace': 'sentry',
- 'service.version': SDK_VERSION,
- },
- },
- response: {
- status_code: 200,
- },
- },
- spans: [],
- start_timestamp: 1733821670,
- timestamp: 1733821672,
- transaction: 'test',
- type: 'transaction',
- transaction_info: { source: 'custom' },
- });
- });
-
- it('adds span link to the trace context when adding with addLink()', () => {
- const span = startInactiveSpan({ name: 'parent1' });
- span.end();
-
- startSpanManual({ name: 'rootSpan' }, rootSpan => {
- rootSpan.addLink({ context: span.spanContext(), attributes: { 'sentry.link.type': 'previous_trace' } });
- rootSpan.end();
-
- const prevTraceId = span.spanContext().traceId;
- const prevSpanId = span.spanContext().spanId;
- const event = createTransactionForOtelSpan(rootSpan as any);
-
- expect(event.contexts?.trace).toEqual(
- expect.objectContaining({
- links: [
- expect.objectContaining({
- attributes: { 'sentry.link.type': 'previous_trace' },
- sampled: true,
- trace_id: expect.stringMatching(prevTraceId),
- span_id: expect.stringMatching(prevSpanId),
- }),
- ],
- }),
- );
- });
- });
-
- it('adds span link to the trace context when linked in span options', () => {
- const span = startInactiveSpan({ name: 'parent1' });
-
- const prevTraceId = span.spanContext().traceId;
- const prevSpanId = span.spanContext().spanId;
-
- const linkedSpan = startInactiveSpan({
- name: 'parent2',
- links: [{ context: span.spanContext(), attributes: { 'sentry.link.type': 'previous_trace' } }],
- });
-
- span.end();
- linkedSpan.end();
-
- const event = createTransactionForOtelSpan(linkedSpan as any);
-
- expect(event.contexts?.trace).toEqual(
- expect.objectContaining({
- links: [
- expect.objectContaining({
- attributes: { 'sentry.link.type': 'previous_trace' },
- sampled: true,
- trace_id: expect.stringMatching(prevTraceId),
- span_id: expect.stringMatching(prevSpanId),
- }),
- ],
- }),
- );
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/trace.test.ts b/dev-packages/opentelemetry-v2-tests/test/trace.test.ts
deleted file mode 100644
index 52d5e67477d0..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/trace.test.ts
+++ /dev/null
@@ -1,1935 +0,0 @@
-/* eslint-disable deprecation/deprecation */
-import type { Span, TimeInput } from '@opentelemetry/api';
-import { context, ROOT_CONTEXT, SpanKind, trace, TraceFlags } from '@opentelemetry/api';
-import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
-import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions';
-import type { Event, Scope } from '@sentry/core';
-import {
- getClient,
- getCurrentScope,
- getDynamicSamplingContextFromClient,
- getDynamicSamplingContextFromSpan,
- getRootSpan,
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
- spanIsSampled,
- spanToJSON,
- suppressTracing,
- withScope,
-} from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import {
- continueTrace,
- startInactiveSpan,
- startSpan,
- startSpanManual,
-} from '../../../packages/opentelemetry/src/trace';
-import type { AbstractSpan } from '../../../packages/opentelemetry/src/types';
-import { getActiveSpan } from '../../../packages/opentelemetry/src/utils/getActiveSpan';
-import { getParentSpanId } from '../../../packages/opentelemetry/src/utils/getParentSpanId';
-import { getSamplingDecision } from '../../../packages/opentelemetry/src/utils/getSamplingDecision';
-import { getSpanKind } from '../../../packages/opentelemetry/src/utils/getSpanKind';
-import { makeTraceState } from '../../../packages/opentelemetry/src/utils/makeTraceState';
-import { spanHasAttributes, spanHasName } from '../../../packages/opentelemetry/src/utils/spanTypes';
-import { isSpan } from './helpers/isSpan';
-import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit';
-
-describe('trace', () => {
- beforeEach(() => {
- mockSdkInit({ tracesSampleRate: 1 });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- describe('startSpan', () => {
- it('works with a sync callback', () => {
- const spans: Span[] = [];
-
- expect(getActiveSpan()).toEqual(undefined);
-
- const res = startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- spans.push(outerSpan);
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getActiveSpan()).toEqual(outerSpan);
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- spans.push(innerSpan);
-
- expect(getSpanName(innerSpan)).toEqual('inner');
- expect(getActiveSpan()).toEqual(innerSpan);
- });
-
- return 'test value';
- });
-
- expect(res).toEqual('test value');
-
- expect(getActiveSpan()).toEqual(undefined);
- expect(spans).toHaveLength(2);
- const [outerSpan, innerSpan] = spans as [Span, Span];
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getSpanName(innerSpan)).toEqual('inner');
-
- expect(getSpanEndTime(outerSpan)).not.toEqual([0, 0]);
- expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]);
- });
-
- it('works with an async callback', async () => {
- const spans: Span[] = [];
-
- expect(getActiveSpan()).toEqual(undefined);
-
- const res = await startSpan({ name: 'outer' }, async outerSpan => {
- expect(outerSpan).toBeDefined();
- spans.push(outerSpan);
-
- await new Promise(resolve => setTimeout(resolve, 10));
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getActiveSpan()).toEqual(outerSpan);
-
- await startSpan({ name: 'inner' }, async innerSpan => {
- expect(innerSpan).toBeDefined();
- spans.push(innerSpan);
-
- await new Promise(resolve => setTimeout(resolve, 10));
-
- expect(getSpanName(innerSpan)).toEqual('inner');
- expect(getActiveSpan()).toEqual(innerSpan);
- });
-
- return 'test value';
- });
-
- expect(res).toEqual('test value');
-
- expect(getActiveSpan()).toEqual(undefined);
- expect(spans).toHaveLength(2);
- const [outerSpan, innerSpan] = spans as [Span, Span];
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getSpanName(innerSpan)).toEqual('inner');
-
- expect(getSpanEndTime(outerSpan)).not.toEqual([0, 0]);
- expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]);
- });
-
- it('works with multiple parallel calls', () => {
- const spans1: Span[] = [];
- const spans2: Span[] = [];
-
- expect(getActiveSpan()).toEqual(undefined);
-
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- spans1.push(outerSpan);
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getActiveSpan()).toEqual(outerSpan);
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- spans1.push(innerSpan);
-
- expect(getSpanName(innerSpan)).toEqual('inner');
- expect(getActiveSpan()).toEqual(innerSpan);
- });
- });
-
- startSpan({ name: 'outer2' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- spans2.push(outerSpan);
-
- expect(getSpanName(outerSpan)).toEqual('outer2');
- expect(getActiveSpan()).toEqual(outerSpan);
-
- startSpan({ name: 'inner2' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- spans2.push(innerSpan);
-
- expect(getSpanName(innerSpan)).toEqual('inner2');
- expect(getActiveSpan()).toEqual(innerSpan);
- });
- });
-
- expect(getActiveSpan()).toEqual(undefined);
- expect(spans1).toHaveLength(2);
- expect(spans2).toHaveLength(2);
- });
-
- it('works with multiple parallel async calls', async () => {
- const spans1: Span[] = [];
- const spans2: Span[] = [];
-
- expect(getActiveSpan()).toEqual(undefined);
-
- const promise1 = startSpan({ name: 'outer' }, async outerSpan => {
- expect(outerSpan).toBeDefined();
- spans1.push(outerSpan);
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getActiveSpan()).toEqual(outerSpan);
- expect(getRootSpan(outerSpan)).toEqual(outerSpan);
-
- await new Promise(resolve => setTimeout(resolve, 10));
-
- await startSpan({ name: 'inner' }, async innerSpan => {
- expect(innerSpan).toBeDefined();
- spans1.push(innerSpan);
-
- expect(getSpanName(innerSpan)).toEqual('inner');
- expect(getActiveSpan()).toEqual(innerSpan);
- expect(getRootSpan(innerSpan)).toEqual(outerSpan);
- });
- });
-
- const promise2 = startSpan({ name: 'outer2' }, async outerSpan => {
- expect(outerSpan).toBeDefined();
- spans2.push(outerSpan);
-
- expect(getSpanName(outerSpan)).toEqual('outer2');
- expect(getActiveSpan()).toEqual(outerSpan);
- expect(getRootSpan(outerSpan)).toEqual(outerSpan);
-
- await new Promise(resolve => setTimeout(resolve, 10));
-
- await startSpan({ name: 'inner2' }, async innerSpan => {
- expect(innerSpan).toBeDefined();
- spans2.push(innerSpan);
-
- expect(getSpanName(innerSpan)).toEqual('inner2');
- expect(getActiveSpan()).toEqual(innerSpan);
- expect(getRootSpan(innerSpan)).toEqual(outerSpan);
- });
- });
-
- await Promise.all([promise1, promise2]);
-
- expect(getActiveSpan()).toEqual(undefined);
- expect(spans1).toHaveLength(2);
- expect(spans2).toHaveLength(2);
- });
-
- it('allows to pass context arguments', () => {
- startSpan(
- {
- name: 'outer',
- },
- span => {
- expect(span).toBeDefined();
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- });
- },
- );
-
- startSpan(
- {
- name: 'outer',
- op: 'my-op',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin',
- },
- },
- span => {
- expect(span).toBeDefined();
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin',
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'my-op',
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- });
- },
- );
- });
-
- it('allows to pass base SpanOptions', () => {
- const date = [5000, 0] as TimeInput;
-
- startSpan(
- {
- name: 'outer',
- kind: SpanKind.CLIENT,
- attributes: {
- test1: 'test 1',
- test2: 2,
- },
- startTime: date,
- },
- span => {
- expect(span).toBeDefined();
- expect(getSpanName(span)).toEqual('outer');
- expect(getSpanStartTime(span)).toEqual(date);
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- test1: 'test 1',
- test2: 2,
- });
- expect(getSpanKind(span)).toEqual(SpanKind.CLIENT);
- },
- );
- });
-
- it('allows to pass a startTime in seconds', () => {
- const startTime = 1708504860.961;
- const start = startSpan({ name: 'outer', startTime: startTime }, span => {
- return getSpanStartTime(span);
- });
-
- expect(start).toEqual([1708504860, 961000000]);
- });
-
- it('allows to pass a scope', () => {
- const initialScope = getCurrentScope();
-
- let manualScope: Scope;
- let parentSpan: Span;
-
- // "hack" to create a manual scope with a parent span
- startSpanManual({ name: 'detached' }, span => {
- parentSpan = span;
- manualScope = getCurrentScope();
- manualScope.setTag('manual', 'tag');
- });
-
- expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag' });
- expect(getCurrentScope()).not.toBe(manualScope!);
-
- getCurrentScope().setTag('outer', 'tag');
-
- startSpan({ name: 'GET users/[id]', scope: manualScope! }, span => {
- // the current scope in the callback is a fork of the manual scope
- expect(getCurrentScope()).not.toBe(initialScope);
- expect(getCurrentScope()).not.toBe(manualScope);
- expect(getCurrentScope().getScopeData().tags).toEqual({ manual: 'tag' });
-
- // getActiveSpan returns the correct span
- expect(getActiveSpan()).toBe(span);
-
- // span hierarchy is correct
- expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId);
-
- // scope data modifications are isolated between original and forked manual scope
- getCurrentScope().setTag('inner', 'tag');
- manualScope!.setTag('manual-scope-inner', 'tag');
-
- expect(getCurrentScope().getScopeData().tags).toEqual({ manual: 'tag', inner: 'tag' });
- expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag', 'manual-scope-inner': 'tag' });
- });
-
- // manualScope modifications remain set outside the callback
- expect(manualScope!.getScopeData().tags).toEqual({ manual: 'tag', 'manual-scope-inner': 'tag' });
-
- // current scope is reset back to initial scope
- expect(getCurrentScope()).toBe(initialScope);
- expect(getCurrentScope().getScopeData().tags).toEqual({ outer: 'tag' });
-
- // although the manual span is still running, it's no longer active due to being outside of the callback
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass a parentSpan', () => {
- let parentSpan: Span;
-
- startSpanManual({ name: 'detached' }, span => {
- parentSpan = span;
- });
-
- startSpan({ name: 'GET users/[id]', parentSpan: parentSpan! }, span => {
- expect(getActiveSpan()).toBe(span);
- expect(spanToJSON(span).parent_span_id).toBe(parentSpan.spanContext().spanId);
- });
-
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass parentSpan=null', () => {
- startSpan({ name: 'GET users/[id' }, () => {
- startSpan({ name: 'child', parentSpan: null }, span => {
- expect(spanToJSON(span).parent_span_id).toBe(undefined);
- });
- });
- });
-
- it('allows to add span links', () => {
- const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });
-
- // @ts-expect-error links exists on span
- expect(rawSpan1?.links).toEqual([]);
-
- const span1JSON = spanToJSON(rawSpan1);
-
- startSpan({ name: '/users/:id' }, rawSpan2 => {
- rawSpan2.addLink({
- context: rawSpan1.spanContext(),
- attributes: {
- 'sentry.link.type': 'previous_trace',
- },
- });
-
- const span2LinkJSON = spanToJSON(rawSpan2).links?.[0];
-
- expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id);
- expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id);
- expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);
- });
- });
-
- it('allows to pass span links in span options', () => {
- const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });
-
- // @ts-expect-error links exists on span
- expect(rawSpan1?.links).toEqual([]);
-
- const span1JSON = spanToJSON(rawSpan1);
-
- startSpan(
- {
- name: '/users/:id',
- links: [
- {
- context: rawSpan1.spanContext(),
- attributes: { 'sentry.link.type': 'previous_trace' },
- },
- ],
- },
- rawSpan2 => {
- const span2LinkJSON = spanToJSON(rawSpan2).links?.[0];
-
- expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id);
- expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id);
- expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);
- },
- );
- });
-
- it('allows to force a transaction with forceTransaction=true', async () => {
- const client = getClient()!;
- const transactionEvents: Event[] = [];
-
- client.getOptions().beforeSendTransaction = event => {
- transactionEvents.push({
- ...event,
- sdkProcessingMetadata: {
- dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext,
- },
- });
- return event;
- };
-
- startSpan({ name: 'outer transaction' }, () => {
- startSpan({ name: 'inner span' }, () => {
- startSpan({ name: 'inner transaction', forceTransaction: true }, () => {
- startSpan({ name: 'inner span 2' }, () => {
- // all good
- });
- });
- });
- });
-
- await client.flush();
-
- const normalizedTransactionEvents = transactionEvents.map(event => {
- return {
- ...event,
- spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })),
- };
- });
-
- expect(normalizedTransactionEvents).toHaveLength(2);
-
- const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction');
- const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction');
-
- const outerTraceId = outerTransaction?.contexts?.trace?.trace_id;
- // The inner transaction should be a child of the last span of the outer transaction
- const innerParentSpanId = outerTransaction?.spans?.[0]?.id;
- const innerSpanId = innerTransaction?.contexts?.trace?.span_id;
-
- expect(outerTraceId).toBeDefined();
- expect(innerParentSpanId).toBeDefined();
- expect(innerSpanId).toBeDefined();
- // inner span ID should _not_ be the parent span ID, but the id of the new span
- expect(innerSpanId).not.toEqual(innerParentSpanId);
-
- expect(outerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- 'sentry.origin': 'manual',
- },
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'manual',
- status: 'ok',
- });
- expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]);
- expect(outerTransaction?.transaction).toEqual('outer transaction');
- expect(outerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
-
- expect(innerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.origin': 'manual',
- },
- parent_span_id: innerParentSpanId,
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: outerTraceId,
- origin: 'manual',
- status: 'ok',
- });
- expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]);
- expect(innerTransaction?.transaction).toEqual('inner transaction');
- expect(innerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
- });
-
- // TODO: propagation scope is not picked up by spans...
-
- describe('onlyIfParent', () => {
- it('does not create a span if there is no parent', () => {
- const span = startSpan({ name: 'test span', onlyIfParent: true }, span => {
- return span;
- });
-
- expect(isSpan(span)).toBe(false);
- });
-
- it('creates a span if there is a parent', () => {
- const span = startSpan({ name: 'parent span' }, () => {
- const span = startSpan({ name: 'test span', onlyIfParent: true }, span => {
- return span;
- });
-
- return span;
- });
-
- expect(isSpan(span)).toBe(true);
- });
- });
- });
-
- describe('startInactiveSpan', () => {
- it('works at the root', () => {
- const span = startInactiveSpan({ name: 'test' });
-
- expect(span).toBeDefined();
- expect(getSpanName(span)).toEqual('test');
- expect(getSpanEndTime(span)).toEqual([0, 0]);
- expect(getActiveSpan()).toBeUndefined();
-
- span.end();
-
- expect(getSpanEndTime(span)).not.toEqual([0, 0]);
- expect(getActiveSpan()).toBeUndefined();
- });
-
- it('works as a child span', () => {
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(getActiveSpan()).toEqual(outerSpan);
-
- const innerSpan = startInactiveSpan({ name: 'test' });
-
- expect(innerSpan).toBeDefined();
- expect(getSpanName(innerSpan)).toEqual('test');
- expect(getSpanEndTime(innerSpan)).toEqual([0, 0]);
- expect(getActiveSpan()).toEqual(outerSpan);
-
- innerSpan.end();
-
- expect(getSpanEndTime(innerSpan)).not.toEqual([0, 0]);
- expect(getActiveSpan()).toEqual(outerSpan);
- });
- });
-
- it('allows to pass context arguments', () => {
- const span = startInactiveSpan({
- name: 'outer',
- });
-
- expect(span).toBeDefined();
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- });
-
- const span2 = startInactiveSpan({
- name: 'outer',
- op: 'my-op',
- attributes: {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin',
- },
- });
-
- expect(span2).toBeDefined();
- expect(getSpanAttributes(span2)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test.origin',
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'my-op',
- });
- });
-
- it('allows to pass base SpanOptions', () => {
- const date = [5000, 0] as TimeInput;
-
- const span = startInactiveSpan({
- name: 'outer',
- kind: SpanKind.CLIENT,
- attributes: {
- test1: 'test 1',
- test2: 2,
- },
- startTime: date,
- });
-
- expect(span).toBeDefined();
- expect(getSpanName(span)).toEqual('outer');
- expect(getSpanStartTime(span)).toEqual(date);
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- test1: 'test 1',
- test2: 2,
- });
- expect(getSpanKind(span)).toEqual(SpanKind.CLIENT);
- });
-
- it('allows to pass a startTime in seconds', () => {
- const startTime = 1708504860.961;
- const span = startInactiveSpan({ name: 'outer', startTime: startTime });
-
- expect(getSpanStartTime(span)).toEqual([1708504860, 961000000]);
- });
-
- it('allows to pass a scope', () => {
- const initialScope = getCurrentScope();
-
- let manualScope: Scope;
-
- const parentSpan = startSpanManual({ name: 'detached' }, span => {
- manualScope = getCurrentScope();
- manualScope.setTag('manual', 'tag');
- return span;
- });
-
- getCurrentScope().setTag('outer', 'tag');
-
- const span = startInactiveSpan({ name: 'GET users/[id]', scope: manualScope! });
- expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId);
-
- expect(getCurrentScope()).toBe(initialScope);
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass a parentSpan', () => {
- let parentSpan: Span;
-
- startSpanManual({ name: 'detached' }, span => {
- parentSpan = span;
- });
-
- const span = startInactiveSpan({ name: 'GET users/[id]', parentSpan: parentSpan! });
-
- expect(getActiveSpan()).toBe(undefined);
- expect(spanToJSON(span).parent_span_id).toBe(parentSpan!.spanContext().spanId);
-
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass parentSpan=null', () => {
- startSpan({ name: 'outer' }, () => {
- const span = startInactiveSpan({ name: 'test span', parentSpan: null });
- expect(spanToJSON(span).parent_span_id).toBe(undefined);
- span.end();
- });
- });
-
- it('allows to pass span links in span options', () => {
- const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });
-
- // @ts-expect-error links exists on span
- expect(rawSpan1?.links).toEqual([]);
-
- const rawSpan2 = startInactiveSpan({
- name: 'GET users/[id]',
- links: [
- {
- context: rawSpan1.spanContext(),
- attributes: { 'sentry.link.type': 'previous_trace' },
- },
- ],
- });
-
- const span1JSON = spanToJSON(rawSpan1);
- const span2JSON = spanToJSON(rawSpan2);
- const span2LinkJSON = span2JSON.links?.[0];
-
- expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id);
- expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id);
- expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);
-
- // sampling decision is inherited
- expect(span2LinkJSON?.sampled).toBe(Boolean(spanToJSON(rawSpan1).data['sentry.sample_rate']));
- });
-
- it('allows to force a transaction with forceTransaction=true', async () => {
- const client = getClient()!;
- const transactionEvents: Event[] = [];
-
- client.getOptions().beforeSendTransaction = event => {
- transactionEvents.push({
- ...event,
- sdkProcessingMetadata: {
- dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext,
- },
- });
- return event;
- };
-
- startSpan({ name: 'outer transaction' }, () => {
- startSpan({ name: 'inner span' }, () => {
- const innerTransaction = startInactiveSpan({ name: 'inner transaction', forceTransaction: true });
- innerTransaction.end();
- });
- });
-
- await client.flush();
-
- const normalizedTransactionEvents = transactionEvents.map(event => {
- return {
- ...event,
- spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })),
- };
- });
-
- expect(normalizedTransactionEvents).toHaveLength(2);
-
- const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction');
- const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction');
-
- const outerTraceId = outerTransaction?.contexts?.trace?.trace_id;
- // The inner transaction should be a child of the last span of the outer transaction
- const innerParentSpanId = outerTransaction?.spans?.[0]?.id;
- const innerSpanId = innerTransaction?.contexts?.trace?.span_id;
-
- expect(outerTraceId).toBeDefined();
- expect(innerParentSpanId).toBeDefined();
- expect(innerSpanId).toBeDefined();
- // inner span ID should _not_ be the parent span ID, but the id of the new span
- expect(innerSpanId).not.toEqual(innerParentSpanId);
-
- expect(outerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- 'sentry.origin': 'manual',
- },
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'manual',
- status: 'ok',
- });
- expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]);
- expect(outerTransaction?.transaction).toEqual('outer transaction');
- expect(outerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
-
- expect(innerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.origin': 'manual',
- },
- parent_span_id: innerParentSpanId,
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: outerTraceId,
- origin: 'manual',
- status: 'ok',
- });
- expect(innerTransaction?.spans).toEqual([]);
- expect(innerTransaction?.transaction).toEqual('inner transaction');
- expect(innerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
- });
-
- describe('onlyIfParent', () => {
- it('does not create a span if there is no parent', () => {
- const span = startInactiveSpan({ name: 'test span', onlyIfParent: true });
-
- expect(isSpan(span)).toBe(false);
- });
-
- it('creates a span if there is a parent', () => {
- const span = startSpan({ name: 'parent span' }, () => {
- const span = startInactiveSpan({ name: 'test span', onlyIfParent: true });
-
- return span;
- });
-
- expect(isSpan(span)).toBe(true);
- });
- });
-
- it('includes the scope at the time the span was started when finished', async () => {
- const beforeSendTransaction = vi.fn(event => event);
-
- const client = getClient()!;
-
- client.getOptions().beforeSendTransaction = beforeSendTransaction;
-
- let span: Span;
-
- const scope = getCurrentScope();
- scope.setTag('outer', 'foo');
-
- withScope(scope => {
- scope.setTag('scope', 1);
- span = startInactiveSpan({ name: 'my-span' });
- scope.setTag('scope_after_span', 2);
- });
-
- withScope(scope => {
- scope.setTag('scope', 2);
- span.end();
- });
-
- await client.flush();
-
- expect(beforeSendTransaction).toHaveBeenCalledTimes(1);
- expect(beforeSendTransaction).toHaveBeenCalledWith(
- expect.objectContaining({
- tags: expect.objectContaining({
- outer: 'foo',
- scope: 1,
- scope_after_span: 2,
- }),
- }),
- expect.anything(),
- );
- });
- });
-
- describe('startSpanManual', () => {
- it('does not automatically finish the span', () => {
- expect(getActiveSpan()).toEqual(undefined);
-
- let _outerSpan: Span | undefined;
- let _innerSpan: Span | undefined;
-
- const res = startSpanManual({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- _outerSpan = outerSpan;
-
- expect(getSpanName(outerSpan)).toEqual('outer');
- expect(getActiveSpan()).toEqual(outerSpan);
-
- startSpanManual({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- _innerSpan = innerSpan;
-
- expect(getSpanName(innerSpan)).toEqual('inner');
- expect(getActiveSpan()).toEqual(innerSpan);
- });
-
- expect(getSpanEndTime(_innerSpan!)).toEqual([0, 0]);
-
- _innerSpan!.end();
-
- expect(getSpanEndTime(_innerSpan!)).not.toEqual([0, 0]);
-
- return 'test value';
- });
-
- expect(getSpanEndTime(_outerSpan!)).toEqual([0, 0]);
-
- _outerSpan!.end();
-
- expect(getSpanEndTime(_outerSpan!)).not.toEqual([0, 0]);
-
- expect(res).toEqual('test value');
-
- expect(getActiveSpan()).toEqual(undefined);
- });
-
- it('allows to pass base SpanOptions', () => {
- const date = [5000, 0] as TimeInput;
-
- startSpanManual(
- {
- name: 'outer',
- kind: SpanKind.CLIENT,
- attributes: {
- test1: 'test 1',
- test2: 2,
- },
- startTime: date,
- },
- span => {
- expect(span).toBeDefined();
- expect(getSpanName(span)).toEqual('outer');
- expect(getSpanStartTime(span)).toEqual(date);
- expect(getSpanAttributes(span)).toEqual({
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- test1: 'test 1',
- test2: 2,
- });
- expect(getSpanKind(span)).toEqual(SpanKind.CLIENT);
- },
- );
- });
-
- it('allows to pass a startTime in seconds', () => {
- const startTime = 1708504860.961;
- const start = startSpanManual({ name: 'outer', startTime: startTime }, span => {
- const start = getSpanStartTime(span);
- span.end();
- return start;
- });
-
- expect(start).toEqual([1708504860, 961000000]);
- });
-
- it('allows to pass a scope', () => {
- const initialScope = getCurrentScope();
-
- let manualScope: Scope;
- let parentSpan: Span;
-
- startSpanManual({ name: 'detached' }, span => {
- parentSpan = span;
- manualScope = getCurrentScope();
- manualScope.setTag('manual', 'tag');
- });
-
- getCurrentScope().setTag('outer', 'tag');
-
- startSpanManual({ name: 'GET users/[id]', scope: manualScope! }, span => {
- expect(getCurrentScope()).not.toBe(initialScope);
-
- expect(getCurrentScope()).toEqual(manualScope);
- expect(getActiveSpan()).toBe(span);
-
- expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId);
-
- span.end();
- });
-
- expect(getCurrentScope()).toBe(initialScope);
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass a parentSpan', () => {
- let parentSpan: Span;
-
- startSpanManual({ name: 'detached' }, span => {
- parentSpan = span;
- });
-
- startSpanManual({ name: 'GET users/[id]', parentSpan: parentSpan! }, span => {
- expect(getActiveSpan()).toBe(span);
- expect(spanToJSON(span).parent_span_id).toBe(parentSpan.spanContext().spanId);
-
- span.end();
- });
-
- expect(getActiveSpan()).toBe(undefined);
- });
-
- it('allows to pass parentSpan=null', () => {
- startSpan({ name: 'outer' }, () => {
- startSpanManual({ name: 'GET users/[id]', parentSpan: null }, span => {
- expect(spanToJSON(span).parent_span_id).toBe(undefined);
- span.end();
- });
- });
- });
-
- it('allows to add span links', () => {
- const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });
-
- // @ts-expect-error links exists on span
- expect(rawSpan1?.links).toEqual([]);
-
- const span1JSON = spanToJSON(rawSpan1);
-
- startSpanManual({ name: '/users/:id' }, rawSpan2 => {
- rawSpan2.addLink({
- context: rawSpan1.spanContext(),
- attributes: {
- 'sentry.link.type': 'previous_trace',
- },
- });
-
- const span2LinkJSON = spanToJSON(rawSpan2).links?.[0];
-
- expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id);
- expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id);
- expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);
- });
- });
-
- it('allows to pass span links in span options', () => {
- const rawSpan1 = startInactiveSpan({ name: 'pageload_span' });
-
- // @ts-expect-error links exists on span
- expect(rawSpan1?.links).toEqual([]);
-
- const span1JSON = spanToJSON(rawSpan1);
-
- startSpanManual(
- {
- name: '/users/:id',
- links: [
- {
- context: rawSpan1.spanContext(),
- attributes: { 'sentry.link.type': 'previous_trace' },
- },
- ],
- },
- rawSpan2 => {
- const span2LinkJSON = spanToJSON(rawSpan2).links?.[0];
-
- expect(span2LinkJSON?.attributes?.['sentry.link.type']).toBe('previous_trace');
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(rawSpan1._spanContext.traceId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.traceId).toEqual(span1JSON.trace_id);
- expect(span2LinkJSON?.trace_id).toBe(span1JSON.trace_id);
-
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(rawSpan1?._spanContext.spanId);
- // @ts-expect-error links and _spanContext exist on span
- expect(rawSpan2?.links?.[0].context.spanId).toEqual(span1JSON.span_id);
- expect(span2LinkJSON?.span_id).toBe(span1JSON.span_id);
- },
- );
- });
-
- it('allows to force a transaction with forceTransaction=true', async () => {
- const client = getClient()!;
- const transactionEvents: Event[] = [];
-
- client.getOptions().beforeSendTransaction = event => {
- transactionEvents.push({
- ...event,
- sdkProcessingMetadata: {
- dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext,
- },
- });
- return event;
- };
-
- startSpanManual({ name: 'outer transaction' }, span => {
- startSpanManual({ name: 'inner span' }, span => {
- startSpanManual({ name: 'inner transaction', forceTransaction: true }, span => {
- startSpanManual({ name: 'inner span 2' }, span => {
- // all good
- span.end();
- });
- span.end();
- });
- span.end();
- });
- span.end();
- });
-
- await client.flush();
-
- const normalizedTransactionEvents = transactionEvents.map(event => {
- return {
- ...event,
- spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })),
- };
- });
-
- expect(normalizedTransactionEvents).toHaveLength(2);
-
- const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction');
- const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction');
-
- const outerTraceId = outerTransaction?.contexts?.trace?.trace_id;
- // The inner transaction should be a child of the last span of the outer transaction
- const innerParentSpanId = outerTransaction?.spans?.[0]?.id;
- const innerSpanId = innerTransaction?.contexts?.trace?.span_id;
-
- expect(outerTraceId).toBeDefined();
- expect(innerParentSpanId).toBeDefined();
- expect(innerSpanId).toBeDefined();
- // inner span ID should _not_ be the parent span ID, but the id of the new span
- expect(innerSpanId).not.toEqual(innerParentSpanId);
-
- expect(outerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.sample_rate': 1,
- 'sentry.origin': 'manual',
- },
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- origin: 'manual',
- status: 'ok',
- });
- expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]);
- expect(outerTransaction?.transaction).toEqual('outer transaction');
- expect(outerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
-
- expect(innerTransaction?.contexts?.trace).toEqual({
- data: {
- 'sentry.source': 'custom',
- 'sentry.origin': 'manual',
- },
- parent_span_id: innerParentSpanId,
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: outerTraceId,
- origin: 'manual',
- status: 'ok',
- });
- expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]);
- expect(innerTransaction?.transaction).toEqual('inner transaction');
- expect(innerTransaction?.sdkProcessingMetadata).toEqual({
- dynamicSamplingContext: {
- environment: 'production',
- public_key: 'username',
- trace_id: outerTraceId,
- sample_rate: '1',
- transaction: 'outer transaction',
- sampled: 'true',
- sample_rand: expect.any(String),
- },
- });
- });
-
- describe('onlyIfParent', () => {
- it('does not create a span if there is no parent', () => {
- const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => {
- return span;
- });
-
- expect(isSpan(span)).toBe(false);
- });
-
- it('creates a span if there is a parent', () => {
- const span = startSpan({ name: 'parent span' }, () => {
- const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => {
- return span;
- });
-
- return span;
- });
-
- expect(isSpan(span)).toBe(true);
- });
- });
- });
-
- describe('propagation', () => {
- it('starts new trace, if there is no parent', () => {
- withScope(scope => {
- const propagationContext = scope.getPropagationContext();
- const span = startInactiveSpan({ name: 'test span' });
-
- expect(span).toBeDefined();
- const traceId = spanToJSON(span).trace_id;
- expect(traceId).toMatch(/[a-f0-9]{32}/);
- expect(spanToJSON(span).parent_span_id).toBe(undefined);
- expect(spanToJSON(span).trace_id).not.toEqual(propagationContext.traceId);
-
- expect(getDynamicSamplingContextFromSpan(span)).toEqual({
- trace_id: traceId,
- environment: 'production',
- public_key: 'username',
- sample_rate: '1',
- sampled: 'true',
- transaction: 'test span',
- sample_rand: expect.any(String),
- });
- });
- });
-
- // Note: This _should_ never happen, when we have an incoming trace, we should always have a parent span
- it('starts new trace, ignoring parentSpanId, if there is no parent', () => {
- withScope(scope => {
- const propagationContext = scope.getPropagationContext();
- propagationContext.parentSpanId = '1121201211212012';
- const span = startInactiveSpan({ name: 'test span' });
-
- expect(span).toBeDefined();
- const traceId = spanToJSON(span).trace_id;
- expect(traceId).toMatch(/[a-f0-9]{32}/);
- expect(spanToJSON(span).parent_span_id).toBe(undefined);
- expect(spanToJSON(span).trace_id).not.toEqual(propagationContext.traceId);
-
- expect(getDynamicSamplingContextFromSpan(span)).toEqual({
- environment: 'production',
- public_key: 'username',
- trace_id: traceId,
- sample_rate: '1',
- sampled: 'true',
- transaction: 'test span',
- sample_rand: expect.any(String),
- });
- });
- });
-
- it('picks up the trace context from the parent without DSC', () => {
- withScope(scope => {
- const propagationContext = scope.getPropagationContext();
-
- startSpan({ name: 'parent span' }, parentSpan => {
- const span = startInactiveSpan({ name: 'test span' });
-
- expect(span).toBeDefined();
- expect(spanToJSON(span).trace_id).toEqual(parentSpan.spanContext().traceId);
- expect(spanToJSON(span).parent_span_id).toEqual(parentSpan.spanContext().spanId);
- expect(getDynamicSamplingContextFromSpan(span)).toEqual({
- ...getDynamicSamplingContextFromClient(propagationContext.traceId, getClient()!),
- trace_id: parentSpan.spanContext().traceId,
- transaction: 'parent span',
- sampled: 'true',
- sample_rate: '1',
- sample_rand: expect.any(String),
- });
- });
- });
- });
-
- it('picks up the trace context from the parent with DSC', () => {
- withScope(() => {
- const ctx = trace.setSpanContext(ROOT_CONTEXT, {
- traceId: '12312012123120121231201212312012',
- spanId: '1121201211212012',
- isRemote: false,
- traceFlags: TraceFlags.SAMPLED,
- traceState: makeTraceState({
- dsc: {
- release: '1.0',
- environment: 'production',
- },
- }),
- });
-
- context.with(ctx, () => {
- const span = startInactiveSpan({ name: 'test span' });
-
- expect(span).toBeDefined();
- expect(spanToJSON(span).trace_id).toEqual('12312012123120121231201212312012');
- expect(spanToJSON(span).parent_span_id).toEqual('1121201211212012');
- expect(getDynamicSamplingContextFromSpan(span)).toEqual({
- release: '1.0',
- environment: 'production',
- });
- });
- });
- });
-
- it('picks up the trace context from a remote parent', () => {
- withScope(() => {
- const ctx = trace.setSpanContext(ROOT_CONTEXT, {
- traceId: '12312012123120121231201212312012',
- spanId: '1121201211212012',
- isRemote: true,
- traceFlags: TraceFlags.SAMPLED,
- traceState: makeTraceState({
- dsc: {
- release: '1.0',
- environment: 'production',
- },
- }),
- });
-
- context.with(ctx, () => {
- const span = startInactiveSpan({ name: 'test span' });
-
- expect(span).toBeDefined();
- expect(spanToJSON(span).trace_id).toEqual('12312012123120121231201212312012');
- expect(spanToJSON(span).parent_span_id).toEqual('1121201211212012');
- expect(getDynamicSamplingContextFromSpan(span)).toEqual({
- release: '1.0',
- environment: 'production',
- });
- });
- });
- });
- });
-});
-
-describe('trace (tracing disabled)', () => {
- beforeEach(() => {
- mockSdkInit({ tracesSampleRate: 0 });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('startSpan calls callback without span', () => {
- const val = startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(false);
-
- return 'test value';
- });
-
- expect(val).toEqual('test value');
- });
-
- it('startInactiveSpan returns a NonRecordinSpan', () => {
- const span = startInactiveSpan({ name: 'test' });
-
- expect(span).toBeDefined();
- expect(span.isRecording()).toBe(false);
- });
-});
-
-describe('trace (sampling)', () => {
- afterEach(async () => {
- await cleanupOtel();
- vi.clearAllMocks();
- });
-
- it('samples with a tracesSampleRate, when Math.random() > tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- mockSdkInit({ tracesSampleRate: 0.5 });
-
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(false);
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- expect(innerSpan.isRecording()).toBe(false);
- });
- });
- });
-
- it('samples with a tracesSampleRate, when Math.random() < tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.4);
-
- mockSdkInit({ tracesSampleRate: 0.5 });
-
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(true);
- // All fields are empty for NonRecordingSpan
- expect(getSpanName(outerSpan)).toBe('outer');
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- expect(innerSpan.isRecording()).toBe(true);
- expect(getSpanName(innerSpan)).toBe('inner');
- });
- });
- });
-
- it('positive parent sampling takes precedence over tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- mockSdkInit({ tracesSampleRate: 1 });
-
- // This will def. be sampled because of the tracesSampleRate
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(true);
- expect(getSpanName(outerSpan)).toBe('outer');
-
- // Now let's mutate the tracesSampleRate so that the next entry _should_ not be sampled
- // but it will because of parent sampling
- const client = getClient();
- client!.getOptions().tracesSampleRate = 0.5;
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- expect(innerSpan.isRecording()).toBe(true);
- expect(getSpanName(innerSpan)).toBe('inner');
- });
- });
- });
-
- it('negative parent sampling takes precedence over tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- mockSdkInit({ tracesSampleRate: 0.5 });
-
- // This will def. be unsampled because of the tracesSampleRate
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(false);
-
- // Now let's mutate the tracesSampleRate so that the next entry _should_ be sampled
- // but it will remain unsampled because of parent sampling
- const client = getClient();
- client!.getOptions().tracesSampleRate = 1;
-
- startSpan({ name: 'inner' }, innerSpan => {
- expect(innerSpan).toBeDefined();
- expect(innerSpan.isRecording()).toBe(false);
- });
- });
- });
-
- it('positive remote parent sampling takes precedence over tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- mockSdkInit({ tracesSampleRate: 0.5 });
-
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
- const parentSpanId = '6e0c63257de34c92';
-
- const spanContext = {
- traceId,
- spanId: parentSpanId,
- sampled: true,
- isRemote: true,
- traceFlags: TraceFlags.SAMPLED,
- };
-
- // We simulate the correct context we'd normally get from the SentryPropagator
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- // This will def. be sampled because of the tracesSampleRate
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(true);
- expect(getSpanName(outerSpan)).toBe('outer');
- });
- });
- });
-
- it('negative remote parent sampling takes precedence over tracesSampleRate', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- mockSdkInit({ tracesSampleRate: 0.5 });
-
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
- const parentSpanId = '6e0c63257de34c92';
-
- const spanContext = {
- traceId,
- spanId: parentSpanId,
- sampled: false,
- isRemote: true,
- traceFlags: TraceFlags.NONE,
- };
-
- // We simulate the correct context we'd normally get from the SentryPropagator
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- // This will def. be sampled because of the tracesSampleRate
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- expect(outerSpan.isRecording()).toBe(false);
- });
- });
- });
-
- it('samples with a tracesSampler returning a boolean', () => {
- let tracesSamplerResponse: boolean = true;
-
- const tracesSampler = vi.fn(() => {
- return tracesSamplerResponse;
- });
-
- mockSdkInit({ tracesSampler });
-
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan).toBeDefined();
- });
-
- expect(tracesSampler).toBeCalledTimes(1);
- expect(tracesSampler).toHaveBeenLastCalledWith({
- parentSampled: undefined,
- name: 'outer',
- attributes: {},
- inheritOrSampleWith: expect.any(Function),
- });
-
- // Now return `false`, it should not sample
- tracesSamplerResponse = false;
-
- startSpan({ name: 'outer2' }, outerSpan => {
- expect(outerSpan.isRecording()).toBe(false);
-
- startSpan({ name: 'inner2' }, innerSpan => {
- expect(innerSpan.isRecording()).toBe(false);
- });
- });
-
- expect(tracesSampler).toHaveBeenCalledTimes(2);
- expect(tracesSampler).toHaveBeenCalledWith(
- expect.objectContaining({
- parentSampled: undefined,
- name: 'outer',
- attributes: {},
- }),
- );
- expect(tracesSampler).toHaveBeenCalledWith(
- expect.objectContaining({
- parentSampled: undefined,
- name: 'outer2',
- attributes: {},
- }),
- );
-
- // Only root spans should go through the sampler
- expect(tracesSampler).not.toHaveBeenLastCalledWith({
- name: 'inner2',
- });
- });
-
- it('samples with a tracesSampler returning a number', () => {
- vi.spyOn(Math, 'random').mockImplementation(() => 0.6);
-
- let tracesSamplerResponse: number = 1;
-
- const tracesSampler = vi.fn(() => {
- return tracesSamplerResponse;
- });
-
- mockSdkInit({ tracesSampler });
-
- startSpan(
- {
- name: 'outer',
- op: 'test.op',
- attributes: { attr1: 'yes', attr2: 1 },
- },
- outerSpan => {
- expect(outerSpan).toBeDefined();
- },
- );
-
- expect(tracesSampler).toHaveBeenCalledTimes(1);
- expect(tracesSampler).toHaveBeenLastCalledWith({
- parentSampled: undefined,
- name: 'outer',
- attributes: {
- attr1: 'yes',
- attr2: 1,
- 'sentry.op': 'test.op',
- },
- inheritOrSampleWith: expect.any(Function),
- });
-
- // Now return `0`, it should not sample
- tracesSamplerResponse = 0;
-
- startSpan({ name: 'outer2' }, outerSpan => {
- expect(outerSpan.isRecording()).toBe(false);
-
- startSpan({ name: 'inner2' }, innerSpan => {
- expect(innerSpan.isRecording()).toBe(false);
- });
- });
-
- expect(tracesSampler).toHaveBeenCalledTimes(2);
- expect(tracesSampler).toHaveBeenCalledWith(
- expect.objectContaining({
- parentSampled: undefined,
- name: 'outer2',
- attributes: {},
- }),
- );
-
- // Only root spans should be passed to tracesSampler
- expect(tracesSampler).not.toHaveBeenLastCalledWith(
- expect.objectContaining({
- name: 'inner2',
- }),
- );
-
- // Now return `0.4`, it should not sample
- tracesSamplerResponse = 0.4;
-
- startSpan({ name: 'outer3' }, outerSpan => {
- expect(outerSpan.isRecording()).toBe(false);
- });
-
- expect(tracesSampler).toHaveBeenCalledTimes(3);
- expect(tracesSampler).toHaveBeenLastCalledWith({
- parentSampled: undefined,
- name: 'outer3',
- attributes: {},
- inheritOrSampleWith: expect.any(Function),
- });
- });
-
- it('samples with a tracesSampler even if parent is remotely sampled', () => {
- const tracesSampler = vi.fn(() => {
- return false;
- });
-
- mockSdkInit({ tracesSampler });
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
- const parentSpanId = '6e0c63257de34c92';
-
- const spanContext = {
- traceId,
- spanId: parentSpanId,
- sampled: true,
- isRemote: true,
- traceFlags: TraceFlags.SAMPLED,
- };
-
- // We simulate the correct context we'd normally get from the SentryPropagator
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- // This will def. be sampled because of the tracesSampleRate
- startSpan({ name: 'outer' }, outerSpan => {
- expect(outerSpan.isRecording()).toBe(false);
- });
- });
-
- expect(tracesSampler).toBeCalledTimes(1);
- expect(tracesSampler).toHaveBeenLastCalledWith({
- parentSampled: true,
- name: 'outer',
- attributes: {},
- inheritOrSampleWith: expect.any(Function),
- });
- });
-
- it('ignores parent span context if it is invalid', () => {
- mockSdkInit({ tracesSampleRate: 1 });
- const traceId = 'd4cda95b652f4a1592b449d5929fda1b';
-
- const spanContext = {
- traceId,
- spanId: 'INVALID',
- traceFlags: TraceFlags.SAMPLED,
- };
-
- context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => {
- startSpan({ name: 'outer' }, span => {
- expect(span.isRecording()).toBe(true);
- expect(span.spanContext().spanId).not.toBe('INVALID');
- expect(span.spanContext().spanId).toMatch(/[a-f0-9]{16}/);
- expect(span.spanContext().traceId).not.toBe(traceId);
- expect(span.spanContext().traceId).toMatch(/[a-f0-9]{32}/);
- });
- });
- });
-});
-
-describe('HTTP methods (sampling)', () => {
- beforeEach(() => {
- mockSdkInit({ tracesSampleRate: 1 });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('does sample when HTTP method is other than OPTIONS or HEAD', () => {
- const spanGET = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'GET' } }, span => {
- return span;
- });
- expect(spanIsSampled(spanGET)).toBe(true);
- expect(getSamplingDecision(spanGET.spanContext())).toBe(true);
-
- const spanPOST = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'POST' } }, span => {
- return span;
- });
- expect(spanIsSampled(spanPOST)).toBe(true);
- expect(getSamplingDecision(spanPOST.spanContext())).toBe(true);
-
- const spanPUT = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'PUT' } }, span => {
- return span;
- });
- expect(spanIsSampled(spanPUT)).toBe(true);
- expect(getSamplingDecision(spanPUT.spanContext())).toBe(true);
-
- const spanDELETE = startSpanManual(
- { name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'DELETE' } },
- span => {
- return span;
- },
- );
- expect(spanIsSampled(spanDELETE)).toBe(true);
- expect(getSamplingDecision(spanDELETE.spanContext())).toBe(true);
- });
-
- it('does not sample when HTTP method is OPTIONS', () => {
- const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'OPTIONS' } }, span => {
- return span;
- });
- expect(spanIsSampled(span)).toBe(false);
- expect(getSamplingDecision(span.spanContext())).toBe(false);
- });
-
- it('does not sample when HTTP method is HEAD', () => {
- const span = startSpanManual({ name: 'test span', attributes: { [SEMATTRS_HTTP_METHOD]: 'HEAD' } }, span => {
- return span;
- });
- expect(spanIsSampled(span)).toBe(false);
- expect(getSamplingDecision(span.spanContext())).toBe(false);
- });
-});
-
-describe('continueTrace', () => {
- beforeEach(() => {
- mockSdkInit({ tracesSampleRate: 1 });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('works without trace & baggage data', () => {
- const scope = continueTrace({ sentryTrace: undefined, baggage: undefined }, () => {
- const span = getActiveSpan()!;
- expect(span).toBeUndefined();
- return getCurrentScope();
- });
-
- expect(scope.getPropagationContext()).toEqual({
- traceId: expect.any(String),
- sampleRand: expect.any(Number),
- });
-
- expect(scope.getScopeData().sdkProcessingMetadata).toEqual({});
- });
-
- it('works with trace data', () => {
- continueTrace(
- {
- sentryTrace: '12312012123120121231201212312012-1121201211212012-0',
- baggage: undefined,
- },
- () => {
- const span = getActiveSpan()!;
- expect(span).toBeDefined();
- expect(spanToJSON(span)).toEqual({
- span_id: '1121201211212012',
- trace_id: '12312012123120121231201212312012',
- data: {},
- start_timestamp: 0,
- });
- expect(getSamplingDecision(span.spanContext())).toBe(false);
- expect(spanIsSampled(span)).toBe(false);
- },
- );
- });
-
- it('works with trace & baggage data', () => {
- continueTrace(
- {
- sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
- baggage: 'sentry-version=1.0,sentry-environment=production',
- },
- () => {
- const span = getActiveSpan()!;
- expect(span).toBeDefined();
- expect(spanToJSON(span)).toEqual({
- span_id: '1121201211212012',
- trace_id: '12312012123120121231201212312012',
- data: {},
- start_timestamp: 0,
- });
- expect(getSamplingDecision(span.spanContext())).toBe(true);
- expect(spanIsSampled(span)).toBe(true);
- },
- );
- });
-
- it('works with trace & 3rd party baggage data', () => {
- continueTrace(
- {
- sentryTrace: '12312012123120121231201212312012-1121201211212012-1',
- baggage: 'sentry-version=1.0,sentry-environment=production,dogs=great,cats=boring',
- },
- () => {
- const span = getActiveSpan()!;
- expect(span).toBeDefined();
- expect(spanToJSON(span)).toEqual({
- span_id: '1121201211212012',
- trace_id: '12312012123120121231201212312012',
- data: {},
- start_timestamp: 0,
- });
- expect(getSamplingDecision(span.spanContext())).toBe(true);
- expect(spanIsSampled(span)).toBe(true);
- },
- );
- });
-
- it('returns response of callback', () => {
- const result = continueTrace(
- {
- sentryTrace: '12312012123120121231201212312012-1121201211212012-0',
- baggage: undefined,
- },
- () => {
- return 'aha';
- },
- );
-
- expect(result).toEqual('aha');
- });
-});
-
-describe('suppressTracing', () => {
- beforeEach(() => {
- mockSdkInit({ tracesSampleRate: 1 });
- });
-
- afterEach(async () => {
- await cleanupOtel();
- });
-
- it('works for a root span', () => {
- const span = suppressTracing(() => {
- return startInactiveSpan({ name: 'span' });
- });
-
- expect(span.isRecording()).toBe(false);
- expect(spanIsSampled(span)).toBe(false);
- });
-
- it('works for a child span', () => {
- startSpan({ name: 'outer' }, span => {
- expect(span.isRecording()).toBe(true);
- expect(spanIsSampled(span)).toBe(true);
-
- const child1 = startInactiveSpan({ name: 'inner1' });
-
- expect(child1.isRecording()).toBe(true);
- expect(spanIsSampled(child1)).toBe(true);
-
- const child2 = suppressTracing(() => {
- return startInactiveSpan({ name: 'span' });
- });
-
- expect(child2.isRecording()).toBe(false);
- expect(spanIsSampled(child2)).toBe(false);
- });
- });
-
- it('works for a child span with forceTransaction=true', () => {
- startSpan({ name: 'outer' }, span => {
- expect(span.isRecording()).toBe(true);
- expect(spanIsSampled(span)).toBe(true);
-
- const child = suppressTracing(() => {
- return startInactiveSpan({ name: 'span', forceTransaction: true });
- });
-
- expect(child.isRecording()).toBe(false);
- expect(spanIsSampled(child)).toBe(false);
- });
- });
-});
-
-function getSpanName(span: AbstractSpan): string | undefined {
- return spanHasName(span) ? span.name : undefined;
-}
-
-function getSpanEndTime(span: AbstractSpan): [number, number] | undefined {
- return (span as ReadableSpan).endTime;
-}
-
-function getSpanStartTime(span: AbstractSpan): [number, number] | undefined {
- return (span as ReadableSpan).startTime;
-}
-
-function getSpanAttributes(span: AbstractSpan): Record | undefined {
- return spanHasAttributes(span) ? span.attributes : undefined;
-}
-
-function getSpanParentSpanId(span: AbstractSpan): string | undefined {
- return getParentSpanId(span as ReadableSpan);
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/tsconfig.json b/dev-packages/opentelemetry-v2-tests/test/tsconfig.json
deleted file mode 100644
index 38ca0b13bcdd..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/tsconfig.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "../tsconfig.test.json"
-}
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts
deleted file mode 100644
index c91e49ea5f84..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/getActiveSpan.test.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { trace } from '@opentelemetry/api';
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import { getRootSpan } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { getActiveSpan } from '../../../../packages/opentelemetry/src/utils/getActiveSpan';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('getActiveSpan', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions());
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- it('returns undefined if no span is active', () => {
- const span = getActiveSpan();
- expect(span).toBeUndefined();
- });
-
- it('returns undefined if no provider is active', async () => {
- await provider?.forceFlush();
- await provider?.shutdown();
- provider = undefined;
-
- const span = getActiveSpan();
- expect(span).toBeUndefined();
- });
-
- it('returns currently active span', () => {
- const tracer = trace.getTracer('test');
-
- expect(getActiveSpan()).toBeUndefined();
-
- tracer.startActiveSpan('test', span => {
- expect(getActiveSpan()).toBe(span);
-
- const inner1 = tracer.startSpan('inner1');
-
- expect(getActiveSpan()).toBe(span);
-
- inner1.end();
-
- tracer.startActiveSpan('inner2', inner2 => {
- expect(getActiveSpan()).toBe(inner2);
-
- inner2.end();
- });
-
- expect(getActiveSpan()).toBe(span);
-
- span.end();
- });
-
- expect(getActiveSpan()).toBeUndefined();
- });
-
- it('returns currently active span in concurrent spans', () => {
- const tracer = trace.getTracer('test');
-
- expect(getActiveSpan()).toBeUndefined();
-
- tracer.startActiveSpan('test1', span => {
- expect(getActiveSpan()).toBe(span);
-
- tracer.startActiveSpan('inner1', inner1 => {
- expect(getActiveSpan()).toBe(inner1);
- inner1.end();
- });
-
- span.end();
- });
-
- tracer.startActiveSpan('test2', span => {
- expect(getActiveSpan()).toBe(span);
-
- tracer.startActiveSpan('inner2', inner => {
- expect(getActiveSpan()).toBe(inner);
- inner.end();
- });
-
- span.end();
- });
-
- expect(getActiveSpan()).toBeUndefined();
- });
-});
-
-describe('getRootSpan', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- [provider] = setupOtel(client);
- });
-
- afterEach(async () => {
- await provider?.forceFlush();
- await provider?.shutdown();
- });
-
- it('returns currently active root span', () => {
- const tracer = trace.getTracer('test');
-
- tracer.startActiveSpan('test', span => {
- expect(getRootSpan(span)).toBe(span);
-
- const inner1 = tracer.startSpan('inner1');
-
- expect(getRootSpan(inner1)).toBe(span);
-
- inner1.end();
-
- tracer.startActiveSpan('inner2', inner2 => {
- expect(getRootSpan(inner2)).toBe(span);
-
- inner2.end();
- });
-
- span.end();
- });
- });
-
- it('returns currently active root span in concurrent spans', () => {
- const tracer = trace.getTracer('test');
-
- tracer.startActiveSpan('test1', span => {
- expect(getRootSpan(span)).toBe(span);
-
- tracer.startActiveSpan('inner1', inner1 => {
- expect(getRootSpan(inner1)).toBe(span);
- inner1.end();
- });
-
- span.end();
- });
-
- tracer.startActiveSpan('test2', span => {
- expect(getRootSpan(span)).toBe(span);
-
- tracer.startActiveSpan('inner2', inner => {
- expect(getRootSpan(inner)).toBe(span);
- inner.end();
- });
-
- span.end();
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts
deleted file mode 100644
index 3f0914b6afb7..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/getRequestSpanData.test.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/* eslint-disable deprecation/deprecation */
-import type { Span } from '@opentelemetry/api';
-import { trace } from '@opentelemetry/api';
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { getRequestSpanData } from '../../../../packages/opentelemetry/src/utils/getRequestSpanData';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('getRequestSpanData', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- function createSpan(name: string): Span {
- return trace.getTracer('test').startSpan(name);
- }
-
- it('works with basic span', () => {
- const span = createSpan('test-span');
- const data = getRequestSpanData(span);
-
- expect(data).toEqual({});
- });
-
- it('works with http span', () => {
- const span = createSpan('test-span');
- span.setAttributes({
- [SEMATTRS_HTTP_URL]: 'http://example.com?foo=bar#baz',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- });
-
- const data = getRequestSpanData(span);
-
- expect(data).toEqual({
- url: 'http://example.com',
- 'http.method': 'GET',
- 'http.query': '?foo=bar',
- 'http.fragment': '#baz',
- });
- });
-
- it('works without method', () => {
- const span = createSpan('test-span');
- span.setAttributes({
- [SEMATTRS_HTTP_URL]: 'http://example.com',
- });
-
- const data = getRequestSpanData(span);
-
- expect(data).toEqual({
- url: 'http://example.com',
- 'http.method': 'GET',
- });
- });
-
- it('works with incorrect URL', () => {
- const span = createSpan('test-span');
- span.setAttributes({
- [SEMATTRS_HTTP_URL]: 'malformed-url-here',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- });
-
- const data = getRequestSpanData(span);
-
- expect(data).toEqual({
- url: 'malformed-url-here',
- 'http.method': 'GET',
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts
deleted file mode 100644
index 16dacdafe8ee..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/getSpanKind.test.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { Span } from '@opentelemetry/api';
-import { SpanKind } from '@opentelemetry/api';
-import { describe, expect, it } from 'vitest';
-import { getSpanKind } from '../../../../packages/opentelemetry/src/utils/getSpanKind';
-
-describe('getSpanKind', () => {
- it('works', () => {
- expect(getSpanKind({} as Span)).toBe(SpanKind.INTERNAL);
- expect(getSpanKind({ kind: SpanKind.CLIENT } as unknown as Span)).toBe(SpanKind.CLIENT);
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts
deleted file mode 100644
index 136b6251523d..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/getTraceData.test.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { context, trace } from '@opentelemetry/api';
-import { getCurrentScope, setAsyncContextStrategy } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { getTraceData } from '../../../../packages/opentelemetry/src/utils/getTraceData';
-import { makeTraceState } from '../../../../packages/opentelemetry/src/utils/makeTraceState';
-import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit';
-
-describe('getTraceData', () => {
- beforeEach(() => {
- setAsyncContextStrategy(undefined);
- mockSdkInit();
- });
-
- afterEach(async () => {
- await cleanupOtel();
- vi.clearAllMocks();
- });
-
- it('returns the tracing data from the span, if a span is available', () => {
- const ctx = trace.setSpanContext(context.active(), {
- traceId: '12345678901234567890123456789012',
- spanId: '1234567890123456',
- traceFlags: 1,
- });
-
- context.with(ctx, () => {
- const data = getTraceData();
-
- expect(data).toEqual({
- 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
- baggage:
- 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true',
- });
- });
- });
-
- it('allows to pass a span directly', () => {
- const ctx = trace.setSpanContext(context.active(), {
- traceId: '12345678901234567890123456789012',
- spanId: '1234567890123456',
- traceFlags: 1,
- });
-
- const span = trace.getSpan(ctx)!;
-
- const data = getTraceData({ span });
-
- expect(data).toEqual({
- 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
- baggage:
- 'sentry-environment=production,sentry-public_key=username,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true',
- });
- });
-
- it('returns propagationContext DSC data if no span is available', () => {
- getCurrentScope().setPropagationContext({
- traceId: '12345678901234567890123456789012',
- sampleRand: Math.random(),
- sampled: true,
- dsc: {
- environment: 'staging',
- public_key: 'key',
- trace_id: '12345678901234567890123456789012',
- },
- });
-
- const traceData = getTraceData();
-
- expect(traceData['sentry-trace']).toMatch(/^12345678901234567890123456789012-[a-f0-9]{16}-1$/);
- expect(traceData.baggage).toEqual(
- 'sentry-environment=staging,sentry-public_key=key,sentry-trace_id=12345678901234567890123456789012',
- );
- });
-
- it('works with an span with frozen DSC in traceState', () => {
- const ctx = trace.setSpanContext(context.active(), {
- traceId: '12345678901234567890123456789012',
- spanId: '1234567890123456',
- traceFlags: 1,
- traceState: makeTraceState({
- dsc: { environment: 'test-dev', public_key: '456', trace_id: '12345678901234567890123456789088' },
- }),
- });
-
- context.with(ctx, () => {
- const data = getTraceData();
-
- expect(data).toEqual({
- 'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
- baggage: 'sentry-environment=test-dev,sentry-public_key=456,sentry-trace_id=12345678901234567890123456789088',
- });
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts
deleted file mode 100644
index 87d7daa4a43a..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/groupSpansWithParents.test.ts
+++ /dev/null
@@ -1,174 +0,0 @@
-import { trace } from '@opentelemetry/api';
-import type { BasicTracerProvider, ReadableSpan } from '@opentelemetry/sdk-trace-base';
-import type { Span } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { withActiveSpan } from '../../../../packages/opentelemetry/src/trace';
-import { groupSpansWithParents } from '../../../../packages/opentelemetry/src/utils/groupSpansWithParents';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('groupSpansWithParents', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- it('works with no spans', () => {
- const actual = groupSpansWithParents([]);
- expect(actual).toEqual([]);
- });
-
- it('works with a single root span & in-order spans', () => {
- const tracer = trace.getTracer('test');
- const rootSpan = tracer.startSpan('root') as unknown as ReadableSpan;
- const parentSpan1 = withActiveSpan(
- rootSpan as unknown as Span,
- () => tracer.startSpan('parent1') as unknown as ReadableSpan,
- );
- const parentSpan2 = withActiveSpan(
- rootSpan as unknown as Span,
- () => tracer.startSpan('parent2') as unknown as ReadableSpan,
- );
- const child1 = withActiveSpan(
- parentSpan1 as unknown as Span,
- () => tracer.startSpan('child1') as unknown as ReadableSpan,
- );
-
- const actual = groupSpansWithParents([rootSpan, parentSpan1, parentSpan2, child1]);
- expect(actual).toHaveLength(4);
-
- // Ensure parent & span is correctly set
- const rootRef = actual.find(ref => ref.span === rootSpan);
- const parent1Ref = actual.find(ref => ref.span === parentSpan1);
- const parent2Ref = actual.find(ref => ref.span === parentSpan2);
- const child1Ref = actual.find(ref => ref.span === child1);
-
- expect(rootRef).toBeDefined();
- expect(parent1Ref).toBeDefined();
- expect(parent2Ref).toBeDefined();
- expect(child1Ref).toBeDefined();
-
- expect(rootRef?.parentNode).toBeUndefined();
- expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]);
-
- expect(parent1Ref?.span).toBe(parentSpan1);
- expect(parent2Ref?.span).toBe(parentSpan2);
-
- expect(parent1Ref?.parentNode).toBe(rootRef);
- expect(parent2Ref?.parentNode).toBe(rootRef);
-
- expect(parent1Ref?.children).toEqual([child1Ref]);
- expect(parent2Ref?.children).toEqual([]);
-
- expect(child1Ref?.parentNode).toBe(parent1Ref);
- expect(child1Ref?.children).toEqual([]);
- });
-
- it('works with a spans with missing root span', () => {
- const tracer = trace.getTracer('test');
-
- // We create this root span here, but we do not pass it to `groupSpansWithParents` below
- const rootSpan = tracer.startSpan('root') as unknown as ReadableSpan;
- const parentSpan1 = withActiveSpan(
- rootSpan as unknown as Span,
- () => tracer.startSpan('parent1') as unknown as ReadableSpan,
- );
- const parentSpan2 = withActiveSpan(
- rootSpan as unknown as Span,
- () => tracer.startSpan('parent2') as unknown as ReadableSpan,
- );
- const child1 = withActiveSpan(
- parentSpan1 as unknown as Span,
- () => tracer.startSpan('child1') as unknown as ReadableSpan,
- );
-
- const actual = groupSpansWithParents([parentSpan1, parentSpan2, child1]);
- expect(actual).toHaveLength(4);
-
- // Ensure parent & span is correctly set
- const rootRef = actual.find(ref => ref.id === rootSpan.spanContext().spanId);
- const parent1Ref = actual.find(ref => ref.span === parentSpan1);
- const parent2Ref = actual.find(ref => ref.span === parentSpan2);
- const child1Ref = actual.find(ref => ref.span === child1);
-
- expect(rootRef).toBeDefined();
- expect(parent1Ref).toBeDefined();
- expect(parent2Ref).toBeDefined();
- expect(child1Ref).toBeDefined();
-
- expect(rootRef?.parentNode).toBeUndefined();
- expect(rootRef?.span).toBeUndefined();
- expect(rootRef?.children).toEqual([parent1Ref, parent2Ref]);
-
- expect(parent1Ref?.span).toBe(parentSpan1);
- expect(parent2Ref?.span).toBe(parentSpan2);
-
- expect(parent1Ref?.parentNode).toBe(rootRef);
- expect(parent2Ref?.parentNode).toBe(rootRef);
-
- expect(parent1Ref?.children).toEqual([child1Ref]);
- expect(parent2Ref?.children).toEqual([]);
-
- expect(child1Ref?.parentNode).toBe(parent1Ref);
- expect(child1Ref?.children).toEqual([]);
- });
-
- it('works with multiple root spans & out-of-order spans', () => {
- const tracer = trace.getTracer('test');
- const rootSpan1 = tracer.startSpan('root1') as unknown as ReadableSpan;
- const rootSpan2 = tracer.startSpan('root2') as unknown as ReadableSpan;
- const parentSpan1 = withActiveSpan(
- rootSpan1 as unknown as Span,
- () => tracer.startSpan('parent1') as unknown as ReadableSpan,
- );
- const parentSpan2 = withActiveSpan(
- rootSpan2 as unknown as Span,
- () => tracer.startSpan('parent2') as unknown as ReadableSpan,
- );
- const childSpan1 = withActiveSpan(
- parentSpan1 as unknown as Span,
- () => tracer.startSpan('child1') as unknown as ReadableSpan,
- );
-
- const actual = groupSpansWithParents([childSpan1, parentSpan1, parentSpan2, rootSpan2, rootSpan1]);
- expect(actual).toHaveLength(5);
-
- // Ensure parent & span is correctly set
- const root1Ref = actual.find(ref => ref.span === rootSpan1);
- const root2Ref = actual.find(ref => ref.span === rootSpan2);
- const parent1Ref = actual.find(ref => ref.span === parentSpan1);
- const parent2Ref = actual.find(ref => ref.span === parentSpan2);
- const child1Ref = actual.find(ref => ref.span === childSpan1);
-
- expect(root1Ref).toBeDefined();
- expect(root2Ref).toBeDefined();
- expect(parent1Ref).toBeDefined();
- expect(parent2Ref).toBeDefined();
- expect(child1Ref).toBeDefined();
-
- expect(root1Ref?.parentNode).toBeUndefined();
- expect(root1Ref?.children).toEqual([parent1Ref]);
-
- expect(root2Ref?.parentNode).toBeUndefined();
- expect(root2Ref?.children).toEqual([parent2Ref]);
-
- expect(parent1Ref?.span).toBe(parentSpan1);
- expect(parent2Ref?.span).toBe(parentSpan2);
-
- expect(parent1Ref?.parentNode).toBe(root1Ref);
- expect(parent2Ref?.parentNode).toBe(root2Ref);
-
- expect(parent1Ref?.children).toEqual([child1Ref]);
- expect(parent2Ref?.children).toEqual([]);
-
- expect(child1Ref?.parentNode).toBe(parent1Ref);
- expect(child1Ref?.children).toEqual([]);
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts
deleted file mode 100644
index b479da0d61ad..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/mapStatus.test.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-/* eslint-disable deprecation/deprecation */
-import type { Span } from '@opentelemetry/api';
-import { trace } from '@opentelemetry/api';
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import { SEMATTRS_HTTP_STATUS_CODE, SEMATTRS_RPC_GRPC_STATUS_CODE } from '@opentelemetry/semantic-conventions';
-import type { SpanStatus } from '@sentry/core';
-import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { mapStatus } from '../../../../packages/opentelemetry/src/utils/mapStatus';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('mapStatus', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- function createSpan(name: string): Span {
- return trace.getTracer('test').startSpan(name);
- }
-
- const statusTestTable: [undefined | number | string, undefined | string, SpanStatus][] = [
- // http codes
- [400, undefined, { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }],
- [401, undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }],
- [403, undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }],
- [404, undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }],
- [409, undefined, { code: SPAN_STATUS_ERROR, message: 'already_exists' }],
- [429, undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }],
- [499, undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }],
- [500, undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }],
- [501, undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }],
- [503, undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }],
- [504, undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }],
- [999, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }],
-
- // grpc codes
- [undefined, '1', { code: SPAN_STATUS_ERROR, message: 'cancelled' }],
- [undefined, '2', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }],
- [undefined, '3', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }],
- [undefined, '4', { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }],
- [undefined, '5', { code: SPAN_STATUS_ERROR, message: 'not_found' }],
- [undefined, '6', { code: SPAN_STATUS_ERROR, message: 'already_exists' }],
- [undefined, '7', { code: SPAN_STATUS_ERROR, message: 'permission_denied' }],
- [undefined, '8', { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }],
- [undefined, '9', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }],
- [undefined, '10', { code: SPAN_STATUS_ERROR, message: 'aborted' }],
- [undefined, '11', { code: SPAN_STATUS_ERROR, message: 'out_of_range' }],
- [undefined, '12', { code: SPAN_STATUS_ERROR, message: 'unimplemented' }],
- [undefined, '13', { code: SPAN_STATUS_ERROR, message: 'internal_error' }],
- [undefined, '14', { code: SPAN_STATUS_ERROR, message: 'unavailable' }],
- [undefined, '15', { code: SPAN_STATUS_ERROR, message: 'data_loss' }],
- [undefined, '16', { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }],
- [undefined, '999', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }],
-
- // http takes precedence over grpc
- [400, '2', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }],
- ];
-
- it.each(statusTestTable)('works with httpCode=%s, grpcCode=%s', (httpCode, grpcCode, expected) => {
- const span = createSpan('test-span');
- span.setStatus({ code: 0 }); // UNSET
-
- if (httpCode) {
- span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, httpCode);
- }
-
- if (grpcCode) {
- span.setAttribute(SEMATTRS_RPC_GRPC_STATUS_CODE, grpcCode);
- }
-
- const actual = mapStatus(span);
- expect(actual).toEqual(expected);
- });
-
- it('works with string SEMATTRS_HTTP_STATUS_CODE', () => {
- const span = createSpan('test-span');
-
- span.setStatus({ code: 0 }); // UNSET
- span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, '400');
-
- const actual = mapStatus(span);
- expect(actual).toEqual({ code: SPAN_STATUS_ERROR, message: 'invalid_argument' });
- });
-
- it('returns ok span status when is UNSET present on span', () => {
- const span = createSpan('test-span');
- span.setStatus({ code: 0 }); // UNSET
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_OK });
- });
-
- it('returns ok span status when already present on span', () => {
- const span = createSpan('test-span');
- span.setStatus({ code: 1 }); // OK
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_OK });
- });
-
- it('returns error status when span already has error status', () => {
- const span = createSpan('test-span');
- span.setStatus({ code: 2, message: 'invalid_argument' }); // ERROR
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'invalid_argument' });
- });
-
- it('returns error status when span already has error status without message', () => {
- const span = createSpan('test-span');
- span.setStatus({ code: 2 }); // ERROR
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'unknown_error' });
- });
-
- it('infers error status form attributes when span already has error status without message', () => {
- const span = createSpan('test-span');
- span.setAttribute(SEMATTRS_HTTP_STATUS_CODE, 500);
- span.setStatus({ code: 2 }); // ERROR
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
- });
-
- it('returns unknown error status when code is unknown', () => {
- const span = createSpan('test-span');
- span.setStatus({ code: -1 as 0 });
- expect(mapStatus(span)).toEqual({ code: SPAN_STATUS_ERROR, message: 'unknown_error' });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts
deleted file mode 100644
index 56d50a3b2fbc..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/parseSpanDescription.test.ts
+++ /dev/null
@@ -1,690 +0,0 @@
-/* eslint-disable deprecation/deprecation */
-import type { Span } from '@opentelemetry/api';
-import { SpanKind } from '@opentelemetry/api';
-import {
- ATTR_HTTP_ROUTE,
- SEMATTRS_DB_STATEMENT,
- SEMATTRS_DB_SYSTEM,
- SEMATTRS_FAAS_TRIGGER,
- SEMATTRS_HTTP_HOST,
- SEMATTRS_HTTP_METHOD,
- SEMATTRS_HTTP_STATUS_CODE,
- SEMATTRS_HTTP_TARGET,
- SEMATTRS_HTTP_URL,
- SEMATTRS_MESSAGING_SYSTEM,
- SEMATTRS_RPC_SERVICE,
-} from '@opentelemetry/semantic-conventions';
-import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
-import { describe, expect, it } from 'vitest';
-import {
- descriptionForHttpMethod,
- getSanitizedUrl,
- getUserUpdatedNameAndSource,
- parseSpanDescription,
-} from '../../../../packages/opentelemetry/src/utils/parseSpanDescription';
-
-describe('parseSpanDescription', () => {
- it.each([
- [
- 'works without attributes & name',
- undefined,
- undefined,
- undefined,
- {
- description: '',
- op: undefined,
- source: 'custom',
- },
- ],
- [
- 'works with empty attributes',
- {},
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'test name',
- op: undefined,
- source: 'custom',
- },
- ],
- [
- 'works with deprecated http method',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'test name',
- op: 'http.client',
- source: 'custom',
- },
- ],
- [
- 'works with http method',
- {
- 'http.request.method': 'GET',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'test name',
- op: 'http.client',
- source: 'custom',
- },
- ],
- [
- 'works with db system',
- {
- [SEMATTRS_DB_SYSTEM]: 'mysql',
- [SEMATTRS_DB_STATEMENT]: 'SELECT * from users',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'SELECT * from users',
- op: 'db',
- source: 'task',
- },
- ],
- [
- 'works with db system and custom source',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_DB_SYSTEM]: 'mysql',
- [SEMATTRS_DB_STATEMENT]: 'SELECT * from users',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'test name',
- op: 'db',
- source: 'custom',
- },
- ],
- [
- 'works with db system and custom source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_DB_SYSTEM]: 'mysql',
- [SEMATTRS_DB_STATEMENT]: 'SELECT * from users',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'custom name',
- op: 'db',
- source: 'custom',
- },
- ],
- [
- 'works with db system and component source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
- [SEMATTRS_DB_SYSTEM]: 'mysql',
- [SEMATTRS_DB_STATEMENT]: 'SELECT * from users',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'custom name',
- op: 'db',
- source: 'component',
- },
- ],
- [
- 'works with db system without statement',
- {
- [SEMATTRS_DB_SYSTEM]: 'mysql',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- description: 'test name',
- op: 'db',
- source: 'task',
- },
- ],
- [
- 'works with rpc service',
- {
- [SEMATTRS_RPC_SERVICE]: 'rpc-test-service',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'rpc',
- source: 'route',
- },
- ],
- [
- 'works with rpc service and custom source',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_RPC_SERVICE]: 'rpc-test-service',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'rpc',
- source: 'custom',
- },
- ],
- [
- 'works with rpc service and custom source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_RPC_SERVICE]: 'rpc-test-service',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'rpc',
- source: 'custom',
- },
- ],
- [
- 'works with rpc service and component source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
- [SEMATTRS_RPC_SERVICE]: 'rpc-test-service',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'rpc',
- source: 'component',
- },
- ],
- [
- 'works with messaging system',
- {
- [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'message',
- source: 'route',
- },
- ],
- [
- 'works with messaging system and custom source',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'message',
- source: 'custom',
- },
- ],
- [
- 'works with messaging system and custom source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'message',
- source: 'custom',
- },
- ],
- [
- 'works with messaging system and component source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
- [SEMATTRS_MESSAGING_SYSTEM]: 'test-messaging-system',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'message',
- source: 'component',
- },
- ],
- [
- 'works with faas trigger',
- {
- [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'test-faas-trigger',
- source: 'route',
- },
- ],
- [
- 'works with faas trigger and custom source',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger',
- },
- 'test name',
- undefined,
- {
- description: 'test name',
- op: 'test-faas-trigger',
- source: 'custom',
- },
- ],
- [
- 'works with faas trigger and custom source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'test-faas-trigger',
- source: 'custom',
- },
- ],
- [
- 'works with faas trigger and component source and custom name',
- {
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
- [SEMATTRS_FAAS_TRIGGER]: 'test-faas-trigger',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- undefined,
- {
- description: 'custom name',
- op: 'test-faas-trigger',
- source: 'component',
- },
- ],
- ])('%s', (_, attributes, name, kind, expected) => {
- const actual = parseSpanDescription({ attributes, kind, name } as unknown as Span);
- expect(actual).toEqual(expected);
- });
-});
-
-describe('descriptionForHttpMethod', () => {
- it.each([
- [
- 'works without attributes',
- 'GET',
- {},
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'test name',
- source: 'custom',
- },
- ],
- [
- 'works with basic client GET',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path',
- [SEMATTRS_HTTP_TARGET]: '/my-path',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'GET https://www.example.com/my-path',
- data: {
- url: 'https://www.example.com/my-path',
- },
- source: 'url',
- },
- ],
- [
- 'works with prefetch request',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path',
- [SEMATTRS_HTTP_TARGET]: '/my-path',
- 'sentry.http.prefetch': true,
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client.prefetch',
- description: 'GET https://www.example.com/my-path',
- data: {
- url: 'https://www.example.com/my-path',
- },
- source: 'url',
- },
- ],
- [
- 'works with basic server POST',
- 'POST',
- {
- [SEMATTRS_HTTP_METHOD]: 'POST',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path',
- [SEMATTRS_HTTP_TARGET]: '/my-path',
- },
- 'test name',
- SpanKind.SERVER,
- {
- op: 'http.server',
- description: 'POST /my-path',
- data: {
- url: 'https://www.example.com/my-path',
- },
- source: 'url',
- },
- ],
- [
- 'works with client GET with route',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123',
- [SEMATTRS_HTTP_TARGET]: '/my-path/123',
- [ATTR_HTTP_ROUTE]: '/my-path/:id',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'GET /my-path/:id',
- data: {
- url: 'https://www.example.com/my-path/123',
- },
- source: 'route',
- },
- ],
- [
- 'works with basic client GET with SpanKind.INTERNAL',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path',
- [SEMATTRS_HTTP_TARGET]: '/my-path',
- },
- 'test name',
- SpanKind.INTERNAL,
- {
- op: 'http',
- description: 'test name',
- data: {
- url: 'https://www.example.com/my-path',
- },
- source: 'custom',
- },
- ],
- [
- "doesn't overwrite span name with source custom",
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123',
- [SEMATTRS_HTTP_TARGET]: '/my-path/123',
- [ATTR_HTTP_ROUTE]: '/my-path/:id',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'test name',
- data: {
- url: 'https://www.example.com/my-path/123',
- },
- source: 'custom',
- },
- ],
- [
- 'takes user-passed span name (with source custom)',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123',
- [SEMATTRS_HTTP_TARGET]: '/my-path/123',
- [ATTR_HTTP_ROUTE]: '/my-path/:id',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'custom name',
- data: {
- url: 'https://www.example.com/my-path/123',
- },
- source: 'custom',
- },
- ],
- [
- 'takes user-passed span name (with source component)',
- 'GET',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123',
- [SEMATTRS_HTTP_TARGET]: '/my-path/123',
- [ATTR_HTTP_ROUTE]: '/my-path/:id',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- },
- 'test name',
- SpanKind.CLIENT,
- {
- op: 'http.client',
- description: 'custom name',
- data: {
- url: 'https://www.example.com/my-path/123',
- },
- source: 'component',
- },
- ],
- ])('%s', (_, httpMethod, attributes, name, kind, expected) => {
- const actual = descriptionForHttpMethod({ attributes, kind, name }, httpMethod);
- expect(actual).toEqual(expected);
- });
-});
-
-describe('getSanitizedUrl', () => {
- it.each([
- [
- 'works without attributes',
- {},
- SpanKind.CLIENT,
- {
- urlPath: undefined,
- url: undefined,
- fragment: undefined,
- query: undefined,
- hasRoute: false,
- },
- ],
- [
- 'uses url without query for client request',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/?what=true',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.CLIENT,
- {
- urlPath: 'http://example.com/',
- url: 'http://example.com/',
- fragment: undefined,
- query: '?what=true',
- hasRoute: false,
- },
- ],
- [
- 'uses url without hash for client request',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/sub#hash',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/sub#hash',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.CLIENT,
- {
- urlPath: 'http://example.com/sub',
- url: 'http://example.com/sub',
- fragment: '#hash',
- query: undefined,
- hasRoute: false,
- },
- ],
- [
- 'uses route if available for client request',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/?what=true',
- [ATTR_HTTP_ROUTE]: '/my-route',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.CLIENT,
- {
- urlPath: '/my-route',
- url: 'http://example.com/',
- fragment: undefined,
- query: '?what=true',
- hasRoute: true,
- },
- ],
- [
- 'falls back to target for client request if url not available',
- {
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/?what=true',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.CLIENT,
- {
- urlPath: '/',
- url: undefined,
- fragment: undefined,
- query: undefined,
- hasRoute: false,
- },
- ],
- [
- 'uses target without query for server request',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/?what=true',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.SERVER,
- {
- urlPath: '/',
- url: 'http://example.com/',
- fragment: undefined,
- query: '?what=true',
- hasRoute: false,
- },
- ],
- [
- 'uses target without hash for server request',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/sub#hash',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.SERVER,
- {
- urlPath: '/sub',
- url: 'http://example.com/',
- fragment: undefined,
- query: '?what=true',
- hasRoute: false,
- },
- ],
- [
- 'uses route for server request if available',
- {
- [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true',
- [SEMATTRS_HTTP_METHOD]: 'GET',
- [SEMATTRS_HTTP_TARGET]: '/?what=true',
- [ATTR_HTTP_ROUTE]: '/my-route',
- [SEMATTRS_HTTP_HOST]: 'example.com:80',
- [SEMATTRS_HTTP_STATUS_CODE]: 200,
- },
- SpanKind.SERVER,
- {
- urlPath: '/my-route',
- url: 'http://example.com/',
- fragment: undefined,
- query: '?what=true',
- hasRoute: true,
- },
- ],
- ])('%s', (_, attributes, kind, expected) => {
- const actual = getSanitizedUrl(attributes, kind);
-
- expect(actual).toEqual(expected);
- });
-});
-
-describe('getUserUpdatedNameAndSource', () => {
- it('returns param name if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not set', () => {
- expect(getUserUpdatedNameAndSource('base name', {})).toEqual({ description: 'base name', source: 'custom' });
- });
-
- it('returns param name with custom fallback source if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not set', () => {
- expect(getUserUpdatedNameAndSource('base name', {}, 'route')).toEqual({
- description: 'base name',
- source: 'route',
- });
- });
-
- it('returns param name if `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute is not a string', () => {
- expect(getUserUpdatedNameAndSource('base name', { [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 123 })).toEqual({
- description: 'base name',
- source: 'custom',
- });
- });
-
- it.each(['custom', 'task', 'url', 'route'])(
- 'returns `SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME` attribute if is a string and source is %s',
- source => {
- expect(
- getUserUpdatedNameAndSource('base name', {
- [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: 'custom name',
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
- }),
- ).toEqual({
- description: 'custom name',
- source,
- });
- },
- );
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts
deleted file mode 100644
index 8f453bb9792c..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/setupCheck.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { SentrySampler } from '../../../../packages/opentelemetry/src/sampler';
-import { SentrySpanProcessor } from '../../../../packages/opentelemetry/src/spanProcessor';
-import { openTelemetrySetupCheck } from '../../../../packages/opentelemetry/src/utils/setupCheck';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('openTelemetrySetupCheck', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- cleanupOtel(provider);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- it('returns empty array by default', () => {
- const setup = openTelemetrySetupCheck();
- expect(setup).toEqual([]);
- });
-
- it('returns all setup parts', () => {
- const client = new TestClient(getDefaultTestClientOptions());
- [provider] = setupOtel(client);
-
- const setup = openTelemetrySetupCheck();
- expect(setup).toEqual(['SentrySpanProcessor', 'SentrySampler', 'SentryPropagator', 'SentryContextManager']);
- });
-
- it('returns partial setup parts', () => {
- const client = new TestClient(getDefaultTestClientOptions());
- provider = new BasicTracerProvider({
- sampler: new SentrySampler(client),
- spanProcessors: [new SentrySpanProcessor()],
- });
-
- const setup = openTelemetrySetupCheck();
- expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']);
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts
deleted file mode 100644
index fbf6e1b69991..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/setupEventContextTrace.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import { captureException, setCurrentClient } from '@sentry/core';
-import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
-import { setupEventContextTrace } from '../../../../packages/opentelemetry/src/setupEventContextTrace';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import type { TestClientInterface } from '../helpers/TestClient';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-const PUBLIC_DSN = 'https://username@domain/123';
-
-describe('setupEventContextTrace', () => {
- const beforeSend = vi.fn(() => null);
- let client: TestClientInterface;
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- client = new TestClient(
- getDefaultTestClientOptions({
- sampleRate: 1,
- tracesSampleRate: 1,
- beforeSend,
- debug: true,
- dsn: PUBLIC_DSN,
- }),
- );
-
- setCurrentClient(client);
- client.init();
-
- setupEventContextTrace(client);
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- beforeSend.mockReset();
- cleanupOtel(provider);
- });
-
- afterAll(() => {
- vi.clearAllMocks();
- });
-
- it('works with no active span', async () => {
- const error = new Error('test');
- captureException(error);
- await client.flush();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- },
- }),
- }),
- expect.objectContaining({
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- }),
- );
- });
-
- it('works with active span', async () => {
- const error = new Error('test');
-
- let outerId: string | undefined;
- let innerId: string | undefined;
- let traceId: string | undefined;
-
- client.tracer.startActiveSpan('outer', outerSpan => {
- outerId = outerSpan.spanContext().spanId;
- traceId = outerSpan.spanContext().traceId;
-
- client.tracer.startActiveSpan('inner', innerSpan => {
- innerId = innerSpan.spanContext().spanId;
- captureException(error);
- });
- });
-
- await client.flush();
-
- expect(outerId).toBeDefined();
- expect(innerId).toBeDefined();
- expect(traceId).toBeDefined();
-
- expect(beforeSend).toHaveBeenCalledTimes(1);
- expect(beforeSend).toHaveBeenCalledWith(
- expect.objectContaining({
- contexts: expect.objectContaining({
- trace: {
- span_id: innerId,
- parent_span_id: outerId,
- trace_id: traceId,
- },
- }),
- }),
- expect.objectContaining({
- event_id: expect.any(String),
- originalException: error,
- syntheticException: expect.any(Error),
- }),
- );
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts
deleted file mode 100644
index c1f9fe2a18c7..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/spanToJSON.test.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { Span, SpanOptions } from '@opentelemetry/api';
-import { trace } from '@opentelemetry/api';
-import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
-import {
- SEMANTIC_ATTRIBUTE_SENTRY_OP,
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
- SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
- spanToJSON,
-} from '@sentry/core';
-import { afterEach, beforeEach, describe, expect, it } from 'vitest';
-import { setupOtel } from '../helpers/initOtel';
-import { cleanupOtel } from '../helpers/mockSdkInit';
-import { getDefaultTestClientOptions, TestClient } from '../helpers/TestClient';
-
-describe('spanToJSON', () => {
- describe('OpenTelemetry Span', () => {
- let provider: BasicTracerProvider | undefined;
-
- beforeEach(() => {
- const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- [provider] = setupOtel(client);
- });
-
- afterEach(() => {
- cleanupOtel(provider);
- });
-
- function createSpan(name: string, params?: SpanOptions): Span {
- return trace.getTracer('test').startSpan(name, params);
- }
-
- it('works with a simple span', () => {
- const span = createSpan('test span', { startTime: [123, 0] });
-
- expect(spanToJSON(span)).toEqual({
- span_id: span.spanContext().spanId,
- trace_id: span.spanContext().traceId,
- start_timestamp: 123,
- description: 'test span',
- data: {
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- },
- });
- });
-
- it('works with a full span', () => {
- const span = createSpan('test span', { startTime: [123, 0] });
-
- span.setAttributes({
- attr1: 'value1',
- attr2: 2,
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'test op',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto',
- });
-
- span.setStatus({ code: 2, message: 'unknown_error' });
- span.end([456, 0]);
-
- expect(spanToJSON(span)).toEqual({
- span_id: span.spanContext().spanId,
- trace_id: span.spanContext().traceId,
- start_timestamp: 123,
- timestamp: 456,
- description: 'test span',
- op: 'test op',
- origin: 'auto',
- data: {
- attr1: 'value1',
- attr2: 2,
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'test op',
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto',
- [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
- },
- status: 'unknown_error',
- });
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts b/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts
deleted file mode 100644
index 00c9eccdf98e..000000000000
--- a/dev-packages/opentelemetry-v2-tests/test/utils/spanTypes.test.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { Span } from '@opentelemetry/api';
-import { describe, expect, it } from 'vitest';
-import {
- spanHasAttributes,
- spanHasEvents,
- spanHasKind,
- spanHasParentId,
-} from '../../../../packages/opentelemetry/src/utils/spanTypes';
-
-describe('spanTypes', () => {
- describe('spanHasAttributes', () => {
- it.each([
- [{}, false],
- [{ attributes: null }, false],
- [{ attributes: {} }, true],
- ])('works with %p', (span, expected) => {
- const castSpan = span as unknown as Span;
- const actual = spanHasAttributes(castSpan);
-
- expect(actual).toBe(expected);
-
- if (actual) {
- expect(castSpan.attributes).toBeDefined();
- }
- });
- });
-
- describe('spanHasKind', () => {
- it.each([
- [{}, false],
- [{ kind: null }, false],
- [{ kind: 0 }, true],
- [{ kind: 5 }, true],
- [{ kind: 'TEST_KIND' }, false],
- ])('works with %p', (span, expected) => {
- const castSpan = span as unknown as Span;
- const actual = spanHasKind(castSpan);
-
- expect(actual).toBe(expected);
-
- if (actual) {
- expect(castSpan.kind).toBeDefined();
- }
- });
- });
-
- describe('spanHasParentId', () => {
- it.each([
- [{}, false],
- [{ parentSpanId: null }, false],
- [{ parentSpanId: 'TEST_PARENT_ID' }, true],
- ])('works with %p', (span, expected) => {
- const castSpan = span as unknown as Span;
- const actual = spanHasParentId(castSpan);
-
- expect(actual).toBe(expected);
-
- if (actual) {
- expect(castSpan.parentSpanId).toBeDefined();
- }
- });
- });
-
- describe('spanHasEvents', () => {
- it.each([
- [{}, false],
- [{ events: null }, false],
- [{ events: [] }, true],
- ])('works with %p', (span, expected) => {
- const castSpan = span as unknown as Span;
- const actual = spanHasEvents(castSpan);
-
- expect(actual).toBe(expected);
-
- if (actual) {
- expect(castSpan.events).toBeDefined();
- }
- });
- });
-});
diff --git a/dev-packages/opentelemetry-v2-tests/tsconfig.json b/dev-packages/opentelemetry-v2-tests/tsconfig.json
deleted file mode 100644
index b9f9b425c7df..000000000000
--- a/dev-packages/opentelemetry-v2-tests/tsconfig.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "outDir": "./build",
- "types": ["node", "vitest/globals"]
- },
- "include": ["test/**/*", "vite.config.ts"]
-}
diff --git a/dev-packages/opentelemetry-v2-tests/tsconfig.test.json b/dev-packages/opentelemetry-v2-tests/tsconfig.test.json
deleted file mode 100644
index ca7dbeb3be94..000000000000
--- a/dev-packages/opentelemetry-v2-tests/tsconfig.test.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "extends": "./tsconfig.json",
-
- "include": ["test/**/*", "vite.config.ts"],
-
- "compilerOptions": {
- // should include all types from `./tsconfig.json` plus types for all test frameworks used
- "types": ["node"]
-
- // other package-specific, test-specific options
- }
-}
diff --git a/dev-packages/opentelemetry-v2-tests/vite.config.ts b/dev-packages/opentelemetry-v2-tests/vite.config.ts
deleted file mode 100644
index d7ea407dfac7..000000000000
--- a/dev-packages/opentelemetry-v2-tests/vite.config.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import baseConfig from '../../vite/vite.config';
-
-export default {
- ...baseConfig,
- test: {
- ...baseConfig.test,
- coverage: {
- enabled: false,
- },
- },
-};
diff --git a/package.json b/package.json
index f2cbefa5faa4..af9e6d764565 100644
--- a/package.json
+++ b/package.json
@@ -100,8 +100,7 @@
"dev-packages/size-limit-gh-action",
"dev-packages/clear-cache-gh-action",
"dev-packages/external-contributor-gh-action",
- "dev-packages/rollup-utils",
- "dev-packages/opentelemetry-v2-tests"
+ "dev-packages/rollup-utils"
],
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index 4e04a83226ee..0bca7574c42c 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -65,9 +65,9 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/instrumentation-aws-lambda": "0.50.3",
- "@opentelemetry/instrumentation-aws-sdk": "0.49.1",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/instrumentation-aws-lambda": "0.54.0",
+ "@opentelemetry/instrumentation-aws-sdk": "0.56.0",
"@sentry/core": "9.40.0",
"@sentry/node": "9.40.0",
"@types/aws-lambda": "^8.10.62"
diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json
index 4a2d3f32c3fe..a2502a70cd0c 100644
--- a/packages/nestjs/package.json
+++ b/packages/nestjs/package.json
@@ -45,9 +45,9 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "0.57.2",
- "@opentelemetry/instrumentation-nestjs-core": "0.44.1",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/instrumentation-nestjs-core": "0.49.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/core": "9.40.0",
"@sentry/node": "9.40.0"
diff --git a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
index 968c24a469e4..85eaae360b1c 100644
--- a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
+++ b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
@@ -1,9 +1,9 @@
-import { isWrapped } from '@opentelemetry/core';
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
+ isWrapped,
} from '@opentelemetry/instrumentation';
import { captureException, SDK_VERSION, startSpan } from '@sentry/core';
import { getEventSpanOptions } from './helpers';
diff --git a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts
index fff9f92616f3..04b20f5d4d6a 100644
--- a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts
+++ b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts
@@ -1,9 +1,9 @@
-import { isWrapped } from '@opentelemetry/core';
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
+ isWrapped,
} from '@opentelemetry/instrumentation';
import type { Span } from '@sentry/core';
import {
diff --git a/packages/node-core/package.json b/packages/node-core/package.json
index 1962ff71925a..752af8fc0042 100644
--- a/packages/node-core/package.json
+++ b/packages/node-core/package.json
@@ -72,11 +72,11 @@
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/resources": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1",
+ "@opentelemetry/context-async-hooks": "^2.0.0",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/resources": "^2.0.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@types/node": "^18.19.1"
},
diff --git a/packages/node-core/test/helpers/mockSdkInit.ts b/packages/node-core/test/helpers/mockSdkInit.ts
index ce82f92de3d8..0ea8a93cb064 100644
--- a/packages/node-core/test/helpers/mockSdkInit.ts
+++ b/packages/node-core/test/helpers/mockSdkInit.ts
@@ -1,6 +1,6 @@
import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api';
-import { Resource } from '@opentelemetry/resources';
-import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
+import { type SpanProcessor, BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
@@ -64,12 +64,14 @@ export function setupOtel(client: NodeClient): BasicTracerProvider | undefined {
// Create and configure TracerProvider with same config as Node SDK
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
- resource: new Resource({
- [ATTR_SERVICE_NAME]: 'node',
- // eslint-disable-next-line deprecation/deprecation
- [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
- [ATTR_SERVICE_VERSION]: SDK_VERSION,
- }),
+ resource: defaultResource().merge(
+ resourceFromAttributes({
+ [ATTR_SERVICE_NAME]: 'node',
+ // eslint-disable-next-line deprecation/deprecation
+ [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
+ [ATTR_SERVICE_VERSION]: SDK_VERSION,
+ }),
+ ),
forceFlushTimeoutMillis: 500,
spanProcessors: [
new SentrySpanProcessor({
@@ -128,6 +130,30 @@ export function cleanupOtel(_provider?: BasicTracerProvider): void {
resetGlobals();
}
+export function getSpanProcessor(): SentrySpanProcessor | undefined {
+ const client = getClient();
+ if (!client?.traceProvider) {
+ return undefined;
+ }
+
+ const provider = getProvider(client.traceProvider);
+ if (!provider) {
+ return undefined;
+ }
+
+ // Access the span processors from the provider via _activeSpanProcessor
+ // Casted as any because _activeSpanProcessor is marked as readonly
+ const multiSpanProcessor = (provider as any)._activeSpanProcessor as
+ | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
+ | undefined;
+
+ const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
+ (spanProcessor: SpanProcessor) => spanProcessor instanceof SentrySpanProcessor,
+ ) as SentrySpanProcessor | undefined;
+
+ return spanProcessor;
+}
+
export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined {
let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider();
diff --git a/packages/node-core/test/integration/transactions.test.ts b/packages/node-core/test/integration/transactions.test.ts
index 0ce3f7c99984..7b13a400dedb 100644
--- a/packages/node-core/test/integration/transactions.test.ts
+++ b/packages/node-core/test/integration/transactions.test.ts
@@ -1,11 +1,9 @@
import { context, trace, TraceFlags } from '@opentelemetry/api';
-import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import type { TransactionEvent } from '@sentry/core';
import { debug, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
-import { SentrySpanProcessor } from '@sentry/opentelemetry';
import { afterEach, describe, expect, it, vi } from 'vitest';
import * as Sentry from '../../src';
-import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit';
+import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit';
describe('Integration | Transactions', () => {
afterEach(() => {
@@ -562,13 +560,7 @@ describe('Integration | Transactions', () => {
mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
diff --git a/packages/node/package.json b/packages/node/package.json
index 9e26d5756c65..4d4e9b4225d0 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -66,33 +66,33 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "^0.57.2",
- "@opentelemetry/instrumentation-amqplib": "^0.46.1",
- "@opentelemetry/instrumentation-connect": "0.43.1",
- "@opentelemetry/instrumentation-dataloader": "0.16.1",
- "@opentelemetry/instrumentation-express": "0.47.1",
- "@opentelemetry/instrumentation-fs": "0.19.1",
- "@opentelemetry/instrumentation-generic-pool": "0.43.1",
- "@opentelemetry/instrumentation-graphql": "0.47.1",
- "@opentelemetry/instrumentation-hapi": "0.45.2",
- "@opentelemetry/instrumentation-http": "0.57.2",
- "@opentelemetry/instrumentation-ioredis": "0.47.1",
- "@opentelemetry/instrumentation-kafkajs": "0.7.1",
- "@opentelemetry/instrumentation-knex": "0.44.1",
- "@opentelemetry/instrumentation-koa": "0.47.1",
- "@opentelemetry/instrumentation-lru-memoizer": "0.44.1",
- "@opentelemetry/instrumentation-mongodb": "0.52.0",
- "@opentelemetry/instrumentation-mongoose": "0.46.1",
- "@opentelemetry/instrumentation-mysql": "0.45.1",
- "@opentelemetry/instrumentation-mysql2": "0.45.2",
- "@opentelemetry/instrumentation-pg": "0.51.1",
- "@opentelemetry/instrumentation-redis-4": "0.46.1",
- "@opentelemetry/instrumentation-tedious": "0.18.1",
- "@opentelemetry/instrumentation-undici": "0.10.1",
- "@opentelemetry/resources": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1",
+ "@opentelemetry/context-async-hooks": "^2.0.0",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
+ "@opentelemetry/instrumentation-amqplib": "0.50.0",
+ "@opentelemetry/instrumentation-connect": "0.47.0",
+ "@opentelemetry/instrumentation-dataloader": "0.21.0",
+ "@opentelemetry/instrumentation-express": "0.52.0",
+ "@opentelemetry/instrumentation-fs": "0.23.0",
+ "@opentelemetry/instrumentation-generic-pool": "0.47.0",
+ "@opentelemetry/instrumentation-graphql": "0.51.0",
+ "@opentelemetry/instrumentation-hapi": "0.50.0",
+ "@opentelemetry/instrumentation-http": "0.203.0",
+ "@opentelemetry/instrumentation-ioredis": "0.51.0",
+ "@opentelemetry/instrumentation-kafkajs": "0.12.0",
+ "@opentelemetry/instrumentation-knex": "0.48.0",
+ "@opentelemetry/instrumentation-koa": "0.51.0",
+ "@opentelemetry/instrumentation-lru-memoizer": "0.48.0",
+ "@opentelemetry/instrumentation-mongodb": "0.56.0",
+ "@opentelemetry/instrumentation-mongoose": "0.50.0",
+ "@opentelemetry/instrumentation-mysql": "0.49.0",
+ "@opentelemetry/instrumentation-mysql2": "0.49.0",
+ "@opentelemetry/instrumentation-pg": "0.55.0",
+ "@opentelemetry/instrumentation-redis": "0.51.0",
+ "@opentelemetry/instrumentation-tedious": "0.22.0",
+ "@opentelemetry/instrumentation-undici": "0.14.0",
+ "@opentelemetry/resources": "^2.0.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@prisma/instrumentation": "6.11.1",
"@sentry/core": "9.40.0",
diff --git a/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts b/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts
deleted file mode 100644
index f6a83e31b073..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/enums/AttributeNames.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export enum AttributeNames {
- EXPRESS_TYPE = 'express.type',
- EXPRESS_NAME = 'express.name',
-}
diff --git a/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts b/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts
deleted file mode 100644
index 5cfc47c555d9..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/enums/ExpressLayerType.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-export enum ExpressLayerType {
- ROUTER = 'router',
- MIDDLEWARE = 'middleware',
- REQUEST_HANDLER = 'request_handler',
-}
diff --git a/packages/node/src/integrations/tracing/express-v5/instrumentation.ts b/packages/node/src/integrations/tracing/express-v5/instrumentation.ts
deleted file mode 100644
index bc810341db35..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/instrumentation.ts
+++ /dev/null
@@ -1,326 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/no-unsafe-member-access */
-/* eslint-disable @typescript-eslint/member-ordering */
-/* eslint-disable guard-for-in */
-/* eslint-disable @typescript-eslint/ban-types */
-/* eslint-disable prefer-rest-params */
-/* eslint-disable @typescript-eslint/no-this-alias */
-/* eslint-disable jsdoc/require-jsdoc */
-/* eslint-disable @typescript-eslint/explicit-function-return-type */
-/* eslint-disable @typescript-eslint/explicit-member-accessibility */
-
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import type { Attributes } from '@opentelemetry/api';
-import { context, diag, SpanStatusCode, trace } from '@opentelemetry/api';
-import { getRPCMetadata, RPCType } from '@opentelemetry/core';
-import {
- InstrumentationBase,
- InstrumentationNodeModuleDefinition,
- isWrapped,
- safeExecuteInTheMiddle,
-} from '@opentelemetry/instrumentation';
-import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
-import type * as express from 'express';
-import { AttributeNames } from './enums/AttributeNames';
-import { ExpressLayerType } from './enums/ExpressLayerType';
-import type { ExpressLayer, ExpressRouter, PatchedRequest } from './internal-types';
-import { _LAYERS_STORE_PROPERTY, kLayerPatched } from './internal-types';
-import type { ExpressInstrumentationConfig, ExpressRequestInfo } from './types';
-import { asErrorAndMessage, getLayerMetadata, getLayerPath, isLayerIgnored, storeLayerPath } from './utils';
-
-export const PACKAGE_VERSION = '0.1.0';
-export const PACKAGE_NAME = '@sentry/instrumentation-express-v5';
-
-/** Express instrumentation for OpenTelemetry */
-export class ExpressInstrumentationV5 extends InstrumentationBase {
- constructor(config: ExpressInstrumentationConfig = {}) {
- super(PACKAGE_NAME, PACKAGE_VERSION, config);
- }
-
- init() {
- return [
- new InstrumentationNodeModuleDefinition(
- 'express',
- ['>=5.0.0'],
- moduleExports => this._setup(moduleExports),
- moduleExports => this._tearDown(moduleExports),
- ),
- ];
- }
-
- private _setup(moduleExports: any) {
- const routerProto = moduleExports.Router.prototype;
- // patch express.Router.route
- if (isWrapped(routerProto.route)) {
- this._unwrap(routerProto, 'route');
- }
- this._wrap(routerProto, 'route', this._getRoutePatch());
- // patch express.Router.use
- if (isWrapped(routerProto.use)) {
- this._unwrap(routerProto, 'use');
- }
- this._wrap(routerProto, 'use', this._getRouterUsePatch() as any);
- // patch express.Application.use
- if (isWrapped(moduleExports.application.use)) {
- this._unwrap(moduleExports.application, 'use');
- }
- this._wrap(moduleExports.application, 'use', this._getAppUsePatch() as any);
- return moduleExports;
- }
-
- private _tearDown(moduleExports: any) {
- if (moduleExports === undefined) return;
- const routerProto = moduleExports.Router.prototype;
- this._unwrap(routerProto, 'route');
- this._unwrap(routerProto, 'use');
- this._unwrap(moduleExports.application, 'use');
- }
-
- /**
- * Get the patch for Router.route function
- */
- private _getRoutePatch() {
- const instrumentation = this;
- return function (original: express.Router['route']) {
- return function route_trace(this: ExpressRouter, ...args: Parameters) {
- const route = original.apply(this, args);
- const layer = this.stack[this.stack.length - 1] as ExpressLayer;
- instrumentation._applyPatch(layer, getLayerPath(args));
- return route;
- };
- };
- }
-
- /**
- * Get the patch for Router.use function
- */
- private _getRouterUsePatch() {
- const instrumentation = this;
- return function (original: express.Router['use']) {
- return function use(this: express.Application, ...args: Parameters) {
- const route = original.apply(this, args);
- const layer = this.stack[this.stack.length - 1] as ExpressLayer;
- instrumentation._applyPatch(layer, getLayerPath(args));
- return route;
- };
- };
- }
-
- /**
- * Get the patch for Application.use function
- */
- private _getAppUsePatch() {
- const instrumentation = this;
- return function (original: express.Application['use']) {
- return function use(
- // In express 5.x the router is stored in `router` whereas in 4.x it's stored in `_router`
- this: { _router?: ExpressRouter; router?: ExpressRouter },
- ...args: Parameters
- ) {
- // if we access app.router in express 4.x we trigger an assertion error
- // This property existed in v3, was removed in v4 and then re-added in v5
- const router = this.router;
- const route = original.apply(this, args);
- if (router) {
- const layer = router.stack[router.stack.length - 1] as ExpressLayer;
- instrumentation._applyPatch(layer, getLayerPath(args));
- }
- return route;
- };
- };
- }
-
- /** Patch each express layer to create span and propagate context */
- private _applyPatch(this: ExpressInstrumentationV5, layer: ExpressLayer, layerPath?: string) {
- const instrumentation = this;
- // avoid patching multiple times the same layer
- if (layer[kLayerPatched] === true) return;
- layer[kLayerPatched] = true;
-
- this._wrap(layer, 'handle', original => {
- // TODO: instrument error handlers
- if (original.length === 4) return original;
-
- const patched = function (this: ExpressLayer, req: PatchedRequest, res: express.Response) {
- storeLayerPath(req, layerPath);
- const route = (req[_LAYERS_STORE_PROPERTY] as string[])
- .filter(path => path !== '/' && path !== '/*')
- .join('')
- // remove duplicate slashes to normalize route
- .replace(/\/{2,}/g, '/');
-
- const actualRoute = route.length > 0 ? route : undefined;
-
- const attributes: Attributes = {
- // eslint-disable-next-line deprecation/deprecation
- [SEMATTRS_HTTP_ROUTE]: actualRoute,
- };
- const metadata = getLayerMetadata(route, layer, layerPath);
- const type = metadata.attributes[AttributeNames.EXPRESS_TYPE] as ExpressLayerType;
-
- const rpcMetadata = getRPCMetadata(context.active());
- if (rpcMetadata?.type === RPCType.HTTP) {
- rpcMetadata.route = actualRoute;
- }
-
- // verify against the config if the layer should be ignored
- if (isLayerIgnored(metadata.name, type, instrumentation.getConfig())) {
- if (type === ExpressLayerType.MIDDLEWARE) {
- (req[_LAYERS_STORE_PROPERTY] as string[]).pop();
- }
- return original.apply(this, arguments);
- }
-
- if (trace.getSpan(context.active()) === undefined) {
- return original.apply(this, arguments);
- }
-
- const spanName = instrumentation._getSpanName(
- {
- request: req,
- layerType: type,
- route,
- },
- metadata.name,
- );
- const span = instrumentation.tracer.startSpan(spanName, {
- attributes: Object.assign(attributes, metadata.attributes),
- });
-
- const { requestHook } = instrumentation.getConfig();
- if (requestHook) {
- safeExecuteInTheMiddle(
- () =>
- requestHook(span, {
- request: req,
- layerType: type,
- route,
- }),
- e => {
- if (e) {
- diag.error('express instrumentation: request hook failed', e);
- }
- },
- true,
- );
- }
-
- let spanHasEnded = false;
- if (metadata.attributes[AttributeNames.EXPRESS_TYPE] !== ExpressLayerType.MIDDLEWARE) {
- span.end();
- spanHasEnded = true;
- }
- // listener for response.on('finish')
- const onResponseFinish = () => {
- if (spanHasEnded === false) {
- spanHasEnded = true;
- span.end();
- }
- };
-
- // verify we have a callback
- const args = Array.from(arguments);
- const callbackIdx = args.findIndex(arg => typeof arg === 'function');
- if (callbackIdx >= 0) {
- arguments[callbackIdx] = function () {
- // express considers anything but an empty value, "route" or "router"
- // passed to its callback to be an error
- const maybeError = arguments[0];
- const isError = ![undefined, null, 'route', 'router'].includes(maybeError);
- if (!spanHasEnded && isError) {
- const [error, message] = asErrorAndMessage(maybeError);
- span.recordException(error);
- span.setStatus({
- code: SpanStatusCode.ERROR,
- message,
- });
- }
-
- if (spanHasEnded === false) {
- spanHasEnded = true;
- req.res?.removeListener('finish', onResponseFinish);
- span.end();
- }
- if (!(req.route && isError)) {
- (req[_LAYERS_STORE_PROPERTY] as string[]).pop();
- }
- const callback = args[callbackIdx] as Function;
- return callback.apply(this, arguments);
- };
- }
-
- try {
- return original.apply(this, arguments);
- } catch (anyError) {
- const [error, message] = asErrorAndMessage(anyError);
- span.recordException(error);
- span.setStatus({
- code: SpanStatusCode.ERROR,
- message,
- });
- throw anyError;
- } finally {
- /**
- * At this point if the callback wasn't called, that means either the
- * layer is asynchronous (so it will call the callback later on) or that
- * the layer directly end the http response, so we'll hook into the "finish"
- * event to handle the later case.
- */
- if (!spanHasEnded) {
- res.once('finish', onResponseFinish);
- }
- }
- };
-
- // `handle` isn't just a regular function in some cases. It also contains
- // some properties holding metadata and state so we need to proxy them
- // through through patched function
- // ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1950
- // Also some apps/libs do their own patching before OTEL and have these properties
- // in the proptotype. So we use a `for...in` loop to get own properties and also
- // any enumerable prop in the prototype chain
- // ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2271
- for (const key in original) {
- Object.defineProperty(patched, key, {
- get() {
- return original[key];
- },
- set(value) {
- original[key] = value;
- },
- });
- }
- return patched;
- });
- }
-
- _getSpanName(info: ExpressRequestInfo, defaultName: string) {
- const { spanNameHook } = this.getConfig();
-
- if (!(spanNameHook instanceof Function)) {
- return defaultName;
- }
-
- try {
- return spanNameHook(info, defaultName) ?? defaultName;
- } catch (err) {
- diag.error('express instrumentation: error calling span name rewrite hook', err);
- return defaultName;
- }
- }
-}
diff --git a/packages/node/src/integrations/tracing/express-v5/internal-types.ts b/packages/node/src/integrations/tracing/express-v5/internal-types.ts
deleted file mode 100644
index 482dc0b6b4ea..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/internal-types.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/ban-types */
-
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import type { Request } from 'express';
-
-/**
- * This symbol is used to mark express layer as being already instrumented
- * since its possible to use a given layer multiple times (ex: middlewares)
- */
-export const kLayerPatched: unique symbol = Symbol('express-layer-patched');
-
-/**
- * This const define where on the `request` object the Instrumentation will mount the
- * current stack of express layer.
- *
- * It is necessary because express doesn't store the different layers
- * (ie: middleware, router etc) that it called to get to the current layer.
- * Given that, the only way to know the route of a given layer is to
- * store the path of where each previous layer has been mounted.
- *
- * ex: bodyParser > auth middleware > /users router > get /:id
- * in this case the stack would be: ["/users", "/:id"]
- *
- * ex2: bodyParser > /api router > /v1 router > /users router > get /:id
- * stack: ["/api", "/v1", "/users", ":id"]
- *
- */
-export const _LAYERS_STORE_PROPERTY = '__ot_middlewares';
-
-export type PatchedRequest = {
- [_LAYERS_STORE_PROPERTY]?: string[];
-} & Request;
-export type PathParams = string | RegExp | Array;
-
-// https://github.com/expressjs/express/blob/main/lib/router/index.js#L53
-export type ExpressRouter = {
- stack: ExpressLayer[];
-};
-
-// https://github.com/expressjs/express/blob/main/lib/router/layer.js#L33
-export type ExpressLayer = {
- handle: Function & Record;
- [kLayerPatched]?: boolean;
- name: string;
- path: string;
- route?: ExpressLayer;
-};
diff --git a/packages/node/src/integrations/tracing/express-v5/types.ts b/packages/node/src/integrations/tracing/express-v5/types.ts
deleted file mode 100644
index 0623cac1cbc5..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/types.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import type { Span } from '@opentelemetry/api';
-import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
-import type { ExpressLayerType } from './enums/ExpressLayerType';
-
-export type LayerPathSegment = string | RegExp | number;
-
-export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);
-
-export type ExpressRequestInfo = {
- /** An express request object */
- request: T;
- route: string;
- layerType: ExpressLayerType;
-};
-
-export type SpanNameHook = (
- info: ExpressRequestInfo,
- /**
- * If no decision is taken based on RequestInfo, the default name
- * supplied by the instrumentation can be used instead.
- */
- defaultName: string,
-) => string;
-
-/**
- * Function that can be used to add custom attributes to the current span or the root span on
- * a Express request
- * @param span - The Express middleware layer span.
- * @param info - An instance of ExpressRequestInfo that contains info about the request such as the route, and the layer type.
- */
-export interface ExpressRequestCustomAttributeFunction {
- (span: Span, info: ExpressRequestInfo): void;
-}
-
-/**
- * Options available for the Express Instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-express#express-instrumentation-options))
- */
-export interface ExpressInstrumentationConfig extends InstrumentationConfig {
- /** Ignore specific based on their name */
- ignoreLayers?: IgnoreMatcher[];
- /** Ignore specific layers based on their type */
- ignoreLayersType?: ExpressLayerType[];
- spanNameHook?: SpanNameHook;
-
- /** Function for adding custom attributes on Express request */
- requestHook?: ExpressRequestCustomAttributeFunction;
-}
diff --git a/packages/node/src/integrations/tracing/express-v5/utils.ts b/packages/node/src/integrations/tracing/express-v5/utils.ts
deleted file mode 100644
index 85bf42958bdd..000000000000
--- a/packages/node/src/integrations/tracing/express-v5/utils.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-/* eslint-disable @typescript-eslint/explicit-function-return-type */
-/* eslint-disable @typescript-eslint/no-unsafe-member-access */
-
-/*
- * Copyright The OpenTelemetry Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import type { Attributes } from '@opentelemetry/api';
-import { AttributeNames } from './enums/AttributeNames';
-import { ExpressLayerType } from './enums/ExpressLayerType';
-import type { ExpressLayer, PatchedRequest } from './internal-types';
-import { _LAYERS_STORE_PROPERTY } from './internal-types';
-import type { ExpressInstrumentationConfig, IgnoreMatcher, LayerPathSegment } from './types';
-
-/**
- * Store layers path in the request to be able to construct route later
- * @param request The request where
- * @param [value] the value to push into the array
- */
-export const storeLayerPath = (request: PatchedRequest, value?: string): void => {
- if (Array.isArray(request[_LAYERS_STORE_PROPERTY]) === false) {
- Object.defineProperty(request, _LAYERS_STORE_PROPERTY, {
- enumerable: false,
- value: [],
- });
- }
- if (value === undefined) return;
- (request[_LAYERS_STORE_PROPERTY] as string[]).push(value);
-};
-
-/**
- * Recursively search the router path from layer stack
- * @param path The path to reconstruct
- * @param layer The layer to reconstruct from
- * @returns The reconstructed path
- */
-export const getRouterPath = (path: string, layer: ExpressLayer): string => {
- const stackLayer = layer.handle?.stack?.[0];
-
- if (stackLayer?.route?.path) {
- return `${path}${stackLayer.route.path}`;
- }
-
- if (stackLayer?.handle?.stack) {
- return getRouterPath(path, stackLayer);
- }
-
- return path;
-};
-
-/**
- * Parse express layer context to retrieve a name and attributes.
- * @param route The route of the layer
- * @param layer Express layer
- * @param [layerPath] if present, the path on which the layer has been mounted
- */
-export const getLayerMetadata = (
- route: string,
- layer: ExpressLayer,
- layerPath?: string,
-): {
- attributes: Attributes;
- name: string;
-} => {
- if (layer.name === 'router') {
- const maybeRouterPath = getRouterPath('', layer);
- const extractedRouterPath = maybeRouterPath ? maybeRouterPath : layerPath || route || '/';
-
- return {
- attributes: {
- [AttributeNames.EXPRESS_NAME]: extractedRouterPath,
- [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.ROUTER,
- },
- name: `router - ${extractedRouterPath}`,
- };
- } else if (layer.name === 'bound dispatch' || layer.name === 'handle') {
- return {
- attributes: {
- [AttributeNames.EXPRESS_NAME]: (route || layerPath) ?? 'request handler',
- [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.REQUEST_HANDLER,
- },
- name: `request handler${layer.path ? ` - ${route || layerPath}` : ''}`,
- };
- } else {
- return {
- attributes: {
- [AttributeNames.EXPRESS_NAME]: layer.name,
- [AttributeNames.EXPRESS_TYPE]: ExpressLayerType.MIDDLEWARE,
- },
- name: `middleware - ${layer.name}`,
- };
- }
-};
-
-/**
- * Check whether the given obj match pattern
- * @param constant e.g URL of request
- * @param obj obj to inspect
- * @param pattern Match pattern
- */
-const satisfiesPattern = (constant: string, pattern: IgnoreMatcher): boolean => {
- if (typeof pattern === 'string') {
- return pattern === constant;
- } else if (pattern instanceof RegExp) {
- return pattern.test(constant);
- } else if (typeof pattern === 'function') {
- return pattern(constant);
- } else {
- throw new TypeError('Pattern is in unsupported datatype');
- }
-};
-
-/**
- * Check whether the given request is ignored by configuration
- * It will not re-throw exceptions from `list` provided by the client
- * @param constant e.g URL of request
- * @param [list] List of ignore patterns
- * @param [onException] callback for doing something when an exception has
- * occurred
- */
-export const isLayerIgnored = (
- name: string,
- type: ExpressLayerType,
- config?: ExpressInstrumentationConfig,
-): boolean => {
- if (Array.isArray(config?.ignoreLayersType) && config?.ignoreLayersType?.includes(type)) {
- return true;
- }
- if (Array.isArray(config?.ignoreLayers) === false) return false;
- try {
- for (const pattern of config!.ignoreLayers!) {
- if (satisfiesPattern(name, pattern)) {
- return true;
- }
- }
- } catch {
- /* catch block */
- }
-
- return false;
-};
-
-/**
- * Converts a user-provided error value into an error and error message pair
- *
- * @param error - User-provided error value
- * @returns Both an Error or string representation of the value and an error message
- */
-export const asErrorAndMessage = (error: unknown): [error: string | Error, message: string] =>
- error instanceof Error ? [error, error.message] : [String(error), String(error)];
-
-/**
- * Extracts the layer path from the route arguments
- *
- * @param args - Arguments of the route
- * @returns The layer path
- */
-export const getLayerPath = (args: [LayerPathSegment | LayerPathSegment[], ...unknown[]]): string | undefined => {
- const firstArg = args[0];
-
- if (Array.isArray(firstArg)) {
- return firstArg.map(arg => extractLayerPathSegment(arg) || '').join(',');
- }
-
- return extractLayerPathSegment(firstArg);
-};
-
-const extractLayerPathSegment = (arg: LayerPathSegment) => {
- if (typeof arg === 'string') {
- return arg;
- }
-
- if (arg instanceof RegExp || typeof arg === 'number') {
- return arg.toString();
- }
-
- return;
-};
diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts
index 24155e2f5452..fed0a65cfd0b 100644
--- a/packages/node/src/integrations/tracing/express.ts
+++ b/packages/node/src/integrations/tracing/express.ts
@@ -15,10 +15,8 @@ import {
} from '@sentry/core';
import { addOriginToSpan, ensureIsWrapped, generateInstrumentOnce } from '@sentry/node-core';
import { DEBUG_BUILD } from '../../debug-build';
-import { ExpressInstrumentationV5 } from './express-v5/instrumentation';
const INTEGRATION_NAME = 'Express';
-const INTEGRATION_NAME_V5 = 'Express-V5';
function requestHook(span: Span): void {
addOriginToSpan(span, 'auto.http.otel.express');
@@ -61,21 +59,11 @@ export const instrumentExpress = generateInstrumentOnce(
}),
);
-export const instrumentExpressV5 = generateInstrumentOnce(
- INTEGRATION_NAME_V5,
- () =>
- new ExpressInstrumentationV5({
- requestHook: span => requestHook(span),
- spanNameHook: (info, defaultName) => spanNameHook(info, defaultName),
- }),
-);
-
const _expressIntegration = (() => {
return {
name: INTEGRATION_NAME,
setupOnce() {
instrumentExpress();
- instrumentExpressV5();
},
};
}) satisfies IntegrationFn;
diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts
index 54fb4c72be2d..f27b3cf615e8 100644
--- a/packages/node/src/integrations/tracing/index.ts
+++ b/packages/node/src/integrations/tracing/index.ts
@@ -2,7 +2,7 @@ import type { Integration } from '@sentry/core';
import { instrumentOtelHttp } from '../http';
import { amqplibIntegration, instrumentAmqplib } from './amqplib';
import { connectIntegration, instrumentConnect } from './connect';
-import { expressIntegration, instrumentExpress, instrumentExpressV5 } from './express';
+import { expressIntegration, instrumentExpress } from './express';
import { fastifyIntegration, instrumentFastify, instrumentFastifyV3 } from './fastify';
import { genericPoolIntegration, instrumentGenericPool } from './genericPool';
import { graphqlIntegration, instrumentGraphql } from './graphql';
@@ -59,7 +59,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
return [
instrumentOtelHttp,
instrumentExpress,
- instrumentExpressV5,
instrumentConnect,
instrumentFastify,
instrumentFastifyV3,
diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts
index 66c95705e457..308c8be29abe 100644
--- a/packages/node/src/integrations/tracing/redis.ts
+++ b/packages/node/src/integrations/tracing/redis.ts
@@ -1,7 +1,7 @@
import type { Span } from '@opentelemetry/api';
import type { RedisResponseCustomAttributeFunction } from '@opentelemetry/instrumentation-ioredis';
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
-import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4';
+import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis';
import type { IntegrationFn } from '@sentry/core';
import {
defineIntegration,
@@ -81,7 +81,7 @@ const instrumentIORedis = generateInstrumentOnce('IORedis', () => {
});
});
-const instrumentRedis4 = generateInstrumentOnce('Redis-4', () => {
+const instrumentRedisModule = generateInstrumentOnce('Redis', () => {
return new RedisInstrumentation({
responseHook: cacheResponseHook,
});
@@ -91,7 +91,7 @@ const instrumentRedis4 = generateInstrumentOnce('Redis-4', () => {
export const instrumentRedis = Object.assign(
(): void => {
instrumentIORedis();
- instrumentRedis4();
+ instrumentRedisModule();
// todo: implement them gradually
// new LegacyRedisInstrumentation({}),
diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts
index 4e58414f347a..fc6b02c3830d 100644
--- a/packages/node/src/sdk/initOtel.ts
+++ b/packages/node/src/sdk/initOtel.ts
@@ -1,5 +1,5 @@
import { context, propagation, trace } from '@opentelemetry/api';
-import { Resource } from '@opentelemetry/resources';
+import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import {
@@ -109,12 +109,14 @@ export function setupOtel(client: NodeClient, options: AdditionalOpenTelemetryOp
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
- resource: new Resource({
- [ATTR_SERVICE_NAME]: 'node',
- // eslint-disable-next-line deprecation/deprecation
- [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
- [ATTR_SERVICE_VERSION]: SDK_VERSION,
- }),
+ resource: defaultResource().merge(
+ resourceFromAttributes({
+ [ATTR_SERVICE_NAME]: 'node',
+ // eslint-disable-next-line deprecation/deprecation
+ [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
+ [ATTR_SERVICE_VERSION]: SDK_VERSION,
+ }),
+ ),
forceFlushTimeoutMillis: 500,
spanProcessors: [
new SentrySpanProcessor({
diff --git a/packages/node/test/helpers/mockSdkInit.ts b/packages/node/test/helpers/mockSdkInit.ts
index 29e19f50e0f8..dc4c3586d978 100644
--- a/packages/node/test/helpers/mockSdkInit.ts
+++ b/packages/node/test/helpers/mockSdkInit.ts
@@ -1,6 +1,7 @@
import { context, propagation, ProxyTracerProvider, trace } from '@opentelemetry/api';
-import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
+import { type SpanProcessor, BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import { getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
+import { SentrySpanProcessor } from '@sentry/opentelemetry';
import type { NodeClient } from '../../src';
import { init } from '../../src/sdk';
import type { NodeClientOptions } from '../../src/types';
@@ -42,6 +43,29 @@ export function cleanupOtel(_provider?: BasicTracerProvider): void {
propagation.disable();
}
+export function getSpanProcessor(): SentrySpanProcessor | undefined {
+ const client = getClient();
+ if (!client?.traceProvider) {
+ return undefined;
+ }
+
+ const provider = getProvider(client.traceProvider);
+ if (!provider) {
+ return undefined;
+ }
+
+ // Access the span processors from the provider via _activeSpanProcessor
+ const multiSpanProcessor = provider?.['_activeSpanProcessor'] as
+ | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
+ | undefined;
+
+ const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
+ (spanProcessor: SpanProcessor) => spanProcessor instanceof SentrySpanProcessor,
+ ) as SentrySpanProcessor | undefined;
+
+ return spanProcessor;
+}
+
export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined {
let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider();
diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts
index 0ce3f7c99984..7b13a400dedb 100644
--- a/packages/node/test/integration/transactions.test.ts
+++ b/packages/node/test/integration/transactions.test.ts
@@ -1,11 +1,9 @@
import { context, trace, TraceFlags } from '@opentelemetry/api';
-import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import type { TransactionEvent } from '@sentry/core';
import { debug, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core';
-import { SentrySpanProcessor } from '@sentry/opentelemetry';
import { afterEach, describe, expect, it, vi } from 'vitest';
import * as Sentry from '../../src';
-import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit';
+import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit';
describe('Integration | Transactions', () => {
afterEach(() => {
@@ -562,13 +560,7 @@ describe('Integration | Transactions', () => {
mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json
index 6ff4e1de048e..6734b3183b22 100644
--- a/packages/opentelemetry/package.json
+++ b/packages/opentelemetry/package.json
@@ -50,9 +50,9 @@
},
"devDependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/context-async-hooks": "^1.30.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1",
+ "@opentelemetry/context-async-hooks": "^2.0.0",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.34.0"
},
"scripts": {
diff --git a/packages/opentelemetry/test/asyncContextStrategy.test.ts b/packages/opentelemetry/test/asyncContextStrategy.test.ts
index 6541e8b9220c..89a3ab856075 100644
--- a/packages/opentelemetry/test/asyncContextStrategy.test.ts
+++ b/packages/opentelemetry/test/asyncContextStrategy.test.ts
@@ -23,7 +23,7 @@ describe('asyncContextStrategy', () => {
const options = getDefaultTestClientOptions();
const client = new TestClient(options);
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
setOpenTelemetryContextAsyncContextStrategy();
});
diff --git a/packages/opentelemetry/test/helpers/initOtel.ts b/packages/opentelemetry/test/helpers/initOtel.ts
index fd7a33884b5c..bf281f716657 100644
--- a/packages/opentelemetry/test/helpers/initOtel.ts
+++ b/packages/opentelemetry/test/helpers/initOtel.ts
@@ -1,6 +1,6 @@
import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api';
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
-import { Resource } from '@opentelemetry/resources';
+import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import {
ATTR_SERVICE_NAME,
@@ -49,23 +49,27 @@ export function initOtel(): void {
setupEventContextTrace(client);
enhanceDscWithOpenTelemetryRootSpanName(client);
- const provider = setupOtel(client);
+ const [provider, spanProcessor] = setupOtel(client);
client.traceProvider = provider;
+ client.spanProcessor = spanProcessor;
}
/** Just exported for tests. */
-export function setupOtel(client: TestClientInterface): BasicTracerProvider {
+export function setupOtel(client: TestClientInterface): [BasicTracerProvider, SentrySpanProcessor] {
+ const spanProcessor = new SentrySpanProcessor();
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
- resource: new Resource({
- [ATTR_SERVICE_NAME]: 'opentelemetry-test',
- // eslint-disable-next-line deprecation/deprecation
- [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
- [ATTR_SERVICE_VERSION]: SDK_VERSION,
- }),
+ resource: defaultResource().merge(
+ resourceFromAttributes({
+ [ATTR_SERVICE_NAME]: 'opentelemetry-test',
+ // eslint-disable-next-line deprecation/deprecation
+ [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
+ [ATTR_SERVICE_VERSION]: SDK_VERSION,
+ }),
+ ),
forceFlushTimeoutMillis: 500,
- spanProcessors: [new SentrySpanProcessor()],
+ spanProcessors: [spanProcessor],
});
// We use a custom context manager to keep context in sync with sentry scope
@@ -75,5 +79,5 @@ export function setupOtel(client: TestClientInterface): BasicTracerProvider {
propagation.setGlobalPropagator(new SentryPropagator());
context.setGlobalContextManager(new SentryContextManager());
- return provider;
+ return [provider, spanProcessor];
}
diff --git a/dev-packages/opentelemetry-v2-tests/test/helpers/isSpan.ts b/packages/opentelemetry/test/helpers/isSpan.ts
similarity index 100%
rename from dev-packages/opentelemetry-v2-tests/test/helpers/isSpan.ts
rename to packages/opentelemetry/test/helpers/isSpan.ts
diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts
index 486397e32cef..91b1369eb928 100644
--- a/packages/opentelemetry/test/helpers/mockSdkInit.ts
+++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts
@@ -3,6 +3,7 @@ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import type { ClientOptions, Options } from '@sentry/core';
import { flush, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core';
import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy';
+import { SentrySpanProcessor } from '../../src/spanProcessor';
import type { OpenTelemetryClient } from '../../src/types';
import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck';
import { initOtel } from './initOtel';
@@ -51,6 +52,20 @@ export async function cleanupOtel(_provider?: BasicTracerProvider): Promise();
+ if (!client) {
+ return undefined;
+ }
+
+ const spanProcessor = client.spanProcessor;
+ if (spanProcessor instanceof SentrySpanProcessor) {
+ return spanProcessor;
+ }
+
+ return undefined;
+}
+
export function getProvider(_provider?: BasicTracerProvider): BasicTracerProvider | undefined {
let provider = _provider || getClient()?.traceProvider || trace.getTracerProvider();
diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts
index b476d7536e5e..570df4a86aa8 100644
--- a/packages/opentelemetry/test/integration/transactions.test.ts
+++ b/packages/opentelemetry/test/integration/transactions.test.ts
@@ -1,7 +1,6 @@
import type { SpanContext } from '@opentelemetry/api';
import { context, ROOT_CONTEXT, trace, TraceFlags } from '@opentelemetry/api';
import { TraceState } from '@opentelemetry/core';
-import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import type { Event, TransactionEvent } from '@sentry/core';
import {
addBreadcrumb,
@@ -15,10 +14,9 @@ import {
} from '@sentry/core';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { SENTRY_TRACE_STATE_DSC } from '../../src/constants';
-import { SentrySpanProcessor } from '../../src/spanProcessor';
import { startInactiveSpan, startSpan } from '../../src/trace';
import { makeTraceState } from '../../src/utils/makeTraceState';
-import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit';
+import { cleanupOtel, getSpanProcessor, mockSdkInit } from '../helpers/mockSdkInit';
import type { TestClientInterface } from '../helpers/TestClient';
describe('Integration | Transactions', () => {
@@ -444,13 +442,7 @@ describe('Integration | Transactions', () => {
mockSdkInit({ tracesSampleRate: 1, beforeSendTransaction });
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
@@ -522,13 +514,7 @@ describe('Integration | Transactions', () => {
},
});
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
@@ -580,13 +566,7 @@ describe('Integration | Transactions', () => {
},
});
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
@@ -651,13 +631,7 @@ describe('Integration | Transactions', () => {
},
});
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
@@ -713,13 +687,7 @@ describe('Integration | Transactions', () => {
},
});
- const provider = getProvider();
- const multiSpanProcessor = provider?.activeSpanProcessor as
- | (SpanProcessor & { _spanProcessors?: SpanProcessor[] })
- | undefined;
- const spanProcessor = multiSpanProcessor?.['_spanProcessors']?.find(
- spanProcessor => spanProcessor instanceof SentrySpanProcessor,
- ) as SentrySpanProcessor | undefined;
+ const spanProcessor = getSpanProcessor();
const exporter = spanProcessor ? spanProcessor['_exporter'] : undefined;
diff --git a/packages/opentelemetry/test/sampler.test.ts b/packages/opentelemetry/test/sampler.test.ts
index 6ae5c3d48308..b7ffd9522837 100644
--- a/packages/opentelemetry/test/sampler.test.ts
+++ b/packages/opentelemetry/test/sampler.test.ts
@@ -81,11 +81,13 @@ describe('SentrySampler', () => {
const links = undefined;
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links);
- expect(actual).toEqual({
- decision: SamplingDecision.RECORD_AND_SAMPLED,
- attributes: { 'sentry.sample_rate': 1 },
- traceState: expect.any(TraceState),
- });
+ expect(actual).toEqual(
+ expect.objectContaining({
+ decision: SamplingDecision.RECORD_AND_SAMPLED,
+ attributes: { 'sentry.sample_rate': 1 },
+ }),
+ );
+ expect(actual.traceState?.constructor.name).toBe('TraceState');
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0);
spyOnDroppedEvent.mockReset();
diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts
index d8432172a601..173bd6359a5f 100644
--- a/packages/opentelemetry/test/trace.test.ts
+++ b/packages/opentelemetry/test/trace.test.ts
@@ -2,7 +2,6 @@
import type { Span, TimeInput } from '@opentelemetry/api';
import { context, ROOT_CONTEXT, SpanKind, trace, TraceFlags } from '@opentelemetry/api';
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
-import { Span as SpanClass } from '@opentelemetry/sdk-trace-base';
import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions';
import type { Event, Scope } from '@sentry/core';
import {
@@ -21,6 +20,7 @@ import {
withScope,
} from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { getParentSpanId } from '../../../packages/opentelemetry/src/utils/getParentSpanId';
import { continueTrace, startInactiveSpan, startSpan, startSpanManual } from '../src/trace';
import type { AbstractSpan } from '../src/types';
import { getActiveSpan } from '../src/utils/getActiveSpan';
@@ -28,6 +28,7 @@ import { getSamplingDecision } from '../src/utils/getSamplingDecision';
import { getSpanKind } from '../src/utils/getSpanKind';
import { makeTraceState } from '../src/utils/makeTraceState';
import { spanHasAttributes, spanHasName } from '../src/utils/spanTypes';
+import { isSpan } from './helpers/isSpan';
import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit';
describe('trace', () => {
@@ -534,7 +535,7 @@ describe('trace', () => {
return span;
});
- expect(span).not.toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(false);
});
it('creates a span if there is a parent', () => {
@@ -546,7 +547,7 @@ describe('trace', () => {
return span;
});
- expect(span).toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(true);
});
});
});
@@ -826,7 +827,7 @@ describe('trace', () => {
it('does not create a span if there is no parent', () => {
const span = startInactiveSpan({ name: 'test span', onlyIfParent: true });
- expect(span).not.toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(false);
});
it('creates a span if there is a parent', () => {
@@ -836,7 +837,7 @@ describe('trace', () => {
return span;
});
- expect(span).toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(true);
});
});
@@ -1196,7 +1197,7 @@ describe('trace', () => {
return span;
});
- expect(span).not.toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(false);
});
it('creates a span if there is a parent', () => {
@@ -1208,7 +1209,7 @@ describe('trace', () => {
return span;
});
- expect(span).toBeInstanceOf(SpanClass);
+ expect(isSpan(span)).toBe(true);
});
});
});
@@ -1972,5 +1973,5 @@ function getSpanAttributes(span: AbstractSpan): Record | undefi
}
function getSpanParentSpanId(span: AbstractSpan): string | undefined {
- return (span as ReadableSpan).parentSpanId;
+ return getParentSpanId(span as ReadableSpan);
}
diff --git a/packages/opentelemetry/test/utils/getActiveSpan.test.ts b/packages/opentelemetry/test/utils/getActiveSpan.test.ts
index 383f91d5d6af..7a3eefaa6b3d 100644
--- a/packages/opentelemetry/test/utils/getActiveSpan.test.ts
+++ b/packages/opentelemetry/test/utils/getActiveSpan.test.ts
@@ -12,7 +12,7 @@ describe('getActiveSpan', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions());
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
@@ -97,7 +97,7 @@ describe('getRootSpan', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(async () => {
diff --git a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts
index 2f5484d916b9..ad40ec83d480 100644
--- a/packages/opentelemetry/test/utils/getRequestSpanData.test.ts
+++ b/packages/opentelemetry/test/utils/getRequestSpanData.test.ts
@@ -14,7 +14,7 @@ describe('getRequestSpanData', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
diff --git a/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts b/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts
index d8ccec93f3e2..c71569c322d5 100644
--- a/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts
+++ b/packages/opentelemetry/test/utils/groupSpansWithParents.test.ts
@@ -13,7 +13,7 @@ describe('groupSpansWithParents', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
diff --git a/packages/opentelemetry/test/utils/mapStatus.test.ts b/packages/opentelemetry/test/utils/mapStatus.test.ts
index 4147eeca2251..1831ec01fc95 100644
--- a/packages/opentelemetry/test/utils/mapStatus.test.ts
+++ b/packages/opentelemetry/test/utils/mapStatus.test.ts
@@ -16,7 +16,7 @@ describe('mapStatus', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
diff --git a/packages/opentelemetry/test/utils/setupCheck.test.ts b/packages/opentelemetry/test/utils/setupCheck.test.ts
index a2b204d063b6..526945108ba7 100644
--- a/packages/opentelemetry/test/utils/setupCheck.test.ts
+++ b/packages/opentelemetry/test/utils/setupCheck.test.ts
@@ -25,20 +25,18 @@ describe('openTelemetrySetupCheck', () => {
it('returns all setup parts', () => {
const client = new TestClient(getDefaultTestClientOptions());
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
const setup = openTelemetrySetupCheck();
- expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor', 'SentryPropagator', 'SentryContextManager']);
+ expect(setup).toEqual(['SentrySpanProcessor', 'SentrySampler', 'SentryPropagator', 'SentryContextManager']);
});
it('returns partial setup parts', () => {
const client = new TestClient(getDefaultTestClientOptions());
provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
+ spanProcessors: [new SentrySpanProcessor()],
});
- // We want to test this deprecated case also works
- // eslint-disable-next-line deprecation/deprecation
- provider.addSpanProcessor(new SentrySpanProcessor());
const setup = openTelemetrySetupCheck();
expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']);
diff --git a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts
index a705b546e610..19c8e178c160 100644
--- a/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts
+++ b/packages/opentelemetry/test/utils/setupEventContextTrace.test.ts
@@ -29,7 +29,7 @@ describe('setupEventContextTrace', () => {
client.init();
setupEventContextTrace(client);
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
diff --git a/packages/opentelemetry/test/utils/spanToJSON.test.ts b/packages/opentelemetry/test/utils/spanToJSON.test.ts
index 19115a18e0f7..c1f9fe2a18c7 100644
--- a/packages/opentelemetry/test/utils/spanToJSON.test.ts
+++ b/packages/opentelemetry/test/utils/spanToJSON.test.ts
@@ -18,7 +18,7 @@ describe('spanToJSON', () => {
beforeEach(() => {
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 }));
- provider = setupOtel(client);
+ [provider] = setupOtel(client);
});
afterEach(() => {
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 965392e80c10..0b796d62b893 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -35,8 +35,8 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/instrumentation": "0.57.2",
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/instrumentation": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@sentry/browser": "9.40.0",
"@sentry/cli": "^2.46.0",
diff --git a/packages/remix/package.json b/packages/remix/package.json
index ba0dccdb3786..8e3eb52a6504 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -65,7 +65,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/instrumentation": "^0.57.2",
+ "@opentelemetry/instrumentation": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@remix-run/router": "1.x",
"@sentry/cli": "^2.46.0",
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
index 63209f104add..a5926948be3a 100644
--- a/packages/vercel-edge/package.json
+++ b/packages/vercel-edge/package.json
@@ -40,15 +40,15 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/resources": "^1.30.1",
- "@opentelemetry/semantic-conventions": "^1.34.0",
- "@sentry/core": "9.40.0",
- "@sentry/opentelemetry": "9.40.0"
+ "@opentelemetry/resources": "^2.0.0",
+ "@sentry/core": "9.40.0"
},
"devDependencies": {
"@edge-runtime/types": "3.0.1",
- "@opentelemetry/core": "^1.30.1",
- "@opentelemetry/sdk-trace-base": "^1.30.1"
+ "@opentelemetry/core": "^2.0.0",
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
+ "@opentelemetry/semantic-conventions": "^1.34.0",
+ "@sentry/opentelemetry": "9.40.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/vercel-edge/rollup.npm.config.mjs b/packages/vercel-edge/rollup.npm.config.mjs
index 3cfd779d57f6..ae01f43703d0 100644
--- a/packages/vercel-edge/rollup.npm.config.mjs
+++ b/packages/vercel-edge/rollup.npm.config.mjs
@@ -4,7 +4,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants, plugins } from '@sentry-inter
export default makeNPMConfigVariants(
makeBaseNPMConfig({
entrypoints: ['src/index.ts'],
- bundledBuiltins: ['perf_hooks'],
+ bundledBuiltins: ['perf_hooks', 'util'],
packageSpecificConfig: {
context: 'globalThis',
output: {
@@ -21,9 +21,10 @@ export default makeNPMConfigVariants(
}),
{
// This plugin is needed because otel imports `performance` from `perf_hooks` and also uses it via the `performance` global.
+ // It also imports `inspect` and `promisify` from node's `util` which are not available in the edge runtime so we need to define a polyfill.
// Both of these APIs are not available in the edge runtime so we need to define a polyfill.
// Vercel does something similar in the `@vercel/otel` package: https://github.com/vercel/otel/blob/087601ae585cb116bb2b46c211d014520de76c71/packages/otel/build.ts#L62
- name: 'perf-hooks-performance-polyfill',
+ name: 'edge-runtime-polyfills',
banner: `
{
if (globalThis.performance === undefined) {
@@ -37,6 +38,8 @@ export default makeNPMConfigVariants(
resolveId: source => {
if (source === 'perf_hooks') {
return '\0perf_hooks_sentry_shim';
+ } else if (source === 'util') {
+ return '\0util_sentry_shim';
} else {
return null;
}
@@ -49,6 +52,22 @@ export default makeNPMConfigVariants(
now: () => Date.now()
}
`;
+ } else if (id === '\0util_sentry_shim') {
+ return `
+ export const inspect = (object) =>
+ JSON.stringify(object, null, 2);
+
+ export const promisify = (fn) => {
+ return (...args) => {
+ return new Promise((resolve, reject) => {
+ fn(...args, (err, result) => {
+ if (err) reject(err);
+ else resolve(result);
+ });
+ });
+ };
+ };
+ `;
} else {
return null;
}
diff --git a/packages/vercel-edge/src/client.ts b/packages/vercel-edge/src/client.ts
index bdf8512be933..a34d1b36f09c 100644
--- a/packages/vercel-edge/src/client.ts
+++ b/packages/vercel-edge/src/client.ts
@@ -39,11 +39,8 @@ export class VercelEdgeClient extends ServerRuntimeClient {
const provider = this.traceProvider;
- const spanProcessor = provider?.activeSpanProcessor;
- if (spanProcessor) {
- await spanProcessor.forceFlush();
- }
+ await provider?.forceFlush();
if (this.getOptions().sendClientReports) {
this._flushOutcomes();
diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts
index 12485a6c6579..ba83d7752ed3 100644
--- a/packages/vercel-edge/src/sdk.ts
+++ b/packages/vercel-edge/src/sdk.ts
@@ -1,5 +1,5 @@
import { context, diag, DiagLogLevel, propagation, trace } from '@opentelemetry/api';
-import { Resource } from '@opentelemetry/resources';
+import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
import {
ATTR_SERVICE_NAME,
@@ -158,12 +158,14 @@ export function setupOtel(client: VercelEdgeClient): void {
// Create and configure NodeTracerProvider
const provider = new BasicTracerProvider({
sampler: new SentrySampler(client),
- resource: new Resource({
- [ATTR_SERVICE_NAME]: 'edge',
- // eslint-disable-next-line deprecation/deprecation
- [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
- [ATTR_SERVICE_VERSION]: SDK_VERSION,
- }),
+ resource: defaultResource().merge(
+ resourceFromAttributes({
+ [ATTR_SERVICE_NAME]: 'edge',
+ // eslint-disable-next-line deprecation/deprecation
+ [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry',
+ [ATTR_SERVICE_VERSION]: SDK_VERSION,
+ }),
+ ),
forceFlushTimeoutMillis: 500,
spanProcessors: [
new SentrySpanProcessor({
diff --git a/yarn.lock b/yarn.lock
index 16bf8fe6d899..10c669dc1da4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5843,249 +5843,252 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
-"@opentelemetry/context-async-hooks@^1.30.1":
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz#4f76280691a742597fd0bf682982126857622948"
- integrity sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==
-
"@opentelemetry/context-async-hooks@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.0.tgz#c98a727238ca199cda943780acf6124af8d8cd80"
integrity sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA==
-"@opentelemetry/core@1.30.1", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.26.0", "@opentelemetry/core@^1.30.1", "@opentelemetry/core@^1.8.0":
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.30.1.tgz#a0b468bb396358df801881709ea38299fc30ab27"
- integrity sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==
- dependencies:
- "@opentelemetry/semantic-conventions" "1.28.0"
-
-"@opentelemetry/core@2.0.0", "@opentelemetry/core@^2.0.0":
+"@opentelemetry/core@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.0.0.tgz#37e9f0e9ddec4479b267aca6f32d88757c941b3a"
integrity sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ==
dependencies:
"@opentelemetry/semantic-conventions" "^1.29.0"
-"@opentelemetry/instrumentation-amqplib@^0.46.1":
- version "0.46.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.46.1.tgz#7101678488d0e942162ca85c9ac6e93e1f3e0008"
- integrity sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ==
+"@opentelemetry/core@2.0.1", "@opentelemetry/core@^2.0.0":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.0.1.tgz#44e1149d5666a4743cde943ef89841db3ce0f8bc"
+ integrity sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/semantic-conventions" "^1.27.0"
+ "@opentelemetry/semantic-conventions" "^1.29.0"
-"@opentelemetry/instrumentation-aws-lambda@0.50.3":
- version "0.50.3"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.50.3.tgz#bf76bd137780004aecfbb5c8335482afe5739878"
- integrity sha512-kotm/mRvSWUauudxcylc5YCDei+G/r+jnOH6q5S99aPLQ/Ms8D2yonMIxEJUILIPlthEmwLYxkw3ualWzMjm/A==
+"@opentelemetry/instrumentation-amqplib@0.50.0":
+ version "0.50.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.50.0.tgz#91899a7e2821db956daeaa803d3bd8f5af8b8050"
+ integrity sha512-kwNs/itehHG/qaQBcVrLNcvXVPW0I4FCOVtw3LHMLdYIqD7GJ6Yv2nX+a4YHjzbzIeRYj8iyMp0Bl7tlkidq5w==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
- "@types/aws-lambda" "8.10.147"
-"@opentelemetry/instrumentation-aws-sdk@0.49.1":
- version "0.49.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.49.1.tgz#e5de7235af82a5b77eca2132da62d41d64dbbba9"
- integrity sha512-Vbj4BYeV/1K4Pbbfk+gQ8gwYL0w+tBeUwG88cOxnF7CLPO1XnskGV8Q3Gzut2Ah/6Dg17dBtlzEqL3UiFP2Z6A==
+"@opentelemetry/instrumentation-aws-lambda@0.54.0":
+ version "0.54.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.54.0.tgz#835263593aa988ec460e840d3d47110392aaf92e"
+ integrity sha512-uiYI+kcMUJ/H9cxAwB8c9CaG8behLRgcYSOEA8M/tMQ54Y1ZmzAuEE3QKOi21/s30x5Q+by9g7BwiVfDtqzeMA==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/propagation-utils" "^0.30.16"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
+ "@types/aws-lambda" "8.10.150"
+
+"@opentelemetry/instrumentation-aws-sdk@0.56.0":
+ version "0.56.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.56.0.tgz#a65cd88351b7bd8566413798764679295166754a"
+ integrity sha512-Jl2B/FYEb6tBCk9G31CMomKPikGU2g+CEhrGddDI0o1YeNpg3kAO9dExF+w489/IJUGZX6/wudyNvV7z4k9NjQ==
+ dependencies:
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/propagation-utils" "^0.31.3"
+ "@opentelemetry/semantic-conventions" "^1.34.0"
-"@opentelemetry/instrumentation-connect@0.43.1":
- version "0.43.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.43.1.tgz#8ce88b94ce211c7bbdc9bd984b7a37876061bde3"
- integrity sha512-ht7YGWQuV5BopMcw5Q2hXn3I8eG8TH0J/kc/GMcW4CuNTgiP6wCu44BOnucJWL3CmFWaRHI//vWyAhaC8BwePw==
+"@opentelemetry/instrumentation-connect@0.47.0":
+ version "0.47.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.47.0.tgz#47271b8454fa88d97aa78e175c3d0cb7e10bd9e2"
+ integrity sha512-pjenvjR6+PMRb6/4X85L4OtkQCootgb/Jzh/l/Utu3SJHBid1F+gk9sTGU2FWuhhEfV6P7MZ7BmCdHXQjgJ42g==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@types/connect" "3.4.38"
-"@opentelemetry/instrumentation-dataloader@0.16.1":
- version "0.16.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.16.1.tgz#5d1d2c79f067c3102df7101f1753060ed93a1566"
- integrity sha512-K/qU4CjnzOpNkkKO4DfCLSQshejRNAJtd4esgigo/50nxCB6XCyi1dhAblUHM9jG5dRm8eu0FB+t87nIo99LYQ==
+"@opentelemetry/instrumentation-dataloader@0.21.0":
+ version "0.21.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.0.tgz#19202a85000cae9612f74bc689005ed3164e30a4"
+ integrity sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
-"@opentelemetry/instrumentation-express@0.47.1":
- version "0.47.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.47.1.tgz#7cf74f35e43cc3c8186edd1249fdb225849c48b2"
- integrity sha512-QNXPTWteDclR2B4pDFpz0TNghgB33UMjUt14B+BZPmtH1MwUFAfLHBaP5If0Z5NZC+jaH8oF2glgYjrmhZWmSw==
+"@opentelemetry/instrumentation-express@0.52.0":
+ version "0.52.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.52.0.tgz#d87d2130fe779dd757db28edb78262af83510d5b"
+ integrity sha512-W7pizN0Wh1/cbNhhTf7C62NpyYw7VfCFTYg0DYieSTrtPBT1vmoSZei19wfKLnrMsz3sHayCg0HxCVL2c+cz5w==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-fs@0.19.1":
- version "0.19.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.19.1.tgz#ebfe40781949574a66a82b8511d9bcd414dbfe98"
- integrity sha512-6g0FhB3B9UobAR60BGTcXg4IHZ6aaYJzp0Ki5FhnxyAPt8Ns+9SSvgcrnsN2eGmk3RWG5vYycUGOEApycQL24A==
+"@opentelemetry/instrumentation-fs@0.23.0":
+ version "0.23.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.23.0.tgz#e3cd3a53fa975c69de33e207b35561f3f90106f0"
+ integrity sha512-Puan+QopWHA/KNYvDfOZN6M/JtF6buXEyD934vrb8WhsX1/FuM7OtoMlQyIqAadnE8FqqDL4KDPiEfCQH6pQcQ==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
-"@opentelemetry/instrumentation-generic-pool@0.43.1":
- version "0.43.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.43.1.tgz#6d1e181b32debc9510bdbbd63fe4ce5bc310d577"
- integrity sha512-M6qGYsp1cURtvVLGDrPPZemMFEbuMmCXgQYTReC/IbimV5sGrLBjB+/hANUpRZjX67nGLdKSVLZuQQAiNz+sww==
+"@opentelemetry/instrumentation-generic-pool@0.47.0":
+ version "0.47.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.47.0.tgz#f5fa9d42236eb7d57fa544954f316faee937b0b4"
+ integrity sha512-UfHqf3zYK+CwDwEtTjaD12uUqGGTswZ7ofLBEdQ4sEJp9GHSSJMQ2hT3pgBxyKADzUdoxQAv/7NqvL42ZI+Qbw==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
-"@opentelemetry/instrumentation-graphql@0.47.1":
- version "0.47.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.47.1.tgz#1037bb546c82060d6d5d6f5dbd8765e31ccf6c26"
- integrity sha512-EGQRWMGqwiuVma8ZLAZnExQ7sBvbOx0N/AE/nlafISPs8S+QtXX+Viy6dcQwVWwYHQPAcuY3bFt3xgoAwb4ZNQ==
+"@opentelemetry/instrumentation-graphql@0.51.0":
+ version "0.51.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.51.0.tgz#1b29aa6330d196d523460e593167dca7dbcd42bb"
+ integrity sha512-LchkOu9X5DrXAnPI1+Z06h/EH/zC7D6sA86hhPrk3evLlsJTz0grPrkL/yUJM9Ty0CL/y2HSvmWQCjbJEz/ADg==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
-"@opentelemetry/instrumentation-hapi@0.45.2":
- version "0.45.2"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.45.2.tgz#14d670e0bbbdf864187a9f80265a9219ed2d01cf"
- integrity sha512-7Ehow/7Wp3aoyCrZwQpU7a2CnoMq0XhIcioFuKjBb0PLYfBfmTsFTUyatlHu0fRxhwcRsSQRTvEhmZu8CppBpQ==
+"@opentelemetry/instrumentation-hapi@0.50.0":
+ version "0.50.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.50.0.tgz#c755e9c21bfeb82046221bfd51303f816ae649e8"
+ integrity sha512-5xGusXOFQXKacrZmDbpHQzqYD1gIkrMWuwvlrEPkYOsjUqGUjl1HbxCsn5Y9bUXOCgP1Lj6A4PcKt1UiJ2MujA==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-http@0.57.2":
- version "0.57.2"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.57.2.tgz#f425eda67b6241c3abe08e4ea972169b85ef3064"
- integrity sha512-1Uz5iJ9ZAlFOiPuwYg29Bf7bJJc/GeoeJIFKJYQf67nTVKFe8RHbEtxgkOmK4UGZNHKXcpW4P8cWBYzBn1USpg==
+"@opentelemetry/instrumentation-http@0.203.0":
+ version "0.203.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.203.0.tgz#21f198547b5c72fc64e83ed25cdc991aef7b8fee"
+ integrity sha512-y3uQAcCOAwnO6vEuNVocmpVzG3PER6/YZqbPbbffDdJ9te5NkHEkfSMNzlC3+v7KlE+WinPGc3N7MR30G1HY2g==
dependencies:
- "@opentelemetry/core" "1.30.1"
- "@opentelemetry/instrumentation" "0.57.2"
- "@opentelemetry/semantic-conventions" "1.28.0"
+ "@opentelemetry/core" "2.0.1"
+ "@opentelemetry/instrumentation" "0.203.0"
+ "@opentelemetry/semantic-conventions" "^1.29.0"
forwarded-parse "2.1.2"
- semver "^7.5.2"
-"@opentelemetry/instrumentation-ioredis@0.47.1":
- version "0.47.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.47.1.tgz#5cedd0ebe8cfd3569513a9b44945827bf844b331"
- integrity sha512-OtFGSN+kgk/aoKgdkKQnBsQFDiG8WdCxu+UrHr0bXScdAmtSzLSraLo7wFIb25RVHfRWvzI5kZomqJYEg/l1iA==
+"@opentelemetry/instrumentation-ioredis@0.51.0":
+ version "0.51.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.51.0.tgz#47360999ad2b035aa2ac604c410272da671142d3"
+ integrity sha512-9IUws0XWCb80NovS+17eONXsw1ZJbHwYYMXiwsfR9TSurkLV5UNbRSKb9URHO+K+pIJILy9wCxvyiOneMr91Ig==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/redis-common" "^0.36.2"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/redis-common" "^0.38.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-kafkajs@0.7.1":
- version "0.7.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.7.1.tgz#cc7a31a5fe2c14171611da8e46827f762f332625"
- integrity sha512-OtjaKs8H7oysfErajdYr1yuWSjMAectT7Dwr+axIoZqT9lmEOkD/H/3rgAs8h/NIuEi2imSXD+vL4MZtOuJfqQ==
+"@opentelemetry/instrumentation-kafkajs@0.12.0":
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.12.0.tgz#231e6cc8a2a70d06162ed7e4ebe2ab5baa3a6670"
+ integrity sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/semantic-conventions" "^1.27.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/semantic-conventions" "^1.30.0"
-"@opentelemetry/instrumentation-knex@0.44.1":
- version "0.44.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.44.1.tgz#72f4efd798695c077ab218045d4c682231fbb36a"
- integrity sha512-U4dQxkNhvPexffjEmGwCq68FuftFK15JgUF05y/HlK3M6W/G2iEaACIfXdSnwVNe9Qh0sPfw8LbOPxrWzGWGMQ==
+"@opentelemetry/instrumentation-knex@0.48.0":
+ version "0.48.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.48.0.tgz#ed24a81dfe6099cfe56136a3fed90565e1259f58"
+ integrity sha512-V5wuaBPv/lwGxuHjC6Na2JFRjtPgstw19jTFl1B1b6zvaX8zVDYUDaR5hL7glnQtUSCMktPttQsgK4dhXpddcA==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/semantic-conventions" "^1.27.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/semantic-conventions" "^1.33.1"
-"@opentelemetry/instrumentation-koa@0.47.1":
- version "0.47.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.47.1.tgz#ba57eccd44a75ec59e3129757fda4e8c8dd7ce2c"
- integrity sha512-l/c+Z9F86cOiPJUllUCt09v+kICKvT+Vg1vOAJHtHPsJIzurGayucfCMq2acd/A/yxeNWunl9d9eqZ0G+XiI6A==
+"@opentelemetry/instrumentation-koa@0.51.0":
+ version "0.51.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.51.0.tgz#1ff57866b7882033639477d3d2d9bada19a2129f"
+ integrity sha512-XNLWeMTMG1/EkQBbgPYzCeBD0cwOrfnn8ao4hWgLv0fNCFQu1kCsJYygz2cvKuCs340RlnG4i321hX7R8gj3Rg==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-lru-memoizer@0.44.1":
- version "0.44.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.44.1.tgz#1f0ec28130f8c379d310dc531a8b25780be8e445"
- integrity sha512-5MPkYCvG2yw7WONEjYj5lr5JFehTobW7wX+ZUFy81oF2lr9IPfZk9qO+FTaM0bGEiymwfLwKe6jE15nHn1nmHg==
+"@opentelemetry/instrumentation-lru-memoizer@0.48.0":
+ version "0.48.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.48.0.tgz#b9fbbd45b7a742a6795bf7166f65684251f184b1"
+ integrity sha512-KUW29wfMlTPX1wFz+NNrmE7IzN7NWZDrmFWHM/VJcmFEuQGnnBuTIdsP55CnBDxKgQ/qqYFp4udQFNtjeFosPw==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
-"@opentelemetry/instrumentation-mongodb@0.52.0":
- version "0.52.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.52.0.tgz#a5ed123f3fac5d7d08347353cd37d9cf00893746"
- integrity sha512-1xmAqOtRUQGR7QfJFfGV/M2kC7wmI2WgZdpru8hJl3S0r4hW0n3OQpEHlSGXJAaNFyvT+ilnwkT+g5L4ljHR6g==
+"@opentelemetry/instrumentation-mongodb@0.56.0":
+ version "0.56.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.56.0.tgz#81281d2d151c3bfb26864c50b938a82ba2831b2d"
+ integrity sha512-YG5IXUUmxX3Md2buVMvxm9NWlKADrnavI36hbJsihqqvBGsWnIfguf0rUP5Srr0pfPqhQjUP+agLMsvu0GmUpA==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-mongoose@0.46.1":
- version "0.46.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.46.1.tgz#23f22b7d4d5a548ac8add2a52ec2fec4e61c7de1"
- integrity sha512-3kINtW1LUTPkiXFRSSBmva1SXzS/72we/jL22N+BnF3DFcoewkdkHPYOIdAAk9gSicJ4d5Ojtt1/HeibEc5OQg==
+"@opentelemetry/instrumentation-mongoose@0.50.0":
+ version "0.50.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.50.0.tgz#1fae5d2769ca7e67d15291fb91b61403839ad91d"
+ integrity sha512-Am8pk1Ct951r4qCiqkBcGmPIgGhoDiFcRtqPSLbJrUZqEPUsigjtMjoWDRLG1Ki1NHgOF7D0H7d+suWz1AAizw==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-mysql2@0.45.2":
- version "0.45.2"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.45.2.tgz#590ed22f274a6999e57c3283433a119274cb572b"
- integrity sha512-h6Ad60FjCYdJZ5DTz1Lk2VmQsShiViKe0G7sYikb0GHI0NVvApp2XQNRHNjEMz87roFttGPLHOYVPlfy+yVIhQ==
+"@opentelemetry/instrumentation-mysql2@0.49.0":
+ version "0.49.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.49.0.tgz#ad518f9420cf8d2035bd4f80519406b66b66bb1a"
+ integrity sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
- "@opentelemetry/sql-common" "^0.40.1"
+ "@opentelemetry/sql-common" "^0.41.0"
-"@opentelemetry/instrumentation-mysql@0.45.1":
- version "0.45.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.45.1.tgz#6fb3fdf7b5afa62bfa4ce73fae213539bb660841"
- integrity sha512-TKp4hQ8iKQsY7vnp/j0yJJ4ZsP109Ht6l4RHTj0lNEG1TfgTrIH5vJMbgmoYXWzNHAqBH2e7fncN12p3BP8LFg==
+"@opentelemetry/instrumentation-mysql@0.49.0":
+ version "0.49.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.49.0.tgz#24fa7473134867236ed4068ee645e51922bcb654"
+ integrity sha512-QU9IUNqNsrlfE3dJkZnFHqLjlndiU39ll/YAAEvWE40sGOCi9AtOF6rmEGzJ1IswoZ3oyePV7q2MP8SrhJfVAA==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
- "@types/mysql" "2.15.26"
+ "@types/mysql" "2.15.27"
-"@opentelemetry/instrumentation-nestjs-core@0.44.1":
- version "0.44.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.44.1.tgz#54ee5877080055732093c59f8a9bc2aba4fae5f0"
- integrity sha512-4TXaqJK27QXoMqrt4+hcQ6rKFd8B6V4JfrTJKnqBmWR1cbaqd/uwyl9yxhNH1JEkyo8GaBfdpBC4ZE4FuUhPmg==
+"@opentelemetry/instrumentation-nestjs-core@0.49.0":
+ version "0.49.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.49.0.tgz#a79428e72c14250c44913a3a57f39c7297aab013"
+ integrity sha512-1R/JFwdmZIk3T/cPOCkVvFQeKYzbbUvDxVH3ShXamUwBlGkdEu5QJitlRMyVNZaHkKZKWgYrBarGQsqcboYgaw==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/semantic-conventions" "^1.27.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/semantic-conventions" "^1.30.0"
-"@opentelemetry/instrumentation-pg@0.51.1":
- version "0.51.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.51.1.tgz#a999a13fa56dc67da49a1ccf8f5e56a9ed409477"
- integrity sha512-QxgjSrxyWZc7Vk+qGSfsejPVFL1AgAJdSBMYZdDUbwg730D09ub3PXScB9d04vIqPriZ+0dqzjmQx0yWKiCi2Q==
+"@opentelemetry/instrumentation-pg@0.55.0":
+ version "0.55.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.55.0.tgz#f411d1e48c50b1c1f0f185d9fe94cfbb8812d8f6"
+ integrity sha512-yfJ5bYE7CnkW/uNsnrwouG/FR7nmg09zdk2MSs7k0ZOMkDDAE3WBGpVFFApGgNu2U+gtzLgEzOQG4I/X+60hXw==
dependencies:
- "@opentelemetry/core" "^1.26.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
- "@opentelemetry/sql-common" "^0.40.1"
- "@types/pg" "8.6.1"
+ "@opentelemetry/sql-common" "^0.41.0"
+ "@types/pg" "8.15.4"
"@types/pg-pool" "2.0.6"
-"@opentelemetry/instrumentation-redis-4@0.46.1":
- version "0.46.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.46.1.tgz#325697dfccda3e70662769c6db230a37812697c6"
- integrity sha512-UMqleEoabYMsWoTkqyt9WAzXwZ4BlFZHO40wr3d5ZvtjKCHlD4YXLm+6OLCeIi/HkX7EXvQaz8gtAwkwwSEvcQ==
+"@opentelemetry/instrumentation-redis@0.51.0":
+ version "0.51.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.51.0.tgz#70504ba6c3856fcb25e436b4915e85efaa7d38a6"
+ integrity sha512-uL/GtBA0u72YPPehwOvthAe+Wf8k3T+XQPBssJmTYl6fzuZjNq8zTfxVFhl9nRFjFVEe+CtiYNT0Q3AyqW1Z0A==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
- "@opentelemetry/redis-common" "^0.36.2"
+ "@opentelemetry/instrumentation" "^0.203.0"
+ "@opentelemetry/redis-common" "^0.38.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
-"@opentelemetry/instrumentation-tedious@0.18.1":
- version "0.18.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.18.1.tgz#d87dba9d0ddfc77f9fcbcceabcc31cb5a5f7bb11"
- integrity sha512-5Cuy/nj0HBaH+ZJ4leuD7RjgvA844aY2WW+B5uLcWtxGjRZl3MNLuxnNg5DYWZNPO+NafSSnra0q49KWAHsKBg==
+"@opentelemetry/instrumentation-tedious@0.22.0":
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.22.0.tgz#f71374c52cb9c57a6b879bea3256a1465c02efbb"
+ integrity sha512-XrrNSUCyEjH1ax9t+Uo6lv0S2FCCykcF7hSxBMxKf7Xn0bPRxD3KyFUZy25aQXzbbbUHhtdxj3r2h88SfEM3aA==
dependencies:
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/instrumentation" "^0.203.0"
"@opentelemetry/semantic-conventions" "^1.27.0"
"@types/tedious" "^4.0.14"
-"@opentelemetry/instrumentation-undici@0.10.1":
- version "0.10.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.10.1.tgz#228b7fc267e55533708be16c43e70bbb51a691de"
- integrity sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ==
+"@opentelemetry/instrumentation-undici@0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz#7a9cd276f7664773b5daf5ae53365b3593e6e7a9"
+ integrity sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==
+ dependencies:
+ "@opentelemetry/core" "^2.0.0"
+ "@opentelemetry/instrumentation" "^0.203.0"
+
+"@opentelemetry/instrumentation@0.203.0", "@opentelemetry/instrumentation@^0.203.0":
+ version "0.203.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz#5c74a41cd6868f7ba47b346ff5a58ea7b18cf381"
+ integrity sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==
dependencies:
- "@opentelemetry/core" "^1.8.0"
- "@opentelemetry/instrumentation" "^0.57.1"
+ "@opentelemetry/api-logs" "0.203.0"
+ import-in-the-middle "^1.8.1"
+ require-in-the-middle "^7.1.1"
-"@opentelemetry/instrumentation@0.57.2", "@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0", "@opentelemetry/instrumentation@^0.57.1", "@opentelemetry/instrumentation@^0.57.2":
+"@opentelemetry/instrumentation@^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0":
version "0.57.2"
resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz#8924549d7941ba1b5c6f04d5529cf48330456d1d"
integrity sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==
@@ -6097,32 +6100,15 @@
semver "^7.5.2"
shimmer "^1.2.1"
-"@opentelemetry/instrumentation@^0.203.0":
- version "0.203.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz#5c74a41cd6868f7ba47b346ff5a58ea7b18cf381"
- integrity sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==
- dependencies:
- "@opentelemetry/api-logs" "0.203.0"
- import-in-the-middle "^1.8.1"
- require-in-the-middle "^7.1.1"
-
-"@opentelemetry/propagation-utils@^0.30.16":
- version "0.30.16"
- resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.16.tgz#6715d0225b618ea66cf34cc3800fa3452a8475fa"
- integrity sha512-ZVQ3Z/PQ+2GQlrBfbMMMT0U7MzvYZLCPP800+ooyaBqm4hMvuQHfP028gB9/db0mwkmyEAMad9houukUVxhwcw==
-
-"@opentelemetry/redis-common@^0.36.2":
- version "0.36.2"
- resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47"
- integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==
+"@opentelemetry/propagation-utils@^0.31.3":
+ version "0.31.3"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.31.3.tgz#42aab61a1a3cec64ce221cbcec5f3f6fc84e9701"
+ integrity sha512-ZI6LKjyo+QYYZY5SO8vfoCQ9A69r1/g+pyjvtu5RSK38npINN1evEmwqbqhbg2CdcIK3a4PN6pDAJz/yC5/gAA==
-"@opentelemetry/resources@1.30.1", "@opentelemetry/resources@^1.30.1":
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.30.1.tgz#a4eae17ebd96947fdc7a64f931ca4b71e18ce964"
- integrity sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==
- dependencies:
- "@opentelemetry/core" "1.30.1"
- "@opentelemetry/semantic-conventions" "1.28.0"
+"@opentelemetry/redis-common@^0.38.0":
+ version "0.38.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.0.tgz#87d2a792dcbcf466a41bb7dfb8a7cd094d643d0b"
+ integrity sha512-4Wc0AWURII2cfXVVoZ6vDqK+s5n4K5IssdrlVrvGsx6OEOKdghKtJZqXAHWFiZv4nTDLH2/2fldjIHY8clMOjQ==
"@opentelemetry/resources@2.0.0":
version "2.0.0"
@@ -6132,14 +6118,13 @@
"@opentelemetry/core" "2.0.0"
"@opentelemetry/semantic-conventions" "^1.29.0"
-"@opentelemetry/sdk-trace-base@^1.30.1":
- version "1.30.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz#41a42234096dc98e8f454d24551fc80b816feb34"
- integrity sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==
+"@opentelemetry/resources@^2.0.0":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.0.1.tgz#0365d134291c0ed18d96444a1e21d0e6a481c840"
+ integrity sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==
dependencies:
- "@opentelemetry/core" "1.30.1"
- "@opentelemetry/resources" "1.30.1"
- "@opentelemetry/semantic-conventions" "1.28.0"
+ "@opentelemetry/core" "2.0.1"
+ "@opentelemetry/semantic-conventions" "^1.29.0"
"@opentelemetry/sdk-trace-base@^2.0.0":
version "2.0.0"
@@ -6150,22 +6135,17 @@
"@opentelemetry/resources" "2.0.0"
"@opentelemetry/semantic-conventions" "^1.29.0"
-"@opentelemetry/semantic-conventions@1.28.0":
- version "1.28.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz#337fb2bca0453d0726696e745f50064411f646d6"
- integrity sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==
-
-"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.34.0":
+"@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.33.1", "@opentelemetry/semantic-conventions@^1.34.0":
version "1.34.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.34.0.tgz#8b6a46681b38a4d5947214033ac48128328c1738"
integrity sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==
-"@opentelemetry/sql-common@^0.40.1":
- version "0.40.1"
- resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz#93fbc48d8017449f5b3c3274f2268a08af2b83b6"
- integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==
+"@opentelemetry/sql-common@^0.41.0":
+ version "0.41.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.41.0.tgz#7ddef1ea7fb6338dcca8a9d2485c7dfd53c076b4"
+ integrity sha512-pmzXctVbEERbqSfiAgdes9Y63xjoOyXcD7B6IXBkVb+vbM7M9U98mn33nGXxPf4dfYR0M+vhcKRZmbSJ7HfqFA==
dependencies:
- "@opentelemetry/core" "^1.1.0"
+ "@opentelemetry/core" "^2.0.0"
"@parcel/watcher-android-arm64@2.5.1":
version "2.5.1"
@@ -8003,10 +7983,10 @@
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
-"@types/aws-lambda@8.10.147", "@types/aws-lambda@^8.10.62":
- version "8.10.147"
- resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.147.tgz#dc5c89aa32f47a9b35e52c32630545c83afa6f2f"
- integrity sha512-nD0Z9fNIZcxYX5Mai2CTmFD7wX7UldCkW2ezCF8D1T5hdiLsnTWDGRpfRYntU6VjTdLQjOvyszru7I1c1oCQew==
+"@types/aws-lambda@8.10.150", "@types/aws-lambda@^8.10.62":
+ version "8.10.150"
+ resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.150.tgz#4998b238750ec389a326a7cdb625808834036bd3"
+ integrity sha512-AX+AbjH/rH5ezX1fbK8onC/a+HyQHo7QGmvoxAE42n22OsciAxvZoZNEr22tbXs8WfP1nIsBjKDpgPm3HjOZbA==
"@types/babel__core@^7.20.1", "@types/babel__core@^7.20.4":
version "7.20.5"
@@ -8584,10 +8564,10 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.32.tgz#f6cd08939ae3ad886fcc92ef7f0109dacddf61ab"
integrity sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==
-"@types/mysql@2.15.26", "@types/mysql@^2.15.21":
- version "2.15.26"
- resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.26.tgz#f0de1484b9e2354d587e7d2bd17a873cc8300836"
- integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==
+"@types/mysql@2.15.27", "@types/mysql@^2.15.21":
+ version "2.15.27"
+ resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.27.tgz#fb13b0e8614d39d42f40f381217ec3215915f1e9"
+ integrity sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==
dependencies:
"@types/node" "*"
@@ -8671,19 +8651,10 @@
dependencies:
"@types/pg" "*"
-"@types/pg@*", "@types/pg@^8.6.5":
- version "8.10.2"
- resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.2.tgz#7814d1ca02c8071f4d0864c1b17c589b061dba43"
- integrity sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==
- dependencies:
- "@types/node" "*"
- pg-protocol "*"
- pg-types "^4.0.1"
-
-"@types/pg@8.6.1":
- version "8.6.1"
- resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9"
- integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==
+"@types/pg@*", "@types/pg@8.15.4", "@types/pg@^8.6.5":
+ version "8.15.4"
+ resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.15.4.tgz#419f791c6fac8e0bed66dd8f514b60f8ba8db46d"
+ integrity sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==
dependencies:
"@types/node" "*"
pg-protocol "*"
@@ -23687,7 +23658,7 @@ object.values@^1.1.1, object.values@^1.1.6:
define-properties "^1.1.4"
es-abstract "^1.20.4"
-obuf@^1.0.0, obuf@^1.1.2, obuf@~1.1.2:
+obuf@^1.0.0, obuf@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
@@ -24533,11 +24504,6 @@ pg-int8@1.0.1:
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==
-pg-numeric@1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a"
- integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==
-
pg-pool@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.0.tgz#134b0213755c5e7135152976488aa7cd7ee1268d"
@@ -24559,19 +24525,6 @@ pg-types@2.2.0, pg-types@^2.2.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
-pg-types@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542"
- integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==
- dependencies:
- pg-int8 "1.0.1"
- pg-numeric "1.0.2"
- postgres-array "~3.0.1"
- postgres-bytea "~3.0.0"
- postgres-date "~2.0.1"
- postgres-interval "^3.0.0"
- postgres-range "^1.1.1"
-
pg@8.16.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.16.0.tgz#40b08eedb5eb1834252cf3e3629503e32e6c6c04"
@@ -25378,33 +25331,16 @@ postgres-array@~2.0.0:
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==
-postgres-array@~3.0.1:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98"
- integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==
-
postgres-bytea@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=
-postgres-bytea@~3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089"
- integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==
- dependencies:
- obuf "~1.1.2"
-
postgres-date@~1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==
-postgres-date@~2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457"
- integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==
-
postgres-interval@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
@@ -25412,16 +25348,6 @@ postgres-interval@^1.1.0:
dependencies:
xtend "^4.0.0"
-postgres-interval@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a"
- integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==
-
-postgres-range@^1.1.1:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76"
- integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==
-
postgres@^3.4.7:
version "3.4.7"
resolved "https://registry.yarnpkg.com/postgres/-/postgres-3.4.7.tgz#122f460a808fe300cae53f592108b9906e625345"
From 5c2d4473a89700d7273e2267c00b20e4feaa17f0 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 10:04:44 +0200
Subject: [PATCH 05/86] chore: Add migration guide for v10 and move v8->v9
guide to docs (#17070)
Co-authored-by: Charly Gomez
---
MIGRATION.md | 520 +-----------------------------------
docs/migration/v8-to-v9.md | 534 +++++++++++++++++++++++++++++++++++++
2 files changed, 547 insertions(+), 507 deletions(-)
create mode 100644 docs/migration/v8-to-v9.md
diff --git a/MIGRATION.md b/MIGRATION.md
index ac2a46a8d50e..947eb822348e 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -5,532 +5,38 @@ These docs walk through how to migrate our JavaScript SDKs through different maj
- Upgrading from [SDK 4.x to 5.x/6.x](./docs/migration/v4-to-v5_v6.md)
- Upgrading from [SDK 6.x to 7.x](./docs/migration/v6-to-v7.md)
- Upgrading from [SDK 7.x to 8.x](./docs/migration/v7-to-v8.md)
-- Upgrading from [SDK 8.x to 9.x](#upgrading-from-8x-to-9x)
+- Upgrading from [SDK 8.x to 9.x](./docs/migration/v8-to-v9.md)
+- Upgrading from [SDK 9.x to 10.x](#upgrading-from-9x-to-10x)
-# Deprecations in 9.x
+# Upgrading from 9.x to 10.x
-## Deprecated `@sentry/core` SDK internal `logger` export
+Version 10 of the Sentry JavaScript SDK primarily focuses on upgrading underlying OpenTelemetry dependencies to v2 with minimal breaking changes.
-The internal SDK `logger` export from `@sentry/core` has been deprecated in favor of the `debug` export. `debug` only exposes `log`, `warn`, and `error` methods but is otherwise identical to `logger`. Note that this deprecation does not affect the `logger` export from other packages (like `@sentry/browser` or `@sentry/node`) which is used for Sentry Logging.
-
-```js
-import { logger, debug } from '@sentry/core';
-
-// before
-logger.info('This is an info message');
-
-// after
-debug.log('This is an info message');
-```
-
-# Upgrading from 8.x to 9.x
-
-Version 9 of the Sentry JavaScript SDK primarily introduces API cleanup and version support changes.
-This update contains behavioral changes that will not be caught by type checkers, linters, or tests, so we recommend carefully reading through the entire migration guide instead of relying on automatic tooling.
-
-Version 9 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or higher (unchanged from v8).
+Version 10 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or higher (unchanged from v9).
Lower versions may continue to work, but may not support all features.
## 1. Version Support Changes:
-Version 9 of the Sentry SDK has new compatibility ranges for runtimes and frameworks.
-
-### General Runtime Support Changes
-
-**ECMAScript Version:** All the JavaScript code in the Sentry SDK packages may now contain ECMAScript 2020 features.
-This includes features like Nullish Coalescing (`??`), Optional Chaining (`?.`), `String.matchAll()`, Logical Assignment Operators (`&&=`, `||=`, `??=`), and `Promise.allSettled()`.
-
-If you observe failures due to syntax or features listed above, it may indicate that your current runtime does not support ES2020.
-If your runtime does not support ES2020, we recommend transpiling the SDK using Babel or similar tooling.
-
-**Node.js:** The minimum supported Node.js version is **18.0.0** (Released Apr 19, 2022), except for ESM-only SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/sveltekit`) which require Node.js version **18.19.1** (Released Feb 14, 2024) or higher.
+Version 10 of the Sentry SDK has new compatibility ranges for runtimes and frameworks.
-**Browsers:** Due to SDK code now including ES2020 features, the minimum supported browser list now looks as follows:
-
-- Chrome 80 (Released Feb 5, 2020)
-- Edge 80 (Released Feb 7, 2020)
-- Safari 14, iOS Safari 14.4 (Released Sep 16, 2020)
-- Firefox 74 (Released Mar 10, 2020)
-- Opera 67 (Released Mar 12, 2020)
-- Samsung Internet 13.0 (Released Nov 20, 2020)
-
-If you need to support older browsers, we recommend transpiling your code using SWC, Babel or similar tooling.
-
-**Deno:** The minimum supported Deno version is now **2.0.0**.
-
-### Framework and Library Support Changes
-
-Support for the following frameworks and library versions are dropped:
-
-- **Remix**: Version `1.x`
-- **TanStack Router**: Version `1.63.0` and lower (relevant when using `tanstackRouterBrowserTracingIntegration`)
-- **SvelteKit**: Version `1.x`
-- **Ember.js**: Version `3.x` and lower (minimum supported version is `4.x`)
-- **Prisma**: Version `5.x`
-
-### TypeScript Version Policy
-
-In preparation for v2 of the OpenTelemetry SDK, the minimum required TypeScript version is increased to version `5.0.4`.
+### `@sentry/node` / All SDKs running in Node.js
-Additionally, like the OpenTelemetry SDK, the Sentry JavaScript SDK will follow [DefinitelyType's version support policy](https://github.com/DefinitelyTyped/DefinitelyTyped#support-window) which has a support time frame of 2 years for any released version of TypeScript.
+All OpenTelemetry dependencies have been bumped to 2.x.x / 0.20x.x respectively and all OpenTelemetry instrumentations have been upgraded to their latest version.
-Older TypeScript versions _may_ continue to be compatible, but no guarantees apply.
+If you cannot run with OpenTelmetry v2 versions, consider either staying on Version 9 of our SDKs or using `@sentry/node-core` instead which ships with widened OpenTelemetry peer dependencies.
### AWS Lambda Layer Changes
-A new AWS Lambda Layer for version 9 will be published as `SentryNodeServerlessSDKv9`.
+A new AWS Lambda Layer for version 10 will be published as `SentryNodeServerlessSDKv10`.
The ARN will be published in the [Sentry docs](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/cjs-layer/) once available.
-The previous `SentryNodeServerlessSDK` layer will not receive new updates anymore.
-
-Updates and fixes for version 8 will be published as `SentryNodeServerlessSDKv8`.
-The ARN will be published in the [Sentry docs](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/cjs-layer/) once available.
-
-## 2. Behavior Changes
-
-### `@sentry/core` / All SDKs
-
-- Dropping spans in the `beforeSendSpan` hook is no longer possible.
- This means you can no longer return `null` from the `beforeSendSpan` hook.
- This hook is intended to be used to add additional data to spans or remove unwanted attributes (for example for PII stripping).
- To control which spans are recorded, we recommend configuring [integrations](https://docs.sentry.io/platforms/javascript/configuration/integrations/) instead.
-
-- The `beforeSendSpan` hook now receives the root span as well as the child spans.
- We recommend checking your `beforeSendSpan` to account for this change.
-
-- The `request` property on the `samplingContext` argument passed to the `tracesSampler` and `profilesSampler` options has been removed.
- `samplingContext.normalizedRequest` can be used instead.
- Note that the type of `normalizedRequest` differs from `request`.
-
-- The `startSpan` behavior was changed if you pass a custom `scope`:
- While in v8, the passed scope was set active directly on the passed scope, in v9, the scope is cloned. This behavior change does not apply to `@sentry/node` where the scope was already cloned.
- This change was made to ensure that the span only remains active within the callback and to align behavior between `@sentry/node` and all other SDKs.
- As a result of the change, span hierarchy should be more accurate.
- However, modifying the scope (for example, setting tags) within the `startSpan` callback behaves a bit differently now.
-
- ```js
- startSpan({ name: 'example', scope: customScope }, () => {
- getCurrentScope().setTag('tag-a', 'a'); // this tag will only remain within the callback
- // set the tag directly on customScope in addition, if you want to to persist the tag outside of the callback
- customScope.setTag('tag-a', 'a');
- });
- ```
-
-- Passing `undefined` as a `tracesSampleRate` option value will now be treated the same as if the attribute was not defined at all.
- In previous versions, it was checked whether the `tracesSampleRate` property existed in the SDK options to decide if trace data should be propagated for tracing.
- Consequentially, this sometimes caused the SDK to propagate negative sampling decisions when `tracesSampleRate: undefined` was passed.
- This is no longer the case and sampling decisions will be deferred to downstream SDKs for distributed tracing.
- This is more of a bugfix rather than a breaking change, however, depending on the setup of your SDKs, an increase in sampled traces may be observed.
-
-- If you use the optional `captureConsoleIntegration` and set `attachStackTrace: true` in your `Sentry.init` call, console messages will no longer be marked as unhandled (`handled: false`) but as handled (`handled: true`).
- If you want to keep sending them as unhandled, configure the `handled` option when adding the integration:
-
- ```js
- Sentry.init({
- integrations: [Sentry.captureConsoleIntegration({ handled: false })],
- attachStackTrace: true,
- });
- ```
-
-### `@sentry/browser` / All SDKs running in the browser
-
-- The SDK no longer instructs the Sentry backend to automatically infer IP addresses by default.
- Depending on the version of the Sentry backend (self-hosted), this may lead to IP addresses no longer showing up in Sentry, and events being grouped to "anonymous users".
- At the time of writing, the Sentry SaaS solution will still continue to infer IP addresses, but this will change in the near future.
- Set `sendDefaultPii: true` in `Sentry.init()` to instruct the Sentry backend to always infer IP addresses.
-
-### `@sentry/node` / All SDKs running in Node.js
-
-- The `tracesSampler` hook will no longer be called for _every_ span.
- Root spans may however have incoming trace data from a different service, for example when using distributed tracing.
-
-- The `requestDataIntegration` will no longer automatically set the user from `request.user` when `express` is used.
- Starting in v9, you'll need to manually call `Sentry.setUser()` e.g. in a middleware to set the user on Sentry events.
-
-- The `processThreadBreadcrumbIntegration` was renamed to `childProcessIntegration`.
+Updates and fixes for version 9 will be published as `SentryNodeServerlessSDKv9`.
-- The `childProcessIntegration`'s (previously `processThreadBreadcrumbIntegration`) `name` value has been changed from `"ProcessAndThreadBreadcrumbs"` to `"ChildProcess"`.
- Any filtering logic for registered integrations should be updated to account for the changed name.
-
-- The `vercelAIIntegration`'s `name` value has been changed from `"vercelAI"` to `"VercelAI"` (capitalized).
- Any filtering logic for registered integrations should be updated to account for the changed name.
-
-- The Prisma integration no longer supports Prisma v5 and supports Prisma v6 by default. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration.
-
- For performance instrumentation using other/older Prisma versions:
-
- 1. Install the `@prisma/instrumentation` package with the desired version.
- 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the `prismaInstrumentation` option of this integration:
-
- ```js
- import { PrismaInstrumentation } from '@prisma/instrumentation';
- Sentry.init({
- integrations: [
- prismaIntegration({
- // Override the default instrumentation that Sentry uses
- prismaInstrumentation: new PrismaInstrumentation(),
- }),
- ],
- });
- ```
-
- The passed instrumentation instance will override the default instrumentation instance the integration would use, while the `prismaIntegration` will still ensure data compatibility for the various Prisma versions.
-
- 1. Depending on your Prisma version (prior to Prisma version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema:
-
- ```
- generator client {
- provider = "prisma-client-js"
- previewFeatures = ["tracing"]
- }
- ```
-
-- When `skipOpenTelemetrySetup: true` is configured, `httpIntegration({ spans: false })` will be configured by default.
- You no longer have to specify this manually.
- With this change, no spans are emitted once `skipOpenTelemetrySetup: true` is configured, without any further configuration being needed.
-
-### All Meta-Framework SDKs (`@sentry/nextjs`, `@sentry/nuxt`, `@sentry/sveltekit`, `@sentry/astro`, `@sentry/solidstart`)
-
-- SDKs no longer transform user-provided values for source map generation in build configurations (like Vite config, Rollup config, or `next.config.js`).
-
- If source maps are explicitly disabled, the SDK will not enable them. If source maps are explicitly enabled, the SDK will not change how they are emitted. **However,** the SDK will also _not_ delete source maps after uploading them. If source map generation is not configured, the SDK will turn it on and delete them after the upload.
-
- To customize which files are deleted after upload, define the `filesToDeleteAfterUpload` array with globs.
-
-### `@sentry/react`
-
-- The `componentStack` field in the `ErrorBoundary` component is now typed as `string` instead of `string | null | undefined` for the `onError` and `onReset` lifecycle methods. This more closely matches the actual behavior of React, which always returns a `string` whenever a component stack is available.
-
- In the `onUnmount` lifecycle method, the `componentStack` field is now typed as `string | null`. The `componentStack` is `null` when no error has been thrown at time of unmount.
-
-### `@sentry/nextjs`
-
-- By default, client-side source maps will now be automatically deleted after being uploaded to Sentry during the build.
- You can opt out of this behavior by explicitly setting `sourcemaps.deleteSourcemapsAfterUpload` to `false` in your Sentry config.
-
-- The Sentry Next.js SDK will no longer use the Next.js Build ID as fallback identifier for releases.
- The SDK will continue to attempt to read CI-provider-specific environment variables and the current git SHA to automatically determine a release name.
- If you examine that you no longer see releases created in Sentry, it is recommended to manually provide a release name to `withSentryConfig` via the `release.name` option.
-
- This behavior was changed because the Next.js Build ID is non-deterministic, causing build artifacts to be non-deterministic, because the release name is injected into client bundles.
-
-- Source maps are now automatically enabled for both client and server builds unless explicitly disabled via `sourcemaps.disable`.
- Client builds use `hidden-source-map` while server builds use `source-map` as their webpack `devtool` setting unless any other value than `false` or `undefined` has been assigned already.
-
-- The `sentry` property on the Next.js config object has officially been discontinued.
- Pass options to `withSentryConfig` directly.
-
-## 3. Package Removals
-
-The `@sentry/utils` package will no longer be published.
-
-The `@sentry/types` package will continue to be published, however, it is deprecated and its API will not be extended.
-It will not be published as part of future major versions.
-
-All exports and APIs of `@sentry/utils` and `@sentry/types` (except for the ones that are explicitly called out in this migration guide to be removed) have been moved into the `@sentry/core` package.
-
-## 4. Removed APIs
+## 2. Removed APIs
### `@sentry/core` / All SDKs
-- **The metrics API has been removed from the SDK.**
-
- The Sentry metrics beta has ended and the metrics API has been removed from the SDK. Learn more in the Sentry [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th).
-
-- The `transactionContext` property on the `samplingContext` argument passed to the `tracesSampler` and `profilesSampler` options has been removed.
- All object attributes are available in the top-level of `samplingContext`:
-
- ```diff
- Sentry.init({
- // Custom traces sampler
- tracesSampler: samplingContext => {
- - if (samplingContext.transactionContext.name === '/health-check') {
- + if (samplingContext.name === '/health-check') {
- return 0;
- } else {
- return 0.5;
- }
- },
-
- // Custom profiles sampler
- profilesSampler: samplingContext => {
- - if (samplingContext.transactionContext.name === '/health-check') {
- + if (samplingContext.name === '/health-check') {
- return 0;
- } else {
- return 0.5;
- }
- },
- })
- ```
-
-- The `enableTracing` option was removed.
- Instead, set `tracesSampleRate: 1` or `tracesSampleRate: 0`.
-
-- The `autoSessionTracking` option was removed.
-
- To enable session tracking, ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. (both are added by default)
-
- To disable session tracking, remove the `browserSessionIntegration` in browser environments, or in server environments configure the `httpIntegration` with the `trackIncomingRequestsAsSessions` option set to `false`.
- Additionally, in Node.js environments, a session was automatically created for every node process when `autoSessionTracking` was set to `true`. This behavior has been replaced by the `processSessionIntegration` which is configured by default.
-
-- The `getCurrentHub()`, `Hub` and `getCurrentHubShim()` APIs have been removed. They were on compatibility life support since the release of v8 and have now been fully removed from the SDK.
-
-- The `addOpenTelemetryInstrumentation` method has been removed. Use the `openTelemetryInstrumentations` option in `Sentry.init()` or your custom Sentry Client instead.
-
- ```js
- import * as Sentry from '@sentry/node';
-
- // before
- Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation());
-
- // after
- Sentry.init({
- openTelemetryInstrumentations: [new GenericPoolInstrumentation()],
- });
- ```
-
-- The `debugIntegration` has been removed. To log outgoing events, use [Hook Options](https://docs.sentry.io/platforms/javascript/configuration/options/#hooks) (`beforeSend`, `beforeSendTransaction`, ...).
-
-- The `sessionTimingIntegration` has been removed. To capture session durations alongside events, use [Context](https://docs.sentry.io/platforms/javascript/enriching-events/context/) (`Sentry.setContext()`).
-
-### Server-side SDKs (`@sentry/node` and all dependents)
-
-- The `addOpenTelemetryInstrumentation` method was removed.
- Use the `openTelemetryInstrumentations` option in `Sentry.init()` or your custom Sentry Client instead.
-
-- `registerEsmLoaderHooks` now only accepts `true | false | undefined`.
- The SDK will default to wrapping modules that are used as part of OpenTelemetry Instrumentation.
-
-- The `nestIntegration` was removed.
- Use the NestJS SDK (`@sentry/nestjs`) instead.
-
-- The `setupNestErrorHandler` was removed.
- Use the NestJS SDK (`@sentry/nestjs`) instead.
-
-### `@sentry/browser`
-
-- The `captureUserFeedback` method has been removed.
- Use the `captureFeedback` method instead and update the `comments` field to `message`.
-
-### `@sentry/nextjs`
-
-- The `hideSourceMaps` option was removed without replacements.
- The SDK emits hidden sourcemaps by default.
-
-### `@sentry/solidstart`
-
-- The `sentrySolidStartVite` plugin is no longer exported. Instead, wrap the SolidStart config with `withSentry` and
- provide Sentry options as the second parameter.
-
- ```ts
- // app.config.ts
- import { defineConfig } from '@solidjs/start/config';
- import { withSentry } from '@sentry/solidstart';
-
- export default defineConfig(
- withSentry(
- {
- /* SolidStart config */
- },
- {
- /* Sentry build-time config (like project and org) */
- },
- ),
- );
- ```
-
-### `@sentry/nestjs`
-
-- Removed `@WithSentry` decorator.
- Use the `@SentryExceptionCaptured` decorator as a drop-in replacement.
-
-- Removed `SentryService`.
-
- - If you are using `@sentry/nestjs` you can safely remove any references to the `SentryService`.
- - If you are using another package migrate to `@sentry/nestjs` and remove the `SentryService` afterward.
-
-- Removed `SentryTracingInterceptor`.
-
- - If you are using `@sentry/nestjs` you can safely remove any references to the `SentryTracingInterceptor`.
- - If you are using another package migrate to `@sentry/nestjs` and remove the `SentryTracingInterceptor` afterward.
-
-- Removed `SentryGlobalGenericFilter`.
- Use the `SentryGlobalFilter` as a drop-in replacement.
-
-- Removed `SentryGlobalGraphQLFilter`.
- Use the `SentryGlobalFilter` as a drop-in replacement.
-
-### `@sentry/react`
-
-- The `wrapUseRoutes` method has been removed.
- Depending on what version of react router you are using, use the `wrapUseRoutesV6` or `wrapUseRoutesV7` methods instead.
-
-- The `wrapCreateBrowserRouter` method has been removed.
- Depending on what version of react router you are using, use the `wrapCreateBrowserRouterV6` or `wrapCreateBrowserRouterV7` methods instead.
-
-### `@sentry/vue`
-
-- The options `tracingOptions`, `trackComponents`, `timeout`, `hooks` have been removed everywhere except in the `tracingOptions` option of `vueIntegration()`.
-
- These options should now be configured as follows:
-
- ```js
- import * as Sentry from '@sentry/vue';
-
- Sentry.init({
- integrations: [
- Sentry.vueIntegration({
- tracingOptions: {
- trackComponents: true,
- timeout: 1000,
- hooks: ['mount', 'update', 'unmount'],
- },
- }),
- ],
- });
- ```
-
-- The option `logErrors` in the `vueIntegration` has been removed. The Sentry Vue error handler will always propagate the error to a user-defined error handler or re-throw the error (which will log the error without modifying).
-
-- The option `stateTransformer` in `createSentryPiniaPlugin()` now receives the full state from all stores as its parameter.
- The top-level keys of the state object are the store IDs.
-
-### `@sentry/nuxt`
-
-- The `tracingOptions` option in `Sentry.init()` was removed in favor of passing the `vueIntegration()` to `Sentry.init({ integrations: [...] })` and setting `tracingOptions` there.
-
-- The option `stateTransformer` in the `piniaIntegration` now receives the full state from all stores as its parameter.
- The top-level keys of the state object are the store IDs.
-
-### `@sentry/vue` and `@sentry/nuxt`
-
-- When component tracking is enabled, "update" spans are no longer created by default.
-
- Add an `"update"` item to the `tracingOptions.hooks` option via the `vueIntegration()` to restore this behavior.
-
- ```ts
- Sentry.init({
- integrations: [
- Sentry.vueIntegration({
- tracingOptions: {
- trackComponents: true,
- hooks: [
- 'mount',
- 'update', // add this line to re-enable update spans
- 'unmount',
- ],
- },
- }),
- ],
- });
- ```
-
-### `@sentry/remix`
-
-- The `autoInstrumentRemix` option was removed.
- The SDK now always behaves as if the option were set to `true`.
-
-### `@sentry/sveltekit`
-
-- The `fetchProxyScriptNonce` option in `sentryHandle()` was removed due to security concerns. If you previously specified this option for your CSP policy, specify a [script hash](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#configure-csp-for-client-side-fetch-instrumentation) in your CSP config or [disable](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#disable-client-side-fetch-proxy-script) the injection of the script entirely.
-
-### `@sentry/core`
-
-- A `sampleRand` field on `PropagationContext` is now required. This is relevant if you used `scope.setPropagationContext(...)`
-
-- The `DEFAULT_USER_INCLUDES` constant has been removed. There is no replacement.
-
-- The `BAGGAGE_HEADER_NAME` export has been removed. Use a `"baggage"` string constant directly instead.
-
-- The `extractRequestData` method has been removed. Manually extract relevant data of request objects instead.
-
-- The `addRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`.
-
-- The `addNormalizedRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`.
-
-- The `generatePropagationContext()` method was removed.
- Use `generateTraceId()` directly.
-
-- The `spanId` field on `propagationContext` was removed.
- It was replaced with an **optional** field `propagationSpanId` having the same semantics but only being defined when a unit of execution should be associated with a particular span ID.
-
-- The `initSessionFlusher` method on the `ServerRuntimeClient` was removed without replacements.
- Any mechanisms creating sessions will flush themselves.
-
-- The `IntegrationClass` type was removed.
- Instead, use `Integration` or `IntegrationFn`.
-
-- The following exports have been removed without replacement:
-
- - `getNumberOfUrlSegments`
- - `validSeverityLevels`
- - `makeFifoCache`
- - `arrayify`
- - `flatten`
- - `urlEncode`
- - `getDomElement`
- - `memoBuilder`
- - `extractPathForTransaction`
- - `_browserPerformanceTimeOriginMode`
- - `addTracingHeadersToFetchRequest`
- - `SessionFlusher`
-
-- The following types have been removed without replacement:
-
- - `Request`
- `RequestEventData`
- - `TransactionNamingScheme`
- - `RequestDataIntegrationOptions`
- - `SessionFlusherLike`
- - `RequestSession`
- - `RequestSessionStatus`
-
-### `@sentry/opentelemetry`
-
-- Removed `getPropagationContextFromSpan` without replacement.
-- Removed `generateSpanContextForPropagationContext` without replacement.
-
-#### Other/Internal Changes
-
-The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior.
-
-- `client._prepareEvent()` now requires both `currentScope` and `isolationScope` to be passed as arguments.
-- `client.recordDroppedEvent()` no longer accepts an `event` as third argument.
- The event was no longer used for some time, instead you can (optionally) pass a count of dropped events as third argument.
-
-## 5. Build Changes
-
-- The CJS code for the SDK now only contains compatibility statements for CJS/ESM in modules that have default exports:
-
- ```js
- Object.defineProperty(exports, '__esModule', { value: true });
- ```
-
- Let us know if this is causing issues in your setup by opening an issue on GitHub.
-
-- `@sentry/deno` is no longer published on the `deno.land` registry so you'll need to import the SDK from npm:
-
- ```javascript
- import * as Sentry from 'npm:@sentry/deno';
-
- Sentry.init({
- dsn: '__DSN__',
- // ...
- });
- ```
-
-## 6. Type Changes
-
-- `Scope` usages now always expect `Scope` instances
-
-- `Client` usages now always expect `BaseClient` instances.
- The abstract `Client` class was removed.
- Client classes now have to extend from `BaseClient`.
-
-These changes should not affect most users unless you relied on passing things with a similar shape to internal methods.
-
-In v8, interfaces have been exported from `@sentry/types`, while implementations have been exported from other packages.
+- TODO: fill in removed APIs
## No Version Support Timeline
diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md
new file mode 100644
index 000000000000..46223d201928
--- /dev/null
+++ b/docs/migration/v8-to-v9.md
@@ -0,0 +1,534 @@
+# Deprecations in 9.x
+
+## Deprecated `@sentry/core` SDK internal `logger` export
+
+The internal SDK `logger` export from `@sentry/core` has been deprecated in favor of the `debug` export. `debug` only exposes `log`, `warn`, and `error` methods but is otherwise identical to `logger`. Note that this deprecation does not affect the `logger` export from other packages (like `@sentry/browser` or `@sentry/node`) which is used for Sentry Logging.
+
+```js
+import { logger, debug } from '@sentry/core';
+
+// before
+logger.info('This is an info message');
+
+// after
+debug.log('This is an info message');
+```
+
+# Upgrading from 8.x to 9.x
+
+Version 9 of the Sentry JavaScript SDK primarily introduces API cleanup and version support changes.
+This update contains behavioral changes that will not be caught by type checkers, linters, or tests, so we recommend carefully reading through the entire migration guide instead of relying on automatic tooling.
+
+Version 9 of the SDK is compatible with Sentry self-hosted versions 24.4.2 or higher (unchanged from v8).
+Lower versions may continue to work, but may not support all features.
+
+## 1. Version Support Changes:
+
+Version 9 of the Sentry SDK has new compatibility ranges for runtimes and frameworks.
+
+### General Runtime Support Changes
+
+**ECMAScript Version:** All the JavaScript code in the Sentry SDK packages may now contain ECMAScript 2020 features.
+This includes features like Nullish Coalescing (`??`), Optional Chaining (`?.`), `String.matchAll()`, Logical Assignment Operators (`&&=`, `||=`, `??=`), and `Promise.allSettled()`.
+
+If you observe failures due to syntax or features listed above, it may indicate that your current runtime does not support ES2020.
+If your runtime does not support ES2020, we recommend transpiling the SDK using Babel or similar tooling.
+
+**Node.js:** The minimum supported Node.js version is **18.0.0** (Released Apr 19, 2022), except for ESM-only SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/sveltekit`) which require Node.js version **18.19.1** (Released Feb 14, 2024) or higher.
+
+**Browsers:** Due to SDK code now including ES2020 features, the minimum supported browser list now looks as follows:
+
+- Chrome 80 (Released Feb 5, 2020)
+- Edge 80 (Released Feb 7, 2020)
+- Safari 14, iOS Safari 14.4 (Released Sep 16, 2020)
+- Firefox 74 (Released Mar 10, 2020)
+- Opera 67 (Released Mar 12, 2020)
+- Samsung Internet 13.0 (Released Nov 20, 2020)
+
+If you need to support older browsers, we recommend transpiling your code using SWC, Babel or similar tooling.
+
+**Deno:** The minimum supported Deno version is now **2.0.0**.
+
+### Framework and Library Support Changes
+
+Support for the following frameworks and library versions are dropped:
+
+- **Remix**: Version `1.x`
+- **TanStack Router**: Version `1.63.0` and lower (relevant when using `tanstackRouterBrowserTracingIntegration`)
+- **SvelteKit**: Version `1.x`
+- **Ember.js**: Version `3.x` and lower (minimum supported version is `4.x`)
+- **Prisma**: Version `5.x`
+
+### TypeScript Version Policy
+
+In preparation for v2 of the OpenTelemetry SDK, the minimum required TypeScript version is increased to version `5.0.4`.
+
+Additionally, like the OpenTelemetry SDK, the Sentry JavaScript SDK will follow [DefinitelyType's version support policy](https://github.com/DefinitelyTyped/DefinitelyTyped#support-window) which has a support time frame of 2 years for any released version of TypeScript.
+
+Older TypeScript versions _may_ continue to be compatible, but no guarantees apply.
+
+### AWS Lambda Layer Changes
+
+A new AWS Lambda Layer for version 9 will be published as `SentryNodeServerlessSDKv9`.
+The ARN will be published in the [Sentry docs](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/cjs-layer/) once available.
+
+The previous `SentryNodeServerlessSDK` layer will not receive new updates anymore.
+
+Updates and fixes for version 8 will be published as `SentryNodeServerlessSDKv8`.
+The ARN will be published in the [Sentry docs](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/cjs-layer/) once available.
+
+## 2. Behavior Changes
+
+### `@sentry/core` / All SDKs
+
+- Dropping spans in the `beforeSendSpan` hook is no longer possible.
+ This means you can no longer return `null` from the `beforeSendSpan` hook.
+ This hook is intended to be used to add additional data to spans or remove unwanted attributes (for example for PII stripping).
+ To control which spans are recorded, we recommend configuring [integrations](https://docs.sentry.io/platforms/javascript/configuration/integrations/) instead.
+
+- The `beforeSendSpan` hook now receives the root span as well as the child spans.
+ We recommend checking your `beforeSendSpan` to account for this change.
+
+- The `request` property on the `samplingContext` argument passed to the `tracesSampler` and `profilesSampler` options has been removed.
+ `samplingContext.normalizedRequest` can be used instead.
+ Note that the type of `normalizedRequest` differs from `request`.
+
+- The `startSpan` behavior was changed if you pass a custom `scope`:
+ While in v8, the passed scope was set active directly on the passed scope, in v9, the scope is cloned. This behavior change does not apply to `@sentry/node` where the scope was already cloned.
+ This change was made to ensure that the span only remains active within the callback and to align behavior between `@sentry/node` and all other SDKs.
+ As a result of the change, span hierarchy should be more accurate.
+ However, modifying the scope (for example, setting tags) within the `startSpan` callback behaves a bit differently now.
+
+ ```js
+ startSpan({ name: 'example', scope: customScope }, () => {
+ getCurrentScope().setTag('tag-a', 'a'); // this tag will only remain within the callback
+ // set the tag directly on customScope in addition, if you want to to persist the tag outside of the callback
+ customScope.setTag('tag-a', 'a');
+ });
+ ```
+
+- Passing `undefined` as a `tracesSampleRate` option value will now be treated the same as if the attribute was not defined at all.
+ In previous versions, it was checked whether the `tracesSampleRate` property existed in the SDK options to decide if trace data should be propagated for tracing.
+ Consequentially, this sometimes caused the SDK to propagate negative sampling decisions when `tracesSampleRate: undefined` was passed.
+ This is no longer the case and sampling decisions will be deferred to downstream SDKs for distributed tracing.
+ This is more of a bugfix rather than a breaking change, however, depending on the setup of your SDKs, an increase in sampled traces may be observed.
+
+- If you use the optional `captureConsoleIntegration` and set `attachStackTrace: true` in your `Sentry.init` call, console messages will no longer be marked as unhandled (`handled: false`) but as handled (`handled: true`).
+ If you want to keep sending them as unhandled, configure the `handled` option when adding the integration:
+
+ ```js
+ Sentry.init({
+ integrations: [Sentry.captureConsoleIntegration({ handled: false })],
+ attachStackTrace: true,
+ });
+ ```
+
+### `@sentry/browser` / All SDKs running in the browser
+
+- The SDK no longer instructs the Sentry backend to automatically infer IP addresses by default.
+ Depending on the version of the Sentry backend (self-hosted), this may lead to IP addresses no longer showing up in Sentry, and events being grouped to "anonymous users".
+ At the time of writing, the Sentry SaaS solution will still continue to infer IP addresses, but this will change in the near future.
+ Set `sendDefaultPii: true` in `Sentry.init()` to instruct the Sentry backend to always infer IP addresses.
+
+### `@sentry/node` / All SDKs running in Node.js
+
+- The `tracesSampler` hook will no longer be called for _every_ span.
+ Root spans may however have incoming trace data from a different service, for example when using distributed tracing.
+
+- The `requestDataIntegration` will no longer automatically set the user from `request.user` when `express` is used.
+ Starting in v9, you'll need to manually call `Sentry.setUser()` e.g. in a middleware to set the user on Sentry events.
+
+- The `processThreadBreadcrumbIntegration` was renamed to `childProcessIntegration`.
+
+- The `childProcessIntegration`'s (previously `processThreadBreadcrumbIntegration`) `name` value has been changed from `"ProcessAndThreadBreadcrumbs"` to `"ChildProcess"`.
+ Any filtering logic for registered integrations should be updated to account for the changed name.
+
+- The `vercelAIIntegration`'s `name` value has been changed from `"vercelAI"` to `"VercelAI"` (capitalized).
+ Any filtering logic for registered integrations should be updated to account for the changed name.
+
+- The Prisma integration no longer supports Prisma v5 and supports Prisma v6 by default. As per Prisma v6, the `previewFeatures = ["tracing"]` client generator option in your Prisma Schema is no longer required to use tracing with the Prisma integration.
+
+ For performance instrumentation using other/older Prisma versions:
+
+ 1. Install the `@prisma/instrumentation` package with the desired version.
+ 1. Pass a `new PrismaInstrumentation()` instance as exported from `@prisma/instrumentation` to the `prismaInstrumentation` option of this integration:
+
+ ```js
+ import { PrismaInstrumentation } from '@prisma/instrumentation';
+ Sentry.init({
+ integrations: [
+ prismaIntegration({
+ // Override the default instrumentation that Sentry uses
+ prismaInstrumentation: new PrismaInstrumentation(),
+ }),
+ ],
+ });
+ ```
+
+ The passed instrumentation instance will override the default instrumentation instance the integration would use, while the `prismaIntegration` will still ensure data compatibility for the various Prisma versions.
+
+ 1. Depending on your Prisma version (prior to Prisma version 6), add `previewFeatures = ["tracing"]` to the client generator block of your Prisma schema:
+
+ ```
+ generator client {
+ provider = "prisma-client-js"
+ previewFeatures = ["tracing"]
+ }
+ ```
+
+- When `skipOpenTelemetrySetup: true` is configured, `httpIntegration({ spans: false })` will be configured by default.
+ You no longer have to specify this manually.
+ With this change, no spans are emitted once `skipOpenTelemetrySetup: true` is configured, without any further configuration being needed.
+
+### All Meta-Framework SDKs (`@sentry/nextjs`, `@sentry/nuxt`, `@sentry/sveltekit`, `@sentry/astro`, `@sentry/solidstart`)
+
+- SDKs no longer transform user-provided values for source map generation in build configurations (like Vite config, Rollup config, or `next.config.js`).
+
+ If source maps are explicitly disabled, the SDK will not enable them. If source maps are explicitly enabled, the SDK will not change how they are emitted. **However,** the SDK will also _not_ delete source maps after uploading them. If source map generation is not configured, the SDK will turn it on and delete them after the upload.
+
+ To customize which files are deleted after upload, define the `filesToDeleteAfterUpload` array with globs.
+
+### `@sentry/react`
+
+- The `componentStack` field in the `ErrorBoundary` component is now typed as `string` instead of `string | null | undefined` for the `onError` and `onReset` lifecycle methods. This more closely matches the actual behavior of React, which always returns a `string` whenever a component stack is available.
+
+ In the `onUnmount` lifecycle method, the `componentStack` field is now typed as `string | null`. The `componentStack` is `null` when no error has been thrown at time of unmount.
+
+### `@sentry/nextjs`
+
+- By default, client-side source maps will now be automatically deleted after being uploaded to Sentry during the build.
+ You can opt out of this behavior by explicitly setting `sourcemaps.deleteSourcemapsAfterUpload` to `false` in your Sentry config.
+
+- The Sentry Next.js SDK will no longer use the Next.js Build ID as fallback identifier for releases.
+ The SDK will continue to attempt to read CI-provider-specific environment variables and the current git SHA to automatically determine a release name.
+ If you examine that you no longer see releases created in Sentry, it is recommended to manually provide a release name to `withSentryConfig` via the `release.name` option.
+
+ This behavior was changed because the Next.js Build ID is non-deterministic, causing build artifacts to be non-deterministic, because the release name is injected into client bundles.
+
+- Source maps are now automatically enabled for both client and server builds unless explicitly disabled via `sourcemaps.disable`.
+ Client builds use `hidden-source-map` while server builds use `source-map` as their webpack `devtool` setting unless any other value than `false` or `undefined` has been assigned already.
+
+- The `sentry` property on the Next.js config object has officially been discontinued.
+ Pass options to `withSentryConfig` directly.
+
+## 3. Package Removals
+
+The `@sentry/utils` package will no longer be published.
+
+The `@sentry/types` package will continue to be published, however, it is deprecated and its API will not be extended.
+It will not be published as part of future major versions.
+
+All exports and APIs of `@sentry/utils` and `@sentry/types` (except for the ones that are explicitly called out in this migration guide to be removed) have been moved into the `@sentry/core` package.
+
+## 4. Removed APIs
+
+### `@sentry/core` / All SDKs
+
+- **The metrics API has been removed from the SDK.**
+
+ The Sentry metrics beta has ended and the metrics API has been removed from the SDK. Learn more in the Sentry [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th).
+
+- The `transactionContext` property on the `samplingContext` argument passed to the `tracesSampler` and `profilesSampler` options has been removed.
+ All object attributes are available in the top-level of `samplingContext`:
+
+ ```diff
+ Sentry.init({
+ // Custom traces sampler
+ tracesSampler: samplingContext => {
+ - if (samplingContext.transactionContext.name === '/health-check') {
+ + if (samplingContext.name === '/health-check') {
+ return 0;
+ } else {
+ return 0.5;
+ }
+ },
+
+ // Custom profiles sampler
+ profilesSampler: samplingContext => {
+ - if (samplingContext.transactionContext.name === '/health-check') {
+ + if (samplingContext.name === '/health-check') {
+ return 0;
+ } else {
+ return 0.5;
+ }
+ },
+ })
+ ```
+
+- The `enableTracing` option was removed.
+ Instead, set `tracesSampleRate: 1` or `tracesSampleRate: 0`.
+
+- The `autoSessionTracking` option was removed.
+
+ To enable session tracking, ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. (both are added by default)
+
+ To disable session tracking, remove the `browserSessionIntegration` in browser environments, or in server environments configure the `httpIntegration` with the `trackIncomingRequestsAsSessions` option set to `false`.
+ Additionally, in Node.js environments, a session was automatically created for every node process when `autoSessionTracking` was set to `true`. This behavior has been replaced by the `processSessionIntegration` which is configured by default.
+
+- The `getCurrentHub()`, `Hub` and `getCurrentHubShim()` APIs have been removed. They were on compatibility life support since the release of v8 and have now been fully removed from the SDK.
+
+- The `addOpenTelemetryInstrumentation` method has been removed. Use the `openTelemetryInstrumentations` option in `Sentry.init()` or your custom Sentry Client instead.
+
+ ```js
+ import * as Sentry from '@sentry/node';
+
+ // before
+ Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation());
+
+ // after
+ Sentry.init({
+ openTelemetryInstrumentations: [new GenericPoolInstrumentation()],
+ });
+ ```
+
+- The `debugIntegration` has been removed. To log outgoing events, use [Hook Options](https://docs.sentry.io/platforms/javascript/configuration/options/#hooks) (`beforeSend`, `beforeSendTransaction`, ...).
+
+- The `sessionTimingIntegration` has been removed. To capture session durations alongside events, use [Context](https://docs.sentry.io/platforms/javascript/enriching-events/context/) (`Sentry.setContext()`).
+
+### Server-side SDKs (`@sentry/node` and all dependents)
+
+- The `addOpenTelemetryInstrumentation` method was removed.
+ Use the `openTelemetryInstrumentations` option in `Sentry.init()` or your custom Sentry Client instead.
+
+- `registerEsmLoaderHooks` now only accepts `true | false | undefined`.
+ The SDK will default to wrapping modules that are used as part of OpenTelemetry Instrumentation.
+
+- The `nestIntegration` was removed.
+ Use the NestJS SDK (`@sentry/nestjs`) instead.
+
+- The `setupNestErrorHandler` was removed.
+ Use the NestJS SDK (`@sentry/nestjs`) instead.
+
+### `@sentry/browser`
+
+- The `captureUserFeedback` method has been removed.
+ Use the `captureFeedback` method instead and update the `comments` field to `message`.
+
+### `@sentry/nextjs`
+
+- The `hideSourceMaps` option was removed without replacements.
+ The SDK emits hidden sourcemaps by default.
+
+### `@sentry/solidstart`
+
+- The `sentrySolidStartVite` plugin is no longer exported. Instead, wrap the SolidStart config with `withSentry` and
+ provide Sentry options as the second parameter.
+
+ ```ts
+ // app.config.ts
+ import { defineConfig } from '@solidjs/start/config';
+ import { withSentry } from '@sentry/solidstart';
+
+ export default defineConfig(
+ withSentry(
+ {
+ /* SolidStart config */
+ },
+ {
+ /* Sentry build-time config (like project and org) */
+ },
+ ),
+ );
+ ```
+
+### `@sentry/nestjs`
+
+- Removed `@WithSentry` decorator.
+ Use the `@SentryExceptionCaptured` decorator as a drop-in replacement.
+
+- Removed `SentryService`.
+
+ - If you are using `@sentry/nestjs` you can safely remove any references to the `SentryService`.
+ - If you are using another package migrate to `@sentry/nestjs` and remove the `SentryService` afterward.
+
+- Removed `SentryTracingInterceptor`.
+
+ - If you are using `@sentry/nestjs` you can safely remove any references to the `SentryTracingInterceptor`.
+ - If you are using another package migrate to `@sentry/nestjs` and remove the `SentryTracingInterceptor` afterward.
+
+- Removed `SentryGlobalGenericFilter`.
+ Use the `SentryGlobalFilter` as a drop-in replacement.
+
+- Removed `SentryGlobalGraphQLFilter`.
+ Use the `SentryGlobalFilter` as a drop-in replacement.
+
+### `@sentry/react`
+
+- The `wrapUseRoutes` method has been removed.
+ Depending on what version of react router you are using, use the `wrapUseRoutesV6` or `wrapUseRoutesV7` methods instead.
+
+- The `wrapCreateBrowserRouter` method has been removed.
+ Depending on what version of react router you are using, use the `wrapCreateBrowserRouterV6` or `wrapCreateBrowserRouterV7` methods instead.
+
+### `@sentry/vue`
+
+- The options `tracingOptions`, `trackComponents`, `timeout`, `hooks` have been removed everywhere except in the `tracingOptions` option of `vueIntegration()`.
+
+ These options should now be configured as follows:
+
+ ```js
+ import * as Sentry from '@sentry/vue';
+
+ Sentry.init({
+ integrations: [
+ Sentry.vueIntegration({
+ tracingOptions: {
+ trackComponents: true,
+ timeout: 1000,
+ hooks: ['mount', 'update', 'unmount'],
+ },
+ }),
+ ],
+ });
+ ```
+
+- The option `logErrors` in the `vueIntegration` has been removed. The Sentry Vue error handler will always propagate the error to a user-defined error handler or re-throw the error (which will log the error without modifying).
+
+- The option `stateTransformer` in `createSentryPiniaPlugin()` now receives the full state from all stores as its parameter.
+ The top-level keys of the state object are the store IDs.
+
+### `@sentry/nuxt`
+
+- The `tracingOptions` option in `Sentry.init()` was removed in favor of passing the `vueIntegration()` to `Sentry.init({ integrations: [...] })` and setting `tracingOptions` there.
+
+- The option `stateTransformer` in the `piniaIntegration` now receives the full state from all stores as its parameter.
+ The top-level keys of the state object are the store IDs.
+
+### `@sentry/vue` and `@sentry/nuxt`
+
+- When component tracking is enabled, "update" spans are no longer created by default.
+
+ Add an `"update"` item to the `tracingOptions.hooks` option via the `vueIntegration()` to restore this behavior.
+
+ ```ts
+ Sentry.init({
+ integrations: [
+ Sentry.vueIntegration({
+ tracingOptions: {
+ trackComponents: true,
+ hooks: [
+ 'mount',
+ 'update', // add this line to re-enable update spans
+ 'unmount',
+ ],
+ },
+ }),
+ ],
+ });
+ ```
+
+### `@sentry/remix`
+
+- The `autoInstrumentRemix` option was removed.
+ The SDK now always behaves as if the option were set to `true`.
+
+### `@sentry/sveltekit`
+
+- The `fetchProxyScriptNonce` option in `sentryHandle()` was removed due to security concerns. If you previously specified this option for your CSP policy, specify a [script hash](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#configure-csp-for-client-side-fetch-instrumentation) in your CSP config or [disable](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#disable-client-side-fetch-proxy-script) the injection of the script entirely.
+
+### `@sentry/core`
+
+- A `sampleRand` field on `PropagationContext` is now required. This is relevant if you used `scope.setPropagationContext(...)`
+
+- The `DEFAULT_USER_INCLUDES` constant has been removed. There is no replacement.
+
+- The `BAGGAGE_HEADER_NAME` export has been removed. Use a `"baggage"` string constant directly instead.
+
+- The `extractRequestData` method has been removed. Manually extract relevant data of request objects instead.
+
+- The `addRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`.
+
+- The `addNormalizedRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`.
+
+- The `generatePropagationContext()` method was removed.
+ Use `generateTraceId()` directly.
+
+- The `spanId` field on `propagationContext` was removed.
+ It was replaced with an **optional** field `propagationSpanId` having the same semantics but only being defined when a unit of execution should be associated with a particular span ID.
+
+- The `initSessionFlusher` method on the `ServerRuntimeClient` was removed without replacements.
+ Any mechanisms creating sessions will flush themselves.
+
+- The `IntegrationClass` type was removed.
+ Instead, use `Integration` or `IntegrationFn`.
+
+- The following exports have been removed without replacement:
+
+ - `getNumberOfUrlSegments`
+ - `validSeverityLevels`
+ - `makeFifoCache`
+ - `arrayify`
+ - `flatten`
+ - `urlEncode`
+ - `getDomElement`
+ - `memoBuilder`
+ - `extractPathForTransaction`
+ - `_browserPerformanceTimeOriginMode`
+ - `addTracingHeadersToFetchRequest`
+ - `SessionFlusher`
+
+- The following types have been removed without replacement:
+
+ - `Request`
+ `RequestEventData`
+ - `TransactionNamingScheme`
+ - `RequestDataIntegrationOptions`
+ - `SessionFlusherLike`
+ - `RequestSession`
+ - `RequestSessionStatus`
+
+### `@sentry/opentelemetry`
+
+- Removed `getPropagationContextFromSpan` without replacement.
+- Removed `generateSpanContextForPropagationContext` without replacement.
+
+#### Other/Internal Changes
+
+The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior.
+
+- `client._prepareEvent()` now requires both `currentScope` and `isolationScope` to be passed as arguments.
+- `client.recordDroppedEvent()` no longer accepts an `event` as third argument.
+ The event was no longer used for some time, instead you can (optionally) pass a count of dropped events as third argument.
+
+## 5. Build Changes
+
+- The CJS code for the SDK now only contains compatibility statements for CJS/ESM in modules that have default exports:
+
+ ```js
+ Object.defineProperty(exports, '__esModule', { value: true });
+ ```
+
+ Let us know if this is causing issues in your setup by opening an issue on GitHub.
+
+- `@sentry/deno` is no longer published on the `deno.land` registry so you'll need to import the SDK from npm:
+
+ ```javascript
+ import * as Sentry from 'npm:@sentry/deno';
+
+ Sentry.init({
+ dsn: '__DSN__',
+ // ...
+ });
+ ```
+
+## 6. Type Changes
+
+- `Scope` usages now always expect `Scope` instances
+
+- `Client` usages now always expect `BaseClient` instances.
+ The abstract `Client` class was removed.
+ Client classes now have to extend from `BaseClient`.
+
+These changes should not affect most users unless you relied on passing things with a similar shape to internal methods.
+
+In v8, interfaces have been exported from `@sentry/types`, while implementations have been exported from other packages.
+
+## No Version Support Timeline
+
+Version support timelines are stressful for everybody using the SDK, so we won't be defining one.
+Instead, we will be applying bug fixes and features to older versions as long as there is demand.
+
+Additionally, we hold ourselves accountable to any security issues, meaning that if any vulnerabilities are found, we will in almost all cases backport them.
+
+Note, that it is decided on a case-per-case basis, what gets backported or not.
+If you need a fix or feature in a previous version of the SDK, please reach out via a GitHub Issue.
From 68117608773a61dca1e8a02103b5f382fa2eb718 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 10:08:22 +0200
Subject: [PATCH 06/86] feat(aws): Add `SentryNodeServerlessSDKv10` v10 AWS
Lambda Layer (#17069)
---
.craft.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.craft.yml b/.craft.yml
index f9c56070d9b2..c5055acf329c 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -146,16 +146,13 @@ targets:
# AWS Lambda Layer target
- name: aws-lambda-layer
includeNames: /^sentry-node-serverless-\d+.\d+.\d+(-(beta|alpha|rc)\.\d+)?\.zip$/
- layerName: SentryNodeServerlessSDKv9
+ layerName: SentryNodeServerlessSDKv10
compatibleRuntimes:
- name: node
versions:
- - nodejs10.x
- - nodejs12.x
- - nodejs14.x
- - nodejs16.x
- nodejs18.x
- nodejs20.x
+ - nodejs22.x
license: MIT
# CDN Bundle Target
From 904932f7cf7558e5254a1bf4556ca2dee23c2c74 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 10:48:28 +0200
Subject: [PATCH 07/86] feat(core)!: Remove `BaseClient` (#17071)
`BaseClient` was deprecated in v9, use `Client` as a direct replacement.
---
MIGRATION.md | 2 +-
packages/core/src/client.ts | 12 ---------
packages/core/src/index.ts | 6 +----
packages/core/test/lib/client.test.ts | 35 ++++++++++-----------------
4 files changed, 15 insertions(+), 40 deletions(-)
diff --git a/MIGRATION.md b/MIGRATION.md
index 947eb822348e..52fa7ecaab98 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -36,7 +36,7 @@ Updates and fixes for version 9 will be published as `SentryNodeServerlessSDKv9`
### `@sentry/core` / All SDKs
-- TODO: fill in removed APIs
+- `BaseClient` was removed, use `Client` as a direct replacement.
## No Version Support Timeline
diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts
index ab450788459c..8b03392106d9 100644
--- a/packages/core/src/client.ts
+++ b/packages/core/src/client.ts
@@ -1246,18 +1246,6 @@ export abstract class Client {
): PromiseLike;
}
-/**
- * @deprecated Use `Client` instead. This alias may be removed in a future major version.
- */
-// TODO(v10): Remove
-export type BaseClient = Client;
-
-/**
- * @deprecated Use `Client` instead. This alias may be removed in a future major version.
- */
-// TODO(v10): Remove
-export const BaseClient = Client;
-
/**
* Verifies that return value of configured `beforeSend` or `beforeSendTransaction` is of expected type, and returns the value if so.
*/
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 3e020fc6aa77..1fc4b47c798a 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -49,11 +49,7 @@ export { Scope } from './scope';
export type { CaptureContext, ScopeContext, ScopeData } from './scope';
export { notifyEventProcessors } from './eventProcessors';
export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api';
-export {
- Client,
- // eslint-disable-next-line deprecation/deprecation
- BaseClient,
-} from './client';
+export { Client } from './client';
export { ServerRuntimeClient } from './server-runtime-client';
export { initAndBind, setCurrentClient } from './sdk';
export { createTransport } from './transports/base';
diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts
index 662d9b6adf67..4b1c7a378114 100644
--- a/packages/core/test/lib/client.test.ts
+++ b/packages/core/test/lib/client.test.ts
@@ -11,7 +11,6 @@ import {
SyncPromise,
withMonitor,
} from '../../src';
-import type { BaseClient, Client } from '../../src/client';
import * as integrationModule from '../../src/integration';
import type { Envelope } from '../../src/types-hoist/envelope';
import type { ErrorEvent, Event, TransactionEvent } from '../../src/types-hoist/event';
@@ -2107,30 +2106,22 @@ describe('Client', () => {
describe('hooks', () => {
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
- // Make sure types work for both Client & BaseClient
- const scenarios = [
- // eslint-disable-next-line deprecation/deprecation
- ['BaseClient', new TestClient(options) as BaseClient],
- ['Client', new TestClient(options) as Client],
- ] as const;
-
- describe.each(scenarios)('with client %s', (_, client) => {
- it('should call a beforeEnvelope hook', () => {
- expect.assertions(1);
-
- const mockEnvelope = [
- {
- event_id: '12345',
- },
- {},
- ] as Envelope;
+ it('should call a beforeEnvelope hook', () => {
+ const client = new TestClient(options);
+ expect.assertions(1);
- client.on('beforeEnvelope', envelope => {
- expect(envelope).toEqual(mockEnvelope);
- });
+ const mockEnvelope = [
+ {
+ event_id: '12345',
+ },
+ {},
+ ] as Envelope;
- client.emit('beforeEnvelope', mockEnvelope);
+ client.on('beforeEnvelope', envelope => {
+ expect(envelope).toEqual(mockEnvelope);
});
+
+ client.emit('beforeEnvelope', mockEnvelope);
});
});
From b5b0b326fef6f829d078b7422cb96dc681de5a82 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 11:50:14 +0200
Subject: [PATCH 08/86] feat(core)!: Remove `hasTracingEnabled` (#17072)
`hasTracingEnabled` was deprecated in v9, use `hasSpansEnabled` as a
direct replacement.
---
MIGRATION.md | 6 +++++-
packages/core/src/index.ts | 2 --
packages/core/src/utils/hasSpansEnabled.ts | 8 --------
...{hasTracingEnabled.test.ts => hasSpansEnabled.test.ts} | 4 +---
4 files changed, 6 insertions(+), 14 deletions(-)
rename packages/core/test/lib/utils/{hasTracingEnabled.test.ts => hasSpansEnabled.test.ts} (83%)
diff --git a/MIGRATION.md b/MIGRATION.md
index 52fa7ecaab98..9e922dcd731b 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -6,7 +6,10 @@ These docs walk through how to migrate our JavaScript SDKs through different maj
- Upgrading from [SDK 6.x to 7.x](./docs/migration/v6-to-v7.md)
- Upgrading from [SDK 7.x to 8.x](./docs/migration/v7-to-v8.md)
- Upgrading from [SDK 8.x to 9.x](./docs/migration/v8-to-v9.md)
-- Upgrading from [SDK 9.x to 10.x](#upgrading-from-9x-to-10x)
+ <<<<<<< HEAD
+- # Upgrading from [SDK 9.x to 10.x](#upgrading-from-9x-to-10x)
+- Upgrading from [SDK 9.x to 10.x](#upgrading-from-8x-to-9x)
+ > > > > > > > f726d5ab5 (chore: Add migration guide for v10 and move v8->v9 guide to docs)
# Upgrading from 9.x to 10.x
@@ -37,6 +40,7 @@ Updates and fixes for version 9 will be published as `SentryNodeServerlessSDKv9`
### `@sentry/core` / All SDKs
- `BaseClient` was removed, use `Client` as a direct replacement.
+- `hasTracingEnabled` was removed, use `hasSpansEnabled` as a direct replacement.
## No Version Support Timeline
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 1fc4b47c798a..9acecf51d170 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -59,8 +59,6 @@ export { getIntegrationsToSetup, addIntegration, defineIntegration } from './int
export { applyScopeDataToEvent, mergeScopeData } from './utils/applyScopeDataToEvent';
export { prepareEvent } from './utils/prepareEvent';
export { createCheckInEnvelope } from './checkin';
-// eslint-disable-next-line deprecation/deprecation
-export { hasTracingEnabled } from './utils/hasSpansEnabled';
export { hasSpansEnabled } from './utils/hasSpansEnabled';
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
export { handleCallbackErrors } from './utils/handleCallbackErrors';
diff --git a/packages/core/src/utils/hasSpansEnabled.ts b/packages/core/src/utils/hasSpansEnabled.ts
index dc59ec770271..26a71eb7ca0b 100644
--- a/packages/core/src/utils/hasSpansEnabled.ts
+++ b/packages/core/src/utils/hasSpansEnabled.ts
@@ -34,11 +34,3 @@ export function hasSpansEnabled(
(options.tracesSampleRate != null || !!options.tracesSampler)
);
}
-
-/**
- * @see JSDoc of `hasSpansEnabled`
- * @deprecated Use `hasSpansEnabled` instead, which is a more accurately named version of this function.
- * This function will be removed in the next major version of the SDK.
- */
-// TODO(v10): Remove this export
-export const hasTracingEnabled = hasSpansEnabled;
diff --git a/packages/core/test/lib/utils/hasTracingEnabled.test.ts b/packages/core/test/lib/utils/hasSpansEnabled.test.ts
similarity index 83%
rename from packages/core/test/lib/utils/hasTracingEnabled.test.ts
rename to packages/core/test/lib/utils/hasSpansEnabled.test.ts
index f4c7a51307c2..ed2ae841fc92 100644
--- a/packages/core/test/lib/utils/hasTracingEnabled.test.ts
+++ b/packages/core/test/lib/utils/hasSpansEnabled.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
-import { hasSpansEnabled, hasTracingEnabled } from '../../../src';
+import { hasSpansEnabled } from '../../../src';
describe('hasSpansEnabled', () => {
const tracesSampler = () => 1;
@@ -15,7 +15,5 @@ describe('hasSpansEnabled', () => {
['With tracesSampler and tracesSampleRate', { tracesSampler, tracesSampleRate }, true],
])('%s', (_: string, input: Parameters[0], output: ReturnType) => {
expect(hasSpansEnabled(input)).toBe(output);
- // eslint-disable-next-line deprecation/deprecation
- expect(hasTracingEnabled(input)).toBe(output);
});
});
From 8cc324c9d1e2588c876993f9ee2034792231b5c4 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 11:51:07 +0200
Subject: [PATCH 09/86] feat(nestjs): Switch to OTel core instrumentation
(#17068)
OTel's instrumentation supports v11 since
[0.46.0](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/de2260023b12c395e6f85592d29113b718a30f63/packages/instrumentation-nestjs-core/CHANGELOG.md?plain=1#L31-L36).
---
.../nestjs-fastify/tests/transactions.test.ts | 2 +-
packages/nestjs/src/decorators.ts | 8 +
packages/nestjs/src/integrations/nest.ts | 4 +-
.../sentry-nest-core-instrumentation.ts | 307 ------------------
.../sentry-nest-event-instrumentation.ts | 4 +
5 files changed, 15 insertions(+), 310 deletions(-)
delete mode 100644 packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts
diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
index 2b8c555d7322..ac4c8bdea83e 100644
--- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
+++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts
@@ -97,7 +97,7 @@ test('Sends an API route transaction', async ({ baseURL }) => {
component: '@nestjs/core',
'nestjs.version': expect.any(String),
'nestjs.type': 'request_context',
- 'http.request.method': 'GET',
+ 'http.method': 'GET',
'http.url': '/test-transaction',
'http.route': '/test-transaction',
'nestjs.controller': 'AppController',
diff --git a/packages/nestjs/src/decorators.ts b/packages/nestjs/src/decorators.ts
index 9ac7315dabd8..7ac4941be877 100644
--- a/packages/nestjs/src/decorators.ts
+++ b/packages/nestjs/src/decorators.ts
@@ -110,10 +110,18 @@ function copyFunctionNameAndMetadata({
});
// copy metadata
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
if (typeof Reflect !== 'undefined' && typeof Reflect.getMetadataKeys === 'function') {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
const originalMetaData = Reflect.getMetadataKeys(originalMethod);
for (const key of originalMetaData) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
const value = Reflect.getMetadata(key, originalMethod);
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
Reflect.defineMetadata(key, value, descriptor.value);
}
}
diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts
index 4cc68c720541..53086b7da302 100644
--- a/packages/nestjs/src/integrations/nest.ts
+++ b/packages/nestjs/src/integrations/nest.ts
@@ -1,13 +1,13 @@
+import { NestInstrumentation as NestInstrumentationCore } from '@opentelemetry/instrumentation-nestjs-core';
import { defineIntegration } from '@sentry/core';
import { generateInstrumentOnce } from '@sentry/node';
-import { NestInstrumentation } from './sentry-nest-core-instrumentation';
import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation';
import { SentryNestInstrumentation } from './sentry-nest-instrumentation';
const INTEGRATION_NAME = 'Nest';
const instrumentNestCore = generateInstrumentOnce('Nest-Core', () => {
- return new NestInstrumentation();
+ return new NestInstrumentationCore();
});
const instrumentNestCommon = generateInstrumentOnce('Nest-Common', () => {
diff --git a/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts
deleted file mode 100644
index aec664633342..000000000000
--- a/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * This file is based on code from the OpenTelemetry Authors
- * Source: https://github.com/open-telemetry/opentelemetry-js-contrib
- *
- * Modified for immediate requirements while maintaining compliance
- * with the original Apache 2.0 license terms.
- *
- * Original License:
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import type { Controller } from '@nestjs/common/interfaces';
-import type { NestFactory } from '@nestjs/core/nest-factory.js';
-import type { RouterExecutionContext } from '@nestjs/core/router/router-execution-context.js';
-import * as api from '@opentelemetry/api';
-import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
-import {
- InstrumentationBase,
- InstrumentationNodeModuleDefinition,
- InstrumentationNodeModuleFile,
- isWrapped,
-} from '@opentelemetry/instrumentation';
-import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions';
-import { SDK_VERSION } from '@sentry/core';
-
-const supportedVersions = ['>=4.0.0 <12'];
-const COMPONENT = '@nestjs/core';
-
-enum AttributeNames {
- VERSION = 'nestjs.version',
- TYPE = 'nestjs.type',
- MODULE = 'nestjs.module',
- CONTROLLER = 'nestjs.controller',
- CALLBACK = 'nestjs.callback',
- PIPES = 'nestjs.pipes',
- INTERCEPTORS = 'nestjs.interceptors',
- GUARDS = 'nestjs.guards',
-}
-
-export enum NestType {
- APP_CREATION = 'app_creation',
- REQUEST_CONTEXT = 'request_context',
- REQUEST_HANDLER = 'handler',
-}
-
-/**
- *
- */
-export class NestInstrumentation extends InstrumentationBase {
- public constructor(config: InstrumentationConfig = {}) {
- super('sentry-nestjs', SDK_VERSION, config);
- }
-
- /**
- *
- */
- public init(): InstrumentationNodeModuleDefinition {
- const module = new InstrumentationNodeModuleDefinition(COMPONENT, supportedVersions);
-
- module.files.push(
- this._getNestFactoryFileInstrumentation(supportedVersions),
- this._getRouterExecutionContextFileInstrumentation(supportedVersions),
- );
-
- return module;
- }
-
- /**
- *
- */
- private _getNestFactoryFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile {
- return new InstrumentationNodeModuleFile(
- '@nestjs/core/nest-factory.js',
- versions,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (NestFactoryStatic: any, moduleVersion?: string) => {
- this._ensureWrapped(
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- NestFactoryStatic.NestFactoryStatic.prototype,
- 'create',
- createWrapNestFactoryCreate(this.tracer, moduleVersion),
- );
- return NestFactoryStatic;
- },
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (NestFactoryStatic: any) => {
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- this._unwrap(NestFactoryStatic.NestFactoryStatic.prototype, 'create');
- },
- );
- }
-
- /**
- *
- */
- private _getRouterExecutionContextFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile {
- return new InstrumentationNodeModuleFile(
- '@nestjs/core/router/router-execution-context.js',
- versions,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (RouterExecutionContext: any, moduleVersion?: string) => {
- this._ensureWrapped(
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- RouterExecutionContext.RouterExecutionContext.prototype,
- 'create',
- createWrapCreateHandler(this.tracer, moduleVersion),
- );
- return RouterExecutionContext;
- },
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (RouterExecutionContext: any) => {
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- this._unwrap(RouterExecutionContext.RouterExecutionContext.prototype, 'create');
- },
- );
- }
-
- /**
- *
- */
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- private _ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any): void {
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- if (isWrapped(obj[methodName])) {
- this._unwrap(obj, methodName);
- }
- this._wrap(obj, methodName, wrapper);
- }
-}
-
-function createWrapNestFactoryCreate(tracer: api.Tracer, moduleVersion?: string) {
- return function wrapCreate(original: typeof NestFactory.create) {
- return function createWithTrace(
- this: typeof NestFactory,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- nestModule: any,
- /* serverOrOptions */
- ) {
- const span = tracer.startSpan('Create Nest App', {
- attributes: {
- component: COMPONENT,
- [AttributeNames.TYPE]: NestType.APP_CREATION,
- [AttributeNames.VERSION]: moduleVersion,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- [AttributeNames.MODULE]: nestModule.name,
- },
- });
- const spanContext = api.trace.setSpan(api.context.active(), span);
-
- return api.context.with(spanContext, async () => {
- try {
- // todo
- // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any
- return await original.apply(this, arguments as any);
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (e: any) {
- throw addError(span, e);
- } finally {
- span.end();
- }
- });
- };
- };
-}
-
-function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) {
- return function wrapCreateHandler(original: RouterExecutionContext['create']) {
- return function createHandlerWithTrace(
- this: RouterExecutionContext,
- instance: Controller,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- callback: (...args: any[]) => unknown,
- ) {
- // todo
- // eslint-disable-next-line prefer-rest-params
- arguments[1] = createWrapHandler(tracer, moduleVersion, callback);
- // todo
- // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any
- const handler = original.apply(this, arguments as any);
- const callbackName = callback.name;
- const instanceName =
- // todo
- // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
- instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance';
- const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName;
-
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
- return function (this: any, req: any, res: any, next: (...args: any[]) => unknown) {
- const span = tracer.startSpan(spanName, {
- attributes: {
- component: COMPONENT,
- [AttributeNames.VERSION]: moduleVersion,
- [AttributeNames.TYPE]: NestType.REQUEST_CONTEXT,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- [ATTR_HTTP_REQUEST_METHOD]: req.method,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, deprecation/deprecation
- [SEMATTRS_HTTP_URL]: req.originalUrl || req.url,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- [ATTR_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath,
- [AttributeNames.CONTROLLER]: instanceName,
- [AttributeNames.CALLBACK]: callbackName,
- },
- });
- const spanContext = api.trace.setSpan(api.context.active(), span);
-
- return api.context.with(spanContext, async () => {
- try {
- // todo
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, prefer-rest-params
- return await handler.apply(this, arguments as unknown);
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (e: any) {
- throw addError(span, e);
- } finally {
- span.end();
- }
- });
- };
- };
- };
-}
-
-function createWrapHandler(
- tracer: api.Tracer,
- moduleVersion: string | undefined,
- // todo
- // eslint-disable-next-line @typescript-eslint/ban-types
- handler: Function,
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
-): (this: RouterExecutionContext) => Promise {
- const spanName = handler.name || 'anonymous nest handler';
- const options = {
- attributes: {
- component: COMPONENT,
- [AttributeNames.VERSION]: moduleVersion,
- [AttributeNames.TYPE]: NestType.REQUEST_HANDLER,
- [AttributeNames.CALLBACK]: handler.name,
- },
- };
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const wrappedHandler = function (this: RouterExecutionContext): Promise {
- const span = tracer.startSpan(spanName, options);
- const spanContext = api.trace.setSpan(api.context.active(), span);
-
- return api.context.with(spanContext, async () => {
- try {
- // todo
- // eslint-disable-next-line prefer-rest-params
- return await handler.apply(this, arguments);
- // todo
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (e: any) {
- throw addError(span, e);
- } finally {
- span.end();
- }
- });
- };
-
- if (handler.name) {
- Object.defineProperty(wrappedHandler, 'name', { value: handler.name });
- }
-
- // Get the current metadata and set onto the wrapper to ensure other decorators ( ie: NestJS EventPattern / RolesGuard )
- // won't be affected by the use of this instrumentation
- Reflect.getMetadataKeys(handler).forEach(metadataKey => {
- Reflect.defineMetadata(metadataKey, Reflect.getMetadata(metadataKey, handler), wrappedHandler);
- });
- return wrappedHandler;
-}
-
-const addError = (span: api.Span, error: Error): Error => {
- span.recordException(error);
- span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
- return error;
-};
diff --git a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
index 85eaae360b1c..a572bb93a52f 100644
--- a/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
+++ b/packages/nestjs/src/integrations/sentry-nest-event-instrumentation.ts
@@ -83,7 +83,11 @@ export class SentryNestEventInstrumentation extends InstrumentationBase {
descriptor.value = async function (...args: unknown[]) {
// When multiple @OnEvent decorators are used on a single method, we need to get all event names
// from the reflector metadata as there is no information during execution which event triggered it
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
if (Reflect.getMetadataKeys(descriptor.value).includes('EVENT_LISTENER_METADATA')) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore - reflect-metadata of nestjs adds these methods to Reflect
const eventData = Reflect.getMetadata('EVENT_LISTENER_METADATA', descriptor.value);
if (Array.isArray(eventData)) {
eventName = eventData
From 74b680d8583af7385dc27507458c7755eed3baff Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Fri, 18 Jul 2025 12:27:58 +0200
Subject: [PATCH 10/86] test(core): Add explicit tests for `tracesSampler`
returning negative sampling decisions (#17055)
Add a couple of unit tests to explicitly test sampling
behaviour of the core SDK's sampling logic when users pass in a
`tracesSampler` function returning negative sampling values (i.e. `0`,
`false`).
---
packages/core/test/lib/tracing/trace.test.ts | 63 ++++++++++++++------
1 file changed, 45 insertions(+), 18 deletions(-)
diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts
index 31d04f0f971d..1b7de2ccf053 100644
--- a/packages/core/test/lib/tracing/trace.test.ts
+++ b/packages/core/test/lib/tracing/trace.test.ts
@@ -660,30 +660,57 @@ describe('startSpan', () => {
});
});
- it('samples with a tracesSampler', () => {
- const tracesSampler = vi.fn(() => {
+ describe('uses tracesSampler if defined', () => {
+ const tracesSampler = vi.fn<() => boolean | number>(() => {
return true;
});
- const options = getDefaultTestClientOptions({ tracesSampler });
- client = new TestClient(options);
- setCurrentClient(client);
- client.init();
+ it.each([true, 1])('returns a positive sampling decision if tracesSampler returns %s', tracesSamplerResult => {
+ tracesSampler.mockReturnValueOnce(tracesSamplerResult);
- startSpan({ name: 'outer', attributes: { test1: 'aa', test2: 'aa', test3: 'bb' } }, outerSpan => {
- expect(outerSpan).toBeDefined();
+ const options = getDefaultTestClientOptions({ tracesSampler });
+ client = new TestClient(options);
+ setCurrentClient(client);
+ client.init();
+
+ startSpan({ name: 'outer', attributes: { test1: 'aa', test2: 'aa', test3: 'bb' } }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ expect(spanIsSampled(outerSpan)).toBe(true);
+ });
+
+ expect(tracesSampler).toBeCalledTimes(1);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: undefined,
+ name: 'outer',
+ attributes: {
+ test1: 'aa',
+ test2: 'aa',
+ test3: 'bb',
+ },
+ inheritOrSampleWith: expect.any(Function),
+ });
});
- expect(tracesSampler).toBeCalledTimes(1);
- expect(tracesSampler).toHaveBeenLastCalledWith({
- parentSampled: undefined,
- name: 'outer',
- attributes: {
- test1: 'aa',
- test2: 'aa',
- test3: 'bb',
- },
- inheritOrSampleWith: expect.any(Function),
+ it.each([false, 0])('returns a negative sampling decision if tracesSampler returns %s', tracesSamplerResult => {
+ tracesSampler.mockReturnValueOnce(tracesSamplerResult);
+
+ const options = getDefaultTestClientOptions({ tracesSampler });
+ client = new TestClient(options);
+ setCurrentClient(client);
+ client.init();
+
+ startSpan({ name: 'outer' }, outerSpan => {
+ expect(outerSpan).toBeDefined();
+ expect(spanIsSampled(outerSpan)).toBe(false);
+ });
+
+ expect(tracesSampler).toBeCalledTimes(1);
+ expect(tracesSampler).toHaveBeenLastCalledWith({
+ parentSampled: undefined,
+ attributes: {},
+ name: 'outer',
+ inheritOrSampleWith: expect.any(Function),
+ });
});
});
From cb88e70dda708e905cf61cbca7881112ed1565f4 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Fri, 18 Jul 2025 07:13:37 -0400
Subject: [PATCH 11/86] feat(core)!: Remove deprecated logger (#17061)
BREAKING CHANGE
In https://github.com/getsentry/sentry-javascript/issues/16901 we
deprecated the `logger` export from `@sentry/core`.
This PR removes it for the v10 release.
---------
Co-authored-by: Andrei Borza
---
MIGRATION.md | 1 +
.../server/plugins/customNitroErrorHandler.ts | 8 +--
packages/core/src/carrier.ts | 4 --
packages/core/src/index.ts | 6 +-
packages/core/src/utils/debug-logger.ts | 60 -------------------
5 files changed, 7 insertions(+), 72 deletions(-)
diff --git a/MIGRATION.md b/MIGRATION.md
index 9e922dcd731b..d44e53a05f37 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -41,6 +41,7 @@ Updates and fixes for version 9 will be published as `SentryNodeServerlessSDKv9`
- `BaseClient` was removed, use `Client` as a direct replacement.
- `hasTracingEnabled` was removed, use `hasSpansEnabled` as a direct replacement.
+- `logger` and type `Logger` were removed, use `debug` and type `SentryDebugLogger` instead.
## No Version Support Timeline
diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts
index 9ca836610f2f..880b43061b93 100644
--- a/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts
+++ b/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts
@@ -1,4 +1,4 @@
-import { Context, GLOBAL_OBJ, flush, logger, vercelWaitUntil } from '@sentry/core';
+import { Context, GLOBAL_OBJ, flush, debug, vercelWaitUntil } from '@sentry/core';
import * as SentryNode from '@sentry/node';
import { H3Error } from 'h3';
import type { CapturedErrorContext } from 'nitropack';
@@ -74,10 +74,10 @@ async function flushWithTimeout(): Promise {
const isDebug = sentryClient ? sentryClient.getOptions().debug : false;
try {
- isDebug && logger.log('Flushing events...');
+ isDebug && debug.log('Flushing events...');
await flush(2000);
- isDebug && logger.log('Done flushing events');
+ isDebug && debug.log('Done flushing events');
} catch (e) {
- isDebug && logger.log('Error while flushing events:\n', e);
+ isDebug && debug.log('Error while flushing events:\n', e);
}
}
diff --git a/packages/core/src/carrier.ts b/packages/core/src/carrier.ts
index 9fde3f7b74f0..201e79cb4514 100644
--- a/packages/core/src/carrier.ts
+++ b/packages/core/src/carrier.ts
@@ -3,7 +3,6 @@ import type { AsyncContextStrategy } from './asyncContext/types';
import type { Client } from './client';
import type { Scope } from './scope';
import type { SerializedLog } from './types-hoist/log';
-import type { Logger } from './utils/debug-logger';
import { SDK_VERSION } from './utils/version';
import { GLOBAL_OBJ } from './utils/worldwide';
@@ -26,9 +25,6 @@ export interface SentryCarrier {
globalScope?: Scope;
defaultIsolationScope?: Scope;
defaultCurrentScope?: Scope;
- /** @deprecated Logger is no longer set. Instead, we keep enabled state in loggerSettings. */
- // eslint-disable-next-line deprecation/deprecation
- logger?: Logger;
loggerSettings?: { enabled: boolean };
/**
* A map of Sentry clients to their log buffers.
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 9acecf51d170..282e075e81ff 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -161,10 +161,8 @@ export {
isVueViewModel,
} from './utils/is';
export { isBrowser } from './utils/isBrowser';
-// eslint-disable-next-line deprecation/deprecation
-export { CONSOLE_LEVELS, consoleSandbox, debug, logger, originalConsoleMethods } from './utils/debug-logger';
-// eslint-disable-next-line deprecation/deprecation
-export type { Logger, SentryDebugLogger } from './utils/debug-logger';
+export { CONSOLE_LEVELS, consoleSandbox, debug, originalConsoleMethods } from './utils/debug-logger';
+export type { SentryDebugLogger } from './utils/debug-logger';
export {
addContextToFrame,
addExceptionMechanism,
diff --git a/packages/core/src/utils/debug-logger.ts b/packages/core/src/utils/debug-logger.ts
index 36e3169b1d52..6fc9c7a8c865 100644
--- a/packages/core/src/utils/debug-logger.ts
+++ b/packages/core/src/utils/debug-logger.ts
@@ -3,24 +3,6 @@ import { DEBUG_BUILD } from '../debug-build';
import type { ConsoleLevel } from '../types-hoist/instrument';
import { GLOBAL_OBJ } from './worldwide';
-/**
- * A Sentry Logger instance.
- *
- * @deprecated Use {@link debug} instead with the {@link SentryDebugLogger} type.
- */
-export interface Logger {
- disable(): void;
- enable(): void;
- isEnabled(): boolean;
- log(...args: Parameters): void;
- info(...args: Parameters): void;
- warn(...args: Parameters): void;
- error(...args: Parameters): void;
- debug(...args: Parameters): void;
- assert(...args: Parameters): void;
- trace(...args: Parameters): void;
-}
-
export interface SentryDebugLogger {
disable(): void;
enable(): void;
@@ -115,18 +97,6 @@ function error(...args: Parameters): void {
_maybeLog('error', ...args);
}
-function _debug(...args: Parameters): void {
- _maybeLog('debug', ...args);
-}
-
-function assert(...args: Parameters): void {
- _maybeLog('assert', ...args);
-}
-
-function trace(...args: Parameters): void {
- _maybeLog('trace', ...args);
-}
-
function _maybeLog(level: ConsoleLevel, ...args: Parameters<(typeof console)[typeof level]>): void {
if (!DEBUG_BUILD) {
return;
@@ -147,36 +117,6 @@ function _getLoggerSettings(): { enabled: boolean } {
return getGlobalSingleton('loggerSettings', () => ({ enabled: false }));
}
-/**
- * This is a logger singleton which either logs things or no-ops if logging is not enabled.
- * The logger is a singleton on the carrier, to ensure that a consistent logger is used throughout the SDK.
- *
- * @deprecated Use {@link debug} instead.
- */
-export const logger = {
- /** Enable logging. */
- enable,
- /** Disable logging. */
- disable,
- /** Check if logging is enabled. */
- isEnabled,
- /** Log a message. */
- log,
- /** Log level info */
- info,
- /** Log a warning. */
- warn,
- /** Log an error. */
- error,
- /** Log a debug message. */
- debug: _debug,
- /** Log an assertion. */
- assert,
- /** Log a trace. */
- trace,
- // eslint-disable-next-line deprecation/deprecation
-} satisfies Logger;
-
/**
* This is a logger singleton which either logs things or no-ops if logging is not enabled.
*/
From d059b06254251507792369171a7f7a3d5a1d7640 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 13:53:17 +0200
Subject: [PATCH 12/86] chore: Fix leftover migration guide merge conflict
(#17084)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
😅
---
MIGRATION.md | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/MIGRATION.md b/MIGRATION.md
index d44e53a05f37..d760698fb8eb 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -6,10 +6,7 @@ These docs walk through how to migrate our JavaScript SDKs through different maj
- Upgrading from [SDK 6.x to 7.x](./docs/migration/v6-to-v7.md)
- Upgrading from [SDK 7.x to 8.x](./docs/migration/v7-to-v8.md)
- Upgrading from [SDK 8.x to 9.x](./docs/migration/v8-to-v9.md)
- <<<<<<< HEAD
-- # Upgrading from [SDK 9.x to 10.x](#upgrading-from-9x-to-10x)
-- Upgrading from [SDK 9.x to 10.x](#upgrading-from-8x-to-9x)
- > > > > > > > f726d5ab5 (chore: Add migration guide for v10 and move v8->v9 guide to docs)
+- Upgrading from [SDK 9.x to 10.x](#upgrading-from-9x-to-10x)
# Upgrading from 9.x to 10.x
From 5eb036fc93edeb23ded14e3db1b93228b7925087 Mon Sep 17 00:00:00 2001
From: Martin Sonnberger
Date: Fri, 18 Jul 2025 16:19:29 +0200
Subject: [PATCH 13/86] feat(aws): Create unified lambda layer for ESM and CJS
(#17012)
This introduces a new AWS lambda layer that supports both ESM and CJS.
Instead of bundling the whole SDK, we install the local NPM package and
then prune all not strictly necessary files from `node_modules` by using
`@vercel/nft` to keep the layer size as small as possible.
closes #16876
closes #16883
closes #16886
closes #16879
---
.../aws-lambda-layer-esm/.npmrc | 2 +
.../aws-lambda-layer-esm/package.json | 23 ++++
.../aws-lambda-layer-esm/playwright.config.ts | 3 +
.../src/lambda-function.mjs | 21 ++++
.../aws-lambda-layer-esm/src/run-lambda.mjs | 8 ++
.../aws-lambda-layer-esm/src/run.mjs | 17 +++
.../start-event-proxy.mjs | 6 +
.../aws-lambda-layer-esm/tests/basic.test.ts | 72 ++++++++++++
dev-packages/rollup-utils/bundleHelpers.mjs | 27 -----
packages/aws-serverless/package.json | 6 +-
packages/aws-serverless/rollup.aws.config.mjs | 39 -------
.../scripts/buildLambdaLayer.ts | 103 +++++++++++++++---
packages/aws-serverless/tsconfig.json | 2 +-
packages/aws-serverless/tsconfig.types.json | 2 +-
14 files changed, 248 insertions(+), 83 deletions(-)
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs
create mode 100644 dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts
delete mode 100644 packages/aws-serverless/rollup.aws.config.mjs
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/.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/aws-lambda-layer-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
new file mode 100644
index 000000000000..7a25061dde1c
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "aws-lambda-layer-esm",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node src/run.mjs",
+ "test": "playwright test",
+ "clean": "npx rimraf node_modules pnpm-lock.yaml",
+ "test:build": "pnpm install",
+ "test:assert": "pnpm test"
+ },
+ "//": "Link from local Lambda layer build",
+ "dependencies": {
+ "@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless"
+ },
+ "devDependencies": {
+ "@sentry-internal/test-utils": "link:../../../test-utils",
+ "@playwright/test": "~1.53.2"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts
new file mode 100644
index 000000000000..174593c307df
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/playwright.config.ts
@@ -0,0 +1,3 @@
+import { getPlaywrightConfig } from '@sentry-internal/test-utils';
+
+export default getPlaywrightConfig();
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs
new file mode 100644
index 000000000000..a9cdd48c1197
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/lambda-function.mjs
@@ -0,0 +1,21 @@
+import * as Sentry from '@sentry/aws-serverless';
+
+import * as http from 'node:http';
+
+async function handle() {
+ await Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => {
+ await new Promise(resolve => {
+ http.get('http://example.com', res => {
+ res.on('data', d => {
+ process.stdout.write(d);
+ });
+
+ res.on('end', () => {
+ resolve();
+ });
+ });
+ });
+ });
+}
+
+export { handle };
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs
new file mode 100644
index 000000000000..c30903f9883d
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run-lambda.mjs
@@ -0,0 +1,8 @@
+import { handle } from './lambda-function.mjs';
+
+const event = {};
+const context = {
+ invokedFunctionArn: 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda',
+ functionName: 'my-lambda',
+};
+await handle(event, context);
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs
new file mode 100644
index 000000000000..4bcd5886a865
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/src/run.mjs
@@ -0,0 +1,17 @@
+import child_process from 'node:child_process';
+
+child_process.execSync('node ./src/run-lambda.mjs', {
+ stdio: 'inherit',
+ env: {
+ ...process.env,
+ // On AWS, LAMBDA_TASK_ROOT is usually /var/task but for testing, we set it to the CWD to correctly apply our handler
+ LAMBDA_TASK_ROOT: process.cwd(),
+ _HANDLER: 'src/lambda-function.handle',
+
+ NODE_OPTIONS: '--import @sentry/aws-serverless/awslambda-auto',
+ SENTRY_DSN: 'http://public@localhost:3031/1337',
+ SENTRY_TRACES_SAMPLE_RATE: '1.0',
+ SENTRY_DEBUG: 'true',
+ },
+ cwd: process.cwd(),
+});
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs
new file mode 100644
index 000000000000..03fc10269998
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/test-utils';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'aws-lambda-layer-esm',
+});
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts
new file mode 100644
index 000000000000..2e02fda6486e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/tests/basic.test.ts
@@ -0,0 +1,72 @@
+import * as child_process from 'child_process';
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/test-utils';
+
+test('Lambda layer SDK bundle sends events', async ({ request }) => {
+ const transactionEventPromise = waitForTransaction('aws-lambda-layer-esm', transactionEvent => {
+ return transactionEvent?.transaction === 'my-lambda';
+ });
+
+ // Waiting for 1s here because attaching the listener for events in `waitForTransaction` is not synchronous
+ // Since in this test, we don't start a browser via playwright, we don't have the usual delays (page.goto, etc)
+ // which are usually enough for us to never have noticed this race condition before.
+ // This is a workaround but probably sufficient as long as we only experience it in this test.
+ await new Promise(resolve =>
+ setTimeout(() => {
+ resolve();
+ }, 1000),
+ );
+
+ child_process.execSync('pnpm start', {
+ stdio: 'ignore',
+ });
+
+ const transactionEvent = await transactionEventPromise;
+
+ // shows the SDK sent a transaction
+ expect(transactionEvent.transaction).toEqual('my-lambda'); // name should be the function name
+ expect(transactionEvent.contexts?.trace).toEqual({
+ data: {
+ 'sentry.sample_rate': 1,
+ 'sentry.source': 'custom',
+ 'sentry.origin': 'auto.otel.aws-lambda',
+ 'sentry.op': 'function.aws.lambda',
+ 'cloud.account.id': '123453789012',
+ 'faas.id': 'arn:aws:lambda:us-east-1:123453789012:function:my-lambda',
+ 'faas.coldstart': true,
+ 'otel.kind': 'SERVER',
+ },
+ op: 'function.aws.lambda',
+ origin: 'auto.otel.aws-lambda',
+ span_id: expect.stringMatching(/[a-f0-9]{16}/),
+ status: 'ok',
+ trace_id: expect.stringMatching(/[a-f0-9]{32}/),
+ });
+
+ expect(transactionEvent.spans).toHaveLength(2);
+
+ // shows that the Otel Http instrumentation is working
+ expect(transactionEvent.spans).toContainEqual(
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'sentry.op': 'http.client',
+ 'sentry.origin': 'auto.http.otel.http',
+ url: 'http://example.com/',
+ }),
+ description: 'GET http://example.com/',
+ op: 'http.client',
+ }),
+ );
+
+ // shows that the manual span creation is working
+ expect(transactionEvent.spans).toContainEqual(
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'sentry.op': 'test',
+ 'sentry.origin': 'manual',
+ }),
+ description: 'manual-span',
+ op: 'test',
+ }),
+ );
+});
diff --git a/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs
index f80b0b7c2e50..1099cb6b6549 100644
--- a/dev-packages/rollup-utils/bundleHelpers.mjs
+++ b/dev-packages/rollup-utils/bundleHelpers.mjs
@@ -90,32 +90,6 @@ export function makeBaseBundleConfig(options) {
plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin],
};
- // used by `@sentry/aws-serverless`, when creating the lambda layer
- const awsLambdaBundleConfig = {
- output: {
- format: 'cjs',
- },
- plugins: [
- jsonPlugin,
- commonJSPlugin,
- // Temporary fix for the lambda layer SDK bundle.
- // This is necessary to apply to our lambda layer bundle because calling `new ImportInTheMiddle()` will throw an
- // that `ImportInTheMiddle` is not a constructor. Instead we modify the code to call `new ImportInTheMiddle.default()`
- // TODO: Remove this plugin once the weird import-in-the-middle exports are fixed, released and we use the respective
- // version in our SDKs. See: https://github.com/getsentry/sentry-javascript/issues/12009#issuecomment-2126211967
- {
- name: 'aws-serverless-lambda-layer-fix',
- transform: code => {
- if (code.includes('ImportInTheMiddle')) {
- return code.replaceAll(/new\s+(ImportInTheMiddle.*)\(/gm, 'new $1.default(');
- }
- },
- },
- ],
- // Don't bundle any of Node's core modules
- external: builtinModules,
- };
-
const workerBundleConfig = {
output: {
format: 'esm',
@@ -143,7 +117,6 @@ export function makeBaseBundleConfig(options) {
const bundleTypeConfigMap = {
standalone: standAloneBundleConfig,
addon: addOnBundleConfig,
- 'aws-lambda': awsLambdaBundleConfig,
'node-worker': workerBundleConfig,
};
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index 0bca7574c42c..17433e6101e5 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -15,6 +15,7 @@
"/build/loader-hook.mjs"
],
"main": "build/npm/cjs/index.js",
+ "module": "build/npm/esm/index.js",
"types": "build/npm/types/index.d.ts",
"exports": {
"./package.json": "./package.json",
@@ -73,10 +74,11 @@
"@types/aws-lambda": "^8.10.62"
},
"devDependencies": {
- "@types/node": "^18.19.1"
+ "@types/node": "^18.19.1",
+ "@vercel/nft": "^0.29.4"
},
"scripts": {
- "build": "run-p build:transpile build:types build:bundle",
+ "build": "run-p build:transpile build:types",
"build:bundle": "yarn build:layer",
"build:layer": "yarn ts-node scripts/buildLambdaLayer.ts",
"build:dev": "run-p build:transpile build:types",
diff --git a/packages/aws-serverless/rollup.aws.config.mjs b/packages/aws-serverless/rollup.aws.config.mjs
deleted file mode 100644
index d9f0720886ef..000000000000
--- a/packages/aws-serverless/rollup.aws.config.mjs
+++ /dev/null
@@ -1,39 +0,0 @@
-import { makeBaseBundleConfig, makeBaseNPMConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils';
-
-export default [
- // The SDK
- ...makeBundleConfigVariants(
- makeBaseBundleConfig({
- // this automatically sets it to be CJS
- bundleType: 'aws-lambda',
- entrypoints: ['src/index.ts'],
- licenseTitle: '@sentry/aws-serverless',
- outputFileBase: () => 'index',
- packageSpecificConfig: {
- output: {
- dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs',
- sourcemap: false,
- },
- },
- }),
- // We only need one copy of the SDK, and we pick the minified one because there's a cap on how big a lambda function
- // plus its dependencies can be, and we might as well take up as little of that space as is necessary. We'll rename
- // it to be `index.js` in the build script, since it's standing in for the index file of the npm package.
- { variants: ['.debug.min.js'] },
- ),
- makeBaseNPMConfig({
- entrypoints: ['src/awslambda-auto.ts'],
- packageSpecificConfig: {
- // Normally `makeNPMConfigVariants` sets both of these values for us, but we don't actually want the ESM variant,
- // and the directory structure is different than normal, so we have to do it ourselves.
- output: {
- format: 'cjs',
- dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs',
- sourcemap: false,
- },
- // We only want `awslambda-auto.js`, not the modules that it imports, because they're all included in the bundle
- // we generate above
- external: ['./index'],
- },
- }),
-];
diff --git a/packages/aws-serverless/scripts/buildLambdaLayer.ts b/packages/aws-serverless/scripts/buildLambdaLayer.ts
index 52c3b50009c5..ad39948f5b0a 100644
--- a/packages/aws-serverless/scripts/buildLambdaLayer.ts
+++ b/packages/aws-serverless/scripts/buildLambdaLayer.ts
@@ -1,6 +1,8 @@
/* eslint-disable no-console */
+import { nodeFileTrace } from '@vercel/nft';
import * as childProcess from 'child_process';
import * as fs from 'fs';
+import * as path from 'path';
import { version } from '../package.json';
/**
@@ -11,21 +13,20 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string {
return String(childProcess.execSync(cmd, { stdio: 'inherit', ...options }));
}
+/**
+ * Build the AWS lambda layer by first installing the local package into `build/aws/dist-serverless/nodejs`.
+ * Then, prune the node_modules directory to remove unused files by first getting all necessary files with
+ * `@vercel/nft` and then deleting all other files inside `node_modules`.
+ * Finally, create a zip file of the layer.
+ */
async function buildLambdaLayer(): Promise {
- // Create the main SDK bundle
- run('yarn rollup --config rollup.aws.config.mjs');
-
- // We build a minified bundle, but it's standing in for the regular `index.js` file listed in `package.json`'s `main`
- // property, so we have to rename it so it's findable.
- fs.renameSync(
- 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.debug.min.js',
- 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.js',
- );
+ console.log('Building Lambda layer.');
+ console.log('Installing local @sentry/aws-serverless into build/aws/dist-serverless/nodejs.');
+ run('npm install . --prefix ./build/aws/dist-serverless/nodejs --install-links --silent');
- // We're creating a bundle for the SDK, but still using it in a Node context, so we need to copy in `package.json`,
- // purely for its `main` property.
- console.log('Copying `package.json` into lambda layer.');
- fs.copyFileSync('package.json', 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/package.json');
+ await pruneNodeModules();
+ fs.rmSync('./build/aws/dist-serverless/nodejs/package.json', { force: true });
+ fs.rmSync('./build/aws/dist-serverless/nodejs/package-lock.json', { force: true });
// The layer also includes `awslambda-auto.js`, a helper file which calls `Sentry.init()` and wraps the lambda
// handler. It gets run when Node is launched inside the lambda, using the environment variable
@@ -61,3 +62,79 @@ function fsForceMkdirSync(path: string): void {
fs.rmSync(path, { recursive: true, force: true });
fs.mkdirSync(path);
}
+
+async function pruneNodeModules(): Promise {
+ const entrypoints = [
+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/esm/index.js',
+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.js',
+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/awslambda-auto.js',
+ './build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/esm/awslambda-auto.js',
+ ];
+
+ const { fileList } = await nodeFileTrace(entrypoints);
+
+ const allFiles = getAllFiles('./build/aws/dist-serverless/nodejs/node_modules');
+
+ const filesToDelete = allFiles.filter(file => !fileList.has(file));
+ console.log(`Removing ${filesToDelete.length} unused files from node_modules.`);
+
+ for (const file of filesToDelete) {
+ try {
+ fs.unlinkSync(file);
+ } catch {
+ console.error(`Error deleting ${file}`);
+ }
+ }
+
+ console.log('Cleaning up empty directories.');
+
+ removeEmptyDirs('./build/aws/dist-serverless/nodejs/node_modules');
+}
+
+function removeEmptyDirs(dir: string): void {
+ try {
+ const entries = fs.readdirSync(dir);
+
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry);
+ const stat = fs.statSync(fullPath);
+ if (stat.isDirectory()) {
+ removeEmptyDirs(fullPath);
+ }
+ }
+
+ const remainingEntries = fs.readdirSync(dir);
+
+ if (remainingEntries.length === 0) {
+ fs.rmdirSync(dir);
+ }
+ } catch {
+ // Directory might not exist or might not be empty, that's ok
+ }
+}
+
+function getAllFiles(dir: string): string[] {
+ const files: string[] = [];
+
+ function walkDirectory(currentPath: string): void {
+ try {
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = path.join(currentPath, entry.name);
+ const relativePath = path.relative(process.cwd(), fullPath);
+
+ if (entry.isDirectory()) {
+ walkDirectory(fullPath);
+ } else {
+ files.push(relativePath);
+ }
+ }
+ } catch {
+ console.log(`Skipping directory ${currentPath}`);
+ }
+ }
+
+ walkDirectory(dir);
+ return files;
+}
diff --git a/packages/aws-serverless/tsconfig.json b/packages/aws-serverless/tsconfig.json
index a2731860dfa0..fd68e15254db 100644
--- a/packages/aws-serverless/tsconfig.json
+++ b/packages/aws-serverless/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
- "include": ["src/**/*"],
+ "include": ["src/**/*", "scripts/**/*"],
"compilerOptions": {
// package-specific options
diff --git a/packages/aws-serverless/tsconfig.types.json b/packages/aws-serverless/tsconfig.types.json
index 4c51bd21e64b..03b57fcaa2a7 100644
--- a/packages/aws-serverless/tsconfig.types.json
+++ b/packages/aws-serverless/tsconfig.types.json
@@ -3,7 +3,7 @@
// We don't ship this in the npm package (it exists purely for controlling what ends up in the AWS lambda layer), so
// no need to build types for it
- "exclude": ["src/index.awslambda.ts"],
+ "exclude": ["src/index.awslambda.ts", "scripts/**/*"],
"compilerOptions": {
"declaration": true,
From c9153578cb96ba2b317e8a1431f3119a8a56d204 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Fri, 18 Jul 2025 19:32:41 +0200
Subject: [PATCH 14/86] chore: Remove `--silent` flag from layer build script
and create dir prior (#17091)
CI for the 10.0.0-alpha.0 release is failing at the build step but it's
unclear why. This hopefully reveals more info.
---
packages/aws-serverless/scripts/buildLambdaLayer.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/aws-serverless/scripts/buildLambdaLayer.ts b/packages/aws-serverless/scripts/buildLambdaLayer.ts
index ad39948f5b0a..7ae000d378dd 100644
--- a/packages/aws-serverless/scripts/buildLambdaLayer.ts
+++ b/packages/aws-serverless/scripts/buildLambdaLayer.ts
@@ -22,7 +22,11 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string {
async function buildLambdaLayer(): Promise {
console.log('Building Lambda layer.');
console.log('Installing local @sentry/aws-serverless into build/aws/dist-serverless/nodejs.');
- run('npm install . --prefix ./build/aws/dist-serverless/nodejs --install-links --silent');
+
+ console.log('Creating target directory for npm install.');
+ fsForceMkdirSync('./build/aws/dist-serverless/nodejs');
+
+ run('npm install . --prefix ./build/aws/dist-serverless/nodejs --install-links');
await pruneNodeModules();
fs.rmSync('./build/aws/dist-serverless/nodejs/package.json', { force: true });
@@ -60,7 +64,7 @@ buildLambdaLayer();
*/
function fsForceMkdirSync(path: string): void {
fs.rmSync(path, { recursive: true, force: true });
- fs.mkdirSync(path);
+ fs.mkdirSync(path, { recursive: true });
}
async function pruneNodeModules(): Promise {
From daf2133c46f530309ff269e86a1867ccc0fabb8e Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Mon, 21 Jul 2025 08:58:45 +0200
Subject: [PATCH 15/86] fix(remix): Ensure source maps upload fails silently if
Sentry CLI fails (#17082)
Sentry CLI's `cli.releases.uploadSourceMaps` method previously never
rejected when the actual CLI binary execution exited with an error code.
In CLI 2.49.0 (and 2.50.0) I added a new live mode `rejectOnError` which
continues to pipe stdio to the process (the remix SDKs' upload script)
but now also rejects on error.
This PR 1. bumps Sentry CLI, 2. configures the CLI to actually reject
now but 3. also catches the rejection and logs a message. I decided to
still continue with the script because we should still delete source
maps. Otherwise, we risk deploying them when users expect them to be
deleted. (i.e. fail silently but correctly :D)
---
packages/remix/package.json | 2 +-
packages/remix/scripts/createRelease.js | 21 ++-
.../test/scripts/upload-sourcemaps.test.ts | 30 ++++
yarn.lock | 128 ++++++++++++------
4 files changed, 130 insertions(+), 51 deletions(-)
diff --git a/packages/remix/package.json b/packages/remix/package.json
index 8e3eb52a6504..f8dde175cf86 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -68,7 +68,7 @@
"@opentelemetry/instrumentation": "^0.203.0",
"@opentelemetry/semantic-conventions": "^1.34.0",
"@remix-run/router": "1.x",
- "@sentry/cli": "^2.46.0",
+ "@sentry/cli": "^2.50.0",
"@sentry/core": "9.40.0",
"@sentry/node": "9.40.0",
"@sentry/react": "9.40.0",
diff --git a/packages/remix/scripts/createRelease.js b/packages/remix/scripts/createRelease.js
index ab969c48be18..f0cd6a53a374 100644
--- a/packages/remix/scripts/createRelease.js
+++ b/packages/remix/scripts/createRelease.js
@@ -27,13 +27,22 @@ async function createRelease(argv, URL_PREFIX, BUILD_PATH) {
await sentry.releases.new(release);
- await sentry.releases.uploadSourceMaps(release, {
- urlPrefix: URL_PREFIX,
- include: [BUILD_PATH],
- useArtifactBundle: !argv.disableDebugIds,
- });
+ try {
+ await sentry.releases.uploadSourceMaps(release, {
+ urlPrefix: URL_PREFIX,
+ include: [BUILD_PATH],
+ useArtifactBundle: !argv.disableDebugIds,
+ live: 'rejectOnError',
+ });
+ } catch (error) {
+ console.warn('[sentry] Failed to upload sourcemaps.');
+ }
- await sentry.releases.finalize(release);
+ try {
+ await sentry.releases.finalize(release);
+ } catch (error) {
+ console.warn('[sentry] Failed to finalize release.');
+ }
if (argv.deleteAfterUpload) {
try {
diff --git a/packages/remix/test/scripts/upload-sourcemaps.test.ts b/packages/remix/test/scripts/upload-sourcemaps.test.ts
index 0634930bf00e..677c602a011f 100644
--- a/packages/remix/test/scripts/upload-sourcemaps.test.ts
+++ b/packages/remix/test/scripts/upload-sourcemaps.test.ts
@@ -5,6 +5,8 @@ const uploadSourceMapsMock = vi.fn();
const finalizeMock = vi.fn();
const proposeVersionMock = vi.fn(() => '0.1.2.3.4');
+const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
// The createRelease script requires the Sentry CLI, which we need to mock so we
// hook require to do this
async function mock(mockedUri: string, stub: any) {
@@ -56,6 +58,7 @@ describe('createRelease', () => {
urlPrefix: '~/build/',
include: ['public/build'],
useArtifactBundle: true,
+ live: 'rejectOnError',
});
expect(finalizeMock).toHaveBeenCalledWith('0.1.2.3');
});
@@ -69,6 +72,7 @@ describe('createRelease', () => {
urlPrefix: '~/build/',
include: ['public/build'],
useArtifactBundle: true,
+ live: 'rejectOnError',
});
expect(finalizeMock).toHaveBeenCalledWith('0.1.2.3.4');
});
@@ -89,9 +93,35 @@ describe('createRelease', () => {
urlPrefix: '~/build/',
include: ['public/build'],
useArtifactBundle: true,
+ live: 'rejectOnError',
});
expect(finalizeMock).toHaveBeenCalledWith('0.1.2.3.4');
});
+
+ it('logs an error when uploadSourceMaps fails', async () => {
+ uploadSourceMapsMock.mockRejectedValue(new Error('Failed to upload sourcemaps'));
+
+ await createRelease({}, '~/build/', 'public/build');
+
+ expect(uploadSourceMapsMock).toHaveBeenCalledWith('0.1.2.3.4', {
+ urlPrefix: '~/build/',
+ include: ['public/build'],
+ useArtifactBundle: true,
+ live: 'rejectOnError',
+ });
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith('[sentry] Failed to upload sourcemaps.');
+
+ expect(finalizeMock).toHaveBeenCalledWith('0.1.2.3.4');
+ });
+
+ it('logs an error when finalize fails', async () => {
+ finalizeMock.mockRejectedValue(new Error('Failed to finalize release'));
+
+ await createRelease({}, '~/build/', 'public/build');
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith('[sentry] Failed to finalize release.');
+ });
});
// To avoid `--isolatedModules` flag as we're not importing
diff --git a/yarn.lock b/yarn.lock
index 10c669dc1da4..fe26776aacf5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6961,75 +6961,115 @@
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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz#e07ff66f03e8cb6e1988b7673ae5dbd6ff957b1d"
- integrity sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==
+"@sentry/cli-darwin@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.49.0.tgz#290657e5840b360cb8ca25c8a78f8c0f15c66b03"
+ integrity sha512-bgowyDeFuXbjkGq1ZKqcWhmzgfBe7oKIXYWJOOps4+32QfG+YsrdNnottHS01td3bzrJq0QnHj8H12fA81DqrA==
+
+"@sentry/cli-darwin@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.50.0.tgz#0fec0ece84afe37b33464ccd514367fc95d507f3"
+ integrity sha512-Aj+cLBZ0dCw+pdUxvJ1U71PnKh2YjvpzLN9h1ZTe8UI3FqmkKkSH/J8mN/5qmR7qUHjDcm2l+wfgVUaaP8CWbA==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz#d5b27e5813e7b3add65c9e3dbdd75a8bea4ef324"
- integrity sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==
+"@sentry/cli-linux-arm64@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.49.0.tgz#a732004d7131f7e7b44f6a64abdccc36efb35d52"
+ integrity sha512-dqxsDUd76aDm03fUwUOs5BR7RHLpSb2EH/B1hlWm0mFvo9uY907XxW9wDFx/qDpCdmpC0aF+lF/lOBOrG9B5Fg==
+
+"@sentry/cli-linux-arm64@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.50.0.tgz#bbafacf82766d45ff05434cd7cabbda7005d1efd"
+ integrity sha512-p6hIh4Bb87qBfEz9w5dxEPAohIKcw68qoy5VUTx+cCanO8uXNWWsT78xtUNFRscW9zc6MxQMSITTWaCEIKvxRA==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz#d2a0f21cd208ef8e844bc5e565b337640d125441"
- integrity sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==
+"@sentry/cli-linux-arm@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.49.0.tgz#73719561510df3369e05e9a4898b4e43b8753e4c"
+ integrity sha512-RBDIjIGmNsFw+a6vAt6m3D7ROKsMEB9i3u+UuIRxk0/DyHTcfVWxnK/ScPXGILM6PxQ2XOBfOKad0mmiDHBzZA==
+
+"@sentry/cli-linux-arm@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.50.0.tgz#e1fed09b94c508db9de5353d8305828b0a3551e9"
+ integrity sha512-SGPAFwOY2of2C+RUBJcxMN2JXikVFEk8ypYOsQTEvV/48cLejcO/O2mHIj/YKgIkrfn3t7LlqdK6g75lkz+F8Q==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz#73368ebe30236c8647caec420f717a7f45410f29"
- integrity sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==
+"@sentry/cli-linux-i686@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.49.0.tgz#8d1bb1378251a3aa995cc4b56bd352fa12a84b66"
+ integrity sha512-gDAd5/vJbEhd4Waud0Cd8ZRqLEagDlOvWwNH3KB694EiHJUwzRSiTA1YUVMYGI8Z9UyEA1sKxARwm2Trv99BxA==
+
+"@sentry/cli-linux-i686@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.50.0.tgz#95f0eb65bdde4c33e492830ae4ac207b60494f8e"
+ integrity sha512-umhGmbiCUG7MvjTm8lXFmFxQjyTVtYakilBwPTVzRELmNKxxhfKRxwSSA+hUKetAUzNd8fJx8K7yqdw+qRA7Pg==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz#49da3dfd873e0e72abef968e1c213b9397e5d70e"
- integrity sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==
+"@sentry/cli-linux-x64@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.49.0.tgz#7bf58fb7005c89fdde4e1262d5ed35e23065aceb"
+ integrity sha512-mbohGvPNhHjUciYNXzkt9TYUebTmxeAp9v9JfLSb/Soz6fubKwEHhpRJuz1zASxVWIR4PuqkePchqN5zhcLC0A==
-"@sentry/cli-win32-arm64@2.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz#4e26b254d5283eb114ac916ac504283a30b2ecdb"
- integrity sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==
+"@sentry/cli-linux-x64@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.50.0.tgz#5266b6b8660e6b72688331b7c702e9d1ca6413ed"
+ integrity sha512-ugIIx9+wUmguxOUe9ZVacvdCffZwqtFSKwpJ06Nqes0XfL4ZER4Qlq3/miCZ8m150C4xK5ym/QCwB41ffBqI4g==
+
+"@sentry/cli-win32-arm64@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.49.0.tgz#2bf6dd911acbe3ddb02eec0afb4301bb8fb25b53"
+ integrity sha512-3zwvsp61EPpSuGpGdXY4JelVJmNEjoj4vn5m6EFoOtk7OUI5/VFqqR4wchjy9Hjm3Eh6MB5K+KTKXs4W2p18ng==
+
+"@sentry/cli-win32-arm64@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.50.0.tgz#663d75fea42b853940c6faacf7ee76a16b449654"
+ integrity sha512-fMyBSKLrVHY9944t8oTpul+6osyQeuN8GGGP3diDxGQpynYL+vhcHZIpXFRH398+3kedG/IFoY7EwGgIEqWzmw==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz#72f7c0a611f17b7e5b34e2b47309d165195a8276"
- integrity sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==
+"@sentry/cli-win32-i686@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.49.0.tgz#32e31472ae6c5f69e538a4061d651937fcb8f14a"
+ integrity sha512-2oWaNl6z0BaOCAjM1Jxequfgjod3XO6wothxow4kA8e9+43JLhgarSdpwJPgQjcVyxjygwQ3/jKPdUFh0qNOmg==
+
+"@sentry/cli-win32-i686@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.50.0.tgz#96813ca970f35a839d7f817534ac556bc1df1567"
+ integrity sha512-VbC+l2Y2kB7Lsun2c8t7ZGwmljmXnyncZLW9PjdEyJSTAJ9GnEnSvyFSPXNLV/eHJnfQffzU7QTjU8vkQ7XMYg==
"@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.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz#8cfd438ec365b0ee925d9724a24b533b4cb75587"
- integrity sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==
+"@sentry/cli-win32-x64@2.49.0":
+ version "2.49.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.49.0.tgz#86aab38cb41f885914d7c99ceaab7b6ce52c72c6"
+ integrity sha512-dR4ulyrA6ZT7x7cg4Rwm0tcHf4TZz5QO6t1W1jX6uJ9n/U0bOSqSFZHNf/RryiUzQE1g8LBthOYyKGMkET6T8w==
+
+"@sentry/cli-win32-x64@2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.50.0.tgz#9f644efed8cb75943078a0ca4e414fa21dda6280"
+ integrity sha512-nMktyF93NtQUOViAAKHpHSWACOGjOkKjiewi4pD6W3sWllFiPPyt15XoyApqWwnICDRQu2DI5vnil4ck6/k7mw==
"@sentry/cli@2.42.2":
version "2.42.2"
@@ -7050,10 +7090,10 @@
"@sentry/cli-win32-i686" "2.42.2"
"@sentry/cli-win32-x64" "2.42.2"
-"@sentry/cli@^2.36.1", "@sentry/cli@^2.46.0":
- version "2.46.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.46.0.tgz#790864874ea04f804053aa85dc94501b2cc321bb"
- integrity sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==
+"@sentry/cli@^2.36.1", "@sentry/cli@^2.46.0", "@sentry/cli@^2.50.0":
+ version "2.50.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.50.0.tgz#7e2298bea9a2bb50126bfb24116ae98199bc1f6f"
+ integrity sha512-OHRRQPUNjBpzOT6arNhxXQ71DKs5jSziCfDzmEGwAs+K8J/I1QxnvJkto88HbXE54oiWhSEJwL0pvcowFXyVbA==
dependencies:
https-proxy-agent "^5.0.0"
node-fetch "^2.6.7"
@@ -7061,14 +7101,14 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
optionalDependencies:
- "@sentry/cli-darwin" "2.46.0"
- "@sentry/cli-linux-arm" "2.46.0"
- "@sentry/cli-linux-arm64" "2.46.0"
- "@sentry/cli-linux-i686" "2.46.0"
- "@sentry/cli-linux-x64" "2.46.0"
- "@sentry/cli-win32-arm64" "2.46.0"
- "@sentry/cli-win32-i686" "2.46.0"
- "@sentry/cli-win32-x64" "2.46.0"
+ "@sentry/cli-darwin" "2.50.0"
+ "@sentry/cli-linux-arm" "2.50.0"
+ "@sentry/cli-linux-arm64" "2.50.0"
+ "@sentry/cli-linux-i686" "2.50.0"
+ "@sentry/cli-linux-x64" "2.50.0"
+ "@sentry/cli-win32-arm64" "2.50.0"
+ "@sentry/cli-win32-i686" "2.50.0"
+ "@sentry/cli-win32-x64" "2.50.0"
"@sentry/rollup-plugin@^3.5.0":
version "3.5.0"
From 93b1c6faac3f8210bfe90e7327bc5c28005a9fa0 Mon Sep 17 00:00:00 2001
From: Lukas Stracke
Date: Mon, 21 Jul 2025 09:07:46 +0200
Subject: [PATCH 16/86] ref(browser): Remove unnecessary CLS web vital check
(#17090)
Just a minor cleanup from a TODO I left a while ago. We can safely
remove this check because CLS isn't reported before FCP is reported.
---
packages/browser-utils/src/metrics/browserMetrics.ts | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts
index e9fa822a431e..d182c64030fa 100644
--- a/packages/browser-utils/src/metrics/browserMetrics.ts
+++ b/packages/browser-utils/src/metrics/browserMetrics.ts
@@ -430,10 +430,8 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
delete _measurements['mark.fid'];
}
- // If FCP is not recorded we should not record the cls value
- // according to the new definition of CLS.
- // TODO: Check if the first condition is still necessary: `onCLS` already only fires once `onFCP` was called.
- if (!('fcp' in _measurements) || !options.recordClsOnPageloadSpan) {
+ // If CLS standalone spans are enabled, don't record CLS as a measurement
+ if (!options.recordClsOnPageloadSpan) {
delete _measurements.cls;
}
From da8dfef10a31fff48939f0f3fc7856e0e5e4de93 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Mon, 21 Jul 2025 09:47:54 +0200
Subject: [PATCH 17/86] chore: Avoid building aws layer for now (#17098)
The current way of building the AWS Lambda Layer is not working during
releases because it tries to install @sentry/aws-serverless from the
registry instead of the workspace.
Temporarily disable it until we figure out how to fix it.
---
packages/aws-serverless/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index 17433e6101e5..225c2cf421fd 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -82,7 +82,7 @@
"build:bundle": "yarn build:layer",
"build:layer": "yarn ts-node scripts/buildLambdaLayer.ts",
"build:dev": "run-p build:transpile build:types",
- "build:transpile": "rollup -c rollup.npm.config.mjs && yarn build:layer",
+ "build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",
"build:types:core": "tsc -p tsconfig.types.json",
"build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8",
From 4c96e5b1f9ad688e1890aa95b5ae19dddc103cbb Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Mon, 21 Jul 2025 04:03:03 -0400
Subject: [PATCH 18/86] feat(core)!: Remove `enableLogs` and `beforeSendLog`
experimental options (#17063)
When we first shipped support for
https://docs.sentry.io/product/explore/logs/ in the JavaScript SDK we
gated the logging related options (`enableLogs`, `beforeSendLog`) behind
`_experiments` namespace.
See https://github.com/getsentry/sentry-javascript/pull/15717 and
https://github.com/getsentry/sentry-javascript/pull/15814 for the PRs
that made this change.
For v10, we want to not make this experimental anymore, so we changed
the logging options to become top-level options to `Sentry.init`.
---
.../suites/public-api/logger/init.js | 4 +-
.../public-api/logger/integration/init.js | 4 +-
.../test-applications/node-express/src/app.ts | 4 +-
.../suites/winston/subject.ts | 4 +-
.../suites/winston/subject.ts | 4 +-
packages/browser/src/client.ts | 3 +-
packages/browser/src/log.ts | 12 ++---
packages/browser/test/client.test.ts | 3 +-
packages/browser/test/log.test.ts | 4 +-
packages/cloudflare/src/logs/exports.ts | 12 ++---
packages/core/src/logs/console-integration.ts | 9 ++--
packages/core/src/logs/exports.ts | 3 +-
packages/core/src/server-runtime-client.ts | 2 +-
packages/core/src/types-hoist/options.ts | 40 ++++++++--------
packages/core/test/lib/logs/exports.test.ts | 46 ++++++++++---------
.../test/lib/server-runtime-client.test.ts | 9 ++--
.../node-core/src/integrations/winston.ts | 2 +-
packages/node-core/src/logs/exports.ts | 12 ++---
packages/node-core/src/sdk/client.ts | 2 +-
packages/node-core/test/sdk/client.test.ts | 4 +-
packages/node/test/sdk/client.test.ts | 4 +-
packages/pino-transport/README.md | 8 ++--
packages/pino-transport/src/index.ts | 2 +-
packages/vercel-edge/src/logs/exports.ts | 12 ++---
24 files changed, 98 insertions(+), 111 deletions(-)
diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/init.js b/dev-packages/browser-integration-tests/suites/public-api/logger/init.js
index 27397e0f90ce..8026df91ea46 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/logger/init.js
+++ b/dev-packages/browser-integration-tests/suites/public-api/logger/init.js
@@ -4,7 +4,5 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
});
diff --git a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/init.js b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/init.js
index e0ceaaebf017..809b78739e77 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/logger/integration/init.js
+++ b/dev-packages/browser-integration-tests/suites/public-api/logger/integration/init.js
@@ -4,8 +4,6 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
integrations: [Sentry.consoleLoggingIntegration()],
});
diff --git a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts
index 9f7b0055b66d..0ac843b5409e 100644
--- a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts
+++ b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts
@@ -13,9 +13,7 @@ Sentry.init({
debug: !!process.env.DEBUG,
tunnel: `http://localhost:3031/`, // proxy server
tracesSampleRate: 1,
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
});
import { TRPCError, initTRPC } from '@trpc/server';
diff --git a/dev-packages/node-core-integration-tests/suites/winston/subject.ts b/dev-packages/node-core-integration-tests/suites/winston/subject.ts
index c8840b855f9b..3c31ddb63fa5 100644
--- a/dev-packages/node-core-integration-tests/suites/winston/subject.ts
+++ b/dev-packages/node-core-integration-tests/suites/winston/subject.ts
@@ -8,9 +8,7 @@ const client = Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0.0',
environment: 'test',
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
transport: loggingTransport,
});
diff --git a/dev-packages/node-integration-tests/suites/winston/subject.ts b/dev-packages/node-integration-tests/suites/winston/subject.ts
index ac70abc50d80..1047f2f1cd47 100644
--- a/dev-packages/node-integration-tests/suites/winston/subject.ts
+++ b/dev-packages/node-integration-tests/suites/winston/subject.ts
@@ -7,9 +7,7 @@ Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0.0',
environment: 'test',
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
transport: loggingTransport,
});
diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts
index 5c50e53e708b..b6c05afe70f7 100644
--- a/packages/browser/src/client.ts
+++ b/packages/browser/src/client.ts
@@ -85,8 +85,7 @@ export class BrowserClient extends Client {
super(opts);
- const { sendDefaultPii, sendClientReports, _experiments } = this._options;
- const enableLogs = _experiments?.enableLogs;
+ const { sendDefaultPii, sendClientReports, enableLogs } = this._options;
if (WINDOW.document && (sendClientReports || enableLogs)) {
WINDOW.document.addEventListener('visibilitychange', () => {
diff --git a/packages/browser/src/log.ts b/packages/browser/src/log.ts
index ef2614b81f55..c21477e378b3 100644
--- a/packages/browser/src/log.ts
+++ b/packages/browser/src/log.ts
@@ -19,7 +19,7 @@ function captureLog(
}
/**
- * @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `trace` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { userId: 100, route: '/dashboard' }.
@@ -48,7 +48,7 @@ export function trace(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `debug` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `debug` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { component: 'Header', state: 'loading' }.
@@ -78,7 +78,7 @@ export function debug(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `info` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `info` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { feature: 'checkout', status: 'completed' }.
@@ -108,7 +108,7 @@ export function info(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `warn` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `warn` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { browser: 'Chrome', version: '91.0' }.
@@ -139,7 +139,7 @@ export function warn(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `error` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `error` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { error: 'NetworkError', url: '/api/data' }.
@@ -171,7 +171,7 @@ export function error(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `fatal` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `fatal` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { appState: 'corrupted', sessionId: 'abc-123' }.
diff --git a/packages/browser/test/client.test.ts b/packages/browser/test/client.test.ts
index c0f4e649501a..2197b6ed03c0 100644
--- a/packages/browser/test/client.test.ts
+++ b/packages/browser/test/client.test.ts
@@ -27,7 +27,6 @@ describe('BrowserClient', () => {
it('does not flush logs when logs are disabled', () => {
client = new BrowserClient(
getDefaultBrowserClientOptions({
- _experiments: { enableLogs: false },
sendClientReports: true,
}),
);
@@ -50,7 +49,7 @@ describe('BrowserClient', () => {
vi.useFakeTimers();
client = new BrowserClient(
getDefaultBrowserClientOptions({
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendClientReports: true,
}),
);
diff --git a/packages/browser/test/log.test.ts b/packages/browser/test/log.test.ts
index 68c87069966c..0967d38531dd 100644
--- a/packages/browser/test/log.test.ts
+++ b/packages/browser/test/log.test.ts
@@ -42,9 +42,7 @@ describe('Logger', () => {
init({
dsn,
transport: makeSimpleTransport,
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
});
});
diff --git a/packages/cloudflare/src/logs/exports.ts b/packages/cloudflare/src/logs/exports.ts
index ef2614b81f55..c21477e378b3 100644
--- a/packages/cloudflare/src/logs/exports.ts
+++ b/packages/cloudflare/src/logs/exports.ts
@@ -19,7 +19,7 @@ function captureLog(
}
/**
- * @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `trace` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { userId: 100, route: '/dashboard' }.
@@ -48,7 +48,7 @@ export function trace(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `debug` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `debug` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { component: 'Header', state: 'loading' }.
@@ -78,7 +78,7 @@ export function debug(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `info` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `info` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { feature: 'checkout', status: 'completed' }.
@@ -108,7 +108,7 @@ export function info(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `warn` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `warn` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { browser: 'Chrome', version: '91.0' }.
@@ -139,7 +139,7 @@ export function warn(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `error` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `error` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { error: 'NetworkError', url: '/api/data' }.
@@ -171,7 +171,7 @@ export function error(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `fatal` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `fatal` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { appState: 'corrupted', sessionId: 'abc-123' }.
diff --git a/packages/core/src/logs/console-integration.ts b/packages/core/src/logs/console-integration.ts
index d2cdc8fa1d48..a69d406a659c 100644
--- a/packages/core/src/logs/console-integration.ts
+++ b/packages/core/src/logs/console-integration.ts
@@ -33,9 +33,9 @@ const _consoleLoggingIntegration = ((options: Partial = {
return {
name: INTEGRATION_NAME,
setup(client) {
- const { _experiments, normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = client.getOptions();
- if (!_experiments?.enableLogs) {
- DEBUG_BUILD && debug.warn('`_experiments.enableLogs` is not enabled, ConsoleLogs integration disabled');
+ const { enableLogs, normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = client.getOptions();
+ if (!enableLogs) {
+ DEBUG_BUILD && debug.warn('`enableLogs` is not enabled, ConsoleLogs integration disabled');
return;
}
@@ -69,7 +69,7 @@ const _consoleLoggingIntegration = ((options: Partial = {
}) satisfies IntegrationFn;
/**
- * Captures calls to the `console` API as logs in Sentry. Requires `_experiments.enableLogs` to be enabled.
+ * Captures calls to the `console` API as logs in Sentry. Requires the `enableLogs` option to be enabled.
*
* @experimental This feature is experimental and may be changed or removed in future versions.
*
@@ -83,6 +83,7 @@ const _consoleLoggingIntegration = ((options: Partial = {
* import * as Sentry from '@sentry/browser';
*
* Sentry.init({
+ * enableLogs: true,
* integrations: [Sentry.consoleLoggingIntegration({ levels: ['error', 'warn'] })],
* });
* ```
diff --git a/packages/core/src/logs/exports.ts b/packages/core/src/logs/exports.ts
index 23246a7e1251..79dfedf4ec87 100644
--- a/packages/core/src/logs/exports.ts
+++ b/packages/core/src/logs/exports.ts
@@ -124,8 +124,7 @@ export function _INTERNAL_captureLog(
return;
}
- const { _experiments, release, environment } = client.getOptions();
- const { enableLogs = false, beforeSendLog } = _experiments ?? {};
+ const { release, environment, enableLogs = false, beforeSendLog } = client.getOptions();
if (!enableLogs) {
DEBUG_BUILD && debug.warn('logging option not enabled, log will not be captured.');
return;
diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts
index 246e0a88bc51..d9c44ed7149d 100644
--- a/packages/core/src/server-runtime-client.ts
+++ b/packages/core/src/server-runtime-client.ts
@@ -49,7 +49,7 @@ export class ServerRuntimeClient<
this._logWeight = 0;
- if (this._options._experiments?.enableLogs) {
+ if (this._options.enableLogs) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const client = this;
diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts
index a4119ed42a6e..7d542b1cb655 100644
--- a/packages/core/src/types-hoist/options.ts
+++ b/packages/core/src/types-hoist/options.ts
@@ -246,25 +246,6 @@ export interface ClientOptions Log | null;
};
/**
@@ -328,6 +309,27 @@ export interface ClientOptions Log | null;
+
/**
* Function to compute tracing sample rate dynamically and filter unwanted traces.
*
diff --git a/packages/core/test/lib/logs/exports.test.ts b/packages/core/test/lib/logs/exports.test.ts
index 536ae9a2adfd..4fd01a16a304 100644
--- a/packages/core/test/lib/logs/exports.test.ts
+++ b/packages/core/test/lib/logs/exports.test.ts
@@ -82,7 +82,7 @@ describe('logAttributeToSerializedLogAttribute', () => {
describe('_INTERNAL_captureLog', () => {
it('captures and sends logs', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
_INTERNAL_captureLog({ level: 'info', message: 'test log message' }, client, undefined);
@@ -99,7 +99,7 @@ describe('_INTERNAL_captureLog', () => {
);
});
- it('does not capture logs when enableLogs experiment is not enabled', () => {
+ it('does not capture logs when enableLogs is not enabled', () => {
const logWarnSpy = vi.spyOn(loggerModule.debug, 'warn').mockImplementation(() => undefined);
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
const client = new TestClient(options);
@@ -113,7 +113,7 @@ describe('_INTERNAL_captureLog', () => {
});
it('includes trace context when available', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
const scope = new Scope();
scope.setPropagationContext({
@@ -134,7 +134,7 @@ describe('_INTERNAL_captureLog', () => {
it('includes release and environment in log attributes when available', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
release: '1.0.0',
environment: 'test',
});
@@ -158,7 +158,7 @@ describe('_INTERNAL_captureLog', () => {
it('includes SDK metadata in log attributes when available', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
const client = new TestClient(options);
// Mock getSdkMetadata to return SDK info
@@ -187,7 +187,7 @@ describe('_INTERNAL_captureLog', () => {
it('does not include SDK metadata in log attributes when not available', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
const client = new TestClient(options);
// Mock getSdkMetadata to return no SDK info
@@ -205,7 +205,7 @@ describe('_INTERNAL_captureLog', () => {
});
it('includes custom attributes in log', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
_INTERNAL_captureLog(
@@ -232,7 +232,7 @@ describe('_INTERNAL_captureLog', () => {
});
it('flushes logs buffer when it reaches max size', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
// Fill the buffer to max size (100 is the MAX_LOG_BUFFER_SIZE constant in client.ts)
@@ -249,7 +249,7 @@ describe('_INTERNAL_captureLog', () => {
});
it('does not flush logs buffer when it is empty', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
const mockSendEnvelope = vi.spyOn(client as any, 'sendEnvelope').mockImplementation(() => {});
_INTERNAL_flushLogsBuffer(client);
@@ -257,7 +257,7 @@ describe('_INTERNAL_captureLog', () => {
});
it('handles parameterized strings correctly', () => {
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
const parameterizedMessage = fmt`Hello ${'John'}, welcome to ${'Sentry'}`;
@@ -290,7 +290,8 @@ describe('_INTERNAL_captureLog', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true, beforeSendLog },
+ enableLogs: true,
+ beforeSendLog,
});
const client = new TestClient(options);
@@ -336,7 +337,8 @@ describe('_INTERNAL_captureLog', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true, beforeSendLog },
+ enableLogs: true,
+ beforeSendLog,
});
const client = new TestClient(options);
@@ -360,7 +362,7 @@ describe('_INTERNAL_captureLog', () => {
it('emits beforeCaptureLog and afterCaptureLog events', () => {
const beforeCaptureLogSpy = vi.spyOn(TestClient.prototype, 'emit');
- const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, _experiments: { enableLogs: true } });
+ const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, enableLogs: true });
const client = new TestClient(options);
const log: Log = {
@@ -380,7 +382,7 @@ describe('_INTERNAL_captureLog', () => {
it('includes user data in log attributes', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
const client = new TestClient(options);
const scope = new Scope();
@@ -412,7 +414,7 @@ describe('_INTERNAL_captureLog', () => {
it('includes partial user data when only some fields are available', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -436,7 +438,7 @@ describe('_INTERNAL_captureLog', () => {
it('includes user email and username without id', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -465,7 +467,7 @@ describe('_INTERNAL_captureLog', () => {
it('does not include user data when user object is empty', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -481,7 +483,7 @@ describe('_INTERNAL_captureLog', () => {
it('combines user data with other log attributes', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
release: '1.0.0',
environment: 'test',
@@ -535,7 +537,7 @@ describe('_INTERNAL_captureLog', () => {
it('handles user data with non-string values', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -564,7 +566,7 @@ describe('_INTERNAL_captureLog', () => {
it('preserves existing user attributes in log and does not override them', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -607,7 +609,7 @@ describe('_INTERNAL_captureLog', () => {
it('only adds scope user data for attributes that do not already exist', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
sendDefaultPii: true,
});
const client = new TestClient(options);
@@ -656,7 +658,7 @@ describe('_INTERNAL_captureLog', () => {
it('overrides user-provided system attributes with SDK values', () => {
const options = getDefaultTestClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
release: 'sdk-release-1.0.0',
environment: 'sdk-environment',
});
diff --git a/packages/core/test/lib/server-runtime-client.test.ts b/packages/core/test/lib/server-runtime-client.test.ts
index d5b9dc33681d..708ac8716070 100644
--- a/packages/core/test/lib/server-runtime-client.test.ts
+++ b/packages/core/test/lib/server-runtime-client.test.ts
@@ -211,7 +211,7 @@ describe('ServerRuntimeClient', () => {
it('flushes logs when weight exceeds 800KB', () => {
const options = getDefaultClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
client = new ServerRuntimeClient(options);
@@ -228,7 +228,7 @@ describe('ServerRuntimeClient', () => {
it('accumulates log weight without flushing when under threshold', () => {
const options = getDefaultClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
client = new ServerRuntimeClient(options);
@@ -245,7 +245,7 @@ describe('ServerRuntimeClient', () => {
it('flushes logs on flush event', () => {
const options = getDefaultClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
client = new ServerRuntimeClient(options);
@@ -265,7 +265,6 @@ describe('ServerRuntimeClient', () => {
it('does not flush logs when logs are disabled', () => {
const options = getDefaultClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: false },
});
client = new ServerRuntimeClient(options);
@@ -282,7 +281,7 @@ describe('ServerRuntimeClient', () => {
it('flushes logs when flush event is triggered', () => {
const options = getDefaultClientOptions({
dsn: PUBLIC_DSN,
- _experiments: { enableLogs: true },
+ enableLogs: true,
});
client = new ServerRuntimeClient(options);
diff --git a/packages/node-core/src/integrations/winston.ts b/packages/node-core/src/integrations/winston.ts
index a485a6c56431..a58a3ea31ad0 100644
--- a/packages/node-core/src/integrations/winston.ts
+++ b/packages/node-core/src/integrations/winston.ts
@@ -28,7 +28,7 @@ interface WinstonTransportOptions {
}
/**
- * Creates a new Sentry Winston transport that fowards logs to Sentry. Requires `_experiments.enableLogs` to be enabled.
+ * Creates a new Sentry Winston transport that fowards logs to Sentry. Requires the `enableLogs` option to be enabled.
*
* Supports Winston 3.x.x.
*
diff --git a/packages/node-core/src/logs/exports.ts b/packages/node-core/src/logs/exports.ts
index c18b69f6770a..665d4d78d9ad 100644
--- a/packages/node-core/src/logs/exports.ts
+++ b/packages/node-core/src/logs/exports.ts
@@ -1,7 +1,7 @@
import { type CaptureLogArgs, captureLog } from './capture';
/**
- * @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `trace` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
@@ -28,7 +28,7 @@ export function trace(...args: CaptureLogArgs): void {
}
/**
- * @summary Capture a log with the `debug` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `debug` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
@@ -55,7 +55,7 @@ export function debug(...args: CaptureLogArgs): void {
}
/**
- * @summary Capture a log with the `info` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `info` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
@@ -82,7 +82,7 @@ export function info(...args: CaptureLogArgs): void {
}
/**
- * @summary Capture a log with the `warn` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `warn` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
@@ -110,7 +110,7 @@ export function warn(...args: CaptureLogArgs): void {
}
/**
- * @summary Capture a log with the `error` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `error` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
@@ -138,7 +138,7 @@ export function error(...args: CaptureLogArgs): void {
}
/**
- * @summary Capture a log with the `fatal` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `fatal` level. Requires the `enableLogs` option to be enabled.
*
* You can either pass a message and attributes or a message template, params and attributes.
*
diff --git a/packages/node-core/src/sdk/client.ts b/packages/node-core/src/sdk/client.ts
index 50b0d4d92b6e..c17ee22d71b4 100644
--- a/packages/node-core/src/sdk/client.ts
+++ b/packages/node-core/src/sdk/client.ts
@@ -45,7 +45,7 @@ export class NodeClient extends ServerRuntimeClient {
super(clientOptions);
- if (this.getOptions()._experiments?.enableLogs) {
+ if (this.getOptions().enableLogs) {
this._logOnExitFlushListener = () => {
_INTERNAL_flushLogsBuffer(this);
};
diff --git a/packages/node-core/test/sdk/client.test.ts b/packages/node-core/test/sdk/client.test.ts
index f053b1ba7e0e..7f57d4772212 100644
--- a/packages/node-core/test/sdk/client.test.ts
+++ b/packages/node-core/test/sdk/client.test.ts
@@ -296,7 +296,7 @@ describe('NodeClient', () => {
describe('log capture', () => {
it('adds server name to log attributes', () => {
- const options = getDefaultNodeClientOptions({ _experiments: { enableLogs: true } });
+ const options = getDefaultNodeClientOptions({ enableLogs: true });
const client = new NodeClient(options);
const log: Log = { level: 'info', message: 'test message', attributes: {} };
@@ -309,7 +309,7 @@ describe('NodeClient', () => {
it('preserves existing log attributes', () => {
const serverName = 'test-server';
- const options = getDefaultNodeClientOptions({ serverName, _experiments: { enableLogs: true } });
+ const options = getDefaultNodeClientOptions({ serverName, enableLogs: true });
const client = new NodeClient(options);
const log: Log = { level: 'info', message: 'test message', attributes: { 'existing.attr': 'value' } };
diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts
index f053b1ba7e0e..7f57d4772212 100644
--- a/packages/node/test/sdk/client.test.ts
+++ b/packages/node/test/sdk/client.test.ts
@@ -296,7 +296,7 @@ describe('NodeClient', () => {
describe('log capture', () => {
it('adds server name to log attributes', () => {
- const options = getDefaultNodeClientOptions({ _experiments: { enableLogs: true } });
+ const options = getDefaultNodeClientOptions({ enableLogs: true });
const client = new NodeClient(options);
const log: Log = { level: 'info', message: 'test message', attributes: {} };
@@ -309,7 +309,7 @@ describe('NodeClient', () => {
it('preserves existing log attributes', () => {
const serverName = 'test-server';
- const options = getDefaultNodeClientOptions({ serverName, _experiments: { enableLogs: true } });
+ const options = getDefaultNodeClientOptions({ serverName, enableLogs: true });
const client = new NodeClient(options);
const log: Log = { level: 'info', message: 'test message', attributes: { 'existing.attr': 'value' } };
diff --git a/packages/pino-transport/README.md b/packages/pino-transport/README.md
index 0bb6aeed81ed..fdc077aa2b01 100644
--- a/packages/pino-transport/README.md
+++ b/packages/pino-transport/README.md
@@ -30,7 +30,7 @@ pnpm add @sentry/pino-transport pino
- Node.js 18+
- Pino v8 or v9
-- `@sentry/node` SDK with `_experiments.enableLogs: true`
+- `@sentry/node` SDK with `enableLogs: true`
## Setup
@@ -41,9 +41,7 @@ import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'YOUR_DSN',
- _experiments: {
- enableLogs: true,
- },
+ enableLogs: true,
});
```
@@ -252,7 +250,7 @@ const logger = pino({
### Logs not appearing in Sentry
-1. Ensure `_experiments.enableLogs: true` is set in your Sentry configuration.
+1. Ensure `enableLogs: true` is set in your Sentry configuration.
2. Check that your DSN is correct and the SDK is properly initialized.
3. Verify the log level is included in the `levels` configuration.
4. Check your Sentry organization stats page to see if logs are being received by Sentry.
diff --git a/packages/pino-transport/src/index.ts b/packages/pino-transport/src/index.ts
index 7bff4dc35327..986c7e892fc2 100644
--- a/packages/pino-transport/src/index.ts
+++ b/packages/pino-transport/src/index.ts
@@ -84,7 +84,7 @@ interface PinoSourceConfig {
}
/**
- * Creates a new Sentry Pino transport that forwards logs to Sentry. Requires `_experiments.enableLogs` to be enabled.
+ * Creates a new Sentry Pino transport that forwards logs to Sentry. Requires the `enableLogs` option to be enabled.
*
* Supports Pino v8 and v9.
*
diff --git a/packages/vercel-edge/src/logs/exports.ts b/packages/vercel-edge/src/logs/exports.ts
index ef2614b81f55..c21477e378b3 100644
--- a/packages/vercel-edge/src/logs/exports.ts
+++ b/packages/vercel-edge/src/logs/exports.ts
@@ -19,7 +19,7 @@ function captureLog(
}
/**
- * @summary Capture a log with the `trace` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `trace` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { userId: 100, route: '/dashboard' }.
@@ -48,7 +48,7 @@ export function trace(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `debug` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `debug` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { component: 'Header', state: 'loading' }.
@@ -78,7 +78,7 @@ export function debug(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `info` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `info` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { feature: 'checkout', status: 'completed' }.
@@ -108,7 +108,7 @@ export function info(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `warn` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `warn` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { browser: 'Chrome', version: '91.0' }.
@@ -139,7 +139,7 @@ export function warn(message: ParameterizedString, attributes?: Log['attributes'
}
/**
- * @summary Capture a log with the `error` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `error` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { error: 'NetworkError', url: '/api/data' }.
@@ -171,7 +171,7 @@ export function error(message: ParameterizedString, attributes?: Log['attributes
}
/**
- * @summary Capture a log with the `fatal` level. Requires `_experiments.enableLogs` to be enabled.
+ * @summary Capture a log with the `fatal` level. Requires the `enableLogs` option to be enabled.
*
* @param message - The message to log.
* @param attributes - Arbitrary structured data that stores information about the log - e.g., { appState: 'corrupted', sessionId: 'abc-123' }.
From 2390462d5210675ce78b1caadf724c251d721b8f Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Mon, 21 Jul 2025 04:28:27 -0400
Subject: [PATCH 19/86] chore: Add migration note about `enableLogs` option
changes (#17100)
Migration note for the changes in
https://github.com/getsentry/sentry-javascript/pull/17063
---
MIGRATION.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/MIGRATION.md b/MIGRATION.md
index d760698fb8eb..63b9b67e1f9d 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -39,6 +39,27 @@ Updates and fixes for version 9 will be published as `SentryNodeServerlessSDKv9`
- `BaseClient` was removed, use `Client` as a direct replacement.
- `hasTracingEnabled` was removed, use `hasSpansEnabled` as a direct replacement.
- `logger` and type `Logger` were removed, use `debug` and type `SentryDebugLogger` instead.
+- The `_experiments.enableLogs` and `_experiments.beforeSendLog` options were removed, use the top-level `enableLogs` and `beforeSendLog` options instead.
+
+```js
+// before
+Sentry.init({
+ _experiments: {
+ enableLogs: true,
+ beforeSendLog: log => {
+ return log;
+ },
+ },
+});
+
+// after
+Sentry.init({
+ enableLogs: true,
+ beforeSendLog: log => {
+ return log;
+ },
+});
+```
## No Version Support Timeline
From c9f3a2e9727189c7caa6fdd302a6580d6e85023f Mon Sep 17 00:00:00 2001
From: Onur Temizkan
Date: Mon, 21 Jul 2025 09:30:27 +0100
Subject: [PATCH 20/86] test(remix): Fix integration test flakes (#17093)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Looks like `example.com` has some kind of rate limit or random
unavailability. Switching to `docs.sentry.io` resolved the flakes. Maybe
we can replace all `example.com` uses in the codebase with a Sentry
domain 🤔 Wdyt?
---
packages/remix/test/integration/app/root.tsx | 4 +--
.../test/client/root-loader.test.ts | 30 +++++++------------
2 files changed, 13 insertions(+), 21 deletions(-)
diff --git a/packages/remix/test/integration/app/root.tsx b/packages/remix/test/integration/app/root.tsx
index 1b8e5e39e8f5..c1d0bf218baa 100644
--- a/packages/remix/test/integration/app/root.tsx
+++ b/packages/remix/test/integration/app/root.tsx
@@ -48,9 +48,9 @@ export const loader: LoaderFunction = async ({ request }) => {
case 'returnRedirect':
return redirect('/?type=plain');
case 'throwRedirectToExternal':
- throw redirect('https://example.com');
+ throw redirect(`https://docs.sentry.io`);
case 'returnRedirectToExternal':
- return redirect('https://example.com');
+ return redirect('https://docs.sentry.io');
default: {
return {};
}
diff --git a/packages/remix/test/integration/test/client/root-loader.test.ts b/packages/remix/test/integration/test/client/root-loader.test.ts
index e9273fbd6caa..fc557509e941 100644
--- a/packages/remix/test/integration/test/client/root-loader.test.ts
+++ b/packages/remix/test/integration/test/client/root-loader.test.ts
@@ -1,4 +1,4 @@
-import { type Page, expect, test } from '@playwright/test';
+import { type Page, expect, test, chromium } from '@playwright/test';
async function getRouteData(page: Page): Promise {
return page.evaluate('window.__remixContext.state.loaderData').catch(err => {
@@ -22,7 +22,6 @@ async function extractTraceAndBaggageFromMeta(
test('should inject `sentry-trace` and `baggage` into root loader returning an empty object.', async ({ page }) => {
await page.goto('/?type=empty');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -38,7 +37,6 @@ test('should inject `sentry-trace` and `baggage` into root loader returning an e
test('should inject `sentry-trace` and `baggage` into root loader returning a plain object.', async ({ page }) => {
await page.goto('/?type=plain');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -56,7 +54,6 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a pl
test('should inject `sentry-trace` and `baggage` into root loader returning a `JSON response`.', async ({ page }) => {
await page.goto('/?type=json');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -74,7 +71,6 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a `J
test('should inject `sentry-trace` and `baggage` into root loader returning a deferred response', async ({ page }) => {
await page.goto('/?type=defer');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -90,7 +86,6 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a de
test('should inject `sentry-trace` and `baggage` into root loader returning `null`.', async ({ page }) => {
await page.goto('/?type=null');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -106,7 +101,6 @@ test('should inject `sentry-trace` and `baggage` into root loader returning `nul
test('should inject `sentry-trace` and `baggage` into root loader returning `undefined`.', async ({ page }) => {
await page.goto('/?type=undefined');
-
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
expect(sentryTrace).toMatch(/.+/);
@@ -124,12 +118,11 @@ test('should inject `sentry-trace` and `baggage` into root loader throwing a red
page,
}) => {
await page.goto('/?type=throwRedirect');
+ const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
// We should be successfully redirected to the path.
expect(page.url()).toEqual(expect.stringContaining('/?type=plain'));
- const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
-
expect(sentryTrace).toMatch(/.+/);
expect(sentryBaggage).toMatch(/.+/);
@@ -143,14 +136,14 @@ test('should inject `sentry-trace` and `baggage` into root loader throwing a red
test('should inject `sentry-trace` and `baggage` into root loader returning a redirection to valid path.', async ({
page,
+ baseURL,
}) => {
- await page.goto('/?type=returnRedirect');
+ await page.goto(`${baseURL}/?type=returnRedirect`);
+ const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
// We should be successfully redirected to the path.
expect(page.url()).toEqual(expect.stringContaining('/?type=plain'));
- const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
-
expect(sentryTrace).toMatch(/.+/);
expect(sentryBaggage).toMatch(/.+/);
@@ -162,11 +155,10 @@ test('should inject `sentry-trace` and `baggage` into root loader returning a re
});
});
-test('should return redirect to an external path with no baggage and trace injected.', async ({ page }) => {
- await page.goto('/?type=returnRedirectToExternal');
+test('should return redirect to an external path with no baggage and trace injected.', async ({ page, baseURL }) => {
+ await page.goto(`${baseURL}/?type=returnRedirectToExternal`);
- // We should be successfully redirected to the external path.
- expect(page.url()).toEqual(expect.stringContaining('https://example.com'));
+ expect(page.url()).toEqual(expect.stringContaining('docs.sentry.io'));
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
@@ -174,11 +166,11 @@ test('should return redirect to an external path with no baggage and trace injec
expect(sentryBaggage).toBeUndefined();
});
-test('should throw redirect to an external path with no baggage and trace injected.', async ({ page }) => {
- await page.goto('/?type=throwRedirectToExternal');
+test('should throw redirect to an external path with no baggage and trace injected.', async ({ page, baseURL }) => {
+ await page.goto(`${baseURL}/?type=throwRedirectToExternal`);
// We should be successfully redirected to the external path.
- expect(page.url()).toEqual(expect.stringContaining('https://example.com'));
+ expect(page.url()).toEqual(expect.stringContaining('docs.sentry.io'));
const { sentryTrace, sentryBaggage } = await extractTraceAndBaggageFromMeta(page);
From 311472973188f8fb6e00fcb0c273c0b2fa8d6ab0 Mon Sep 17 00:00:00 2001
From: Andrei <168741329+andreiborza@users.noreply.github.com>
Date: Mon, 21 Jul 2025 10:57:25 +0200
Subject: [PATCH 21/86] chore: Avoid building aws layer for now (#17099)
Forgot to disable the build in the bundle step.
---
.../test-applications/aws-lambda-layer-cjs/package.json | 3 +++
.../test-applications/aws-lambda-layer-esm/package.json | 3 +++
packages/aws-serverless/package.json | 1 -
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
index 1d98acc92859..3c169365af60 100644
--- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json
@@ -18,5 +18,8 @@
},
"volta": {
"extends": "../../package.json"
+ },
+ "sentryTest": {
+ "skip": true
}
}
diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
index 7a25061dde1c..4ae01902cbfb 100644
--- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
+++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-esm/package.json
@@ -19,5 +19,8 @@
},
"volta": {
"extends": "../../package.json"
+ },
+ "sentryTest": {
+ "skip": true
}
}
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index 225c2cf421fd..ed151413296c 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -79,7 +79,6 @@
},
"scripts": {
"build": "run-p build:transpile build:types",
- "build:bundle": "yarn build:layer",
"build:layer": "yarn ts-node scripts/buildLambdaLayer.ts",
"build:dev": "run-p build:transpile build:types",
"build:transpile": "rollup -c rollup.npm.config.mjs",
From 662da800fcfc25a96fe5ceb78ad9d1ef3405ee13 Mon Sep 17 00:00:00 2001
From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
Date: Mon, 21 Jul 2025 11:08:21 +0200
Subject: [PATCH 22/86] test(astro): Add E2E test case for catch-all routes
(#17085)
Extracting a test case in a new PR while working on route
parametrization.
Continuation of
https://github.com/getsentry/sentry-javascript/pull/17054
Part of https://github.com/getsentry/sentry-javascript/issues/16686
---
.../src/pages/catchAll/[...path].astro | 12 +++++
.../astro-5/tests/tracing.dynamic.test.ts | 51 ++++++++++++++++++-
2 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro
diff --git a/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro
new file mode 100644
index 000000000000..bb225c166241
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/astro-5/src/pages/catchAll/[...path].astro
@@ -0,0 +1,12 @@
+---
+import Layout from '../../layouts/Layout.astro';
+
+export const prerender = false;
+
+const params = Astro.params;
+
+---
+
+
+
This new generator eliminates any headaches that you may have
experienced due to magical
code generation into node_modules and gives you full
control over the generated Prisma Client code. With it moving into
Preview, we hope that even more of you will try it out and share your
feedback with us!
Note: The prisma-client generator will
become the default in Prisma v7, replacing the current
prisma-client-js generator.
In addition to moving it into Preview, we also created a couple of
new ready-to-run examples to help you get started with the new generator
and your favorite framework:
Specify views and migrations folder
locations in prisma.config.ts (Early Access)
As we’re getting closer to the General
Availability release of prisma.config.ts,
we’re adding more capabilities to it. In previous versions, the Prisma
CLI implicitly used to infer the location for migration and SQL view
definition files based on the location of the Prisma schema. In this
release, we’re adding two new fields (migrations and
views) to give you more flexibility and clarity on how to
locate these files: