From 38b7b56762c43298973bde3f458638d3c0a81734 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 22 Jan 2025 16:34:19 +0100 Subject: [PATCH 01/43] chore: Increase date range for MIT licence It's 2025 now. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 4e1c2c384991..98482f33b3be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2012-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 7b0cff11d0a6d4368d171535c27e68f7d0efd519 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 22 Jan 2025 10:38:38 -0500 Subject: [PATCH 02/43] update remaining libraries --- packages/angular/LICENSE | 2 +- packages/astro/LICENSE | 2 +- packages/aws-serverless/LICENSE | 2 +- packages/browser-utils/LICENSE | 2 +- packages/browser/LICENSE | 2 +- packages/bun/LICENSE | 2 +- packages/cloudflare/LICENSE | 2 +- packages/core/LICENSE | 2 +- packages/deno/LICENSE | 2 +- packages/ember/LICENSE | 2 +- packages/eslint-config-sdk/LICENSE | 2 +- packages/eslint-plugin-sdk/LICENSE | 2 +- packages/feedback/LICENSE | 2 +- packages/gatsby/LICENSE | 2 +- packages/google-cloud-serverless/LICENSE | 2 +- packages/integration-shims/LICENSE | 2 +- packages/nestjs/LICENSE | 2 +- packages/nextjs/LICENSE | 2 +- packages/node/LICENSE | 2 +- packages/nuxt/LICENSE | 2 +- packages/opentelemetry/LICENSE | 2 +- packages/profiling-node/LICENSE | 2 +- packages/react/LICENSE | 2 +- packages/remix/LICENSE | 2 +- packages/replay-canvas/LICENSE | 2 +- packages/replay-internal/LICENSE | 2 +- packages/replay-worker/LICENSE | 2 +- packages/solid/LICENSE | 2 +- packages/solidstart/LICENSE | 2 +- packages/svelte/LICENSE | 2 +- packages/sveltekit/LICENSE | 2 +- packages/types/LICENSE | 2 +- packages/typescript/LICENSE | 2 +- packages/vercel-edge/LICENSE | 2 +- packages/vue/LICENSE | 2 +- packages/wasm/LICENSE | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/angular/LICENSE b/packages/angular/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/angular/LICENSE +++ b/packages/angular/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/astro/LICENSE +++ b/packages/astro/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/aws-serverless/LICENSE b/packages/aws-serverless/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/aws-serverless/LICENSE +++ b/packages/aws-serverless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser-utils/LICENSE b/packages/browser-utils/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/browser-utils/LICENSE +++ b/packages/browser-utils/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/browser/LICENSE +++ b/packages/browser/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/bun/LICENSE b/packages/bun/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/bun/LICENSE +++ b/packages/bun/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/cloudflare/LICENSE b/packages/cloudflare/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/cloudflare/LICENSE +++ b/packages/cloudflare/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/core/LICENSE b/packages/core/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/core/LICENSE +++ b/packages/core/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/deno/LICENSE +++ b/packages/deno/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/ember/LICENSE +++ b/packages/ember/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-config-sdk/LICENSE b/packages/eslint-config-sdk/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/eslint-config-sdk/LICENSE +++ b/packages/eslint-config-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-plugin-sdk/LICENSE b/packages/eslint-plugin-sdk/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/eslint-plugin-sdk/LICENSE +++ b/packages/eslint-plugin-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/feedback/LICENSE +++ b/packages/feedback/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/gatsby/LICENSE b/packages/gatsby/LICENSE index 5af93a5bdae5..58ef1814e188 100644 --- a/packages/gatsby/LICENSE +++ b/packages/gatsby/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/google-cloud-serverless/LICENSE b/packages/google-cloud-serverless/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/google-cloud-serverless/LICENSE +++ b/packages/google-cloud-serverless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/integration-shims/LICENSE b/packages/integration-shims/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/integration-shims/LICENSE +++ b/packages/integration-shims/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nestjs/LICENSE b/packages/nestjs/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/nestjs/LICENSE +++ b/packages/nestjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nextjs/LICENSE b/packages/nextjs/LICENSE index 5b55ec3c5dcb..6d1ad17eb71b 100644 --- a/packages/nextjs/LICENSE +++ b/packages/nextjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/node/LICENSE b/packages/node/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/node/LICENSE +++ b/packages/node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nuxt/LICENSE b/packages/nuxt/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/nuxt/LICENSE +++ b/packages/nuxt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/opentelemetry/LICENSE b/packages/opentelemetry/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/opentelemetry/LICENSE +++ b/packages/opentelemetry/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/profiling-node/LICENSE b/packages/profiling-node/LICENSE index 048dee5adaa8..a69d859b8939 100644 --- a/packages/profiling-node/LICENSE +++ b/packages/profiling-node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/react/LICENSE b/packages/react/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/react/LICENSE +++ b/packages/react/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/remix/LICENSE b/packages/remix/LICENSE index 048dee5adaa8..a69d859b8939 100644 --- a/packages/remix/LICENSE +++ b/packages/remix/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/replay-canvas/LICENSE +++ b/packages/replay-canvas/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-internal/LICENSE b/packages/replay-internal/LICENSE index 048dee5adaa8..a69d859b8939 100644 --- a/packages/replay-internal/LICENSE +++ b/packages/replay-internal/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-worker/LICENSE b/packages/replay-worker/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/replay-worker/LICENSE +++ b/packages/replay-worker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solid/LICENSE b/packages/solid/LICENSE index 63e7eb28e19c..9739499d2513 100644 --- a/packages/solid/LICENSE +++ b/packages/solid/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solidstart/LICENSE b/packages/solidstart/LICENSE index 6bfafc44539c..9739499d2513 100644 --- a/packages/solidstart/LICENSE +++ b/packages/solidstart/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/svelte/LICENSE b/packages/svelte/LICENSE index 048dee5adaa8..a69d859b8939 100644 --- a/packages/svelte/LICENSE +++ b/packages/svelte/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/sveltekit/LICENSE +++ b/packages/sveltekit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/types/LICENSE b/packages/types/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/types/LICENSE +++ b/packages/types/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/typescript/LICENSE b/packages/typescript/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/typescript/LICENSE +++ b/packages/typescript/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE index 6bfafc44539c..eaf96aacc0c8 100644 --- a/packages/vercel-edge/LICENSE +++ b/packages/vercel-edge/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE index d5b40b7c4219..9440728b1ced 100644 --- a/packages/vue/LICENSE +++ b/packages/vue/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/wasm/LICENSE b/packages/wasm/LICENSE index 5b55ec3c5dcb..6d1ad17eb71b 100644 --- a/packages/wasm/LICENSE +++ b/packages/wasm/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 5bc0894f31f5ed55847fafeed6c3a5ff72228ba1 Mon Sep 17 00:00:00 2001 From: David Turissini Date: Wed, 22 Jan 2025 23:59:19 -0800 Subject: [PATCH 03/43] chore(repo): Removing unused files (#15140) [remove-unused](https://removeunused.com/) found more unused files, this time in `replay-internal` and `vercel-edge`. --- packages/replay-internal/src/session/index.ts | 1 - packages/vercel-edge/src/transports/types.ts | 8 -------- 2 files changed, 9 deletions(-) delete mode 100644 packages/replay-internal/src/session/index.ts delete mode 100644 packages/vercel-edge/src/transports/types.ts diff --git a/packages/replay-internal/src/session/index.ts b/packages/replay-internal/src/session/index.ts deleted file mode 100644 index 02c9d7a43422..000000000000 --- a/packages/replay-internal/src/session/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './createSession'; diff --git a/packages/vercel-edge/src/transports/types.ts b/packages/vercel-edge/src/transports/types.ts deleted file mode 100644 index 70bf888f2666..000000000000 --- a/packages/vercel-edge/src/transports/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { BaseTransportOptions } from '@sentry/core'; - -export interface VercelEdgeTransportOptions extends BaseTransportOptions { - /** Fetch API init parameters. */ - fetchOptions?: RequestInit; - /** Custom headers for the transport. */ - headers?: { [key: string]: string }; -} From b49c1cc05e79f3854b642066ef1176ef9eb3f8d0 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 23 Jan 2025 08:43:22 +0000 Subject: [PATCH 04/43] fix(react): Support lazy-loaded routes and components. (#15039) Fixes: https://github.com/getsentry/sentry-javascript/issues/15027 This PR adds support for lazily loaded components and routes inside `Suspend` on react-router pageloads / navigations. --- .../react-create-browser-router/src/index.tsx | 11 +- .../src/pages/Index.tsx | 3 + .../src/pages/LazyLoadedInnerRoute.tsx | 14 +++ .../src/pages/LazyLoadedUser.tsx | 23 ++++ .../tests/transactions.test.ts | 102 ++++++++++++++++++ .../react/src/reactrouterv6-compat-utils.tsx | 62 ++++++++--- packages/react/src/types.ts | 11 +- 7 files changed, 205 insertions(+), 21 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx index 88f8cfa502ec..c7ad16eebcf7 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/react'; -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider, @@ -42,6 +42,7 @@ Sentry.init({ }); const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); +const LazyLoadedUser = lazy(() => import('./pages/LazyLoadedUser')); const router = sentryCreateBrowserRouter( [ @@ -49,6 +50,14 @@ const router = sentryCreateBrowserRouter( path: '/', element: , }, + { + path: '/lazy-loaded-user/*', + element: ( + Loading...}> + + + ), + }, { path: '/user/:id', element: , diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx index d6b71a1d1279..12bfb12ec3a9 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx @@ -16,6 +16,9 @@ const Index = () => { navigate + + lazy navigate + ); }; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx new file mode 100644 index 000000000000..1410df69124b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/react'; +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); + +const InnerRoute = () => ( + + I am a lazy loaded user

} /> +
+); + +export default InnerRoute; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx new file mode 100644 index 000000000000..636f99d9c8cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedUser.tsx @@ -0,0 +1,23 @@ +import * as Sentry from '@sentry/react'; +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); +const InnerRoute = React.lazy(() => import('./LazyLoadedInnerRoute')); + +const LazyLoadedUser = () => { + return ( + + Loading...

}> + + + } + /> +
+ ); +}; + +export default LazyLoadedUser; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts index 5ecd098daf94..c35d731915d6 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts @@ -76,3 +76,105 @@ test('Captures a navigation transaction', async ({ page }) => { expect(transactionEvent.spans).toEqual([]); }); + +test('Captures a lazy pageload transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/lazy-loaded-user/5/foo'); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'pageload', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.pageload.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); + +test('Captures a lazy navigation transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'navigation'; + }); + + await page.goto('/'); + const linkElement = page.locator('id=lazy-navigation'); + await linkElement.click(); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'navigation', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.navigation.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 9ca96a03b0de..db65c32cee99 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -61,6 +61,9 @@ export interface ReactRouterOptions { type V6CompatibleVersion = '6' | '7'; +// Keeping as a global variable for cross-usage in multiple functions +const allRoutes = new Set(); + /** * Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions. */ @@ -81,6 +84,10 @@ export function createV6CompatibleWrapCreateBrowserRouter< } return function (routes: RouteObject[], opts?: Record & { basename?: string }): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -90,19 +97,40 @@ export function createV6CompatibleWrapCreateBrowserRouter< // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename); + updatePageloadTransaction( + activeRootSpan, + router.state.location, + routes, + undefined, + basename, + Array.from(allRoutes), + ); } router.subscribe((state: RouterState) => { - const location = state.location; if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - handleNavigation({ - location, - routes, - navigationType: state.historyAction, - version, - basename, - }); + // Wait for the next render if loading an unsettled route + if (state.navigation.state !== 'idle') { + requestAnimationFrame(() => { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + allRoutes: Array.from(allRoutes), + }); + }); + } else { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + allRoutes: Array.from(allRoutes), + }); + } } }); @@ -137,6 +165,10 @@ export function createV6CompatibleWrapCreateMemoryRouter< initialIndex?: number; }, ): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -162,7 +194,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< : router.state.location; if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename); + updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes)); } router.subscribe((state: RouterState) => { @@ -174,6 +206,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< navigationType: state.historyAction, version, basename, + allRoutes: Array.from(allRoutes), }); } }); @@ -248,8 +281,6 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio return origUseRoutes; } - const allRoutes: Set = new Set(); - const SentryRoutes: React.FC<{ children?: React.ReactNode; routes: RouteObject[]; @@ -319,7 +350,6 @@ export function handleNavigation(opts: { allRoutes?: RouteObject[]; }): void { const { location, routes, navigationType, version, matches, basename, allRoutes } = opts; - const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); const client = getClient(); @@ -553,7 +583,7 @@ function updatePageloadTransaction( ): void { const branches = Array.isArray(matches) ? matches - : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]); + : (_matchRoutes(allRoutes || routes, location, basename) as unknown as RouteMatch[]); if (branches) { let name, @@ -569,7 +599,7 @@ function updatePageloadTransaction( [name, source] = getNormalizedName(routes, location, branches, basename); } - getCurrentScope().setTransactionName(name); + getCurrentScope().setTransactionName(name || '/'); if (activeRootSpan) { activeRootSpan.updateName(name); @@ -592,8 +622,6 @@ export function createV6CompatibleWithSentryReactRouterRouting

= new Set(); - const SentryRoutes: React.FC

= (props: P) => { const isMountRenderPass = React.useRef(true); diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 1a40ec4fce91..b29a2dbd1cad 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -182,10 +182,14 @@ export interface RouterInit { hydrationData?: HydrationState; } +export type NavigationState = { + state: 'idle' | 'loading' | 'submitting'; +}; + export type NavigationStates = { - Idle: any; - Loading: any; - Submitting: any; + Idle: NavigationState; + Loading: NavigationState; + Submitting: NavigationState; }; export type Navigation = NavigationStates[keyof NavigationStates]; @@ -202,6 +206,7 @@ export declare enum HistoryAction { export interface RouterState { historyAction: Action | HistoryAction | any; location: Location; + navigation: Navigation; } export interface Router { state: TState; From 1b68b7f17b71e80705f052d1002daec7dfb124f0 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Thu, 23 Jan 2025 11:07:05 +0100 Subject: [PATCH 05/43] Remove date range for LICENSEs In our internal Open Source Legal Policy, we decided that licenses don't require a data range. This also has the advantage of not updating the date range yearly. --- LICENSE | 2 +- packages/angular/LICENSE | 2 +- packages/astro/LICENSE | 2 +- packages/aws-serverless/LICENSE | 2 +- packages/browser-utils/LICENSE | 2 +- packages/browser/LICENSE | 2 +- packages/bun/LICENSE | 2 +- packages/cloudflare/LICENSE | 2 +- packages/core/LICENSE | 2 +- packages/deno/LICENSE | 2 +- packages/ember/LICENSE | 2 +- packages/eslint-config-sdk/LICENSE | 2 +- packages/eslint-plugin-sdk/LICENSE | 2 +- packages/feedback/LICENSE | 2 +- packages/gatsby/LICENSE | 2 +- packages/google-cloud-serverless/LICENSE | 2 +- packages/integration-shims/LICENSE | 2 +- packages/nestjs/LICENSE | 2 +- packages/nextjs/LICENSE | 2 +- packages/node/LICENSE | 2 +- packages/nuxt/LICENSE | 2 +- packages/opentelemetry/LICENSE | 2 +- packages/profiling-node/LICENSE | 2 +- packages/react/LICENSE | 2 +- packages/remix/LICENSE | 2 +- packages/replay-canvas/LICENSE | 2 +- packages/replay-internal/LICENSE | 2 +- packages/replay-worker/LICENSE | 2 +- packages/solid/LICENSE | 2 +- packages/solidstart/LICENSE | 2 +- packages/svelte/LICENSE | 2 +- packages/sveltekit/LICENSE | 2 +- packages/types/LICENSE | 2 +- packages/typescript/LICENSE | 2 +- packages/vercel-edge/LICENSE | 2 +- packages/vue/LICENSE | 2 +- packages/wasm/LICENSE | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/LICENSE b/LICENSE index 98482f33b3be..84d8d8c065fc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2012 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/angular/LICENSE b/packages/angular/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/angular/LICENSE +++ b/packages/angular/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/astro/LICENSE +++ b/packages/astro/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/aws-serverless/LICENSE b/packages/aws-serverless/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/aws-serverless/LICENSE +++ b/packages/aws-serverless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser-utils/LICENSE b/packages/browser-utils/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/browser-utils/LICENSE +++ b/packages/browser-utils/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/browser/LICENSE +++ b/packages/browser/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/bun/LICENSE b/packages/bun/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/bun/LICENSE +++ b/packages/bun/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/cloudflare/LICENSE b/packages/cloudflare/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/cloudflare/LICENSE +++ b/packages/cloudflare/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/core/LICENSE b/packages/core/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/core/LICENSE +++ b/packages/core/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/deno/LICENSE +++ b/packages/deno/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/ember/LICENSE +++ b/packages/ember/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-config-sdk/LICENSE b/packages/eslint-config-sdk/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/eslint-config-sdk/LICENSE +++ b/packages/eslint-config-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-plugin-sdk/LICENSE b/packages/eslint-plugin-sdk/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/eslint-plugin-sdk/LICENSE +++ b/packages/eslint-plugin-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/feedback/LICENSE +++ b/packages/feedback/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/gatsby/LICENSE b/packages/gatsby/LICENSE index 58ef1814e188..b956a1944c7b 100644 --- a/packages/gatsby/LICENSE +++ b/packages/gatsby/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/google-cloud-serverless/LICENSE b/packages/google-cloud-serverless/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/google-cloud-serverless/LICENSE +++ b/packages/google-cloud-serverless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/integration-shims/LICENSE b/packages/integration-shims/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/integration-shims/LICENSE +++ b/packages/integration-shims/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nestjs/LICENSE b/packages/nestjs/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/nestjs/LICENSE +++ b/packages/nestjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nextjs/LICENSE b/packages/nextjs/LICENSE index 6d1ad17eb71b..917e31f85b7a 100644 --- a/packages/nextjs/LICENSE +++ b/packages/nextjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2021 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/node/LICENSE b/packages/node/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/node/LICENSE +++ b/packages/node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nuxt/LICENSE b/packages/nuxt/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/nuxt/LICENSE +++ b/packages/nuxt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/opentelemetry/LICENSE b/packages/opentelemetry/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/opentelemetry/LICENSE +++ b/packages/opentelemetry/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/profiling-node/LICENSE b/packages/profiling-node/LICENSE index a69d859b8939..293314012679 100644 --- a/packages/profiling-node/LICENSE +++ b/packages/profiling-node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/react/LICENSE b/packages/react/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/react/LICENSE +++ b/packages/react/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/remix/LICENSE b/packages/remix/LICENSE index a69d859b8939..293314012679 100644 --- a/packages/remix/LICENSE +++ b/packages/remix/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-canvas/LICENSE b/packages/replay-canvas/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/replay-canvas/LICENSE +++ b/packages/replay-canvas/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-internal/LICENSE b/packages/replay-internal/LICENSE index a69d859b8939..293314012679 100644 --- a/packages/replay-internal/LICENSE +++ b/packages/replay-internal/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-worker/LICENSE b/packages/replay-worker/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/replay-worker/LICENSE +++ b/packages/replay-worker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solid/LICENSE b/packages/solid/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/solid/LICENSE +++ b/packages/solid/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solidstart/LICENSE b/packages/solidstart/LICENSE index 9739499d2513..63e7eb28e19c 100644 --- a/packages/solidstart/LICENSE +++ b/packages/solidstart/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/svelte/LICENSE b/packages/svelte/LICENSE index a69d859b8939..293314012679 100644 --- a/packages/svelte/LICENSE +++ b/packages/svelte/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/sveltekit/LICENSE +++ b/packages/sveltekit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/types/LICENSE b/packages/types/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/types/LICENSE +++ b/packages/types/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/typescript/LICENSE b/packages/typescript/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/typescript/LICENSE +++ b/packages/typescript/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE index eaf96aacc0c8..b3c4b18a6317 100644 --- a/packages/vercel-edge/LICENSE +++ b/packages/vercel-edge/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE index 9440728b1ced..9f2152e89993 100644 --- a/packages/vue/LICENSE +++ b/packages/vue/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/wasm/LICENSE b/packages/wasm/LICENSE index 6d1ad17eb71b..917e31f85b7a 100644 --- a/packages/wasm/LICENSE +++ b/packages/wasm/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2025 Functional Software, Inc. dba Sentry +Copyright (c) 2021 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From d789766b6fcdc468819ba25b18c557c8232d1ed6 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 23 Jan 2025 15:33:22 +0100 Subject: [PATCH 06/43] fix(node): Missing `release` from ANR sessions (#15138) --- .../node-integration-tests/suites/anr/basic-session.js | 2 +- dev-packages/node-integration-tests/suites/anr/test.ts | 3 +++ packages/node/src/integrations/anr/worker.ts | 8 +++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/anr/basic-session.js b/dev-packages/node-integration-tests/suites/anr/basic-session.js index 9700131a6040..7971d547c884 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -9,7 +9,7 @@ setTimeout(() => { Sentry.init({ dsn: process.env.SENTRY_DSN, - release: '1.0', + release: '1.0.0', integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index ec980f07f123..9a91d4f205c6 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -188,6 +188,9 @@ describe('should report ANR when event loop blocked', () => { session: { status: 'abnormal', abnormal_mechanism: 'anr_foreground', + attrs: { + release: '1.0.0', + }, }, }) .expect({ event: ANR_EVENT_WITH_SCOPE }) diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 8900b423710b..2eebfe40309b 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -46,7 +46,13 @@ async function sendAbnormalSession(): Promise { // of we have an existing session passed from the main thread, send it as abnormal if (session) { log('Sending abnormal session'); - updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' }); + + updateSession(session, { + status: 'abnormal', + abnormal_mechanism: 'anr_foreground', + release: options.release, + environment: options.environment, + }); const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata, options.tunnel); // Log the envelope so to aid in testing From 9622bba15719fa82e5b4b6863297cd01337e4525 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 23 Jan 2025 09:33:50 -0500 Subject: [PATCH 07/43] feat(core): Add client outcomes for breadcrumbs buffer (#15082) ref https://github.com/getsentry/team-sdks/issues/116 This PR implements a new client discard reason for `buffer_overflow`. This will be used to track when the internal breadcrumbs buffer overflows for the new logs product that we are working on. This is documented in develop here: https://github.com/getsentry/sentry-docs/pull/12395 Note: The reason we have `buffer_overflow` as a separate item to `queue_overflow` is that in the future when we send log items in envelopes we'll increment `queue_overflow` for the transport queue. We want to differentiate between the transport queue and the internal buffer explicitly. --- packages/core/src/scope.ts | 8 +++++--- packages/core/src/types-hoist/clientreport.ts | 3 ++- packages/core/src/types-hoist/datacategory.ts | 6 +++++- packages/core/test/lib/client.test.ts | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index ce559d589fe3..a302e5a14c34 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -479,9 +479,11 @@ export class Scope { ...breadcrumb, }; - const breadcrumbs = this._breadcrumbs; - breadcrumbs.push(mergedBreadcrumb); - this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs; + this._breadcrumbs.push(mergedBreadcrumb); + if (this._breadcrumbs.length > maxCrumbs) { + this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs); + this._client?.recordDroppedEvent('buffer_overflow', 'log_item'); + } this._notifyScopeListeners(); diff --git a/packages/core/src/types-hoist/clientreport.ts b/packages/core/src/types-hoist/clientreport.ts index b6ab1766e68c..069adec43c62 100644 --- a/packages/core/src/types-hoist/clientreport.ts +++ b/packages/core/src/types-hoist/clientreport.ts @@ -8,7 +8,8 @@ export type EventDropReason = | 'ratelimit_backoff' | 'sample_rate' | 'send_error' - | 'internal_sdk_error'; + | 'internal_sdk_error' + | 'buffer_overflow'; export type Outcome = { reason: EventDropReason; diff --git a/packages/core/src/types-hoist/datacategory.ts b/packages/core/src/types-hoist/datacategory.ts index da90cc0ca90b..2e636b605fcf 100644 --- a/packages/core/src/types-hoist/datacategory.ts +++ b/packages/core/src/types-hoist/datacategory.ts @@ -14,7 +14,7 @@ export type DataCategory = | 'replay' // Events with `event_type` csp, hpkp, expectct, expectstaple | 'security' - // Attachment bytes stored (unused for rate limiting + // Attachment bytes stored (unused for rate limiting) | 'attachment' // Session update events | 'session' @@ -28,5 +28,9 @@ export type DataCategory = | 'feedback' // Span | 'span' + // Log event + | 'log_item' + // Log bytes stored (unused for rate limiting) + | 'log_byte' // Unknown data category | 'unknown'; diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 19a10f7f509a..c415f1ceb411 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -164,6 +164,22 @@ describe('Client', () => { expect(isolationScopeBreadcrumbs).toEqual([{ message: 'hello3', timestamp: expect.any(Number) }]); }); + test('it records `buffer_overflow` client discard reason when buffer overflows', () => { + const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); + const client = new TestClient(options); + const recordLostEventSpy = jest.spyOn(client, 'recordDroppedEvent'); + setCurrentClient(client); + getIsolationScope().setClient(client); + client.init(); + + addBreadcrumb({ message: 'hello1' }); + addBreadcrumb({ message: 'hello2' }); + addBreadcrumb({ message: 'hello3' }); + + expect(recordLostEventSpy).toHaveBeenCalledTimes(2); + expect(recordLostEventSpy).toHaveBeenLastCalledWith('buffer_overflow', 'log_item'); + }); + test('calls `beforeBreadcrumb` and adds the breadcrumb without any changes', () => { const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); From ad5418d67ba4ef28c359cecf97bc70eafc71d40a Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 23 Jan 2025 16:04:05 +0100 Subject: [PATCH 08/43] fix(nextjs): Flush with `waitUntil` in `captureRequestError` (#15146) --- packages/nextjs/src/common/captureRequestError.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/nextjs/src/common/captureRequestError.ts b/packages/nextjs/src/common/captureRequestError.ts index 26fdaab4953b..c872d70f8334 100644 --- a/packages/nextjs/src/common/captureRequestError.ts +++ b/packages/nextjs/src/common/captureRequestError.ts @@ -1,5 +1,7 @@ import type { RequestEventData } from '@sentry/core'; +import { vercelWaitUntil } from '@sentry/core'; import { captureException, headersToDict, withScope } from '@sentry/core'; +import { flushSafelyWithTimeout } from './utils/responseEnd'; type RequestInfo = { path: string; @@ -39,5 +41,7 @@ export function captureRequestError(error: unknown, request: RequestInfo, errorC handled: false, }, }); + + vercelWaitUntil(flushSafelyWithTimeout()); }); } From 6b66a2844a5dc95249e789e2139246c639158e0f Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:33:08 +0100 Subject: [PATCH 09/43] feat(solidstart)!: No longer export `sentrySolidStartVite` (#15143) Since the default way of setting up the SDK and passing the Sentry options is by wrapping the SolidStart config with `withSentry`, the previous method of using the `sentrySolidStartVite` plugin is no longer supported. --- CHANGELOG.md | 44 -------------------- docs/migration/v8-to-v9.md | 16 +++++++ packages/solidstart/src/config/withSentry.ts | 2 +- packages/solidstart/src/index.server.ts | 1 - packages/solidstart/src/index.types.ts | 1 - packages/solidstart/src/vite/index.ts | 1 - 6 files changed, 17 insertions(+), 48 deletions(-) delete mode 100644 packages/solidstart/src/vite/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbfecab7da4..10de3a8c8ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,50 +12,6 @@ Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions! -- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))** - -To enable the SolidStart SDK, wrap your SolidStart Config with `withSentry`. The `sentrySolidStartVite` plugin is now automatically -added by `withSentry` and you can pass the Sentry build-time options like this: - -```js -import { defineConfig } from '@solidjs/start/config'; -import { withSentry } from '@sentry/solidstart'; - -export default defineConfig( - withSentry( - { - /* Your SolidStart config options... */ - }, - { - // Options for setting up source maps - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - }, - ), -); -``` - -With the `withSentry` wrapper, the Sentry server config should not be added to the `public` directory anymore. -Add the Sentry server config in `src/instrument.server.ts`. Then, the server config will be placed inside the server build output as `instrument.server.mjs`. - -Now, there are two options to set up the SDK: - -1. **(recommended)** Provide an `--import` CLI flag to the start command like this (path depends on your server setup): - `node --import ./.output/server/instrument.server.mjs .output/server/index.mjs` -2. Add `autoInjectServerSentry: 'top-level-import'` and the Sentry config will be imported at the top of the server entry (comes with tracing limitations) - ```js - withSentry( - { - /* Your SolidStart config options... */ - }, - { - // Optional: Install Sentry with a top-level import - autoInjectServerSentry: 'top-level-import', - }, - ); - ``` - ## 9.0.0-alpha.0 This is an alpha release of the upcoming major release of version 9. diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index a6bd0aa35e7c..e279fb0ebc86 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -234,6 +234,22 @@ Sentry.init({ - The `addNormalizedRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. - A `sampleRand` field on `PropagationContext` is now required. This is relevant if you used `scope.setPropagationContext(...)` +### `@sentry/solidstart` + +- The `sentrySolidStartVite` plugin is no longer exported. Instead, wrap the SolidStart config with `withSentry` and + provide Sentry options as the second parameter. + + ``` + // app.config.ts + import { defineConfig } from '@solidjs/start/config'; + import { withSentry } from '@sentry/solidstart'; + + export default defineConfig(withSentry( + { /* SolidStart config */ }, + { /* Sentry build-time config (like project and org) */ }) + ); + ``` + #### Other/Internal Changes The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior. diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index c1050f0da1cc..aa045ade00ab 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -1,6 +1,6 @@ import { logger } from '@sentry/core'; import type { Nitro } from 'nitropack'; -import { addSentryPluginToVite } from '../vite'; +import { addSentryPluginToVite } from '../vite/sentrySolidStartVite'; import type { SentrySolidStartPluginOptions } from '../vite/types'; import { addDynamicImportEntryFileWrapper, diff --git a/packages/solidstart/src/index.server.ts b/packages/solidstart/src/index.server.ts index a20a0367f557..82b6fe6cbff4 100644 --- a/packages/solidstart/src/index.server.ts +++ b/packages/solidstart/src/index.server.ts @@ -1,3 +1,2 @@ export * from './server'; -export * from './vite'; export * from './config'; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 39f9831c543c..54a5ec6d6a3c 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -3,7 +3,6 @@ // exports in this file - which we do below. export * from './client'; export * from './server'; -export * from './vite'; export * from './config'; import type { Client, Integration, Options, StackParser } from '@sentry/core'; diff --git a/packages/solidstart/src/vite/index.ts b/packages/solidstart/src/vite/index.ts deleted file mode 100644 index 464bbd604fbe..000000000000 --- a/packages/solidstart/src/vite/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './sentrySolidStartVite'; From 965185cbb8bf801fcddebea3a0fead4e3f152c29 Mon Sep 17 00:00:00 2001 From: Jacob Hands Date: Thu, 23 Jan 2025 11:41:14 -0600 Subject: [PATCH 10/43] feat(core): Improve error formatting in ZodErrors integration (#15111) - Include full key path rather than the top level key in title - Improve message for validation issues with no path - Add option to include extended issue information as an attachment --- packages/core/package.json | 5 +- packages/core/src/integrations/zoderrors.ts | 170 +++++-- .../test/lib/integrations/zoderrrors.test.ts | 432 +++++++++++++++++- yarn.lock | 43 +- 4 files changed, 576 insertions(+), 74 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 06e8d253a628..7724f703833e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -61,5 +61,8 @@ "volta": { "extends": "../../package.json" }, - "sideEffects": false + "sideEffects": false, + "devDependencies": { + "zod": "^3.24.1" + } } diff --git a/packages/core/src/integrations/zoderrors.ts b/packages/core/src/integrations/zoderrors.ts index a408285800d9..4859ca5167fa 100644 --- a/packages/core/src/integrations/zoderrors.ts +++ b/packages/core/src/integrations/zoderrors.ts @@ -5,33 +5,45 @@ import { truncate } from '../utils-hoist/string'; interface ZodErrorsOptions { key?: string; + /** + * Limits the number of Zod errors inlined in each Sentry event. + * + * @default 10 + */ limit?: number; + /** + * Save full list of Zod issues as an attachment in Sentry + * + * @default false + */ + saveZodIssuesAsAttachment?: boolean; } const DEFAULT_LIMIT = 10; const INTEGRATION_NAME = 'ZodErrors'; -// Simplified ZodIssue type definition +/** + * Simplified ZodIssue type definition + */ interface ZodIssue { path: (string | number)[]; message?: string; - expected?: string | number; - received?: string | number; + expected?: unknown; + received?: unknown; unionErrors?: unknown[]; keys?: unknown[]; + invalid_literal?: unknown; } interface ZodError extends Error { issues: ZodIssue[]; - - get errors(): ZodError['issues']; } function originalExceptionIsZodError(originalException: unknown): originalException is ZodError { return ( isError(originalException) && originalException.name === 'ZodError' && - Array.isArray((originalException as ZodError).errors) + Array.isArray((originalException as ZodError).issues) ); } @@ -45,9 +57,18 @@ type SingleLevelZodIssue = { /** * Formats child objects or arrays to a string - * That is preserved when sent to Sentry + * that is preserved when sent to Sentry. + * + * Without this, we end up with something like this in Sentry: + * + * [ + * [Object], + * [Object], + * [Object], + * [Object] + * ] */ -function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { +export function flattenIssue(issue: ZodIssue): SingleLevelZodIssue { return { ...issue, path: 'path' in issue && Array.isArray(issue.path) ? issue.path.join('.') : undefined, @@ -56,64 +77,145 @@ function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { }; } +/** + * Takes ZodError issue path array and returns a flattened version as a string. + * This makes it easier to display paths within a Sentry error message. + * + * Array indexes are normalized to reduce duplicate entries + * + * @param path ZodError issue path + * @returns flattened path + * + * @example + * flattenIssuePath([0, 'foo', 1, 'bar']) // -> '.foo..bar' + */ +export function flattenIssuePath(path: Array): string { + return path + .map(p => { + if (typeof p === 'number') { + return ''; + } else { + return p; + } + }) + .join('.'); +} + /** * Zod error message is a stringified version of ZodError.issues * This doesn't display well in the Sentry UI. Replace it with something shorter. */ -function formatIssueMessage(zodError: ZodError): string { +export function formatIssueMessage(zodError: ZodError): string { const errorKeyMap = new Set(); for (const iss of zodError.issues) { - if (iss.path?.[0]) { - errorKeyMap.add(iss.path[0]); + const issuePath = flattenIssuePath(iss.path); + if (issuePath.length > 0) { + errorKeyMap.add(issuePath); } } - const errorKeys = Array.from(errorKeyMap); + const errorKeys = Array.from(errorKeyMap); + if (errorKeys.length === 0) { + // If there are no keys, then we're likely validating the root + // variable rather than a key within an object. This attempts + // to extract what type it was that failed to validate. + // For example, z.string().parse(123) would return "string" here. + let rootExpectedType = 'variable'; + if (zodError.issues.length > 0) { + const iss = zodError.issues[0]; + if (iss !== undefined && 'expected' in iss && typeof iss.expected === 'string') { + rootExpectedType = iss.expected; + } + } + return `Failed to validate ${rootExpectedType}`; + } return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`; } /** - * Applies ZodError issues to an event extras and replaces the error message + * Applies ZodError issues to an event extra and replaces the error message */ -export function applyZodErrorsToEvent(limit: number, event: Event, hint?: EventHint): Event { +export function applyZodErrorsToEvent( + limit: number, + saveZodIssuesAsAttachment: boolean = false, + event: Event, + hint: EventHint, +): Event { if ( !event.exception?.values || - !hint?.originalException || + !hint.originalException || !originalExceptionIsZodError(hint.originalException) || hint.originalException.issues.length === 0 ) { return event; } - return { - ...event, - exception: { - ...event.exception, - values: [ - { - ...event.exception.values[0], - value: formatIssueMessage(hint.originalException), + try { + const issuesToFlatten = saveZodIssuesAsAttachment + ? hint.originalException.issues + : hint.originalException.issues.slice(0, limit); + const flattenedIssues = issuesToFlatten.map(flattenIssue); + + if (saveZodIssuesAsAttachment) { + // Sometimes having the full error details can be helpful. + // Attachments have much higher limits, so we can include the full list of issues. + if (!Array.isArray(hint.attachments)) { + hint.attachments = []; + } + hint.attachments.push({ + filename: 'zod_issues.json', + data: JSON.stringify({ + issues: flattenedIssues, + }), + }); + } + + return { + ...event, + exception: { + ...event.exception, + values: [ + { + ...event.exception.values[0], + value: formatIssueMessage(hint.originalException), + }, + ...event.exception.values.slice(1), + ], + }, + extra: { + ...event.extra, + 'zoderror.issues': flattenedIssues.slice(0, limit), + }, + }; + } catch (e) { + // Hopefully we never throw errors here, but record it + // with the event just in case. + return { + ...event, + extra: { + ...event.extra, + 'zoderrors sentry integration parse error': { + message: 'an exception was thrown while processing ZodError within applyZodErrorsToEvent()', + error: e instanceof Error ? `${e.name}: ${e.message}\n${e.stack}` : 'unknown', }, - ...event.exception.values.slice(1), - ], - }, - extra: { - ...event.extra, - 'zoderror.issues': hint.originalException.errors.slice(0, limit).map(formatIssueTitle), - }, - }; + }, + }; + } } const _zodErrorsIntegration = ((options: ZodErrorsOptions = {}) => { - const limit = options.limit || DEFAULT_LIMIT; + const limit = options.limit ?? DEFAULT_LIMIT; return { name: INTEGRATION_NAME, - processEvent(originalEvent, hint) { - const processedEvent = applyZodErrorsToEvent(limit, originalEvent, hint); + processEvent(originalEvent, hint): Event { + const processedEvent = applyZodErrorsToEvent(limit, options.saveZodIssuesAsAttachment, originalEvent, hint); return processedEvent; }, }; }) satisfies IntegrationFn; +/** + * Sentry integration to process Zod errors, making them easier to work with in Sentry. + */ export const zodErrorsIntegration = defineIntegration(_zodErrorsIntegration); diff --git a/packages/core/test/lib/integrations/zoderrrors.test.ts b/packages/core/test/lib/integrations/zoderrrors.test.ts index 924ee5dd27da..cd80e2347f36 100644 --- a/packages/core/test/lib/integrations/zoderrrors.test.ts +++ b/packages/core/test/lib/integrations/zoderrrors.test.ts @@ -1,6 +1,12 @@ +import { z } from 'zod'; import type { Event, EventHint } from '../../../src/types-hoist'; -import { applyZodErrorsToEvent } from '../../../src/integrations/zoderrors'; +import { + applyZodErrorsToEvent, + flattenIssue, + flattenIssuePath, + formatIssueMessage, +} from '../../../src/integrations/zoderrors'; // Simplified type definition interface ZodIssue { @@ -40,13 +46,13 @@ describe('applyZodErrorsToEvent()', () => { test('should not do anything if exception is not a ZodError', () => { const event: Event = {}; const eventHint: EventHint = { originalException: new Error() }; - applyZodErrorsToEvent(100, event, eventHint); + applyZodErrorsToEvent(100, false, event, eventHint); // no changes expect(event).toStrictEqual({}); }); - test('should add ZodError issues to extras and format message', () => { + test('should add ZodError issues to extra and format message', () => { const issues = [ { code: 'invalid_type', @@ -71,13 +77,13 @@ describe('applyZodErrorsToEvent()', () => { }; const eventHint: EventHint = { originalException }; - const processedEvent = applyZodErrorsToEvent(100, event, eventHint); + const processedEvent = applyZodErrorsToEvent(100, false, event, eventHint); expect(processedEvent.exception).toStrictEqual({ values: [ { type: 'Error', - value: 'Failed to validate keys: names', + value: 'Failed to validate keys: names.', }, ], }); @@ -92,5 +98,421 @@ describe('applyZodErrorsToEvent()', () => { }, ], }); + + // No attachments added + expect(eventHint.attachments).toBe(undefined); + }); + + test('should add all ZodError issues as attachment', () => { + const issues = [ + { + code: 'invalid_type', + expected: 'string', + received: 'number', + path: ['names', 1], + keys: ['extra'], + message: 'Invalid input: expected string, received number', + }, + { + code: 'invalid_type', + expected: 'string', + received: 'number', + path: ['foo', 1], + keys: ['extra2'], + message: 'Invalid input: expected string, received number', + }, + ] satisfies ZodIssue[]; + const originalException = ZodError.create(issues); + + const event: Event = { + exception: { + values: [ + { + type: 'Error', + value: originalException.message, + }, + ], + }, + }; + + const eventHint: EventHint = { originalException }; + const processedEvent = applyZodErrorsToEvent(1, true, event, eventHint); + + expect(processedEvent.exception).toStrictEqual({ + values: [ + { + type: 'Error', + value: 'Failed to validate keys: names., foo.', + }, + ], + }); + + // Only adds the first issue to extra due to the limit + expect(processedEvent.extra).toStrictEqual({ + 'zoderror.issues': [ + { + ...issues[0], + path: issues[0]?.path.join('.'), + keys: JSON.stringify(issues[0]?.keys), + unionErrors: undefined, + }, + ], + }); + + // hint attachments contains the full issue list + expect(Array.isArray(eventHint.attachments)).toBe(true); + expect(eventHint.attachments?.length).toBe(1); + const attachment = eventHint.attachments?.[0]; + if (attachment === undefined) { + throw new Error('attachment is undefined'); + } + expect(attachment.filename).toBe('zod_issues.json'); + expect(JSON.parse(attachment.data.toString())).toMatchInlineSnapshot(` +Object { + "issues": Array [ + Object { + "code": "invalid_type", + "expected": "string", + "keys": "[\\"extra\\"]", + "message": "Invalid input: expected string, received number", + "path": "names.1", + "received": "number", + }, + Object { + "code": "invalid_type", + "expected": "string", + "keys": "[\\"extra2\\"]", + "message": "Invalid input: expected string, received number", + "path": "foo.1", + "received": "number", + }, + ], +} +`); + }); +}); + +describe('flattenIssue()', () => { + it('flattens path field', () => { + const zodError = z + .object({ + foo: z.string().min(1), + nested: z.object({ + bar: z.literal('baz'), + }), + }) + .safeParse({ + foo: '', + nested: { + bar: 'not-baz', + }, + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": Array [ + "foo", + ], + "type": "string", + }, + Object { + "code": "invalid_literal", + "expected": "baz", + "message": "Invalid literal value, expected \\"baz\\"", + "path": Array [ + "nested", + "bar", + ], + "received": "not-baz", + }, +] +`); + + const issues = zodError.issues; + expect(issues.length).toBe(2); + + // Format it for use in Sentry + expect(issues.map(flattenIssue)).toMatchInlineSnapshot(` +Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "keys": undefined, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": "foo", + "type": "string", + "unionErrors": undefined, + }, + Object { + "code": "invalid_literal", + "expected": "baz", + "keys": undefined, + "message": "Invalid literal value, expected \\"baz\\"", + "path": "nested.bar", + "received": "not-baz", + "unionErrors": undefined, + }, +] +`); + + expect(zodError.flatten(flattenIssue)).toMatchInlineSnapshot(` +Object { + "fieldErrors": Object { + "foo": Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "keys": undefined, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": "foo", + "type": "string", + "unionErrors": undefined, + }, + ], + "nested": Array [ + Object { + "code": "invalid_literal", + "expected": "baz", + "keys": undefined, + "message": "Invalid literal value, expected \\"baz\\"", + "path": "nested.bar", + "received": "not-baz", + "unionErrors": undefined, + }, + ], + }, + "formErrors": Array [], +} +`); + }); + + it('flattens keys field to string', () => { + const zodError = z + .object({ + foo: z.string().min(1), + }) + .strict() + .safeParse({ + foo: 'bar', + extra_key_abc: 'hello', + extra_key_def: 'world', + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "unrecognized_keys", + "keys": Array [ + "extra_key_abc", + "extra_key_def", + ], + "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'", + "path": Array [], + }, +] +`); + + const issues = zodError.issues; + expect(issues.length).toBe(1); + + // Format it for use in Sentry + const iss = issues[0]; + if (iss === undefined) { + throw new Error('iss is undefined'); + } + const formattedIssue = flattenIssue(iss); + + // keys is now a string rather than array. + // Note: path is an empty string because the issue is at the root. + // TODO: Maybe somehow make it clearer that this is at the root? + expect(formattedIssue).toMatchInlineSnapshot(` +Object { + "code": "unrecognized_keys", + "keys": "[\\"extra_key_abc\\",\\"extra_key_def\\"]", + "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'", + "path": "", + "unionErrors": undefined, +} +`); + expect(typeof formattedIssue.keys === 'string').toBe(true); + }); +}); + +describe('flattenIssuePath()', () => { + it('returns single path', () => { + expect(flattenIssuePath(['foo'])).toBe('foo'); + }); + + it('flattens nested string paths', () => { + expect(flattenIssuePath(['foo', 'bar'])).toBe('foo.bar'); + }); + + it('uses placeholder for path index within array', () => { + expect(flattenIssuePath([0, 'foo', 1, 'bar', 'baz'])).toBe('.foo..bar.baz'); + }); +}); + +describe('formatIssueMessage()', () => { + it('adds invalid keys to message', () => { + const zodError = z + .object({ + foo: z.string().min(1), + nested: z.object({ + bar: z.literal('baz'), + }), + }) + .safeParse({ + foo: '', + nested: { + bar: 'not-baz', + }, + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate keys: foo, nested.bar"'); + }); + + describe('adds expected type if root variable is invalid', () => { + test('object', () => { + const zodError = z + .object({ + foo: z.string().min(1), + }) + .safeParse(123).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "object", + "message": "Expected object, received number", + "path": Array [], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate object"'); + }); + + test('number', () => { + const zodError = z.number().safeParse('123').error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "number", + "message": "Expected number, received string", + "path": Array [], + "received": "string", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate number"'); + }); + + test('string', () => { + const zodError = z.string().safeParse(123).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "string", + "message": "Expected string, received number", + "path": Array [], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate string"'); + }); + + test('array', () => { + const zodError = z.string().array().safeParse('123').error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "array", + "message": "Expected array, received string", + "path": Array [], + "received": "string", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate array"'); + }); + + test('wrong type in array', () => { + const zodError = z.string().array().safeParse([123]).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "string", + "message": "Expected string, received number", + "path": Array [ + 0, + ], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate keys: "'); + }); }); }); diff --git a/yarn.lock b/yarn.lock index 5627089a7941..2e69bdb6d480 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7890,12 +7890,7 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -27426,16 +27421,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27538,14 +27524,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -30335,16 +30314,7 @@ wrangler@^3.67.1: optionalDependencies: fsevents "~2.3.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -30646,6 +30616,11 @@ zod@^3.22.3, zod@^3.22.4: resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== +zod@^3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== + zone.js@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.12.0.tgz#a4a6e5fab6d34bd37d89c77e89ac2e6f4a3d2c30" From 2e708dcdd257a75fa585475a9e536e7840d71616 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Thu, 23 Jan 2025 19:18:23 +0100 Subject: [PATCH 11/43] chore: Add external contributor to CHANGELOG.md (#15152) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #15111 Co-authored-by: Abhijeet Prasad --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10de3a8c8ca5..c0861fcc85cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, and @jahands. Thank you for your contributions! ## 9.0.0-alpha.0 From 5bc956dba00cdeb94462c2eaf9a2a18a6653b0c5 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 24 Jan 2025 16:09:15 +0100 Subject: [PATCH 12/43] Reference LOGAF Scale from develop docs (#15154) Link to the Sentry develop docs for explaining the LOGAF scale. --- docs/pr-reviews.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/pr-reviews.md b/docs/pr-reviews.md index 87b5faafb950..65703c2cebb6 100644 --- a/docs/pr-reviews.md +++ b/docs/pr-reviews.md @@ -2,12 +2,8 @@ Make sure to open PRs against `develop` branch. -For feedback in PRs, we use the [LOGAF scale](https://blog.danlew.net/2020/04/15/the-logaf-scale/) to specify how -important a comment is: - -- `l`: low - nitpick. You may address this comment, but you don't have to. -- `m`: medium - normal comment. Worth addressing and fixing. -- `h`: high - Very important. We must not merge this PR without addressing this issue. +For feedback in PRs, we use the [LOGAF scale](https://develop.sentry.dev/engineering-practices/code-review/#logaf-scale) to specify how +important a comment is. You only need one approval from a maintainer to be able to merge. For some PRs, asking specific or multiple people for review might be adequate. You can either assign SDK team members directly (e.g. if you have some people in mind who are From 8e37842bff9a4930967859157f469a5a9f145aef Mon Sep 17 00:00:00 2001 From: Nathan Kleyn Date: Fri, 24 Jan 2025 18:08:30 +0000 Subject: [PATCH 13/43] fix(bun): Ensure instrumentation of `Bun.serve` survives a server reload (#15148) If `#reload` is called on an instance of `Bun.serve`, the Sentry intrumentation doesn't surive. This is because the Bun instrumentation works by using `Proxy` on the call to `Bun.serve`, which isn't called for a reload. We can't wrap the serve created by calling `Bun.serve` with a `Proxy` as Bun seems to do some internal checks using `instanceof` which break if the instance is now reporting itself as a `ProxyObject`. --- packages/bun/src/integrations/bunserver.ts | 13 +- .../bun/test/integrations/bunserver.test.ts | 130 +++++++++++++----- packages/bun/test/sdk.test.ts | 20 ++- 3 files changed, 119 insertions(+), 44 deletions(-) diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index d8ee46abae73..1f1974839455 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -47,7 +47,18 @@ export function instrumentBunServe(): void { Bun.serve = new Proxy(Bun.serve, { apply(serveTarget, serveThisArg, serveArgs: Parameters) { instrumentBunServeOptions(serveArgs[0]); - return serveTarget.apply(serveThisArg, serveArgs); + const server: ReturnType = serveTarget.apply(serveThisArg, serveArgs); + + // A Bun server can be reloaded, re-wrap any fetch function passed to it + // We can't use a Proxy for this as Bun does `instanceof` checks internally that fail if we + // wrap the Server instance. + const originalReload: typeof server.reload = server.reload.bind(server); + server.reload = (serveOptions: Parameters[0]) => { + instrumentBunServeOptions(serveOptions); + return originalReload(serveOptions); + }; + + return server; }, }); } diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index b1dc17381ccb..dd1f738a334b 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -1,67 +1,87 @@ -import { beforeAll, beforeEach, describe, expect, test } from 'bun:test'; +import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'; +import type { Span } from '@sentry/core'; import { getDynamicSamplingContextFromSpan, setCurrentClient, spanIsSampled, spanToJSON } from '@sentry/core'; import { BunClient } from '../../src/client'; import { instrumentBunServe } from '../../src/integrations/bunserver'; import { getDefaultBunClientOptions } from '../helpers'; -// Fun fact: Bun = 2 21 14 :) -const DEFAULT_PORT = 22114; - describe('Bun Serve Integration', () => { let client: BunClient; + // Fun fact: Bun = 2 21 14 :) + let port: number = 22114; beforeAll(() => { instrumentBunServe(); }); beforeEach(() => { - const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true }); + const options = getDefaultBunClientOptions({ tracesSampleRate: 1 }); client = new BunClient(options); setCurrentClient(client); client.init(); }); + afterEach(() => { + // Don't reuse the port; Bun server stops lazily so tests may accidentally hit a server still closing from a + // previous test + port += 1; + }); + test('generates a transaction around a request', async () => { + let generatedSpan: Span | undefined; + client.on('spanEnd', span => { - expect(spanToJSON(span).status).toBe('ok'); - expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(span).op).toEqual('http.server'); - expect(spanToJSON(span).description).toEqual('GET /'); + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); + await fetch(`http://localhost:${port}/`); + server.stop(); - await fetch('http://localhost:22114/'); + if (!generatedSpan) { + throw 'No span was generated in the test'; + } - server.stop(); + expect(spanToJSON(generatedSpan).status).toBe('ok'); + expect(spanToJSON(generatedSpan).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(generatedSpan).op).toEqual('http.server'); + expect(spanToJSON(generatedSpan).description).toEqual('GET /'); }); test('generates a post transaction', async () => { + let generatedSpan: Span | undefined; + client.on('spanEnd', span => { - expect(spanToJSON(span).status).toBe('ok'); - expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(span).op).toEqual('http.server'); - expect(spanToJSON(span).description).toEqual('POST /'); + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'POST', }); server.stop(); + + if (!generatedSpan) { + throw 'No span was generated in the test'; + } + + expect(spanToJSON(generatedSpan).status).toBe('ok'); + expect(spanToJSON(generatedSpan).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(generatedSpan).op).toEqual('http.server'); + expect(spanToJSON(generatedSpan).description).toEqual('POST /'); }); test('continues a trace', async () => { @@ -70,55 +90,93 @@ describe('Bun Serve Integration', () => { const PARENT_SAMPLED = '1'; const SENTRY_TRACE_HEADER = `${TRACE_ID}-${PARENT_SPAN_ID}-${PARENT_SAMPLED}`; - const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-environment=production'; + const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-sample_rand=0.42,sentry-environment=production'; - client.on('spanEnd', span => { - expect(span.spanContext().traceId).toBe(TRACE_ID); - expect(spanToJSON(span).parent_span_id).toBe(PARENT_SPAN_ID); - expect(spanIsSampled(span)).toBe(true); - expect(span.isRecording()).toBe(false); + let generatedSpan: Span | undefined; - expect(getDynamicSamplingContextFromSpan(span)).toStrictEqual({ - version: '1.0', - environment: 'production', - }); + client.on('spanEnd', span => { + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { headers: { 'sentry-trace': SENTRY_TRACE_HEADER, baggage: SENTRY_BAGGAGE_HEADER }, }); server.stop(); + + if (!generatedSpan) { + throw 'No span was generated in the test'; + } + + expect(generatedSpan.spanContext().traceId).toBe(TRACE_ID); + expect(spanToJSON(generatedSpan).parent_span_id).toBe(PARENT_SPAN_ID); + expect(spanIsSampled(generatedSpan)).toBe(true); + expect(generatedSpan.isRecording()).toBe(false); + + expect(getDynamicSamplingContextFromSpan(generatedSpan)).toStrictEqual({ + version: '1.0', + sample_rand: '0.42', + environment: 'production', + }); }); test('does not create transactions for OPTIONS or HEAD requests', async () => { - client.on('spanEnd', () => { - // This will never run, but we want to make sure it doesn't run. - expect(false).toEqual(true); + let generatedSpan: Span | undefined; + + client.on('spanEnd', span => { + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'OPTIONS', }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'HEAD', }); server.stop(); + + expect(generatedSpan).toBeUndefined(); + }); + + test('intruments the server again if it is reloaded', async () => { + let serverWasInstrumented = false; + client.on('spanEnd', () => { + serverWasInstrumented = true; + }); + + const server = Bun.serve({ + async fetch(_req) { + return new Response('Bun!'); + }, + port, + }); + + server.reload({ + async fetch(_req) { + return new Response('Reloaded Bun!'); + }, + }); + + await fetch(`http://localhost:${port}/`); + + server.stop(); + + expect(serverWasInstrumented).toBeTrue(); }); }); diff --git a/packages/bun/test/sdk.test.ts b/packages/bun/test/sdk.test.ts index a548cc2614c7..11870f30c101 100644 --- a/packages/bun/test/sdk.test.ts +++ b/packages/bun/test/sdk.test.ts @@ -1,14 +1,20 @@ -import { expect, test } from 'bun:test'; +import { describe, expect, test } from 'bun:test'; import { init } from '../src/index'; -test("calling init shouldn't fail", () => { - init({ +describe('Bun SDK', () => { + const initOptions = { dsn: 'https://00000000000000000000000000000000@o000000.ingest.sentry.io/0000000', + tracesSampleRate: 1, + }; + + test("calling init shouldn't fail", () => { + expect(() => { + init(initOptions); + }).not.toThrow(); }); - expect(true).toBe(true); -}); -test('should return client from init', () => { - expect(init({})).not.toBeUndefined(); + test('should return client from init', () => { + expect(init(initOptions)).not.toBeUndefined(); + }); }); From a67d3ca606589326719481fa5405e8f67ed273b9 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 24 Jan 2025 19:21:02 +0100 Subject: [PATCH 14/43] chore: Add external contributor to CHANGELOG.md (#15156) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #15148 --------- Co-authored-by: AbhiPrasad <18689448+AbhiPrasad@users.noreply.github.com> Co-authored-by: Abhijeet Prasad --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0861fcc85cc..9afee28eb184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, and @jahands. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, and @nathankleyn. Thank you for your contributions! ## 9.0.0-alpha.0 From 70d53a992836945fc80db2e0a16b74ed1acaecbe Mon Sep 17 00:00:00 2001 From: "Randolf J." <34705014+jrandolf@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:00:30 -0800 Subject: [PATCH 15/43] fix(core): Pass `module` into `loadModule` (#15139) The `loadModule` function currently utilizes `require` to load modules starting from the `@sentry/core` directory. This approach is incompatible with package managers like pnpm, which do not hoist dependencies. Consequently, when another module, such as @sentry/nextjs, invokes `loadModule`, it fails to locate its own dependencies because the search initiates within the @sentry/core tree. --- packages/core/src/utils-hoist/node.ts | 10 ++++++---- packages/nextjs/src/config/webpack.ts | 2 +- packages/remix/src/utils/instrumentServer.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/src/utils-hoist/node.ts b/packages/core/src/utils-hoist/node.ts index a0311efc7a93..94e8001863aa 100644 --- a/packages/core/src/utils-hoist/node.ts +++ b/packages/core/src/utils-hoist/node.ts @@ -41,21 +41,23 @@ function dynamicRequire(mod: any, request: string): any { * That is to mimic the behavior of `require.resolve` exactly. * * @param moduleName module name to require + * @param existingModule module to use for requiring * @returns possibly required module */ -export function loadModule(moduleName: string): T | undefined { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function loadModule(moduleName: string, existingModule: any = module): T | undefined { let mod: T | undefined; try { - mod = dynamicRequire(module, moduleName); + mod = dynamicRequire(existingModule, moduleName); } catch (e) { // no-empty } if (!mod) { try { - const { cwd } = dynamicRequire(module, 'process'); - mod = dynamicRequire(module, `${cwd()}/node_modules/${moduleName}`) as T; + const { cwd } = dynamicRequire(existingModule, 'process'); + mod = dynamicRequire(existingModule, `${cwd()}/node_modules/${moduleName}`) as T; } catch (e) { // no-empty } diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index bb73a2fb1859..8dbebf3935df 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -332,7 +332,7 @@ export function constructWebpackConfigFunction( // Symbolication for dev-mode errors is done elsewhere. if (!isDev) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin') ?? {}; + const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin', module) ?? {}; if (sentryWebpackPlugin) { if (!userSentryOptions.sourcemaps?.disable) { diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index a0dd11874416..c6113ea7f0a3 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -301,7 +301,7 @@ const makeWrappedCreateRequestHandler = () => export function instrumentServer(): void { const pkg = loadModule<{ createRequestHandler: CreateRequestHandlerFunction; - }>('@remix-run/server-runtime'); + }>('@remix-run/server-runtime', module); if (!pkg) { DEBUG_BUILD && logger.warn('Remix SDK was unable to require `@remix-run/server-runtime` package.'); From 6571e0c608e70b462253ebcbbc158004fa57ae75 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 27 Jan 2025 10:16:11 +0100 Subject: [PATCH 16/43] chore: Add external contributor to CHANGELOG.md (#15165) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #15139 --------- Co-authored-by: Lukas Stracke --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9afee28eb184..a30b5747e9dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, and @nathankleyn. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions! ## 9.0.0-alpha.0 From 7a208e17d23edb9c5d0afc7709c46fb9ffffa3e3 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 27 Jan 2025 15:26:39 +0100 Subject: [PATCH 17/43] test(e2e): Unflake replay recording data optional e2e test (#15168) Relax the assertion for the replay recording data test so that we allow arbitrary order but still assert on the exact amount and contents of the received recording items. closes #15167 --- .../react-send-to-sentry/tests/send-to-sentry.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts index d9c3e09f2ad2..dc33d271bc18 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts @@ -190,7 +190,7 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { if (response.ok) { const data = await response.json(); - return data[0]; + return { data: data[0], length: data[0].length }; } return response.status; @@ -199,5 +199,6 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { timeout: EVENT_POLLING_TIMEOUT, }, ) - .toEqual(ReplayRecordingData); + // Check that that all expected data is present but relax the order to avoid flakes + .toEqual({ data: expect.arrayContaining(ReplayRecordingData), length: ReplayRecordingData.length }); }); From a0c126b954ab2e7e3a13543ce04924e5d3162681 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 27 Jan 2025 16:46:13 +0100 Subject: [PATCH 18/43] test(e2e/nextjs): Avoid making request to example.com (#15170) example.com still seems flaky, let's avoid making requests to it --- .../nextjs-14/app/request-instrumentation/page.tsx | 4 ++-- .../nextjs-14/tests/request-instrumentation.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx index 0d877296cced..c73e6f79db00 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx @@ -3,9 +3,9 @@ import https from 'https'; export const dynamic = 'force-dynamic'; export default async function Page() { - await fetch('https://example.com/', { cache: 'no-cache' }).then(res => res.text()); + await fetch('https://github.com/', { cache: 'no-cache' }).then(res => res.text()); await new Promise(resolve => { - https.get('https://example.com/', res => { + https.get('https://github.com/', res => { res.on('data', () => { // Noop consuming some data so that request can close :) }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts index d26d4e871b6e..2446ffa68659 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts @@ -19,7 +19,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => { 'sentry.op': 'http.client', 'sentry.origin': 'auto.http.otel.node_fetch', }), - description: 'GET https://example.com/', + description: 'GET https://github.com/', }), ); @@ -30,7 +30,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => { 'sentry.op': 'http.client', 'sentry.origin': 'auto.http.otel.http', }), - description: 'GET https://example.com/', + description: 'GET https://github.com/', }), ); }); From 6ce53653c19105681784ae4b9d614b69970f5d84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 15:47:51 +0000 Subject: [PATCH 19/43] feat(deps): bump @sentry/cli from 2.39.1 to 2.41.1 (#15173) --- packages/remix/package.json | 2 +- yarn.lock | 95 +++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index 6c88c8f08797..c40305ee8140 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -54,7 +54,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.0", "@remix-run/router": "1.x", - "@sentry/cli": "^2.39.1", + "@sentry/cli": "^2.41.1", "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", "@sentry/opentelemetry": "9.0.0-alpha.0", diff --git a/yarn.lock b/yarn.lock index 2e69bdb6d480..d80fe6f3225e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6612,37 +6612,72 @@ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781" integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ== +"@sentry/cli-darwin@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.41.1.tgz#ca7e12bf1ad59bc2df35868ae98abc8869108efa" + integrity sha512-7pS3pu/SuhE6jOn3wptstAg6B5nUP878O6s+2svT7b5fKNfYUi/6NPK6dAveh2Ca0rwVq40TO4YFJabWMgTpdQ== + "@sentry/cli-linux-arm64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6" integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw== +"@sentry/cli-linux-arm64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.41.1.tgz#948e8af8290418b1562db3531db08e69e39d74bb" + integrity sha512-EzYCEnnENBnS5kpNW+2dBcrPZn1MVfywh2joGVQZTpmgDL5YFJ59VOd+K0XuEwqgFI8BSNI14KXZ75s4DD1/Vw== + "@sentry/cli-linux-arm@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c" integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ== +"@sentry/cli-linux-arm@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.41.1.tgz#1e5fa971ae8dfb3ea5564c8503b4e635ae6aed8a" + integrity sha512-wNUvquD6qjOCczvuBGf9OiD29nuQ6yf8zzfyPJa5Bdx1QXuteKsKb6HBrMwuIR3liyuu0duzHd+H/+p1n541Hg== + "@sentry/cli-linux-i686@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201" integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg== +"@sentry/cli-linux-i686@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.41.1.tgz#3f01aff314f2ad8fd761f3e6e807a5ec09ae4eb4" + integrity sha512-urpQCWrdYnSAsZY3udttuMV88wTJzKZL10xsrp7sjD/Hd+O6qSLVLkxebIlxts70jMLLFHYrQ2bkRg5kKuX6Fg== + "@sentry/cli-linux-x64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889" integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA== +"@sentry/cli-linux-x64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.41.1.tgz#30dbf966a4b4c1721ffccd901dfcb6f967db073d" + integrity sha512-ZqpYwHXAaK4MMEFlyaLYr6mJTmpy9qP6n30jGhLTW7kHKS3s6GPLCSlNmIfeClrInEt0963fM633ZRnXa04VPw== + "@sentry/cli-win32-i686@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35" integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q== +"@sentry/cli-win32-i686@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.41.1.tgz#f88eeb5d2d4ee46c38d8616ae1eb484108ea71c2" + integrity sha512-AuRimCeVsx99DIOr9cwdYBHk39tlmAuPDdy2r16iNzY0InXs4xOys4gGzM7N4vlFQvFkzuc778Su0HkfasgprA== + "@sentry/cli-win32-x64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb" integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw== -"@sentry/cli@2.39.1", "@sentry/cli@^2.36.1", "@sentry/cli@^2.39.1": +"@sentry/cli-win32-x64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.41.1.tgz#eefd95a2aa184adb464334e265b55a9142070f6f" + integrity sha512-6JcPvXGye61+wPp0xdzfc2YLE/Dcud8JdaK8VxLM3b/8+Em7E+UyliDu3uF8+YGUqizY5JYTd3fs17DC8DZhLw== + +"@sentry/cli@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29" integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ== @@ -6661,6 +6696,25 @@ "@sentry/cli-win32-i686" "2.39.1" "@sentry/cli-win32-x64" "2.39.1" +"@sentry/cli@^2.36.1", "@sentry/cli@^2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.41.1.tgz#a9467ca3ff4acfcdedec1565c9ff726b93758d29" + integrity sha512-0GVmDiTV7R1492wkVY4bGcfC0fSmRmQjuxaaPI8CIV9B2VP9pBVCUizi1mevXaaE4I3fM60LI+XYrKFEneuVog== + dependencies: + https-proxy-agent "^5.0.0" + node-fetch "^2.6.7" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + optionalDependencies: + "@sentry/cli-darwin" "2.41.1" + "@sentry/cli-linux-arm" "2.41.1" + "@sentry/cli-linux-arm64" "2.41.1" + "@sentry/cli-linux-i686" "2.41.1" + "@sentry/cli-linux-x64" "2.41.1" + "@sentry/cli-win32-i686" "2.41.1" + "@sentry/cli-win32-x64" "2.41.1" + "@sentry/rollup-plugin@2.22.7": version "2.22.7" resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.7.tgz#994bb859437eb1e5fd34c485aaa79ba14354778f" @@ -7890,7 +7944,12 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8": +"@types/history-4@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history-5@npm:@types/history@4.7.8": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -27421,7 +27480,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27524,7 +27592,14 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -27689,6 +27764,7 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" + uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" @@ -30314,7 +30390,16 @@ wrangler@^3.67.1: optionalDependencies: fsevents "~2.3.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 5ef37773fb9bb8c9baa929a1eddab59fcaf1c6c9 Mon Sep 17 00:00:00 2001 From: chris-basebone <100691954+chris-basebone@users.noreply.github.com> Date: Tue, 28 Jan 2025 09:48:22 +0200 Subject: [PATCH 20/43] feat(nuxt): Add `url` to `SourcemapsUploadOptions` (#15171) --------- Co-authored-by: Lukas Stracke --- packages/nuxt/src/common/types.ts | 7 +++++++ packages/nuxt/src/vite/sourceMaps.ts | 1 + packages/nuxt/test/vite/sourceMaps.test.ts | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/packages/nuxt/src/common/types.ts b/packages/nuxt/src/common/types.ts index 93ca94016924..8a9a453ff7db 100644 --- a/packages/nuxt/src/common/types.ts +++ b/packages/nuxt/src/common/types.ts @@ -32,6 +32,13 @@ type SourceMapsOptions = { */ org?: string; + /** + * The URL of your Sentry instance if you're using self-hosted Sentry. + * + * @default https://sentry.io by default the plugin will point towards the Sentry SaaS URL + */ + url?: string; + /** * The project slug of your Sentry project. * Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable. diff --git a/packages/nuxt/src/vite/sourceMaps.ts b/packages/nuxt/src/vite/sourceMaps.ts index 2f90094e6138..0b264e822bcc 100644 --- a/packages/nuxt/src/vite/sourceMaps.ts +++ b/packages/nuxt/src/vite/sourceMaps.ts @@ -91,6 +91,7 @@ export function getPluginOptions( project: sourceMapsUploadOptions.project ?? process.env.SENTRY_PROJECT, authToken: sourceMapsUploadOptions.authToken ?? process.env.SENTRY_AUTH_TOKEN, telemetry: sourceMapsUploadOptions.telemetry ?? true, + url: sourceMapsUploadOptions.url ?? process.env.SENTRY_URL, debug: moduleOptions.debug ?? false, _metaOptions: { telemetry: { diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts index 0c90429fa8d5..b33d314f5166 100644 --- a/packages/nuxt/test/vite/sourceMaps.test.ts +++ b/packages/nuxt/test/vite/sourceMaps.test.ts @@ -20,6 +20,7 @@ describe('getPluginOptions', () => { SENTRY_ORG: 'default-org', SENTRY_PROJECT: 'default-project', SENTRY_AUTH_TOKEN: 'default-token', + SENTRY_URL: 'https://santry.io', }; process.env = { ...defaultEnv }; @@ -31,6 +32,7 @@ describe('getPluginOptions', () => { org: 'default-org', project: 'default-project', authToken: 'default-token', + url: 'https://santry.io', telemetry: true, sourcemaps: expect.objectContaining({ rewriteSources: expect.any(Function), @@ -114,6 +116,7 @@ describe('getPluginOptions', () => { assets: ['custom-assets/**/*'], filesToDeleteAfterUpload: ['delete-this.js'], }, + url: 'https://santry.io', }, debug: true, unstable_sentryBundlerPluginOptions: { @@ -124,6 +127,7 @@ describe('getPluginOptions', () => { release: { name: 'test-release', }, + url: 'https://suntry.io', }, }; const options = getPluginOptions(customOptions, false); @@ -140,6 +144,7 @@ describe('getPluginOptions', () => { release: expect.objectContaining({ name: 'test-release', }), + url: 'https://suntry.io', }), ); }); From 3a1006ea49d2aa67c27f917c30aceae16cf01eef Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 28 Jan 2025 08:58:36 +0100 Subject: [PATCH 21/43] chore: Add external contributor to CHANGELOG.md (#15203) --------- Co-authored-by: Lukas Stracke --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a30b5747e9dc..ad2232acfbb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions! ## 9.0.0-alpha.0 From 76b5c33173157bcf3b1997135f0c1fc54f021abf Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 28 Jan 2025 16:51:30 +0100 Subject: [PATCH 22/43] test: Fix nextjs build warning (#15207) --- .../e2e-tests/test-applications/nextjs-app-dir/assert-build.ts | 2 +- packages/nextjs/src/config/webpack.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts index 955988101724..70564e0c12bb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts @@ -10,7 +10,7 @@ const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8'); // Assert that there was no funky build time warning when we are on a stable (pinned) version if (nextjsVersion !== 'latest' && !nextjsVersion.includes('-canary') && !nextjsVersion.includes('-rc')) { - assert.doesNotMatch(buildStderr, /Import trace for requested module/); // This is Next.js/Webpack speech for "something is off" + assert.doesNotMatch(buildStderr, /Import trace for requested module/, `Build warning in output:\n${buildStderr}`); // This is Next.js/Webpack speech for "something is off" } // Assert that all static components stay static and all dynamic components stay dynamic diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 8dbebf3935df..f82bb4a0476e 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -728,6 +728,7 @@ function addOtelWarningIgnoreRule(newConfig: WebpackConfigObjectWithModuleRules) // We provide these objects in addition to the hook above to provide redundancy in case the hook fails. { module: /@opentelemetry\/instrumentation/, message: /Critical dependency/ }, { module: /@prisma\/instrumentation/, message: /Critical dependency/ }, + { module: /require-in-the-middle/, message: /Critical dependency/ }, ] satisfies IgnoreWarningsOption; if (newConfig.ignoreWarnings === undefined) { From a27652f09538e91d3c6c23f8eeadb75743067428 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Tue, 28 Jan 2025 17:59:05 +0100 Subject: [PATCH 23/43] chore(deps): Update playwright to 1.50.0 (#15164) Also align to use the same version everywhere, tilde-restricted. --- .github/workflows/build.yml | 2 +- .../browser-integration-tests/package.json | 3 +- .../scripts/detectFlakyTests.ts | 49 ++---- .../onError/syntax-errors/test.ts | 2 +- .../test.ts-snapshots/seg-0-snap-full | 113 ------------- .../test.ts-snapshots/seg-1-snap-incremental | 45 ----- .../seg-1-snap-incremental-chromium | 4 +- .../test.ts-snapshots/seg-2-snap-full | 113 ------------- .../test.ts-snapshots/seg-3-snap-incremental | 45 ----- .../seg-3-snap-incremental-chromium | 4 +- .../test.ts-snapshots/seg-4-snap-full | 156 ------------------ .../test.ts-snapshots/seg-5-snap-incremental | 45 ----- .../test.ts-snapshots/seg-6-snap-incremental | 54 ------ .../test.ts-snapshots/seg-7-snap-incremental | 54 ------ .../test.ts-snapshots/seg-8-snap-full | 113 ------------- .../test.ts-snapshots/seg-9-snap-incremental | 45 ----- .../seg-9-snap-incremental-chromium | 4 +- .../suites/replay/sessionExpiry/test.ts | 2 +- .../test.ts-snapshots/snapshot-0-webkit.json | 113 ------------- .../test.ts-snapshots/snapshot-2-webkit.json | 127 -------------- .../suites/stacktraces/init.js | 6 + .../suites/stacktraces/template.html | 2 - .../http-timings/init.js | 6 + .../interactions/init.js | 6 + .../long-animation-frame-enabled/test.ts | 9 +- .../test.ts | 9 +- .../tracing/metrics/web-vitals-lcp/test.ts | 53 +----- .../test-applications/angular-17/package.json | 2 +- .../test-applications/angular-18/package.json | 2 +- .../test-applications/angular-19/package.json | 2 +- .../test-applications/astro-4/package.json | 2 +- .../test-applications/astro-5/package.json | 2 +- .../aws-lambda-layer-cjs/package.json | 2 +- .../aws-serverless-esm/package.json | 2 +- .../create-next-app/package.json | 2 +- .../package.json | 2 +- .../create-remix-app-express/package.json | 2 +- .../create-remix-app-v2/package.json | 2 +- .../default-browser/package.json | 2 +- .../ember-classic/package.json | 2 +- .../ember-embroider/package.json | 2 +- .../test-applications/nestjs-8/package.json | 2 +- .../nestjs-basic-with-graphql/package.json | 2 +- .../nestjs-basic/package.json | 2 +- .../nestjs-distributed-tracing/package.json | 2 +- .../nestjs-fastify/package.json | 2 +- .../nestjs-graphql/package.json | 2 +- .../package.json | 2 +- .../nestjs-with-submodules/package.json | 2 +- .../test-applications/nextjs-13/package.json | 2 +- .../test-applications/nextjs-14/package.json | 2 +- .../test-applications/nextjs-15/package.json | 2 +- .../nextjs-app-dir/package.json | 2 +- .../test-applications/nextjs-t3/package.json | 2 +- .../nextjs-turbo/package.json | 2 +- .../node-connect/package.json | 2 +- .../node-express-cjs-preload/package.json | 2 +- .../node-express-esm-loader/package.json | 2 +- .../node-express-esm-preload/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../node-express-send-to-sentry/package.json | 2 +- .../node-express/package.json | 2 +- .../node-fastify-5/package.json | 2 +- .../node-fastify/package.json | 2 +- .../test-applications/node-hapi/package.json | 2 +- .../test-applications/node-koa/package.json | 2 +- .../node-otel-custom-sampler/package.json | 2 +- .../node-otel-sdk-node/package.json | 2 +- .../node-otel-without-tracing/package.json | 2 +- .../test-applications/node-otel/package.json | 2 +- .../node-profiling/package.json | 2 +- .../nuxt-3-dynamic-import/package.json | 2 +- .../test-applications/nuxt-3-min/package.json | 2 +- .../nuxt-3-top-level-import/package.json | 2 +- .../test-applications/nuxt-3/package.json | 2 +- .../test-applications/nuxt-4/package.json | 2 +- .../test-applications/react-17/package.json | 2 +- .../test-applications/react-19/package.json | 2 +- .../react-create-browser-router/package.json | 2 +- .../react-create-hash-router/package.json | 2 +- .../react-create-memory-router/package.json | 2 +- .../react-router-5/package.json | 2 +- .../package.json | 2 +- .../react-router-6-use-routes/package.json | 2 +- .../react-router-6/package.json | 2 +- .../react-router-7-spa/package.json | 2 +- .../react-send-to-sentry/package.json | 2 +- .../solid-solidrouter/package.json | 2 +- .../test-applications/solid/package.json | 2 +- .../solidstart-dynamic-import/package.json | 2 +- .../solidstart-spa/package.json | 2 +- .../solidstart-top-level-import/package.json | 2 +- .../test-applications/solidstart/package.json | 2 +- .../test-applications/svelte-5/package.json | 2 +- .../sveltekit-2-svelte-5/package.json | 2 +- .../sveltekit-2-twp/package.json | 2 +- .../sveltekit-2/package.json | 2 +- .../tanstack-router/package.json | 2 +- .../test-applications/vue-3/package.json | 2 +- .../test-applications/webpack-4/package.json | 2 +- .../test-applications/webpack-5/package.json | 2 +- dev-packages/test-utils/package.json | 4 +- packages/browser/src/transports/fetch.ts | 2 +- .../browser/src/utils/lazyLoadIntegration.ts | 2 +- .../browser/test/transports/fetch.test.ts | 2 +- packages/deno/src/transports/index.ts | 2 +- yarn.lock | 28 ++-- 108 files changed, 148 insertions(+), 1226 deletions(-) delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full delete mode 100644 dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental delete mode 100644 dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json delete mode 100644 dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24f3ee0454f2..34bb9eb5799f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -600,7 +600,7 @@ jobs: env: PW_BUNDLE: ${{ matrix.bundle }} working-directory: dev-packages/browser-integration-tests - run: yarn test:ci${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }} + run: yarn test:all${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }} - name: Upload Playwright Traces uses: actions/upload-artifact@v4 diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index bfd1ed3d2717..77cf36078a2a 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -35,13 +35,12 @@ "test:loader:replay_buffer": "PW_BUNDLE=loader_replay_buffer yarn test:loader", "test:loader:full": "PW_BUNDLE=loader_tracing_replay yarn test:loader", "test:loader:debug": "PW_BUNDLE=loader_debug yarn test:loader", - "test:ci": "yarn test:all", "test:update-snapshots": "yarn test:all --update-snapshots", "test:detect-flaky": "ts-node scripts/detectFlakyTests.ts" }, "dependencies": { "@babel/preset-typescript": "^7.16.7", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/rrweb": "2.31.0", "@sentry/browser": "9.0.0-alpha.0", "axios": "1.7.7", diff --git a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts index bf653dfad6b7..6fa8e8ddd416 100644 --- a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts +++ b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts @@ -4,14 +4,9 @@ import * as path from 'path'; import * as glob from 'glob'; /** - * The number of browsers we run the tests in. + * Assume that each test runs for 3s. */ -const NUM_BROWSERS = 3; - -/** - * Assume that each test runs for 2s. - */ -const ASSUMED_TEST_DURATION_SECONDS = 2; +const ASSUMED_TEST_DURATION_SECONDS = 3; /** * We keep the runtime of the detector if possible under 30min. @@ -51,22 +46,12 @@ ${changedPaths.join('\n')} try { await new Promise((resolve, reject) => { const cp = childProcess.spawn( - `npx playwright test ${ - testPaths.length ? testPaths.join(' ') : './suites' - } --reporter='line' --repeat-each ${repeatEachCount}`, - { shell: true, cwd }, + `npx playwright test ${testPaths.length ? testPaths.join(' ') : './suites'} --repeat-each ${repeatEachCount}`, + { shell: true, cwd, stdio: 'inherit' }, ); let error: Error | undefined; - cp.stdout.on('data', data => { - console.log(data ? (data as object).toString() : ''); - }); - - cp.stderr.on('data', data => { - console.log(data ? (data as object).toString() : ''); - }); - cp.on('error', e => { console.error(e); error = e; @@ -107,15 +92,16 @@ function getPerTestRunCount(testPaths: string[]) { const estimatedNumberOfTests = testPaths.map(getApproximateNumberOfTests).reduce((a, b) => a + b); console.log(`Estimated number of tests: ${estimatedNumberOfTests}`); - // tests are usually run against all browsers we test with, so let's assume this - const testRunCount = estimatedNumberOfTests * NUM_BROWSERS; + const testRunCount = estimatedNumberOfTests; console.log(`Estimated test runs for one round: ${testRunCount}`); const estimatedTestRuntime = testRunCount * ASSUMED_TEST_DURATION_SECONDS; console.log(`Estimated test runtime: ${estimatedTestRuntime}s`); const expectedPerTestRunCount = Math.floor(MAX_TARGET_TEST_RUNTIME_SECONDS / estimatedTestRuntime); - console.log(`Expected per-test run count: ${expectedPerTestRunCount}`); + console.log( + `Calculated # of repetitions: ${expectedPerTestRunCount} (min ${MIN_PER_TEST_RUN_COUNT}, max ${MAX_PER_TEST_RUN_COUNT})`, + ); return Math.min(MAX_PER_TEST_RUN_COUNT, Math.max(expectedPerTestRunCount, MIN_PER_TEST_RUN_COUNT)); } @@ -128,22 +114,7 @@ function getTestPaths(): string[] { cwd: path.join(__dirname, '../'), }); - return paths.map(p => path.dirname(p)); -} - -function logError(error: unknown) { - if (process.env.CI) { - console.log('::group::Test failed'); - } else { - console.error(' ⚠️ Test failed:'); - } - - console.log((error as any).stdout); - console.log((error as any).stderr); - - if (process.env.CI) { - console.log('::endgroup::'); - } + return paths.map(p => `${path.dirname(p)}/`); } /** @@ -156,7 +127,7 @@ function logError(error: unknown) { function getApproximateNumberOfTests(testPath: string): number { try { const content = fs.readFileSync(path.join(process.cwd(), testPath, 'test.ts'), 'utf-8'); - const matches = content.match(/it\(|test\(|sentryTest\(/g); + const matches = content.match(/sentryTest\(/g); return Math.max(matches ? matches.length : 1, 1); } catch (e) { console.error(`Could not read file ${testPath}`); diff --git a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts index c11d1897e1c2..51ac86e0cf62 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts @@ -27,7 +27,7 @@ sentryTest('should catch syntax errors', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData.exception?.values?.[0]).toMatchObject({ type: 'SyntaxError', - value: "Unexpected token '{'", + value: "Failed to execute 'appendChild' on 'Node': Unexpected token '{'", mechanism: { type: 'onerror', handled: false, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full deleted file mode 100644 index 1c3d1f22aeba..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full +++ /dev/null @@ -1,156 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "********* ****", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "spa-navigation" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** * *** **********", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/index.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** **** ** ***** ****", - "id": 20 - } - ], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 21 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 22 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental deleted file mode 100644 index 6dd84be3e2dc..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental deleted file mode 100644 index 575f1210087b..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 15, - "x": 157.13999938964844, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 6, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 15 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 15, - "x": 157.13999938964844, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 15, - "x": 157, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental deleted file mode 100644 index f952a6e3bfaa..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 6, - "id": 15 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts index 79c7758ec099..6bb199c42146 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts @@ -15,7 +15,7 @@ import { const SESSION_TIMEOUT = 2000; sentryTest('handles an expired session', async ({ browserName, forceFlushReplay, getLocalTestUrl, page }) => { - if (shouldSkipReplayTest() || browserName === 'webkit') { + if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json deleted file mode 100644 index d510b410a343..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 1')", - "id": "button1" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 2')", - "id": "button2" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] -} \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json deleted file mode 100644 index 13e5b1b70103..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "rootId": 16, - "id": 17 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "rootId": 16, - "id": 20 - } - ], - "rootId": 16, - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 21 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 23 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 1')", - "id": "button1" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "rootId": 16, - "id": 25 - } - ], - "rootId": 16, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 26 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 2')", - "id": "button2" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "rootId": 16, - "id": 28 - } - ], - "rootId": 16, - "id": 27 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 29 - }, - { - "type": 3, - "textContent": "\n\n", - "rootId": 16, - "id": 30 - } - ], - "rootId": 16, - "id": 22 - } - ], - "rootId": 16, - "id": 18 - } - ], - "id": 16 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] -} \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/init.js b/dev-packages/browser-integration-tests/suites/stacktraces/init.js index d8c94f36fdd0..ce283e32d303 100644 --- a/dev-packages/browser-integration-tests/suites/stacktraces/init.js +++ b/dev-packages/browser-integration-tests/suites/stacktraces/init.js @@ -4,4 +4,10 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/template.html b/dev-packages/browser-integration-tests/suites/stacktraces/template.html index d91677daaab5..39082f45e532 100644 --- a/dev-packages/browser-integration-tests/suites/stacktraces/template.html +++ b/dev-packages/browser-integration-tests/suites/stacktraces/template.html @@ -3,9 +3,7 @@ - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js index e32d09a13fab..f6291c3183c7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js @@ -13,4 +13,10 @@ Sentry.init({ }), ], tracesSampleRate: 1, + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js index 846538e7f3f0..718f7cca0005 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js @@ -14,4 +14,10 @@ Sentry.init({ }), ], tracesSampleRate: 1, + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index b127a1f674a1..ca46bc078e90 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -84,13 +84,11 @@ sentryTest( const eventData = await promise; - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || []; - expect(uiSpans?.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick', - )!; + const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; expect(eventListenerUISpan).toEqual( expect.objectContaining({ @@ -100,6 +98,7 @@ sentryTest( data: { 'browser.script.invoker': 'BUTTON#clickme.onclick', 'browser.script.invoker_type': 'event-listener', + 'code.filepath': 'https://example.com/path/to/script.js', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts index 51447ee84586..b43e383be985 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -86,13 +86,11 @@ sentryTest( const eventData = await promise; - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || []; - expect(uiSpans?.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick', - )!; + const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; expect(eventListenerUISpan).toEqual( expect.objectContaining({ @@ -102,6 +100,7 @@ sentryTest( data: { 'browser.script.invoker': 'BUTTON#clickme.onclick', 'browser.script.invoker_type': 'event-listener', + 'code.filepath': 'https://example.com/path/to/script.js', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts index 1cfee3b670a7..c4b41923a082 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts @@ -5,21 +5,16 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -/* - Because we "serve" the html test page as a static file, all requests for the image - are considered 3rd party requests. So the LCP value we obtain for the image is also - considered a 3rd party LCP value, meaning `renderTime` is only set if we also - return the `Timing-Allow-Origin` header. -*/ - -sentryTest('captures LCP vitals with element details.', async ({ browserName, getLocalTestUrl, page }) => { +sentryTest('captures LCP vitals with element details', async ({ browserName, getLocalTestUrl, page }) => { if (shouldSkipTracingTest() || browserName !== 'chromium') { sentryTest.skip(); } page.route('**', route => route.continue()); page.route('**/my/image.png', async (route: Route) => { - return route.fulfill({ path: `${__dirname}/assets/sentry-logo-600x179.png` }); + return route.fulfill({ + path: `${__dirname}/assets/sentry-logo-600x179.png`, + }); }); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -31,42 +26,8 @@ sentryTest('captures LCP vitals with element details.', async ({ browserName, ge expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true); expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0); expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0); + expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0); - // renderTime is 0 because we don't return the `Timing-Allow-Origin` header - // and the image is loaded from a 3rd party origin - expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBe(0); - - // The LCP value should be the loadTime because the renderTime is not set - expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.loadTime']); + // The LCP value should be the renderTime because the renderTime is set + expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']); }); - -sentryTest( - 'captures LCP renderTime when returning Timing-Allow-Origin header.', - async ({ browserName, getLocalTestUrl, page }) => { - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - page.route('**', route => route.continue()); - page.route('**/my/image.png', async (route: Route) => { - return route.fulfill({ - path: `${__dirname}/assets/sentry-logo-600x179.png`, - headers: { 'Timing-Allow-Origin': '*' }, - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - const [eventData] = await Promise.all([getFirstSentryEnvelopeRequest(page), page.goto(url)]); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.lcp?.value).toBeDefined(); - - expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true); - expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0); - expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0); - expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0); - - // The LCP value should be the renderTime because the renderTime is set - expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']); - }, -); diff --git a/dev-packages/e2e-tests/test-applications/angular-17/package.json b/dev-packages/e2e-tests/test-applications/angular-17/package.json index 682c47d30329..e6b1df6cd387 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-17/package.json @@ -29,7 +29,7 @@ "zone.js": "~0.14.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@angular-devkit/build-angular": "^17.1.1", "@angular/cli": "^17.1.1", diff --git a/dev-packages/e2e-tests/test-applications/angular-18/package.json b/dev-packages/e2e-tests/test-applications/angular-18/package.json index aec1b1d9dac0..288b1b119912 100644 --- a/dev-packages/e2e-tests/test-applications/angular-18/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-18/package.json @@ -29,7 +29,7 @@ "zone.js": "~0.14.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@angular-devkit/build-angular": "^18.0.0", "@angular/cli": "^18.0.0", diff --git a/dev-packages/e2e-tests/test-applications/angular-19/package.json b/dev-packages/e2e-tests/test-applications/angular-19/package.json index c8ae32b52378..f7544ccb5239 100644 --- a/dev-packages/e2e-tests/test-applications/angular-19/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-19/package.json @@ -32,7 +32,7 @@ "@angular-devkit/build-angular": "^19.0.0", "@angular/cli": "^19.0.0", "@angular/compiler-cli": "^19.0.0", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/jasmine": "~5.1.0", "http-server": "^14.1.1", diff --git a/dev-packages/e2e-tests/test-applications/astro-4/package.json b/dev-packages/e2e-tests/test-applications/astro-4/package.json index b80e408b3e32..4e49cf150bb4 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/package.json +++ b/dev-packages/e2e-tests/test-applications/astro-4/package.json @@ -14,7 +14,7 @@ "dependencies": { "@astrojs/check": "0.9.2", "@astrojs/node": "8.3.4", - "@playwright/test": "^1.46.0", + "@playwright/test": "~1.50.0", "@sentry/astro": "* || latest", "@sentry-internal/test-utils": "link:../../../test-utils", "@spotlightjs/astro": "2.1.6", diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json index 170c716be756..189c2834f3ae 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/package.json +++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/internal-helpers": "^0.4.2", "@astrojs/node": "^9.0.0", - "@playwright/test": "^1.46.0", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/astro": "latest || *", "astro": "^5.0.3" diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json index 01375fe0c346..ab822e9d669d 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json index 67aa6bc247d5..746abf5b4cbc 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "^1.41.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/package.json b/dev-packages/e2e-tests/test-applications/create-next-app/package.json index e70e7ed4c797..3e8416b3aaee 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/package.json +++ b/dev-packages/e2e-tests/test-applications/create-next-app/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json index b37c7a8c0705..20433c4cd15f 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json @@ -25,7 +25,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "^2.7.2", "@sentry/core": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json index 5c362ffb97a1..c44b7a8fe5bc 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json @@ -28,7 +28,7 @@ "source-map-support": "^0.5.21" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "^2.7.2", "@sentry/core": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json index 977408d0945a..e8a03932560a 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "2.7.2", "@remix-run/eslint-config": "2.7.2", diff --git a/dev-packages/e2e-tests/test-applications/default-browser/package.json b/dev-packages/e2e-tests/test-applications/default-browser/package.json index a147fa576d8a..5f70156c1e1c 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/package.json +++ b/dev-packages/e2e-tests/test-applications/default-browser/package.json @@ -28,7 +28,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "webpack": "^5.91.0", "serve": "14.0.1", diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json index a0c0b4101d09..d29714415897 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -24,7 +24,7 @@ "@ember/optional-features": "~2.0.0", "@glimmer/component": "~1.1.2", "@glimmer/tracking": "~1.1.2", - "@playwright/test": "~1.44.1", + "@playwright/test": "~1.50.0", "@ember/string": "~3.1.1", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/ember": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json index b96b70876f53..a7b63be1218f 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -50,7 +50,7 @@ "loader.js": "^4.7.0", "tracked-built-ins": "^3.3.0", "webpack": "^5.91.0", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry/ember": "latest || *", "@sentry-internal/test-utils": "link:../../../test-utils", "@tsconfig/ember": "^3.0.6", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json index 2c749afae8a4..a4a046b9106d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json @@ -25,7 +25,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json index dea9e11f1423..816c5ec0fcce 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json @@ -27,7 +27,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json index 0de1aa1b3e6a..3d367978c6c7 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json @@ -25,7 +25,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json index 7ea84e7afe05..133c56648f1f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json @@ -24,7 +24,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json index d456c22370df..88092675cc98 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json @@ -26,7 +26,7 @@ "fastify": "^4.28.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json index d6e198cd7567..0f645056e025 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json @@ -27,7 +27,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json index 34d6004ebd8e..791c3df2305b 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json @@ -23,7 +23,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json index a54eb72275a8..dfd015acade9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json @@ -23,7 +23,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json index d81ca4fa6443..cb78ab4ecb4b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/browser-utils": "latest || *", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json index b041f71f7f7e..a1e33dbb10ec 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 9e99e182f4db..66b38e2e5cc0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -24,7 +24,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json index 492f8d94ef71..f779d9ece306 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json @@ -25,7 +25,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json index 1ebef0ce37ae..4c6f9f281406 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json @@ -29,7 +29,7 @@ "zod": "^3.23.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/eslint": "^8.56.10", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json index d0d1a3abe349..d2cdcfb89561 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-connect/package.json b/dev-packages/e2e-tests/test-applications/node-connect/package.json index 743b079c8af1..f882e55d8797 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect/package.json +++ b/dev-packages/e2e-tests/test-applications/node-connect/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json index 363c1e06636c..c1544b4df93b 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json index 6156211e27f8..2d61a1c4f7ef 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json index 03f483307290..fac430272a92 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json index 844ca51fd038..81a659a7dbfb 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json index b6daa54355f3..e9989a5790a6 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json @@ -22,7 +22,7 @@ "zod": "~3.22.4" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json index 5f3442eb3af9..338a7ccbd604 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json @@ -19,7 +19,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/node-express/package.json b/dev-packages/e2e-tests/test-applications/node-express/package.json index 37d5ed592db3..c1a608833bdb 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express/package.json @@ -22,7 +22,7 @@ "zod": "~3.22.4" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "resolutions": { diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json index f720b711d1fa..f9f4f726eb0e 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.2" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/package.json b/dev-packages/e2e-tests/test-applications/node-fastify/package.json index 255238f0f74f..9b9f584cc359 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/package.json b/dev-packages/e2e-tests/test-applications/node-hapi/package.json index 2eda8acc7589..3f18419d1d4a 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/package.json +++ b/dev-packages/e2e-tests/test-applications/node-hapi/package.json @@ -16,7 +16,7 @@ "@sentry/node": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-koa/package.json b/dev-packages/e2e-tests/test-applications/node-koa/package.json index 9bcb3cc8754b..55299faa3e4a 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/package.json +++ b/dev-packages/e2e-tests/test-applications/node-koa/package.json @@ -18,7 +18,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json index ff4017ca0f3c..c5fe928a931e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json @@ -21,7 +21,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json index 1240dc5e9923..efb74e7346ad 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index a45eb3470c7d..b097e5b91930 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -25,7 +25,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel/package.json b/dev-packages/e2e-tests/test-applications/node-otel/package.json index e01886a3318f..3112ce669479 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/package.json b/dev-packages/e2e-tests/test-applications/node-profiling/package.json index d78ca10fa25d..c48ab9c3d42d 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling/package.json +++ b/dev-packages/e2e-tests/test-applications/node-profiling/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@electron/rebuild": "^3.7.0", - "@playwright/test": "^1.48.2", + "@playwright/test": "~1.50.0", "@sentry/electron": "latest || *", "@sentry/node": "latest || *", "@sentry/profiling-node": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json index ac18cebec975..8555f0da7a58 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json index 54bacf4ee358..7fb034434fdf 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json index 9d3dc0066912..1e6590da7a10 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" } } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json index 80b76aed58ac..df88dd717a8d 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" } } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json index 0a278f07eedd..a1cbc9164e9e 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.2", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/react-17/package.json b/dev-packages/e2e-tests/test-applications/react-17/package.json index ab3022bb3c80..c9273d01f090 100644 --- a/dev-packages/e2e-tests/test-applications/react-17/package.json +++ b/dev-packages/e2e-tests/test-applications/react-17/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-19/package.json b/dev-packages/e2e-tests/test-applications/react-19/package.json index 1abc74715831..e2f4ec9cfd16 100644 --- a/dev-packages/e2e-tests/test-applications/react-19/package.json +++ b/dev-packages/e2e-tests/test-applications/react-19/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json index a4e7dae6d1e2..fae000b256e3 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json @@ -30,7 +30,7 @@ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json index 757b27c65b84..b086b8228aec 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json @@ -41,7 +41,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json index dc6c9b4340f0..1f00f44ff43b 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json @@ -30,7 +30,7 @@ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/package.json b/dev-packages/e2e-tests/test-applications/react-router-5/package.json index 16c7f16df16d..f0c52de6990e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-5/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-5/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json index 3c3323d2c4cc..768c836abba8 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1", "npm-run-all2": "^6.2.0" diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json index 7f68ec2a7ec4..fdb36e4d3b7b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json @@ -41,7 +41,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/package.json b/dev-packages/e2e-tests/test-applications/react-router-6/package.json index 575f417e2bc2..0a9f0cd2a5c0 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1", "npm-run-all2": "^6.2.0" diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json index 1c505f3195a3..e8730c8f9091 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json @@ -11,7 +11,7 @@ "react-router": "^7.0.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "vite": "^6.0.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json index 35b01833874a..07eec924f7b7 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json index 0c727d46de50..36d0fc2854f7 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json @@ -14,7 +14,7 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "autoprefixer": "^10.4.17", diff --git a/dev-packages/e2e-tests/test-applications/solid/package.json b/dev-packages/e2e-tests/test-applications/solid/package.json index d61ac0a0a322..830450e7328e 100644 --- a/dev-packages/e2e-tests/test-applications/solid/package.json +++ b/dev-packages/e2e-tests/test-applications/solid/package.json @@ -14,7 +14,7 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "autoprefixer": "^10.4.17", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json index 62393e038dce..495b9cb8c94d 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json index 9495309f0464..57a83c35b0e8 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json index 559477a58baa..3d96e0481158 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json index f4059823617a..2bb3afaa29b1 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/package.json b/dev-packages/e2e-tests/test-applications/svelte-5/package.json index ed6cf3ada0d7..39d6250f3b75 100644 --- a/dev-packages/e2e-tests/test-applications/svelte-5/package.json +++ b/dev-packages/e2e-tests/test-applications/svelte-5/package.json @@ -13,7 +13,7 @@ "test:assert": "pnpm test:prod" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/vite-plugin-svelte": "^3.0.2", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json index 88d9a37ab98c..4fa8983ae153 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json @@ -19,7 +19,7 @@ "@spotlightjs/spotlight": "2.0.0-alpha.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json index 5a2d9ce7b4d5..f961077f9977 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json @@ -18,7 +18,7 @@ "@sentry/sveltekit": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json index 3f2f87500e25..f02253198aaa 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -18,7 +18,7 @@ "@sentry/sveltekit": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json index 96a26ee98447..ae25dcc0870b 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json @@ -25,7 +25,7 @@ "@vitejs/plugin-react-swc": "^3.5.0", "typescript": "^5.2.2", "vite": "^5.4.11", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/vue-3/package.json b/dev-packages/e2e-tests/test-applications/vue-3/package.json index 3a2c38f43633..54cbc679e0f4 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/package.json +++ b/dev-packages/e2e-tests/test-applications/vue-3/package.json @@ -21,7 +21,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@tsconfig/node20": "^20.1.2", diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/package.json b/dev-packages/e2e-tests/test-applications/webpack-4/package.json index 95d3d5c39a3e..c143fd9ab82e 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-4/package.json +++ b/dev-packages/e2e-tests/test-applications/webpack-4/package.json @@ -8,7 +8,7 @@ "test:assert": "playwright test" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/browser": "latest || *", "babel-loader": "^8.0.0", diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/package.json b/dev-packages/e2e-tests/test-applications/webpack-5/package.json index 389f817292cd..00cff9198262 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-5/package.json +++ b/dev-packages/e2e-tests/test-applications/webpack-5/package.json @@ -8,7 +8,7 @@ "test:assert": "playwright test" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/browser": "latest || *", "webpack": "^5.91.0", diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 5758b5229e80..a0d9d7e51def 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -41,10 +41,10 @@ "clean": "rimraf -g ./node_modules ./build" }, "peerDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry/core": "9.0.0-alpha.0" }, "volta": { diff --git a/packages/browser/src/transports/fetch.ts b/packages/browser/src/transports/fetch.ts index 5712c78f0c18..9a414a95e927 100644 --- a/packages/browser/src/transports/fetch.ts +++ b/packages/browser/src/transports/fetch.ts @@ -22,7 +22,7 @@ export function makeFetchTransport( const requestOptions: RequestInit = { body: request.body, method: 'POST', - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', headers: options.headers, // Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to // fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)". diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index 2e215fbf764e..e6fea13c4e2a 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -56,7 +56,7 @@ export async function lazyLoadIntegration( const script = WINDOW.document.createElement('script'); script.src = url; script.crossOrigin = 'anonymous'; - script.referrerPolicy = 'origin'; + script.referrerPolicy = 'strict-origin'; if (scriptNonce) { script.setAttribute('nonce', scriptNonce); diff --git a/packages/browser/test/transports/fetch.test.ts b/packages/browser/test/transports/fetch.test.ts index 9a760439733b..89ba25fd5fd8 100644 --- a/packages/browser/test/transports/fetch.test.ts +++ b/packages/browser/test/transports/fetch.test.ts @@ -50,7 +50,7 @@ describe('NewFetchTransport', () => { body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', keepalive: true, - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', }); }); diff --git a/packages/deno/src/transports/index.ts b/packages/deno/src/transports/index.ts index 3d4e26a9f805..9a83dadfff63 100644 --- a/packages/deno/src/transports/index.ts +++ b/packages/deno/src/transports/index.ts @@ -31,7 +31,7 @@ export function makeFetchTransport(options: DenoTransportOptions): Transport { const requestOptions: RequestInit = { body: request.body, method: 'POST', - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', headers: options.headers, }; diff --git a/yarn.lock b/yarn.lock index d80fe6f3225e..505d52f463d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6054,12 +6054,12 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@playwright/test@^1.44.1": - version "1.44.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb" - integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q== +"@playwright/test@~1.50.0": + version "1.50.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.50.0.tgz#25c63a09f833f89da4d54ad67db7900359e2d11d" + integrity sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw== dependencies: - playwright "1.44.1" + playwright "1.50.0" "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" @@ -23984,17 +23984,17 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" - integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== +playwright-core@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.50.0.tgz#28dd6a1488211c193933695ed337a5b44d46867c" + integrity sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ== -playwright@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" - integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== +playwright@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.50.0.tgz#ccaf334f948d78139922844de55a18f8ae785410" + integrity sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w== dependencies: - playwright-core "1.44.1" + playwright-core "1.50.0" optionalDependencies: fsevents "2.3.2" From ec02f84fc6597e73bbc1c6d905da7b12b15dbc07 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 28 Jan 2025 21:48:07 +0100 Subject: [PATCH 24/43] feat(profiling-node): Use `@sentry-internal/node-cpu-profiler` (#15208) This PR removes all the code and CI steps that were moved to `@sentry-internal/node-cpu-profiler`. --------- Co-authored-by: Abhijeet Prasad --- .github/workflows/build.yml | 438 +----- .../bin/darwin-arm64-130/profiling-node.node | Bin 115304 -> 0 bytes packages/profiling-node/binding.gyp | 20 - .../profiling-node/bindings/cpu_profiler.cc | 1226 ----------------- packages/profiling-node/clang-format.js | 26 - packages/profiling-node/package.json | 35 +- packages/profiling-node/rollup.npm.config.mjs | 23 +- packages/profiling-node/scripts/binaries.js | 27 - .../profiling-node/scripts/check-build.js | 56 - .../profiling-node/scripts/copy-target.js | 27 - packages/profiling-node/src/cpu_profiler.ts | 224 --- packages/profiling-node/src/integration.ts | 16 +- .../profiling-node/src/spanProfileUtils.ts | 2 +- packages/profiling-node/src/utils.ts | 13 +- packages/profiling-node/test/bindings.test.ts | 30 - .../profiling-node/test/cpu_profiler.test.ts | 364 ----- .../test/spanProfileUtils.test.ts | 2 +- scripts/ci-unit-tests.ts | 3 +- yarn.lock | 34 +- 19 files changed, 49 insertions(+), 2517 deletions(-) delete mode 100755 packages/profiling-node/bin/darwin-arm64-130/profiling-node.node delete mode 100644 packages/profiling-node/binding.gyp delete mode 100644 packages/profiling-node/bindings/cpu_profiler.cc delete mode 100644 packages/profiling-node/clang-format.js delete mode 100644 packages/profiling-node/scripts/binaries.js delete mode 100644 packages/profiling-node/scripts/check-build.js delete mode 100644 packages/profiling-node/scripts/copy-target.js delete mode 100644 packages/profiling-node/src/cpu_profiler.ts delete mode 100644 packages/profiling-node/test/bindings.test.ts delete mode 100644 packages/profiling-node/test/cpu_profiler.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34bb9eb5799f..c58d8606d4de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,8 +84,6 @@ jobs: echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV # Most changed packages are determined in job_build via Nx - # However, for profiling-node we only want to run certain things when in this specific package - # something changed, not in any of the dependencies (which include core, utils, ...) - name: Determine changed packages uses: dorny/paths-filter@v3.0.1 id: changed @@ -93,9 +91,6 @@ jobs: filters: | workflow: - '.github/**' - profiling_node: - - 'packages/profiling-node/**' - - 'dev-packages/e2e-tests/test-applications/node-profiling/**' any_code: - '!**/*.md' @@ -109,7 +104,6 @@ jobs: # Note: These next three have to be checked as strings ('true'/'false')! is_base_branch: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v9' || github.ref == 'refs/heads/v8'}} is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} - changed_profiling_node: ${{ steps.changed.outputs.profiling_node == 'true' }} changed_ci: ${{ steps.changed.outputs.workflow == 'true' }} changed_any_code: ${{ steps.changed.outputs.any_code == 'true' }} @@ -198,7 +192,6 @@ jobs: changed_deno: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/deno') }} changed_bun: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/bun') }} changed_browser_integration: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry-internal/browser-integration-tests') }} - # If you are looking for changed_profiling_node, this is defined in job_get_metadata job_check_branches: name: Check PR branches @@ -316,7 +309,7 @@ jobs: job_artifacts: name: Upload Artifacts - needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] + needs: [job_get_metadata, job_build] runs-on: ubuntu-20.04 # Build artifacts are only needed for releasing workflow. if: needs.job_get_metadata.outputs.is_release == 'true' @@ -334,13 +327,6 @@ jobs: with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Extract Profiling Node Prebuilt Binaries - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - - name: Pack tarballs run: yarn build:tarball @@ -498,37 +484,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - job_profiling_node_unit_tests: - name: Node Profiling Unit Tests - needs: [job_get_metadata, job_build] - if: | - needs.job_build.outputs.changed_node == 'true' || - needs.job_get_metadata.outputs.changed_profiling_node == 'true' || - github.event_name != 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check out current commit - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - uses: actions/setup-node@v4 - with: - node-version: 20 - - uses: actions/setup-python@v5 - with: - python-version: '3.11.7' - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Build Configure node-gyp - run: yarn lerna run build:bindings:configure --scope @sentry/profiling-node - - name: Build Bindings for Current Environment - run: yarn build --scope @sentry/profiling-node - - name: Unit Test - run: yarn lerna run test --scope @sentry/profiling-node - job_browser_playwright_tests: name: Playwright ${{ matrix.bundle }}${{ matrix.project && matrix.project != 'chromium' && format(' {0}', matrix.project) || ''}}${{ matrix.shard && format(' ({0}/{1})', matrix.shard, matrix.shards) || ''}} Tests needs: [job_get_metadata, job_build] @@ -786,12 +741,10 @@ jobs: name: Prepare E2E tests # We want to run this if: # - The build job was successful, not skipped - # - AND if the profiling node bindings were either successful or skipped if: | always() && - needs.job_build.result == 'success' && - (needs.job_compile_bindings_profiling_node.result == 'success' || needs.job_compile_bindings_profiling_node.result == 'skipped') - needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] + needs.job_build.result == 'success' + needs: [job_get_metadata, job_build] runs-on: ubuntu-20.04-large-js timeout-minutes: 15 outputs: @@ -823,26 +776,6 @@ jobs: # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }} - # Rebuild profiling by compiling TS and pull the precompiled binary artifacts - - name: Build Profiling Node - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') || - (github.event_name != 'pull_request') - run: yarn lerna run build:lib --scope @sentry/profiling-node - - - name: Extract Profiling Node Prebuilt Binaries - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') || - (github.event_name != 'pull_request') - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - # End rebuild profiling - - name: Build tarballs run: yarn build:tarball @@ -1089,137 +1022,20 @@ jobs: directory: dist workingDirectory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - job_profiling_e2e_tests: - name: E2E ${{ matrix.label || matrix.test-application }} Test - # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks - # Dependabot specifically also has access to secrets - # We need to add the `always()` check here because the previous step has this as well :( - # See: https://github.com/actions/runner/issues/2205 - if: - # Only run profiling e2e tests if profiling node bindings have changed - always() && needs.job_e2e_prepare.result == 'success' && - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && - ( - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') - ) - needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - env: - E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} - E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} - E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' - E2E_TEST_SENTRY_PROJECT: 'sentry-javascript-e2e-tests' - strategy: - fail-fast: false - matrix: - test-application: ['node-profiling'] - build-command: - - false - label: - - false - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - - uses: pnpm/action-setup@v4 - with: - version: 9.4.0 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - - name: Build Profiling Node - run: yarn lerna run build:lib --scope @sentry/profiling-node - - - name: Extract Profiling Node Prebuilt Binaries - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - - - name: Restore tarball cache - uses: actions/cache/restore@v4 - id: restore-tarball-cache - with: - path: ${{ github.workspace }}/packages/*/*.tgz - key: ${{ env.BUILD_CACHE_TARBALL_KEY }} - - - name: Build tarballs if not cached - if: steps.restore-tarball-cache.outputs.cache-hit != 'true' - run: yarn build:tarball - - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - - - name: Get node version - id: versions - run: | - echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT - - - name: Validate Verdaccio - run: yarn test:validate - working-directory: dev-packages/e2e-tests - - - name: Prepare Verdaccio - run: yarn test:prepare - working-directory: dev-packages/e2e-tests - env: - E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} - - - name: Setup xvfb and update ubuntu dependencies - run: | - sudo apt-get install xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps - sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ - libnotify-dev libgconf2-dev \ - libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib - - - name: Install dependencies - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - run: yarn install --ignore-engines --frozen-lockfile - - - name: Build E2E app - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 7 - run: yarn ${{ matrix.build-command || 'test:build' }} - - - name: Run E2E test - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 10 - run: | - xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert - job_required_jobs_passed: name: All required jobs passed or were skipped needs: [ job_build, - job_compile_bindings_profiling_node, job_browser_unit_tests, job_bun_unit_tests, job_deno_unit_tests, job_node_unit_tests, - job_profiling_node_unit_tests, job_node_integration_tests, job_browser_playwright_tests, job_browser_loader_tests, job_remix_integration_tests, job_e2e_tests, - job_profiling_e2e_tests, job_artifacts, job_lint, job_check_format, @@ -1233,251 +1049,3 @@ jobs: if: contains(needs.*.result, 'failure') run: | echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 - - job_compile_bindings_profiling_node: - name: Compile profiling-node (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} - needs: [job_get_metadata, job_build] - # Compiling bindings can be very slow (especially on windows), so only run precompile - # Skip precompile unless we are on a release branch as precompile slows down CI times. - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') - runs-on: ${{ matrix.os }} - container: - image: ${{ matrix.container }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - # x64 glibc - - os: ubuntu-20.04 - node: 18 - binary: linux-x64-glibc-108 - - os: ubuntu-20.04 - node: 20 - binary: linux-x64-glibc-115 - - os: ubuntu-20.04 - node: 22 - binary: linux-x64-glibc-127 - - # x64 musl - - os: ubuntu-20.04 - container: node:18-alpine3.17 - node: 18 - binary: linux-x64-musl-108 - - os: ubuntu-20.04 - container: node:20-alpine3.17 - node: 20 - binary: linux-x64-musl-115 - - os: ubuntu-20.04 - container: node:22-alpine3.18 - node: 22 - binary: linux-x64-musl-127 - - # arm64 glibc - - os: ubuntu-20.04 - arch: arm64 - node: 18 - binary: linux-arm64-glibc-108 - - os: ubuntu-20.04 - arch: arm64 - node: 20 - binary: linux-arm64-glibc-115 - - os: ubuntu-20.04 - arch: arm64 - node: 22 - binary: linux-arm64-glibc-127 - - # arm64 musl - - os: ubuntu-20.04 - arch: arm64 - container: node:18-alpine3.17 - node: 18 - binary: linux-arm64-musl-108 - - os: ubuntu-20.04 - arch: arm64 - container: node:20-alpine3.17 - node: 20 - binary: linux-arm64-musl-115 - - os: ubuntu-20.04 - arch: arm64 - container: node:22-alpine3.18 - node: 22 - binary: linux-arm64-musl-127 - - # macos x64 - - os: macos-13 - node: 18 - arch: x64 - binary: darwin-x64-108 - - os: macos-13 - node: 20 - arch: x64 - binary: darwin-x64-115 - - os: macos-13 - node: 22 - arch: x64 - binary: darwin-x64-127 - - # macos arm64 - - os: macos-13 - arch: arm64 - node: 18 - target_platform: darwin - binary: darwin-arm64-108 - - os: macos-13 - arch: arm64 - node: 20 - target_platform: darwin - binary: darwin-arm64-115 - - os: macos-13 - arch: arm64 - node: 22 - target_platform: darwin - binary: darwin-arm64-127 - - # windows x64 - - os: windows-2022 - node: 18 - arch: x64 - binary: win32-x64-108 - - os: windows-2022 - node: 20 - arch: x64 - binary: win32-x64-115 - - os: windows-2022 - node: 22 - arch: x64 - binary: win32-x64-127 - - steps: - - name: Setup (alpine) - if: contains(matrix.container, 'alpine') - run: | - apk add --no-cache build-base git g++ make curl python3 - ln -sf python3 /usr/bin/python - - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - # Note: On alpine images, this does nothing - # The node version will be the one that is installed in the image - # If you want to change the node version, you need to change the image - # For non-alpine imgages, this will install the correct version of node - - name: Setup Node - uses: actions/setup-node@v4 - if: contains(matrix.container, 'alpine') == false - with: - node-version: ${{ matrix.node }} - - - name: Restore dependency cache - uses: actions/cache/restore@v4 - id: restore-dependencies - with: - path: ${{ env.CACHED_DEPENDENCY_PATHS }} - key: ${{ needs.job_build.outputs.dependency_cache_key }} - enableCrossOsArchive: true - - - name: Increase yarn network timeout on Windows - if: contains(matrix.os, 'windows') - run: yarn config set network-timeout 600000 -g - - - name: Install dependencies - if: steps.restore-dependencies.outputs.cache-hit != 'true' - run: yarn install --ignore-engines --frozen-lockfile - env: - SKIP_PLAYWRIGHT_BROWSER_INSTALL: "1" - - - name: Configure safe directory - run: | - git config --global --add safe.directory "*" - - - name: Setup python - uses: actions/setup-python@v5 - if: ${{ !contains(matrix.container, 'alpine') }} - id: python-setup - with: - python-version: '3.8.10' - - - name: Setup (arm64| ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - sudo apt-get update - sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - - name: Setup Musl - if: contains(matrix.container, 'alpine') - run: | - cd packages/profiling-node - curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz - tar -xzvf aarch64-linux-musl-cross.tgz - $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version - - # configure node-gyp - - name: Configure node-gyp - if: matrix.arch != 'arm64' - run: | - cd packages/profiling-node - yarn build:bindings:configure - - - name: Configure node-gyp (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - yarn build:bindings:configure:arm64 - - - name: Configure node-gyp (arm64, darwin) - if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' - run: | - cd packages/profiling-node - yarn build:bindings:configure:arm64 - - # build bindings - - name: Build Bindings - if: matrix.arch != 'arm64' - run: | - yarn lerna run build:bindings --scope @sentry/profiling-node - - - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - CC=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \ - CXX=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ \ - BUILD_ARCH=arm64 \ - yarn build:bindings - - - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - CC=aarch64-linux-gnu-gcc \ - CXX=aarch64-linux-gnu-g++ \ - BUILD_ARCH=arm64 \ - yarn build:bindings:arm64 - - - name: Build Bindings (arm64, darwin) - if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' - run: | - cd packages/profiling-node - BUILD_PLATFORM=darwin \ - BUILD_ARCH=arm64 \ - yarn build:bindings:arm64 - - - name: Build profiling-node & its dependencies - run: yarn build --scope @sentry/profiling-node - - - name: Test Bindings - if: matrix.arch != 'arm64' - run: | - yarn lerna run test --scope @sentry/profiling-node - - - name: Archive Binary - uses: actions/upload-artifact@v4 - with: - name: profiling-node-binaries-${{ github.sha }}-${{ matrix.binary }} - path: ${{ github.workspace }}/packages/profiling-node/lib/sentry_cpu_profiler-${{matrix.binary}}.node - if-no-files-found: error diff --git a/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node b/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node deleted file mode 100755 index 65e97eca7e48d05a45e91933dd230fcc8c543d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115304 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 from defining macros that conflict with - # std::min() and std::max(). We don't use (much) - # but we still inherit it from uv.h. - 'NOMINMAX', - ] - }], - ], -} diff --git a/packages/profiling-node/bindings/cpu_profiler.cc b/packages/profiling-node/bindings/cpu_profiler.cc deleted file mode 100644 index bf3762867769..000000000000 --- a/packages/profiling-node/bindings/cpu_profiler.cc +++ /dev/null @@ -1,1226 +0,0 @@ -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static const uint8_t kMaxStackDepth(128); -static const float kSamplingFrequency(99.0); // 99 to avoid lockstep sampling -static const float kSamplingHz(1 / kSamplingFrequency); -static const int kSamplingInterval(kSamplingHz * 1e6); -static const v8::CpuProfilingNamingMode - kNamingMode(v8::CpuProfilingNamingMode::kDebugNaming); -static const v8::CpuProfilingLoggingMode - kDefaultLoggingMode(v8::CpuProfilingLoggingMode::kEagerLogging); - -enum ProfileFormat { - kFormatThread = 0, - kFormatChunk = 1, -}; - -// Allow users to override the default logging mode via env variable. This is -// useful because sometimes the flow of the profiled program can be to execute -// many sequential transaction - in that case, it may be preferable to set eager -// logging to avoid paying the high cost of profiling for each individual -// transaction (one example for this are jest tests when run with --runInBand -// option). -static const char *kEagerLoggingMode = "eager"; -static const char *kLazyLoggingMode = "lazy"; - -v8::CpuProfilingLoggingMode GetLoggingMode() { - static const char *logging_mode(getenv("SENTRY_PROFILER_LOGGING_MODE")); - - // most times this wont be set so just bail early - if (!logging_mode) { - return kDefaultLoggingMode; - } - - // other times it'll likely be set to lazy as eager is the default - if (strcmp(logging_mode, kLazyLoggingMode) == 0) { - return v8::CpuProfilingLoggingMode::kLazyLogging; - } else if (strcmp(logging_mode, kEagerLoggingMode) == 0) { - return v8::CpuProfilingLoggingMode::kEagerLogging; - } - - return kDefaultLoggingMode; -} - -uint64_t timestamp_milliseconds() { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); -} - -class SentryProfile; -class Profiler; - -enum class ProfileStatus { - kNotStarted, - kStarted, - kStopped, -}; - -class MeasurementsTicker { -private: - uv_timer_t *timer; - uint64_t period_ms; - std::unordered_map> - heap_listeners; - std::unordered_map> - cpu_listeners; - v8::Isolate *isolate; - v8::HeapStatistics heap_stats; - uv_cpu_info_t cpu_stats; - -public: - MeasurementsTicker(uv_loop_t *loop) - : period_ms(100), isolate(v8::Isolate::GetCurrent()) { - timer = new uv_timer_t; - uv_timer_init(loop, timer); - uv_handle_set_data((uv_handle_t *)timer, this); - uv_ref((uv_handle_t *)timer); - } - - static void ticker(uv_timer_t *); - // Memory listeners - void heap_callback(); - void add_heap_listener( - std::string &profile_id, - const std::function cb); - void remove_heap_listener( - std::string &profile_id, - const std::function &cb); - - // CPU listeners - void cpu_callback(); - void add_cpu_listener(std::string &profile_id, - const std::function cb); - void remove_cpu_listener(std::string &profile_id, - const std::function &cb); - - size_t listener_count(); - - ~MeasurementsTicker() { - uv_handle_t *handle = (uv_handle_t *)timer; - - uv_timer_stop(timer); - uv_unref(handle); - - if (!uv_is_closing(handle)) { - uv_close(handle, [](uv_handle_t *handle) { delete handle; }); - } - } -}; - -size_t MeasurementsTicker::listener_count() { - return heap_listeners.size() + cpu_listeners.size(); -} - -// Heap tickers -void MeasurementsTicker::heap_callback() { - isolate->GetHeapStatistics(&heap_stats); - uint64_t ts = uv_hrtime(); - - for (auto cb : heap_listeners) { - cb.second(ts, heap_stats); - } -} - -void MeasurementsTicker::add_heap_listener( - std::string &profile_id, - const std::function cb) { - heap_listeners.emplace(profile_id, cb); - - if (listener_count() == 1) { - uv_timer_set_repeat(timer, period_ms); - uv_timer_start(timer, ticker, 0, period_ms); - } -} - -void MeasurementsTicker::remove_heap_listener( - std::string &profile_id, - const std::function &cb) { - heap_listeners.erase(profile_id); - - if (listener_count() == 0) { - uv_timer_stop(timer); - } -}; - -// CPU tickers -void MeasurementsTicker::cpu_callback() { - uv_cpu_info_t *cpu = &cpu_stats; - int count; - int err = uv_cpu_info(&cpu, &count); - - if (err) { - return; - } - - if (count < 1) { - return; - } - - uint64_t ts = uv_hrtime(); - uint64_t total = 0; - uint64_t idle_total = 0; - - for (int i = 0; i < count; i++) { - uv_cpu_info_t *core = cpu + i; - - total += core->cpu_times.user; - total += core->cpu_times.nice; - total += core->cpu_times.sys; - total += core->cpu_times.idle; - total += core->cpu_times.irq; - - idle_total += core->cpu_times.idle; - } - - double idle_avg = idle_total / count; - double total_avg = total / count; - double rate = 1.0 - idle_avg / total_avg; - - if (rate < 0.0 || isinf(rate) || isnan(rate)) { - rate = 0.0; - } - - auto it = cpu_listeners.begin(); - while (it != cpu_listeners.end()) { - if (it->second(ts, rate)) { - it = cpu_listeners.erase(it); - } else { - ++it; - } - }; - - uv_free_cpu_info(cpu, count); -}; - -void MeasurementsTicker::ticker(uv_timer_t *handle) { - if (handle == nullptr) { - return; - } - - MeasurementsTicker *self = static_cast(handle->data); - self->heap_callback(); - self->cpu_callback(); -} - -void MeasurementsTicker::add_cpu_listener( - std::string &profile_id, const std::function cb) { - cpu_listeners.emplace(profile_id, cb); - - if (listener_count() == 1) { - uv_timer_set_repeat(timer, period_ms); - uv_timer_start(timer, ticker, 0, period_ms); - } -} - -void MeasurementsTicker::remove_cpu_listener( - std::string &profile_id, const std::function &cb) { - cpu_listeners.erase(profile_id); - - if (listener_count() == 0) { - uv_timer_stop(timer); - } -}; - -class Profiler { -public: - std::unordered_map active_profiles; - - MeasurementsTicker measurements_ticker; - v8::CpuProfiler *cpu_profiler; - - explicit Profiler(const napi_env &env, v8::Isolate *isolate) - : measurements_ticker(uv_default_loop()), - cpu_profiler( - v8::CpuProfiler::New(isolate, kNamingMode, GetLoggingMode())) {} -}; - -class SentryProfile { -private: - uint64_t started_at; - uint64_t timestamp; - uint16_t heap_write_index = 0; - uint16_t cpu_write_index = 0; - - std::vector heap_stats_ts; - std::vector heap_stats_usage; - - std::vector cpu_stats_ts; - std::vector cpu_stats_usage; - - const std::function memory_sampler_cb; - const std::function cpu_sampler_cb; - - ProfileStatus status = ProfileStatus::kNotStarted; - std::string id; - -public: - explicit SentryProfile(const char *id) - : started_at(uv_hrtime()), timestamp(timestamp_milliseconds()), - memory_sampler_cb([this](uint64_t ts, v8::HeapStatistics &stats) { - if ((heap_write_index >= heap_stats_ts.capacity()) || - heap_write_index >= heap_stats_usage.capacity()) { - return true; - } - - heap_stats_ts.insert(heap_stats_ts.begin() + heap_write_index, - ts - started_at); - heap_stats_usage.insert( - heap_stats_usage.begin() + heap_write_index, - static_cast(stats.used_heap_size())); - ++heap_write_index; - - return false; - }), - - cpu_sampler_cb([this](uint64_t ts, double rate) { - if (cpu_write_index >= cpu_stats_ts.capacity() || - cpu_write_index >= cpu_stats_usage.capacity()) { - return true; - } - cpu_stats_ts.insert(cpu_stats_ts.begin() + cpu_write_index, - ts - started_at); - cpu_stats_usage.insert(cpu_stats_usage.begin() + cpu_write_index, - rate); - ++cpu_write_index; - return false; - }), - - status(ProfileStatus::kNotStarted), id(id) { - heap_stats_ts.reserve(300); - heap_stats_usage.reserve(300); - cpu_stats_ts.reserve(300); - cpu_stats_usage.reserve(300); - } - - const std::vector &heap_usage_timestamps() const; - const std::vector &heap_usage_values() const; - const uint16_t &heap_usage_write_index() const; - - const std::vector &cpu_usage_timestamps() const; - const std::vector &cpu_usage_values() const; - const uint16_t &cpu_usage_write_index() const; - const uint64_t &profile_start_timestamp() const; - - void Start(Profiler *profiler); - v8::CpuProfile *Stop(Profiler *profiler); -}; - -void SentryProfile::Start(Profiler *profiler) { - v8::Local profile_title = - v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(), - v8::NewStringType::kNormal) - .ToLocalChecked(); - - started_at = uv_hrtime(); - timestamp = timestamp_milliseconds(); - - // Initialize the CPU Profiler - profiler->cpu_profiler->StartProfiling( - profile_title, v8::CpuProfilingMode::kCallerLineNumbers, true, - v8::CpuProfilingOptions::kNoSampleLimit); - - // listen for memory sample ticks - profiler->measurements_ticker.add_cpu_listener(id, cpu_sampler_cb); - profiler->measurements_ticker.add_heap_listener(id, memory_sampler_cb); - - status = ProfileStatus::kStarted; -} - -v8::CpuProfile *SentryProfile::Stop(Profiler *profiler) { - // Stop the CPU Profiler - v8::CpuProfile *profile = profiler->cpu_profiler->StopProfiling( - v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(), - v8::NewStringType::kNormal) - .ToLocalChecked()); - - // Remove the memory sampler - profiler->measurements_ticker.remove_heap_listener(id, memory_sampler_cb); - profiler->measurements_ticker.remove_cpu_listener(id, cpu_sampler_cb); - // If for some reason stopProfiling was called with an invalid profile title - // or if that title had somehow been stopped already, profile will be null. - status = ProfileStatus::kStopped; - return profile; -} - -// Memory getters -const std::vector &SentryProfile::heap_usage_timestamps() const { - return heap_stats_ts; -}; - -const std::vector &SentryProfile::heap_usage_values() const { - return heap_stats_usage; -}; - -const uint16_t &SentryProfile::heap_usage_write_index() const { - return heap_write_index; -}; - -// CPU getters -const std::vector &SentryProfile::cpu_usage_timestamps() const { - return cpu_stats_ts; -}; - -const std::vector &SentryProfile::cpu_usage_values() const { - return cpu_stats_usage; -}; -const uint16_t &SentryProfile::cpu_usage_write_index() const { - return cpu_write_index; -}; -const uint64_t &SentryProfile::profile_start_timestamp() const { - return timestamp; -} - -static void CleanupSentryProfile(Profiler *profiler, - SentryProfile *sentry_profile, - const std::string &profile_id) { - if (sentry_profile == nullptr) { - return; - } - - sentry_profile->Stop(profiler); - profiler->active_profiles.erase(profile_id); - delete sentry_profile; -}; - -#ifdef _WIN32 -static const char kPlatformSeparator = '\\'; -static const char kWinDiskPrefix = ':'; -#else -static const char kPlatformSeparator = '/'; -#endif - -static const char kSentryPathDelimiter = '.'; -static const char kSentryFileDelimiter = ':'; -static const std::string kNodeModulesPath = - std::string("node_modules") + kPlatformSeparator; - -static void GetFrameModule(const std::string &abs_path, std::string &module) { - if (abs_path.empty()) { - return; - } - - module = abs_path; - - // Drop .js extension - size_t module_len = module.length(); - if (module.compare(module_len - 3, 3, ".js") == 0) { - module = module.substr(0, module_len - 3); - } - - // Drop anything before and including node_modules/ - size_t node_modules_pos = module.rfind(kNodeModulesPath); - if (node_modules_pos != std::string::npos) { - module = module.substr(node_modules_pos + 13); - } - - // Replace all path separators with dots except the last one, that one is - // replaced with a colon - int match_count = 0; - for (int pos = module.length() - 1; pos >= 0; pos--) { - // if there is a match and it's not the first character, replace it - if (module[pos] == kPlatformSeparator) { - module[pos] = - match_count == 0 ? kSentryFileDelimiter : kSentryPathDelimiter; - match_count++; - } - } - -#ifdef _WIN32 - // Strip out C: prefix. On Windows, the drive letter is not part of the module - // name - if (module[1] == kWinDiskPrefix) { - // We will try and strip our the disk prefix. - module = module.substr(2, std::string::npos); - } -#endif - - if (module[0] == '.') { - module = module.substr(1, std::string::npos); - } -} - -static napi_value GetFrameModuleWrapped(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2]; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *abs_path = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], abs_path, len + 1, &len) == - napi_ok); - - std::string module; - napi_value napi_module; - - GetFrameModule(abs_path, module); - - assert(napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH, - &napi_module) == napi_ok); - return napi_module; -} - -napi_value -CreateFrameNode(const napi_env &env, const v8::CpuProfileNode &node, - std::unordered_map &module_cache, - napi_value &resources) { - napi_value js_node; - napi_create_object(env, &js_node); - - napi_value lineno_prop; - napi_create_int32(env, node.GetLineNumber(), &lineno_prop); - napi_set_named_property(env, js_node, "lineno", lineno_prop); - - napi_value colno_prop; - napi_create_int32(env, node.GetColumnNumber(), &colno_prop); - napi_set_named_property(env, js_node, "colno", colno_prop); - - if (node.GetSourceType() != v8::CpuProfileNode::SourceType::kScript) { - napi_value system_frame_prop; - napi_get_boolean(env, false, &system_frame_prop); - napi_set_named_property(env, js_node, "in_app", system_frame_prop); - } - - napi_value function; - napi_create_string_utf8(env, node.GetFunctionNameStr(), NAPI_AUTO_LENGTH, - &function); - napi_set_named_property(env, js_node, "function", function); - - const char *resource = node.GetScriptResourceNameStr(); - - if (resource != nullptr) { - // resource is absolute path, set it on the abs_path property - napi_value abs_path_prop; - napi_create_string_utf8(env, resource, NAPI_AUTO_LENGTH, &abs_path_prop); - napi_set_named_property(env, js_node, "abs_path", abs_path_prop); - // Error stack traces are not relative to root dir, doing our own path - // normalization breaks people's code mapping configs so we need to leave it - // as is. - napi_set_named_property(env, js_node, "filename", abs_path_prop); - - std::string module; - std::string resource_str = std::string(resource); - - if (resource_str.empty()) { - return js_node; - } - - if (module_cache.find(resource_str) != module_cache.end()) { - module = module_cache[resource_str]; - } else { - napi_value resource; - napi_create_string_utf8(env, resource_str.c_str(), NAPI_AUTO_LENGTH, - &resource); - napi_set_element(env, resources, module_cache.size(), resource); - - GetFrameModule(resource_str, module); - module_cache.emplace(resource_str, module); - } - - if (!module.empty()) { - napi_value filename_prop; - napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH, - &filename_prop); - napi_set_named_property(env, js_node, "module", filename_prop); - } - } - - return js_node; -}; - -napi_value CreateSample(const napi_env &env, const enum ProfileFormat format, - const uint32_t stack_id, - const int64_t sample_timestamp_ns, - const double chunk_timestamp, - const uint32_t thread_id) { - napi_value js_node; - napi_create_object(env, &js_node); - - napi_value stack_id_prop; - napi_create_uint32(env, stack_id, &stack_id_prop); - napi_set_named_property(env, js_node, "stack_id", stack_id_prop); - - napi_value thread_id_prop; - napi_create_string_utf8(env, std::to_string(thread_id).c_str(), - NAPI_AUTO_LENGTH, &thread_id_prop); - napi_set_named_property(env, js_node, "thread_id", thread_id_prop); - - switch (format) { - case ProfileFormat::kFormatThread: { - napi_value timestamp; - napi_create_int64(env, sample_timestamp_ns, ×tamp); - napi_set_named_property(env, js_node, "elapsed_since_start_ns", timestamp); - } break; - case ProfileFormat::kFormatChunk: { - napi_value timestamp; - napi_create_double(env, chunk_timestamp, ×tamp); - napi_set_named_property(env, js_node, "timestamp", timestamp); - } break; - default: - break; - } - - return js_node; -}; - -std::string kDelimiter = std::string(";"); -std::string hashCpuProfilerNodeByPath(const v8::CpuProfileNode *node, - std::string &path) { - path.clear(); - - while (node != nullptr) { - path.append(std::to_string(node->GetNodeId())); - node = node->GetParent(); - } - - return path; -} - -static void GetSamples(const napi_env &env, const v8::CpuProfile *profile, - ProfileFormat format, - const uint64_t profile_start_timestamp_ms, - const uint32_t thread_id, napi_value &samples, - napi_value &stacks, napi_value &frames, - napi_value &resources) { - const int64_t profile_start_time_us = profile->GetStartTime(); - const int64_t sampleCount = profile->GetSamplesCount(); - - uint32_t unique_stack_id = 0; - uint32_t unique_frame_id = 0; - - // Initialize the lookup tables for stacks and frames, both of these are - // indexed in the sample format we are using to optimize for size. - std::unordered_map frame_lookup_table; - std::unordered_map stack_lookup_table; - std::unordered_map module_cache; - - // At worst, all stacks are unique so reserve the maximum amount of space - stack_lookup_table.reserve(sampleCount); - - std::string node_hash = ""; - - for (int i = 0; i < sampleCount; i++) { - uint32_t stack_index = unique_stack_id; - - const v8::CpuProfileNode *node = profile->GetSample(i); - const int64_t sample_timestamp_us = profile->GetSampleTimestamp(i); - - // If a node was only on top of the stack once, then it will only ever - // be inserted once and there is no need for hashing. - if (node->GetHitCount() > 1) { - hashCpuProfilerNodeByPath(node, node_hash); - - std::unordered_map::iterator - stack_index_cache_hit = stack_lookup_table.find(node_hash); - - // If we have a hit, update the stack index, otherwise - // insert it into the hash table and continue. - if (stack_index_cache_hit == stack_lookup_table.end()) { - stack_lookup_table.emplace(node_hash, stack_index); - } else { - stack_index = stack_index_cache_hit->second; - } - } - - uint64_t sample_delta_us = sample_timestamp_us - profile_start_time_us; - uint64_t sample_timestamp_ns = sample_delta_us * 1e3; - uint64_t sample_offset_from_profile_start_ms = - (sample_timestamp_us - profile_start_time_us) * 1e-3; - double seconds_since_start = - (profile_start_timestamp_ms + sample_offset_from_profile_start_ms) * - 1e-3; - - napi_value sample = nullptr; - sample = CreateSample(env, format, stack_index, sample_timestamp_ns, - seconds_since_start, thread_id); - - if (stack_index != unique_stack_id) { - napi_value index; - napi_create_uint32(env, i, &index); - napi_set_property(env, samples, index, sample); - continue; - } - - // A stack is a list of frames ordered from outermost (top) to innermost - // frame (bottom) - napi_value stack; - napi_create_array(env, &stack); - - uint32_t stack_depth = 0; - - while (node != nullptr && stack_depth < kMaxStackDepth) { - auto nodeId = node->GetNodeId(); - auto frame_index = frame_lookup_table.find(nodeId); - - // If the frame does not exist in the index - if (frame_index == frame_lookup_table.end()) { - frame_lookup_table.emplace(nodeId, unique_frame_id); - - napi_value frame_id; - napi_create_uint32(env, unique_frame_id, &frame_id); - - napi_value depth; - napi_create_uint32(env, stack_depth, &depth); - napi_set_property(env, stack, depth, frame_id); - napi_set_property(env, frames, frame_id, - CreateFrameNode(env, *node, module_cache, resources)); - - unique_frame_id++; - } else { - // If it was already indexed, just add it's id to the stack - napi_value depth; - napi_create_uint32(env, stack_depth, &depth); - - napi_value frame; - napi_create_uint32(env, frame_index->second, &frame); - napi_set_property(env, stack, depth, frame); - }; - - // Continue walking down the stack - node = node->GetParent(); - stack_depth++; - } - - napi_value napi_sample_index; - napi_value napi_stack_index; - - napi_create_uint32(env, i, &napi_sample_index); - napi_set_property(env, samples, napi_sample_index, sample); - napi_create_uint32(env, stack_index, &napi_stack_index); - napi_set_property(env, stacks, napi_stack_index, stack); - - unique_stack_id++; - } -} - -static napi_value TranslateMeasurementsDouble( - const napi_env &env, const enum ProfileFormat format, const char *unit, - const uint64_t profile_start_timestamp_ms, const uint16_t size, - const std::vector &values, - const std::vector ×tamps_ns) { - if (size > values.size() || size > timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "CPU measurement size is larger than the number of " - "values or timestamps"); - return nullptr; - } - - if (values.size() != timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "CPU measurement entries are corrupt, expected " - "values and timestamps to be of equal length"); - return nullptr; - } - - napi_value measurement; - napi_create_object(env, &measurement); - - napi_value unit_string; - napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string); - napi_set_named_property(env, measurement, "unit", unit_string); - - napi_value values_array; - napi_create_array(env, &values_array); - - uint16_t idx = size; - - for (size_t i = 0; i < idx; i++) { - napi_value entry; - napi_create_object(env, &entry); - - napi_value value; - if (napi_create_double(env, values[i], &value) != napi_ok) { - if (napi_create_double(env, 0.0, &value) != napi_ok) { - continue; - } - } - - napi_set_named_property(env, entry, "value", value); - - if (format == ProfileFormat::kFormatThread) { - napi_value ts; - napi_create_int64(env, timestamps_ns[i], &ts); - napi_set_named_property(env, entry, "elapsed_since_start_ns", ts); - } else if (format == ProfileFormat::kFormatChunk) { - napi_value ts; - napi_create_double( - env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts); - napi_set_named_property(env, entry, "timestamp", ts); - } - - napi_set_element(env, values_array, i, entry); - } - - napi_set_named_property(env, measurement, "values", values_array); - - return measurement; -} - -static napi_value -TranslateMeasurements(const napi_env &env, const enum ProfileFormat format, - const char *unit, - const uint64_t profile_start_timestamp_ms, - const uint16_t size, const std::vector &values, - const std::vector ×tamps_ns) { - if (size > values.size() || size > timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "Memory measurement size is larger than the number " - "of values or timestamps"); - return nullptr; - } - - if (values.size() != timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "Memory measurement entries are corrupt, expected " - "values and timestamps to be of equal length"); - return nullptr; - } - - napi_value measurement; - napi_create_object(env, &measurement); - - napi_value unit_string; - napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string); - napi_set_named_property(env, measurement, "unit", unit_string); - - napi_value values_array; - napi_create_array(env, &values_array); - - for (size_t i = 0; i < size; i++) { - napi_value entry; - napi_create_object(env, &entry); - - napi_value value; - napi_create_int64(env, values[i], &value); - - napi_set_named_property(env, entry, "value", value); - switch (format) { - case ProfileFormat::kFormatThread: { - napi_value ts; - napi_create_int64(env, timestamps_ns[i], &ts); - napi_set_named_property(env, entry, "elapsed_since_start_ns", ts); - } break; - case ProfileFormat::kFormatChunk: { - napi_value ts; - napi_create_double( - env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts); - napi_set_named_property(env, entry, "timestamp", ts); - } break; - default: - break; - } - napi_set_element(env, values_array, i, entry); - } - - napi_set_named_property(env, measurement, "values", values_array); - - return measurement; -} - -static napi_value TranslateProfile(const napi_env &env, - const v8::CpuProfile *profile, - const enum ProfileFormat format, - const uint64_t profile_start_timestamp_ms, - const uint32_t thread_id, - bool collect_resources) { - napi_value js_profile; - - napi_create_object(env, &js_profile); - - napi_value logging_mode; - napi_value samples; - napi_value stacks; - napi_value frames; - napi_value resources; - - napi_create_string_utf8( - env, - GetLoggingMode() == v8::CpuProfilingLoggingMode::kEagerLogging ? "eager" - : "lazy", - NAPI_AUTO_LENGTH, &logging_mode); - - napi_create_array(env, &samples); - napi_create_array(env, &stacks); - napi_create_array(env, &frames); - napi_create_array(env, &resources); - - napi_set_named_property(env, js_profile, "samples", samples); - napi_set_named_property(env, js_profile, "stacks", stacks); - napi_set_named_property(env, js_profile, "frames", frames); - napi_set_named_property(env, js_profile, "profiler_logging_mode", - logging_mode); - - GetSamples(env, profile, format, profile_start_timestamp_ms, thread_id, - samples, stacks, frames, resources); - - if (collect_resources) { - napi_set_named_property(env, js_profile, "resources", resources); - } else { - napi_create_array(env, &resources); - napi_set_named_property(env, js_profile, "resources", resources); - } - - return js_profile; -} - -static napi_value StartProfiling(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value argv[1]; - - assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok); - - napi_valuetype callbacktype0; - assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok); - - if (callbacktype0 != napi_string) { - napi_throw_error( - env, "NAPI_ERROR", - "TypeError: StartProfiling expects a string as first argument."); - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *title = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) == - napi_ok); - - if (len < 1) { - napi_throw_error(env, "NAPI_ERROR", - "StartProfiling expects a non-empty string as first " - "argument, got an empty string."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - v8::Isolate *isolate = v8::Isolate::GetCurrent(); - assert(isolate != 0); - - Profiler *profiler; - assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok); - - if (!profiler) { - napi_throw_error(env, "NAPI_ERROR", - "StartProfiling: Profiler is not initialized."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - const std::string profile_id(title); - // In case we have a collision, cleanup the old profile first - auto existing_profile = profiler->active_profiles.find(profile_id); - if (existing_profile != profiler->active_profiles.end()) { - existing_profile->second->Stop(profiler); - CleanupSentryProfile(profiler, existing_profile->second, profile_id); - } - - SentryProfile *sentry_profile = new SentryProfile(title); - sentry_profile->Start(profiler); - - profiler->active_profiles.emplace(profile_id, sentry_profile); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; -} - -// StopProfiling(string title) -// https://v8docs.nodesource.com/node-18.2/d2/d34/classv8_1_1_cpu_profiler.html#a40ca4c8a8aa4c9233aa2a2706457cc80 -static napi_value StopProfiling(napi_env env, napi_callback_info info) { - size_t argc = 4; - napi_value argv[4]; - - assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok); - - if (argc < 3) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects at least three arguments."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the first argument is a string - napi_valuetype callbacktype0; - assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok); - - if (callbacktype0 != napi_string) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects a string as first argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *title = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) == - napi_ok); - - if (len < 1) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a non empty string as first argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the second argument is a number - napi_valuetype callbacktype1; - assert(napi_typeof(env, argv[1], &callbacktype1) == napi_ok); - - if (callbacktype1 != napi_number) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects a format type as second argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the second argument is a number - napi_valuetype callbacktype2; - assert(napi_typeof(env, argv[2], &callbacktype2) == napi_ok); - - if (callbacktype2 != napi_number) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a thread_id integer as third argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - // Get the value of the second argument and convert it to uint8 - int32_t format; - assert(napi_get_value_int32(env, argv[1], &format) == napi_ok); - - // Get the value of the second argument and convert it to uint64 - int64_t thread_id; - assert(napi_get_value_int64(env, argv[2], &thread_id) == napi_ok); - - // Get profiler from instance data - Profiler *profiler; - assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok); - - if (!profiler) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling: Profiler is not initialized."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - const std::string profile_id(title); - auto profile = profiler->active_profiles.find(profile_id); - - // If the profile was never started, silently ignore the call and return null - if (profile == profiler->active_profiles.end()) { - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - v8::CpuProfile *cpu_profile = profile->second->Stop(profiler); - - // If for some reason stopProfiling was called with an invalid profile title - // or if that title had somehow been stopped already, profile will be null. - if (!cpu_profile) { - CleanupSentryProfile(profiler, profile->second, profile_id); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - }; - - napi_valuetype callbacktype3; - assert(napi_typeof(env, argv[3], &callbacktype3) == napi_ok); - - bool collect_resources; - napi_get_value_bool(env, argv[3], &collect_resources); - - const ProfileFormat format_type = static_cast(format); - - if (format_type != ProfileFormat::kFormatThread && - format_type != ProfileFormat::kFormatChunk) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a valid format type as second argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - napi_value js_profile = TranslateProfile( - env, cpu_profile, format_type, profile->second->profile_start_timestamp(), - thread_id, collect_resources); - - napi_value measurements; - napi_create_object(env, &measurements); - - if (profile->second->heap_usage_write_index() > 0) { - static const char *memory_unit = "byte"; - napi_value heap_usage_measurements = - TranslateMeasurements(env, format_type, memory_unit, - profile->second->profile_start_timestamp(), - profile->second->heap_usage_write_index(), - profile->second->heap_usage_values(), - profile->second->heap_usage_timestamps()); - - if (heap_usage_measurements != nullptr) { - napi_set_named_property(env, measurements, "memory_footprint", - heap_usage_measurements); - }; - }; - - if (profile->second->cpu_usage_write_index() > 0) { - static const char *cpu_unit = "percent"; - napi_value cpu_usage_measurements = TranslateMeasurementsDouble( - env, format_type, cpu_unit, profile->second->profile_start_timestamp(), - profile->second->cpu_usage_write_index(), - profile->second->cpu_usage_values(), - profile->second->cpu_usage_timestamps()); - - if (cpu_usage_measurements != nullptr) { - napi_set_named_property(env, measurements, "cpu_usage", - cpu_usage_measurements); - }; - }; - - napi_set_named_property(env, js_profile, "measurements", measurements); - - CleanupSentryProfile(profiler, profile->second, profile_id); - cpu_profile->Delete(); - - return js_profile; -}; - -void FreeAddonData(napi_env env, void *data, void *hint) { - Profiler *profiler = static_cast(data); - - if (profiler == nullptr) { - return; - } - - if (!profiler->active_profiles.empty()) { - for (auto &profile : profiler->active_profiles) { - CleanupSentryProfile(profiler, profile.second, profile.first); - } - } - - if (profiler->cpu_profiler != nullptr) { - profiler->cpu_profiler->Dispose(); - profiler->cpu_profiler = nullptr; - } - - delete profiler; -} - -napi_value Init(napi_env env, napi_value exports) { - v8::Isolate *isolate = v8::Isolate::GetCurrent(); - - if (isolate == nullptr) { - napi_throw_error(env, nullptr, - "Failed to initialize Sentry profiler: isolate is null."); - return NULL; - } - - Profiler *profiler = new Profiler(env, isolate); - profiler->cpu_profiler->SetSamplingInterval(kSamplingInterval); - - if (napi_set_instance_data(env, profiler, FreeAddonData, NULL) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to set instance data for profiler."); - return NULL; - } - - napi_value start_profiling; - if (napi_create_function(env, "startProfiling", NAPI_AUTO_LENGTH, - StartProfiling, exports, - &start_profiling) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create startProfiling function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "startProfiling", - start_profiling) != napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set startProfiling property on exports."); - return NULL; - } - - napi_value stop_profiling; - if (napi_create_function(env, "stopProfiling", NAPI_AUTO_LENGTH, - StopProfiling, exports, - &stop_profiling) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create stopProfiling function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "stopProfiling", stop_profiling) != - napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set stopProfiling property on exports."); - return NULL; - } - - napi_value get_frame_module; - if (napi_create_function(env, "getFrameModule", NAPI_AUTO_LENGTH, - GetFrameModuleWrapped, exports, - &get_frame_module) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create getFrameModule function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "getFrameModule", - get_frame_module) != napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set getFrameModule property on exports."); - return NULL; - } - - return exports; -} - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/packages/profiling-node/clang-format.js b/packages/profiling-node/clang-format.js deleted file mode 100644 index dd001cf28ad7..000000000000 --- a/packages/profiling-node/clang-format.js +++ /dev/null @@ -1,26 +0,0 @@ -const child_process = require('child_process'); - -const args = ['--Werror', '-i', '--style=file', 'bindings/cpu_profiler.cc']; -const cmd = `./node_modules/.bin/clang-format ${args.join(' ')}`; - -try { - child_process.execSync(cmd); -} catch (e) { - // This fails on linux_arm64 - // eslint-disable-next-line no-console - console.log('Running clang format command failed.'); -} - -// eslint-disable-next-line no-console -console.log('clang-format: done, checking tree...'); - -const diff = child_process.execSync('git status --short').toString(); - -if (diff) { - // eslint-disable-next-line no-console - console.error('clang-format: check failed ❌'); - process.exit(1); -} - -// eslint-disable-next-line no-console -console.log('clang-format: check passed ✅'); diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index d07295ba2679..f6713484381b 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -40,53 +40,34 @@ }, "files": [ "/lib", - "/bindings", - "/binding.gyp", "package.json", - "/scripts/binaries.js", - "/scripts/check-build.js", - "/scripts/copy-target.js", "/scripts/prune-profiler-binaries.js" ], "scripts": { - "install": "node scripts/check-build.js", "clean": "rm -rf build && rm -rf lib", - "lint": "yarn lint:eslint && yarn lint:clang", - "lint:eslint": "eslint . --format stylish", - "lint:clang": "node clang-format.js", + "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", - "lint:fix": "yarn fix:eslint && yarn fix:clang", - "lint:fix:clang": "node clang-format.js --fix", - "build": "yarn build:lib && yarn build:bindings:configure && yarn build:bindings", + "build": "yarn build:lib", "build:lib": "yarn build:types && rollup -c rollup.npm.config.mjs", - "build:transpile": "yarn build:bindings:configure && yarn build:bindings && yarn build:lib", + "build:transpile": "yarn build:lib", "build:types:downlevel": "yarn downlevel-dts lib/types lib/types-ts3.8 --to ts3.8", "build:types": "tsc -p tsconfig.types.json && yarn build:types:downlevel", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:bindings:configure": "node-gyp configure", - "build:bindings:configure:arm64": "node-gyp configure --arch=arm64 --target_arch=arm64", - "build:bindings": "node-gyp build && node scripts/copy-target.js", - "build:bindings:arm64": "node-gyp build --arch=arm64 && node scripts/copy-target.js", - "build:dev": "yarn clean && yarn build:bindings:configure && yarn build", + "build:dev": "yarn clean && yarn build", "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", "build:watch": "run-p build:transpile:watch build:types:watch", "build:tarball": "npm pack", - "test:watch": "cross-env SENTRY_PROFILER_BINARY_DIR=build jest --watch", + "test:watch": "jest --watch", "test:bundle": "node test-binaries.esbuild.js", - "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js" + "test": "jest --config jest.config.js" }, "dependencies": { "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", - "detect-libc": "^2.0.2", - "node-abi": "^3.61.0" + "@sentry-internal/node-cpu-profiler": "^2.0.0" }, "devDependencies": { - "@types/node": "^18.19.1", - "@types/node-abi": "^3.0.3", - "clang-format": "^1.8.0", - "cross-env": "^7.0.3", - "node-gyp": "^9.4.1" + "@types/node": "^18.19.1" }, "volta": { "extends": "../../package.json" diff --git a/packages/profiling-node/rollup.npm.config.mjs b/packages/profiling-node/rollup.npm.config.mjs index a9c148306709..05327bc1a29a 100644 --- a/packages/profiling-node/rollup.npm.config.mjs +++ b/packages/profiling-node/rollup.npm.config.mjs @@ -1,20 +1,19 @@ -import commonjs from '@rollup/plugin-commonjs'; -import replace from '@rollup/plugin-replace'; import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; export default makeNPMConfigVariants( makeBaseNPMConfig({ packageSpecificConfig: { - output: { dir: 'lib', preserveModules: false }, - plugins: [ - commonjs(), - replace({ - preventAssignment: false, - values: { - __IMPORT_META_URL_REPLACEMENT__: 'import.meta.url', - }, - }), - ], + output: { + dir: 'lib', + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + // set preserveModules to false because for profiling we actually want + // to bundle everything into one file. + preserveModules: + process.env.SENTRY_BUILD_PRESERVE_MODULES === undefined + ? false + : Boolean(process.env.SENTRY_BUILD_PRESERVE_MODULES), + }, }, }), ); diff --git a/packages/profiling-node/scripts/binaries.js b/packages/profiling-node/scripts/binaries.js deleted file mode 100644 index 2c0c6be2642b..000000000000 --- a/packages/profiling-node/scripts/binaries.js +++ /dev/null @@ -1,27 +0,0 @@ -const os = require('os'); -const path = require('path'); - -const abi = require('node-abi'); -const libc = require('detect-libc'); - -function getModuleName() { - const stdlib = libc.familySync(); - const platform = process.env['BUILD_PLATFORM'] || os.platform(); - const arch = process.env['BUILD_ARCH'] || os.arch(); - - if (platform === 'darwin' && arch === 'arm64') { - const identifier = [platform, 'arm64', abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-'); - return `sentry_cpu_profiler-${identifier}.node`; - } - - const identifier = [platform, arch, stdlib, abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-'); - - return `sentry_cpu_profiler-${identifier}.node`; -} - -const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node'); -const target = path.join(__dirname, '..', 'lib', getModuleName()); - -module.exports.source = source; -module.exports.target = target; -module.exports.getModuleName = getModuleName; diff --git a/packages/profiling-node/scripts/check-build.js b/packages/profiling-node/scripts/check-build.js deleted file mode 100644 index dda96e66b900..000000000000 --- a/packages/profiling-node/scripts/check-build.js +++ /dev/null @@ -1,56 +0,0 @@ -// This is a build script, so some logging is desirable as it allows -// us to follow the code path that triggered the error. -/* eslint-disable no-console */ -const fs = require('fs'); -const child_process = require('child_process'); -const binaries = require('./binaries.js'); - -function clean(err) { - return err.toString().trim(); -} - -function recompileFromSource() { - console.log('@sentry/profiling-node: Compiling from source...'); - let spawn = child_process.spawnSync('npm', ['run', 'build:bindings:configure'], { - stdio: ['inherit', 'inherit', 'pipe'], - env: process.env, - shell: true, - }); - - if (spawn.status !== 0) { - console.log('@sentry/profiling-node: Failed to configure gyp'); - console.log('@sentry/profiling-node:', clean(spawn.stderr)); - return; - } - - spawn = child_process.spawnSync('npm', ['run', 'build:bindings'], { - stdio: ['inherit', 'inherit', 'pipe'], - env: process.env, - shell: true, - }); - if (spawn.status !== 0) { - console.log('@sentry/profiling-node: Failed to build bindings'); - console.log('@sentry/profiling-node:', clean(spawn.stderr)); - return; - } -} - -if (fs.existsSync(binaries.target)) { - try { - console.log(`@sentry/profiling-node: Precompiled binary found, attempting to load ${binaries.target}`); - require(binaries.target); - console.log('@sentry/profiling-node: Precompiled binary found, skipping build from source.'); - } catch (e) { - console.log('@sentry/profiling-node: Precompiled binary found but failed loading'); - console.log('@sentry/profiling-node:', e); - try { - recompileFromSource(); - } catch (e) { - console.log('@sentry/profiling-node: Failed to compile from source'); - throw e; - } - } -} else { - console.log('@sentry/profiling-node: No precompiled binary found'); - recompileFromSource(); -} diff --git a/packages/profiling-node/scripts/copy-target.js b/packages/profiling-node/scripts/copy-target.js deleted file mode 100644 index 8277f1d45290..000000000000 --- a/packages/profiling-node/scripts/copy-target.js +++ /dev/null @@ -1,27 +0,0 @@ -// This is a build script, so some logging is desirable as it allows -// us to follow the code path that triggered the error. -/* eslint-disable no-console */ -const fs = require('fs'); -const path = require('path'); -const process = require('process'); -const binaries = require('./binaries.js'); - -const build = path.resolve(__dirname, '..', 'lib'); - -if (!fs.existsSync(build)) { - fs.mkdirSync(build, { recursive: true }); -} - -const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node'); -const target = path.join(__dirname, '..', 'lib', binaries.getModuleName()); - -if (!fs.existsSync(source)) { - console.log('Source file does not exist:', source); - process.exit(1); -} else { - if (fs.existsSync(target)) { - console.log('Target file already exists, overwriting it'); - } - console.log('Renaming', source, 'to', target); - fs.renameSync(source, target); -} diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts deleted file mode 100644 index a9a6d65ce191..000000000000 --- a/packages/profiling-node/src/cpu_profiler.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { createRequire } from 'node:module'; -import { arch as _arch, platform as _platform } from 'node:os'; -import { join, resolve } from 'node:path'; -import { dirname } from 'node:path'; -import { env, versions } from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { threadId } from 'node:worker_threads'; -import { familySync } from 'detect-libc'; -import { getAbi } from 'node-abi'; - -import { GLOBAL_OBJ, logger } from '@sentry/core'; -import { DEBUG_BUILD } from './debug-build'; -import type { - PrivateV8CpuProfilerBindings, - RawChunkCpuProfile, - RawThreadCpuProfile, - V8CpuProfilerBindings, -} from './types'; -import type { ProfileFormat } from './types'; - -declare const __IMPORT_META_URL_REPLACEMENT__: string; - -const stdlib = familySync(); -const platform = process.env['BUILD_PLATFORM'] || _platform(); -const arch = process.env['BUILD_ARCH'] || _arch(); -const abi = getAbi(versions.node, 'node'); -const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-'); - -/** - * Imports cpp bindings based on the current platform and architecture. - */ -// eslint-disable-next-line complexity -export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { - // We need to work around using import.meta.url directly with __IMPORT_META_URL_REPLACEMENT__ because jest complains about it. - const importMetaUrl = - typeof __IMPORT_META_URL_REPLACEMENT__ !== 'undefined' - ? // This case is always hit when the SDK is built - __IMPORT_META_URL_REPLACEMENT__ - : // This case is hit when the tests are run - pathToFileURL(__filename).href; - - const createdRequire = createRequire(importMetaUrl); - const esmCompatibleDirname = dirname(fileURLToPath(importMetaUrl)); - - // If a binary path is specified, use that. - if (env['SENTRY_PROFILER_BINARY_PATH']) { - const envPath = env['SENTRY_PROFILER_BINARY_PATH']; - return createdRequire(envPath); - } - - // If a user specifies a different binary dir, they are in control of the binaries being moved there - if (env['SENTRY_PROFILER_BINARY_DIR']) { - const binaryPath = join(resolve(env['SENTRY_PROFILER_BINARY_DIR']), `sentry_cpu_profiler-${identifier}`); - return createdRequire(`${binaryPath}.node`); - } - - // We need the fallthrough so that in the end, we can fallback to the dynamic require. - // This is for cases where precompiled binaries were not provided, but may have been compiled from source. - if (platform === 'darwin') { - if (arch === 'x64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-127.node'); - } - } - - if (arch === 'arm64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-127.node'); - } - } - } - - if (platform === 'win32') { - if (arch === 'x64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-win32-x64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-win32-x64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-win32-x64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-win32-x64-127.node'); - } - } - } - - if (platform === 'linux') { - if (arch === 'x64') { - if (stdlib === 'musl') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-127.node'); - } - } - if (stdlib === 'glibc') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-127.node'); - } - } - } - if (arch === 'arm64') { - if (stdlib === 'musl') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-127.node'); - } - } - - if (stdlib === 'glibc') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-127.node'); - } - } - } - } - - const built_from_source_path = resolve(esmCompatibleDirname, '..', `sentry_cpu_profiler-${identifier}`); - return createdRequire(`${built_from_source_path}.node`); -} - -const PrivateCpuProfilerBindings: PrivateV8CpuProfilerBindings = importCppBindingsModule(); - -class Bindings implements V8CpuProfilerBindings { - public startProfiling(name: string): void { - if (!PrivateCpuProfilerBindings) { - DEBUG_BUILD && logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.'); - return; - } - - if (typeof PrivateCpuProfilerBindings.startProfiling !== 'function') { - DEBUG_BUILD && - logger.log('[Profiling] Native startProfiling function is not available, ignoring call to startProfiling.'); - return; - } - - return PrivateCpuProfilerBindings.startProfiling(name); - } - - public stopProfiling(name: string, format: ProfileFormat.THREAD): RawThreadCpuProfile | null; - public stopProfiling(name: string, format: ProfileFormat.CHUNK): RawChunkCpuProfile | null; - public stopProfiling( - name: string, - format: ProfileFormat.CHUNK | ProfileFormat.THREAD, - ): RawThreadCpuProfile | RawChunkCpuProfile | null { - if (!PrivateCpuProfilerBindings) { - DEBUG_BUILD && - logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.'); - return null; - } - - if (typeof PrivateCpuProfilerBindings.stopProfiling !== 'function') { - DEBUG_BUILD && - logger.log('[Profiling] Native stopProfiling function is not available, ignoring call to stopProfiling.'); - return null; - } - - return PrivateCpuProfilerBindings.stopProfiling( - name, - format as unknown as any, - threadId, - !!GLOBAL_OBJ._sentryDebugIds, - ); - } -} - -const CpuProfilerBindings = new Bindings(); - -export { PrivateCpuProfilerBindings }; -export { CpuProfilerBindings }; diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index ca9db531d1e9..5b455f72974d 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import type { Event, IntegrationFn, Profile, ProfileChunk, ProfilingIntegration, Span } from '@sentry/core'; import { LRUMap, @@ -14,7 +15,6 @@ import { uuid4, } from '@sentry/core'; import type { NodeClient } from '@sentry/node'; -import { CpuProfilerBindings } from './cpu_profiler'; import { DEBUG_BUILD } from './debug-build'; import { NODE_MAJOR, NODE_VERSION } from './nodeVersion'; import { MAX_PROFILE_DURATION_MS, maybeProfileSpan, stopSpanProfile } from './spanProfileUtils'; @@ -120,8 +120,8 @@ function setupAutomatedSpanProfiling(client: NodeClient): void { const profilesToAddToEnvelope: Profile[] = []; for (const profiledTransaction of profiledTransactionEvents) { - const profileContext = profiledTransaction.contexts?.['profile']; - const profile_id = profileContext?.['profile_id']; + const profileContext = profiledTransaction.contexts?.profile; + const profile_id = profileContext?.profile_id; if (!profile_id) { throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); @@ -129,7 +129,7 @@ function setupAutomatedSpanProfiling(client: NodeClient): void { // Remove the profile from the transaction context before sending, relay will take care of the rest. if (profileContext) { - delete profiledTransaction.contexts?.['profile']; + delete profiledTransaction.contexts?.profile; } const cpuProfile = takeFromProfileQueue(profile_id); @@ -400,7 +400,7 @@ class ContinuousProfiler { * Assigns thread_id and thread name context to a profiled event. */ private _assignThreadIdContext(event: Event): void { - if (!event?.['contexts']?.['profile']) { + if (!event?.contexts?.profile) { return; } @@ -410,10 +410,10 @@ class ContinuousProfiler { // @ts-expect-error the trace fallback value is wrong, though it should never happen // and in case it does, we dont want to override whatever was passed initially. - event.contexts['trace'] = { - ...(event.contexts?.['trace'] ?? {}), + event.contexts.trace = { + ...(event.contexts?.trace ?? {}), data: { - ...(event.contexts?.['trace']?.['data'] ?? {}), + ...(event.contexts?.trace?.data ?? {}), ['thread.id']: PROFILER_THREAD_ID_STRING, ['thread.name']: PROFILER_THREAD_NAME, }, diff --git a/packages/profiling-node/src/spanProfileUtils.ts b/packages/profiling-node/src/spanProfileUtils.ts index 1ee050ce22e5..342075bde890 100644 --- a/packages/profiling-node/src/spanProfileUtils.ts +++ b/packages/profiling-node/src/spanProfileUtils.ts @@ -1,7 +1,7 @@ +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import type { CustomSamplingContext, Span } from '@sentry/core'; import { logger, spanIsSampled, spanToJSON, uuid4 } from '@sentry/core'; import type { NodeClient } from '@sentry/node'; -import { CpuProfilerBindings } from './cpu_profiler'; import { DEBUG_BUILD } from './debug-build'; import type { RawThreadCpuProfile } from './types'; import { isValidSampleRate } from './utils'; diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index baf3370d6ce8..e6ab3803ebdd 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -1,7 +1,6 @@ import * as os from 'os'; import type { Client, - Context, ContinuousThreadCpuProfile, DebugImage, DsnComponents, @@ -14,6 +13,7 @@ import type { ProfileChunkItem, SdkInfo, ThreadCpuProfile, + TransactionEvent, } from '@sentry/core'; import { createEnvelope, @@ -99,7 +99,7 @@ export function createProfilingEvent(client: Client, profile: RawThreadCpuProfil event_id: event.event_id ?? '', transaction: event.transaction ?? '', start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(), - trace_id: event.contexts?.['trace']?.['trace_id'] ?? '', + trace_id: event.contexts?.trace?.trace_id ?? '', profile_id: profile.profile_id, }); } @@ -352,7 +352,7 @@ export function addProfilesToEnvelope(envelope: Envelope, profiles: Profile[]): * @returns {Event[]} */ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[] { - const events: Event[] = []; + const events: TransactionEvent[] = []; forEachEnvelopeItem(envelope, (item, type) => { if (type !== 'transaction') { @@ -361,18 +361,17 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[ // First item is the type, so we can skip it, everything else is an event for (let j = 1; j < item.length; j++) { - const event = item[j]; + const event = item[j] as TransactionEvent; if (!event) { // Shouldn't happen, but lets be safe continue; } - // @ts-expect-error profile_id is not part of the metadata type - const profile_id = (event.contexts as Context)?.['profile']?.['profile_id']; + const profile_id = event.contexts?.profile?.profile_id; if (event && profile_id) { - events.push(item[j] as Event); + events.push(event); } } }); diff --git a/packages/profiling-node/test/bindings.test.ts b/packages/profiling-node/test/bindings.test.ts deleted file mode 100644 index 27361a87d941..000000000000 --- a/packages/profiling-node/test/bindings.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { platform } from 'os'; -// Contains unit tests for some of the C++ bindings. These functions -// are exported on the private bindings object, so we can test them and -// they should not be used outside of this file. -import { PrivateCpuProfilerBindings } from '../src/cpu_profiler'; - -const cases = [ - ['/Users/jonas/code/node_modules/@scope/package/file.js', '@scope.package:file'], - ['/Users/jonas/code/node_modules/package/dir/file.js', 'package.dir:file'], - ['/Users/jonas/code/node_modules/package/file.js', 'package:file'], - ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'], - - // Preserves non .js extensions - ['/Users/jonas/code/src/file.ts', 'Users.jonas.code.src:file.ts'], - // No extension - ['/Users/jonas/code/src/file', 'Users.jonas.code.src:file'], - // Edge cases that shouldn't happen in practice, but try and handle them so we don't crash - ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'], - ['', ''], -]; - -describe('GetFrameModule', () => { - it.each( - platform() === 'win32' - ? cases.map(([abs_path, expected]) => [abs_path ? `C:${abs_path.replace(/\//g, '\\')}` : '', expected]) - : cases, - )('%s => %s', (abs_path: string, expected: string) => { - expect(PrivateCpuProfilerBindings.getFrameModule(abs_path)).toBe(expected); - }); -}); diff --git a/packages/profiling-node/test/cpu_profiler.test.ts b/packages/profiling-node/test/cpu_profiler.test.ts deleted file mode 100644 index 9240ad636129..000000000000 --- a/packages/profiling-node/test/cpu_profiler.test.ts +++ /dev/null @@ -1,364 +0,0 @@ -import type { ContinuousThreadCpuProfile, ThreadCpuProfile } from '@sentry/core'; -import { CpuProfilerBindings, PrivateCpuProfilerBindings } from '../src/cpu_profiler'; -import type { RawThreadCpuProfile } from '../src/types'; -import { ProfileFormat } from '../src/types'; - -// Required because we test a hypothetical long profile -// and we cannot use advance timers as the c++ relies on -// actual event loop ticks that we cannot advance from jest. -jest.setTimeout(60_000); - -function fail(message: string): never { - throw new Error(message); -} - -const fibonacci = (n: number): number => { - if (n <= 1) { - return n; - } - return fibonacci(n - 1) + fibonacci(n - 2); -}; - -const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -const profiled = async (name: string, fn: () => void) => { - CpuProfilerBindings.startProfiling(name); - await fn(); - return CpuProfilerBindings.stopProfiling(name, ProfileFormat.THREAD); -}; - -const assertValidSamplesAndStacks = ( - stacks: ThreadCpuProfile['stacks'], - samples: ThreadCpuProfile['samples'] | ContinuousThreadCpuProfile['samples'], -) => { - expect(stacks.length).toBeGreaterThan(0); - expect(samples.length).toBeGreaterThan(0); - expect(stacks.length <= samples.length).toBe(true); - - for (const sample of samples) { - if (sample.stack_id === undefined) { - throw new Error(`Sample ${JSON.stringify(sample)} has not stack id associated`); - } - if (!stacks[sample.stack_id]) { - throw new Error(`Failed to find stack for sample: ${JSON.stringify(sample)}`); - } - expect(stacks[sample.stack_id]).not.toBe(undefined); - } - - for (const stack of stacks) { - expect(stack).not.toBe(undefined); - } -}; - -const isValidMeasurementValue = (v: any) => { - if (isNaN(v)) return false; - return typeof v === 'number' && v > 0; -}; - -const assertValidMeasurements = (measurement: RawThreadCpuProfile['measurements']['memory_footprint'] | undefined) => { - if (!measurement) { - throw new Error('Measurement is undefined'); - } - expect(measurement).not.toBe(undefined); - expect(typeof measurement.unit).toBe('string'); - expect(measurement.unit.length).toBeGreaterThan(0); - - for (let i = 0; i < measurement.values.length; i++) { - expect(measurement?.values?.[i]?.elapsed_since_start_ns).toBeGreaterThan(0); - expect(measurement?.values?.[i]?.value).toBeGreaterThan(0); - } -}; - -describe('Private bindings', () => { - it('does not crash if collect resources is false', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - expect(() => { - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false); - if (!profile) throw new Error('No profile'); - }).not.toThrow(); - }); - - it('throws if invalid format is supplied', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - expect(() => { - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', Number.MAX_SAFE_INTEGER, 0, false); - if (!profile) throw new Error('No profile'); - }).toThrow('StopProfiling expects a valid format type as second argument.'); - }); - - it('collects resources', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, true); - if (!profile) throw new Error('No profile'); - - expect(profile.resources.length).toBeGreaterThan(0); - - expect(new Set(profile.resources).size).toBe(profile.resources.length); - - for (const resource of profile.resources) { - expect(typeof resource).toBe('string'); - expect(resource).not.toBe(undefined); - } - }); - - it('does not collect resources', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false); - if (!profile) throw new Error('No profile'); - - expect(profile.resources.length).toBe(0); - }); -}); - -describe('Profiler bindings', () => { - it('exports profiler binding methods', () => { - expect(typeof CpuProfilerBindings['startProfiling']).toBe('function'); - expect(typeof CpuProfilerBindings['stopProfiling']).toBe('function'); - }); - - it('profiles a program', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - - assertValidSamplesAndStacks(profile.stacks, profile.samples); - }); - - it('adds thread_id info', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - const samples = profile.samples; - - if (!samples.length) { - throw new Error('No samples'); - } - for (const sample of samples) { - expect(sample.thread_id).toBe('0'); - } - }); - - it('caps stack depth at 128', async () => { - const recurseToDepth = async (depth: number): Promise => { - if (depth === 0) { - // Wait a bit to make sure stack gets sampled here - await wait(1000); - return 0; - } - const v = await recurseToDepth(depth - 1); - return v; - }; - - const profile = await profiled('profiled-program', async () => { - await recurseToDepth(256); - }); - - if (!profile) fail('Profile is null'); - - for (const stack of profile.stacks) { - expect(stack.length).toBeLessThanOrEqual(128); - } - }); - - it('does not record two profiles when titles match', () => { - CpuProfilerBindings.startProfiling('same-title'); - CpuProfilerBindings.startProfiling('same-title'); - - const first = CpuProfilerBindings.stopProfiling('same-title', 0); - const second = CpuProfilerBindings.stopProfiling('same-title', 0); - - expect(first).not.toBe(null); - expect(second).toBe(null); - }); - - it('multiple calls with same title', () => { - CpuProfilerBindings.startProfiling('same-title'); - expect(() => { - CpuProfilerBindings.stopProfiling('same-title', 0); - CpuProfilerBindings.stopProfiling('same-title', 0); - }).not.toThrow(); - }); - - it('does not crash if stopTransaction is called before startTransaction', () => { - expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); - }); - - it('does crash if name is invalid', () => { - expect(() => CpuProfilerBindings.stopProfiling('', 0)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling(undefined)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling(null)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling({})).toThrow(); - }); - - it('does not throw if stopTransaction is called before startTransaction', () => { - expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); - expect(() => CpuProfilerBindings.stopProfiling('does not exist', 0)).not.toThrow(); - }); - - it('compiles with eager logging by default', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - expect(profile.profiler_logging_mode).toBe('eager'); - }); - - it('chunk format type', async () => { - const fn = async () => { - await wait(1000); - fibonacci(36); - await wait(1000); - }; - - CpuProfilerBindings.startProfiling('non nullable stack'); - await fn(); - const profile = CpuProfilerBindings.stopProfiling('non nullable stack', ProfileFormat.CHUNK); - - if (!profile) fail('Profile is null'); - - for (const sample of profile.samples) { - if (!('timestamp' in sample)) { - throw new Error(`Sample ${JSON.stringify(sample)} has no timestamp`); - } - expect(sample.timestamp).toBeDefined(); - // No older than a minute and not in the future. Timestamp is in seconds so convert to ms - // as the constructor expects ms. - expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeGreaterThan(Date.now() - 60 * 1e3); - expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeLessThanOrEqual(Date.now()); - } - }); - - it('stacks are not null', async () => { - const profile = await profiled('non nullable stack', async () => { - await wait(1000); - fibonacci(36); - await wait(1000); - }); - - if (!profile) fail('Profile is null'); - assertValidSamplesAndStacks(profile.stacks, profile.samples); - }); - - it('samples at ~99hz', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(100); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - if (!profile) fail('Profile is null'); - - // Exception for macos and windows - we seem to get way less samples there, but I'm not sure if that's due to poor - // performance of the actions runner, machine or something else. This needs more investigation to determine - // the cause of low sample count. https://github.com/actions/runner-images/issues/1336 seems relevant. - if (process.platform === 'darwin' || process.platform === 'win32') { - if (profile.samples.length < 2) { - fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 2`); - } - } else { - if (profile.samples.length < 6) { - fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 6`); - } - } - if (profile.samples.length > 15) { - fail(`Too many samples on ${process.platform}, got ${profile.samples.length}`); - } - }); - - it('collects memory footprint', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(1000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - const heap_usage = profile?.measurements['memory_footprint']; - if (!heap_usage) { - throw new Error('memory_footprint is null'); - } - expect(heap_usage.values.length).toBeGreaterThan(6); - expect(heap_usage.values.length).toBeLessThanOrEqual(11); - expect(heap_usage.unit).toBe('byte'); - expect(heap_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true); - assertValidMeasurements(profile.measurements['memory_footprint']); - }); - - it('collects cpu usage', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(1000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - const cpu_usage = profile?.measurements['cpu_usage']; - if (!cpu_usage) { - throw new Error('cpu_usage is null'); - } - expect(cpu_usage.values.length).toBeGreaterThan(6); - expect(cpu_usage.values.length).toBeLessThanOrEqual(11); - expect(cpu_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true); - expect(cpu_usage.unit).toBe('percent'); - assertValidMeasurements(profile.measurements['cpu_usage']); - }); - - it('does not overflow measurement buffer if profile runs longer than 30s', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(35000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - expect(profile).not.toBe(null); - expect(profile?.measurements?.['cpu_usage']?.values.length).toBeLessThanOrEqual(300); - expect(profile?.measurements?.['memory_footprint']?.values.length).toBeLessThanOrEqual(300); - }); - - // eslint-disable-next-line @sentry-internal/sdk/no-skipped-tests - it.skip('includes deopt reason', async () => { - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#52-the-object-being-iterated-is-not-a-simple-enumerable - function iterateOverLargeHashTable() { - const table: Record = {}; - for (let i = 0; i < 1e5; i++) { - table[i] = i; - } - // eslint-disable-next-line - for (const _ in table) { - } - } - - const profile = await profiled('profiled-program', async () => { - iterateOverLargeHashTable(); - }); - - // @ts-expect-error deopt reasons are disabled for now as we need to figure out the backend support - const hasDeoptimizedFrame = profile.frames.some(f => f.deopt_reasons?.length > 0); - expect(hasDeoptimizedFrame).toBe(true); - }); - - it('does not crash if the native startProfiling function is not available', async () => { - const original = PrivateCpuProfilerBindings.startProfiling; - PrivateCpuProfilerBindings.startProfiling = undefined; - - expect(() => { - CpuProfilerBindings.startProfiling('profiled-program'); - }).not.toThrow(); - - PrivateCpuProfilerBindings.startProfiling = original; - }); - - it('does not crash if the native stopProfiling function is not available', async () => { - // eslint-disable-next-line @typescript-eslint/unbound-method - const original = PrivateCpuProfilerBindings.stopProfiling; - PrivateCpuProfilerBindings.stopProfiling = undefined; - - expect(() => { - CpuProfilerBindings.stopProfiling('profiled-program', 0); - }).not.toThrow(); - - PrivateCpuProfilerBindings.stopProfiling = original; - }); -}); diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts index 9974eb6ebc64..758307d3fa34 100644 --- a/packages/profiling-node/test/spanProfileUtils.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.test.ts @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/node'; +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import { getMainCarrier } from '@sentry/core'; import { GLOBAL_OBJ, createEnvelope, logger } from '@sentry/core'; import type { ProfilingIntegration } from '@sentry/core'; import type { ProfileChunk, Transport } from '@sentry/core'; import type { NodeClientOptions } from '@sentry/node/build/types/types'; -import { CpuProfilerBindings } from '../src/cpu_profiler'; import { _nodeProfilingIntegration } from '../src/integration'; function makeClientWithHooks(): [Sentry.NodeClient, Transport] { diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts index 85f852052bac..13cbc6957d5c 100644 --- a/scripts/ci-unit-tests.ts +++ b/scripts/ci-unit-tests.ts @@ -6,7 +6,7 @@ const UNIT_TEST_ENV = process.env.UNIT_TEST_ENV as 'node' | 'browser' | undefine const RUN_AFFECTED = process.argv.includes('--affected'); // These packages are tested separately in CI, so no need to run them here -const DEFAULT_SKIP_PACKAGES = ['@sentry/profiling-node', '@sentry/bun', '@sentry/deno']; +const DEFAULT_SKIP_PACKAGES = ['@sentry/bun', '@sentry/deno']; // All other packages are run for multiple node versions const BROWSER_TEST_PACKAGES = [ @@ -17,7 +17,6 @@ const BROWSER_TEST_PACKAGES = [ '@sentry/angular', '@sentry/solid', '@sentry/svelte', - '@sentry/profiling-node', '@sentry-internal/browser-utils', '@sentry-internal/replay', '@sentry-internal/replay-canvas', diff --git a/yarn.lock b/yarn.lock index 505d52f463d0..f47e402fd49f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6535,6 +6535,14 @@ "@angular-devkit/schematics" "14.2.13" jsonc-parser "3.1.0" +"@sentry-internal/node-cpu-profiler@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.0.0.tgz#76a0d363055876b91663769daee2d4b12321ba3b" + integrity sha512-0pZId+HY/AbNs1+CoCi8wogBWTrRv+DYeOgbevhekzMr5HYsA6PRY21NtHBXMbu0WcswFwaveDKR+sOW1EDHAA== + dependencies: + detect-libc "^2.0.2" + node-abi "^3.61.0" + "@sentry-internal/rrdom@2.31.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.31.0.tgz#548773964167ec104d3cbb9d7a4b25103c091e06" @@ -8113,11 +8121,6 @@ dependencies: "@types/unist" "^2" -"@types/node-abi@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" - integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== - "@types/node-cron@^3.0.11": version "3.0.11" resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" @@ -11915,15 +11918,6 @@ cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -clang-format@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.8.0.tgz#7779df1c5ce1bc8aac1b0b02b4e479191ef21d46" - integrity sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw== - dependencies: - async "^3.2.3" - glob "^7.0.0" - resolve "^1.1.6" - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -12757,13 +12751,6 @@ cronstrue@^2.50.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.50.0.tgz#eabba0f915f186765258b707b7a3950c663b5573" integrity sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -12775,7 +12762,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -22334,7 +22321,7 @@ node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== -node-gyp@^9.0.0, node-gyp@^9.4.1: +node-gyp@^9.0.0: version "9.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== @@ -27764,7 +27751,6 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" From 0963af0fad40edc368888924d11c857cd3dbf111 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 28 Jan 2025 22:52:09 +0100 Subject: [PATCH 25/43] fix(node): Add compatibility layer for Prisma v5 (#15169) --- .../node-integration-tests/package.json | 6 +- .../docker-compose.yml | 2 +- .../suites/tracing/prisma-orm-v5/package.json | 22 ++++++ .../prisma/migrations/migration_lock.toml | 0 .../migrations/sentry_test/migration.sql | 0 .../prisma/schema.prisma | 1 + .../suites/tracing/prisma-orm-v5/scenario.js | 52 ++++++++++++++ .../suites/tracing/prisma-orm-v5/test.ts | 69 +++++++++++++++++++ .../suites/tracing/prisma-orm-v5/yarn.lock | 58 ++++++++++++++++ .../tracing/prisma-orm-v6/docker-compose.yml | 13 ++++ .../package.json | 0 .../prisma/migrations/migration_lock.toml | 3 + .../migrations/sentry_test/migration.sql | 12 ++++ .../prisma-orm-v6/prisma/schema.prisma | 15 ++++ .../{prisma-orm => prisma-orm-v6}/scenario.js | 0 .../{prisma-orm => prisma-orm-v6}/test.ts | 0 .../{prisma-orm => prisma-orm-v6}/yarn.lock | 0 .../node/src/integrations/tracing/prisma.ts | 59 ++++++++++++++-- .../prisma/vendor/v5-tracing-helper.ts | 41 +++++++++++ .../prisma/vendor/v6-tracing-helper.ts | 38 ++++++++++ yarn.lock | 5 -- 21 files changed, 380 insertions(+), 16 deletions(-) rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/docker-compose.yml (81%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/migrations/migration_lock.toml (100%) rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/migrations/sentry_test/migration.sql (100%) rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v5}/prisma/schema.prisma (90%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/package.json (100%) create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql create mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/scenario.js (100%) rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/test.ts (100%) rename dev-packages/node-integration-tests/suites/tracing/{prisma-orm => prisma-orm-v6}/yarn.lock (100%) create mode 100644 packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts create mode 100644 packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 571397d12c8c..7b7fe3482200 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -16,11 +16,12 @@ "build:types": "tsc -p tsconfig.types.json", "clean": "rimraf -g **/node_modules && run-p clean:script", "clean:script": "node scripts/clean.js", - "prisma:init": "cd suites/tracing/prisma-orm && yarn && yarn setup", + "prisma-v5:init": "cd suites/tracing/prisma-orm-v5 && yarn && yarn setup", + "prisma-v6:init": "cd suites/tracing/prisma-orm-v6 && yarn && yarn setup", "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", "type-check": "tsc", - "pretest": "run-s --silent prisma:init", + "pretest": "run-s --silent prisma-v5:init prisma-v6:init", "test": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" }, @@ -30,7 +31,6 @@ "@nestjs/common": "10.4.6", "@nestjs/core": "10.4.6", "@nestjs/platform-express": "10.4.6", - "@prisma/client": "6.2.1", "@sentry/aws-serverless": "9.0.0-alpha.0", "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml similarity index 81% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml index 45caa4bb3179..37d45547b537 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml @@ -4,7 +4,7 @@ services: db: image: postgres:13 restart: always - container_name: integration-tests-prisma + container_name: integration-tests-prisma-v5 ports: - '5433:5432' environment: diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json new file mode 100644 index 000000000000..b8721038c83b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json @@ -0,0 +1,22 @@ +{ + "name": "sentry-prisma-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=18" + }, + "scripts": { + "db-up": "docker compose up -d", + "generate": "prisma generate", + "migrate": "prisma migrate dev -n sentry-test", + "setup": "run-s --silent db-up generate migrate" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@prisma/client": "5.22.0", + "prisma": "5.22.0" + } +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma similarity index 90% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma index 4363c97738ee..52682f1b6cf5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma @@ -5,6 +5,7 @@ datasource db { generator client { provider = "prisma-client-js" + previewFeatures = ["tracing"] } model User { diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js new file mode 100644 index 000000000000..767a6f27bdaa --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js @@ -0,0 +1,52 @@ +const Sentry = require('@sentry/node'); +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.prismaIntegration()], +}); + +const { randomBytes } = require('crypto'); +const { PrismaClient } = require('@prisma/client'); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +async function run() { + const client = new PrismaClient(); + + await Sentry.startSpanManual( + { + name: 'Test Transaction', + op: 'transaction', + }, + async span => { + await client.user.create({ + data: { + name: 'Tilda', + email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, + }, + }); + + await client.user.findMany(); + + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, + }, + }); + + setTimeout(async () => { + span.end(); + await client.$disconnect(); + }, 500); + }, + ); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts new file mode 100644 index 000000000000..0ece02f2f1cb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts @@ -0,0 +1,69 @@ +import { createRunner } from '../../../utils/runner'; + +describe('Prisma ORM Tests', () => { + test('CJS - should instrument PostgreSQL queries from Prisma ORM', done => { + createRunner(__dirname, 'scenario.js') + .expect({ + transaction: transaction => { + expect(transaction.transaction).toBe('Test Transaction'); + const spans = transaction.spans || []; + expect(spans.length).toBeGreaterThanOrEqual(5); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + method: 'create', + model: 'User', + name: 'User.create', + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:operation', + status: 'ok', + }), + ); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:serialize', + status: 'ok', + }), + ); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:connect', + status: 'ok', + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + method: 'findMany', + model: 'User', + name: 'User.findMany', + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:operation', + status: 'ok', + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:serialize', + status: 'ok', + }), + ); + }, + }) + .start(done); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock new file mode 100644 index 000000000000..860aa032d6cc --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock @@ -0,0 +1,58 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802" + integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA== + +"@prisma/debug@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412" + integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ== + +"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2": + version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4" + integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ== + +"@prisma/engines@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84" + integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA== + dependencies: + "@prisma/debug" "5.22.0" + "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/fetch-engine" "5.22.0" + "@prisma/get-platform" "5.22.0" + +"@prisma/fetch-engine@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e" + integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA== + dependencies: + "@prisma/debug" "5.22.0" + "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/get-platform" "5.22.0" + +"@prisma/get-platform@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd" + integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q== + dependencies: + "@prisma/debug" "5.22.0" + +fsevents@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +prisma@5.22.0: + version "5.22.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197" + integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A== + dependencies: + "@prisma/engines" "5.22.0" + optionalDependencies: + fsevents "2.3.3" diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml new file mode 100644 index 000000000000..ddab7cb9c563 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma-v6 + ports: + - '5434:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000000..fbffa92c2bb7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql new file mode 100644 index 000000000000..8619aaceb2b0 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "email" TEXT NOT NULL, + "name" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma new file mode 100644 index 000000000000..71a4923afb8c --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma @@ -0,0 +1,15 @@ +datasource db { + url = "postgresql://prisma:prisma@localhost:5434/tests" + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + email String @unique + name String? +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index ec9bc149410d..58516671c9a3 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -1,11 +1,61 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; // When importing CJS modules into an ESM module, we cannot import the named exports directly. import * as prismaInstrumentation from '@prisma/instrumentation'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, consoleSandbox, defineIntegration, spanToJSON } from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; +import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper'; +import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper'; const INTEGRATION_NAME = 'Prisma'; +const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = + // @ts-expect-error We need to do the following for interop reasons + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; + +type CompatibilityLayerTraceHelper = PrismaV5TracingHelper & PrismaV6TracingHelper; + +function isPrismaV6TracingHelper(helper: unknown): helper is PrismaV6TracingHelper { + return !!helper && typeof helper === 'object' && 'dispatchEngineSpans' in helper; +} + +class SentryPrismaInteropInstrumentation extends EsmInteropPrismaInstrumentation { + public constructor() { + super(); + } + + public enable(): void { + super.enable(); + + // The PrismaIntegration (super class) defines a global variable `global["PRISMA_INSTRUMENTATION"]` when `enable()` is called. This global variable holds a "TracingHelper" which Prisma uses internally to create tracing data. It's their way of not depending on OTEL with their main package. The sucky thing is, prisma broke the interface of the tracing helper with the v6 major update. This means that if you use Prisma 5 with the v6 instrumentation (or vice versa) Prisma just blows up, because tries to call methods on the helper that no longer exist. + // Because we actually want to use the v6 instrumentation and not blow up in Prisma 5 user's faces, what we're doing here is backfilling the v5 method (`createEngineSpan`) with a noop so that no longer crashes when it attempts to call that function. + // We still won't fully emit all the spans, but this could potentially be implemented in the future. + const prismaInstrumentationObject = (globalThis as Record).PRISMA_INSTRUMENTATION; + const prismaTracingHelper = + prismaInstrumentationObject && + typeof prismaInstrumentationObject === 'object' && + 'helper' in prismaInstrumentationObject + ? prismaInstrumentationObject.helper + : undefined; + + let emittedWarning = false; + + if (isPrismaV6TracingHelper(prismaTracingHelper)) { + (prismaTracingHelper as CompatibilityLayerTraceHelper).createEngineSpan = () => { + consoleSandbox(() => { + if (!emittedWarning) { + emittedWarning = true; + // eslint-disable-next-line no-console + console.warn( + '[Sentry] The Sentry SDK supports tracing with Prisma version 5 only with limited capabilities. For full tracing capabilities pass `prismaInstrumentation` for version 5 to the Sentry `prismaIntegration`. Read more: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/', + ); + } + }); + }; + } + } +} + export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>( INTEGRATION_NAME, options => { @@ -14,12 +64,7 @@ export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: return options.prismaInstrumentation; } - const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = - // @ts-expect-error We need to do the following for interop reasons - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; - - return new EsmInteropPrismaInstrumentation({}); + return new SentryPrismaInteropInstrumentation(); }, ); diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts new file mode 100644 index 000000000000..8823a8ca7728 --- /dev/null +++ b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts @@ -0,0 +1,41 @@ +// Vendored from https://github.com/prisma/prisma/blob/718358aa37975c18e5ea62f5b659fb47630b7609/packages/internals/src/tracing/types.ts#L1 + +import type { Context, Span, SpanOptions } from '@opentelemetry/api'; + +type V5SpanCallback = (span?: Span, context?: Context) => R; + +type V5ExtendedSpanOptions = SpanOptions & { + name: string; + internal?: boolean; + middleware?: boolean; + active?: boolean; + context?: Context; +}; + +type EngineSpanEvent = { + span: boolean; + spans: V5EngineSpan[]; +}; + +type V5EngineSpanKind = 'client' | 'internal'; + +type V5EngineSpan = { + span: boolean; + name: string; + trace_id: string; + span_id: string; + parent_span_id: string; + start_time: [number, number]; + end_time: [number, number]; + attributes?: Record; + links?: { trace_id: string; span_id: string }[]; + kind: V5EngineSpanKind; +}; + +export interface PrismaV5TracingHelper { + isEnabled(): boolean; + getTraceParent(context?: Context): string; + createEngineSpan(engineSpanEvent: EngineSpanEvent): void; + getActiveContext(): Context | undefined; + runInChildSpan(nameOrOptions: string | V5ExtendedSpanOptions, callback: V5SpanCallback): R; +} diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts new file mode 100644 index 000000000000..2ad1482a2e1a --- /dev/null +++ b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts @@ -0,0 +1,38 @@ +// https://github.com/prisma/prisma/blob/d45607dfa10c4ef08cb8f79f18fa84ef33910150/packages/internals/src/tracing/types.ts#L1 + +import type { Context, Span, SpanOptions } from '@opentelemetry/api'; + +type V6SpanCallback = (span?: Span, context?: Context) => R; + +type V6ExtendedSpanOptions = SpanOptions & { + name: string; + internal?: boolean; + middleware?: boolean; + active?: boolean; + context?: Context; +}; + +type V6EngineSpanId = string; + +type V6HrTime = [number, number]; + +type EngineSpanKind = 'client' | 'internal'; + +type PrismaV6EngineSpan = { + id: V6EngineSpanId; + parentId: string | null; + name: string; + startTime: V6HrTime; + endTime: V6HrTime; + kind: EngineSpanKind; + attributes?: Record; + links?: V6EngineSpanId[]; +}; + +export interface PrismaV6TracingHelper { + isEnabled(): boolean; + getTraceParent(context?: Context): string; + dispatchEngineSpans(spans: PrismaV6EngineSpan[]): void; + getActiveContext(): Context | undefined; + runInChildSpan(nameOrOptions: string | V6ExtendedSpanOptions, callback: V6SpanCallback): R; +} diff --git a/yarn.lock b/yarn.lock index f47e402fd49f..83ff270a83f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6066,11 +6066,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@prisma/client@6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.2.1.tgz#3d7d0c8669bba490247e1ffff67b93a516bd789f" - integrity sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA== - "@prisma/instrumentation@6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.2.1.tgz#261b885467d36759b7ca01d1b2ca4e1120bda886" From d38b9386580edc9dca3438946b1036fc818470f6 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 29 Jan 2025 14:34:45 +0100 Subject: [PATCH 26/43] ci: Pin node-integration-test version to `18.20.5` (#15220) It _seems_ we've got way more flakes since 18.20.6 was released, let's see if this fixes this possibly... --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c58d8606d4de..3b16742d7638 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -671,7 +671,7 @@ jobs: strategy: fail-fast: false matrix: - node: [18, 20, 22] + node: ['18.20.5', 20, 22] typescript: - false include: From d34e03f9fbc23c4484ccfca9ea84c917919d0d3b Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 29 Jan 2025 14:51:42 +0100 Subject: [PATCH 27/43] ref(core): Don't set `this.name` to `new.target.prototype.constructor.name` on `SentryError` class (#15216) We did some funky stuff in there that does not seem necessary and can even lead to issues. Fixes https://github.com/getsentry/sentry-javascript/issues/15214. --- packages/core/src/client.ts | 7 +++---- packages/core/src/utils-hoist/error.ts | 8 -------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 7334b2d294ed..805596a8a855 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -903,11 +903,10 @@ export abstract class Client { if (DEBUG_BUILD) { // If something's gone wrong, log the error as a warning. If it's just us having used a `SentryError` for // control flow, log just the message (no stack) as a log-level log. - const sentryError = reason as SentryError; - if (sentryError.logLevel === 'log') { - logger.log(sentryError.message); + if (reason instanceof SentryError && reason.logLevel === 'log') { + logger.log(reason.message); } else { - logger.warn(sentryError); + logger.warn(reason); } } return undefined; diff --git a/packages/core/src/utils-hoist/error.ts b/packages/core/src/utils-hoist/error.ts index 622aaff9cf80..5ae28093a8bf 100644 --- a/packages/core/src/utils-hoist/error.ts +++ b/packages/core/src/utils-hoist/error.ts @@ -2,9 +2,6 @@ import type { ConsoleLevel } from '../types-hoist'; /** An error emitted by Sentry SDKs and related utilities. */ export class SentryError extends Error { - /** Display name of this error instance. */ - public name: string; - public logLevel: ConsoleLevel; public constructor( @@ -13,11 +10,6 @@ export class SentryError extends Error { ) { super(message); - this.name = new.target.prototype.constructor.name; - // This sets the prototype to be `Error`, not `SentryError`. It's unclear why we do this, but commenting this line - // out causes various (seemingly totally unrelated) playwright tests consistently time out. FYI, this makes - // instances of `SentryError` fail `obj instanceof SentryError` checks. - Object.setPrototypeOf(this, new.target.prototype); this.logLevel = logLevel; } } From 52c40fc6c669a9bddd83101fe20975719a5f0cd2 Mon Sep 17 00:00:00 2001 From: Kaung Zin Hein <83657429+Zen-cronic@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:10:28 -0500 Subject: [PATCH 28/43] ref(browser): Add protocol attributes to resource spans (#15161) - Relocated `extractNetworkProtocol` util and its unit test to `browser-utils`. - Modified `browserMetrics` test to assert the new metadata. - Added integration test --------- Signed-off-by: Kaung Zin Hein <83657429+Zen-cronic@users.noreply.github.com> Co-authored-by: Lukas Stracke --- .../metrics/pageload-resource-spans/test.ts | 130 ++++++++++++++++-- packages/browser-utils/src/index.ts | 2 + .../src/metrics/browserMetrics.ts | 12 +- packages/browser-utils/src/metrics/utils.ts | 31 +++++ .../test/browser/browserMetrics.test.ts | 16 +++ .../browser-utils/test/browser/utils.test.ts | 43 +++++- packages/browser/src/tracing/request.ts | 32 +---- packages/browser/test/tracing/request.test.ts | 53 +------ 8 files changed, 220 insertions(+), 99 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts index 2343d77f55c7..9478b8f833f7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts @@ -1,24 +1,44 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; +import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -sentryTest('should add resource spans to pageload transaction', async ({ getLocalTestUrl, page }) => { +sentryTest('should add resource spans to pageload transaction', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } + const isWebkitRun = browserName === 'webkit'; + // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). await page.route('https://example.com/path/to/image.svg', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/image.svg` }), + route.fulfill({ + path: `${__dirname}/assets/image.svg`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'image/svg+xml', + }, + }), ); await page.route('https://example.com/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), + route.fulfill({ + path: `${__dirname}/assets/script.js`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'application/javascript', + }, + }), ); await page.route('https://example.com/path/to/style.css', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/style.css` }), + route.fulfill({ + path: `${__dirname}/assets/style.css`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'text/css', + }, + }), ); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -27,11 +47,14 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource')); const scriptSpans = resourceSpans?.filter(({ op }) => op === 'resource.script'); - const linkSpans = resourceSpans?.filter(({ op }) => op === 'resource.link'); - const imgSpans = resourceSpans?.filter(({ op }) => op === 'resource.img'); + const linkSpan = resourceSpans?.filter(({ op }) => op === 'resource.link')[0]; + const imgSpan = resourceSpans?.filter(({ op }) => op === 'resource.img')[0]; + + const spanId = eventData.contexts?.trace?.span_id; + const traceId = eventData.contexts?.trace?.trace_id; - expect(imgSpans).toHaveLength(1); - expect(linkSpans).toHaveLength(1); + expect(spanId).toBeDefined(); + expect(traceId).toBeDefined(); const hasCdnBundle = (process.env.PW_BUNDLE || '').startsWith('bundle'); @@ -41,11 +64,90 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca } expect(scriptSpans?.map(({ description }) => description).sort()).toEqual(expectedScripts); + expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); - const spanId = eventData.contexts?.trace?.span_id; + const customScriptSpan = scriptSpans?.find( + ({ description }) => description === 'https://example.com/path/to/script.js', + ); - expect(spanId).toBeDefined(); - expect(imgSpans?.[0].parent_span_id).toBe(spanId); - expect(linkSpans?.[0].parent_span_id).toBe(spanId); - expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); + expect(imgSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.img', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/image.svg', + op: 'resource.img', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(linkSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.link', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/style.css', + op: 'resource.link', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(customScriptSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + 'sentry.op': 'resource.script', + 'sentry.origin': 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/script.js', + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); }); diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index c71b2d70e31d..30bc3a29888e 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -17,6 +17,8 @@ export { registerInpInteractionListener, } from './metrics/browserMetrics'; +export { extractNetworkProtocol } from './metrics/utils'; + export { addClickKeypressInstrumentationHandler } from './instrument/dom'; export { addHistoryInstrumentationHandler } from './instrument/history'; diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 1d2b6b47c87e..794faa197ad5 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -20,7 +20,13 @@ import { addPerformanceInstrumentationHandler, addTtfbInstrumentationHandler, } from './instrument'; -import { getBrowserPerformanceAPI, isMeasurementValue, msToSec, startAndEndSpan } from './utils'; +import { + extractNetworkProtocol, + getBrowserPerformanceAPI, + isMeasurementValue, + msToSec, + startAndEndSpan, +} from './utils'; import { getActivationStart } from './web-vitals/lib/getActivationStart'; import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry'; import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher'; @@ -596,6 +602,10 @@ export function _addResourceSpans( attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin); + const { name, version } = extractNetworkProtocol(entry.nextHopProtocol); + attributes['network.protocol.name'] = name; + attributes['network.protocol.version'] = version; + const startTimestamp = timeOrigin + startTime; const endTimestamp = startTimestamp + duration; diff --git a/packages/browser-utils/src/metrics/utils.ts b/packages/browser-utils/src/metrics/utils.ts index 91aefa8a8918..aef40d4cf613 100644 --- a/packages/browser-utils/src/metrics/utils.ts +++ b/packages/browser-utils/src/metrics/utils.ts @@ -137,3 +137,34 @@ export function getBrowserPerformanceAPI(): Performance | undefined { export function msToSec(time: number): number { return time / 1000; } + +/** + * Converts ALPN protocol ids to name and version. + * + * (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) + * @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol + */ +export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } { + let name = 'unknown'; + let version = 'unknown'; + let _name = ''; + for (const char of nextHopProtocol) { + // http/1.1 etc. + if (char === '/') { + [name, version] = nextHopProtocol.split('/') as [string, string]; + break; + } + // h2, h3 etc. + if (!isNaN(Number(char))) { + name = _name === 'h' ? 'http' : _name; + version = nextHopProtocol.split(_name)[1] as string; + break; + } + _name += char; + } + if (_name === nextHopProtocol) { + // webrtc, ftp, etc. + name = _name; + } + return { name, version }; +} diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index 2ff1c2df209a..98a3bb375c00 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -131,6 +131,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 256, decodedBodySize: 256, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, resourceEntryName, 123, 456, 100); @@ -150,6 +151,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 256, decodedBodySize: 256, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 456, 100); @@ -169,6 +171,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 456, decodedBodySize: 593, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); const timeOrigin = 100; @@ -195,6 +198,8 @@ describe('_addResourceSpans', () => { ['url.scheme']: 'https', ['server.address']: 'example.com', ['url.same_origin']: true, + ['network.protocol.name']: 'http', + ['network.protocol.version']: '1.1', }, }), ); @@ -233,6 +238,7 @@ describe('_addResourceSpans', () => { const { initiatorType, op } = table[i]!; const entry = mockPerformanceResourceTiming({ initiatorType, + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465); @@ -254,6 +260,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 0, decodedBodySize: 0, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'h2', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -271,6 +278,8 @@ describe('_addResourceSpans', () => { ['url.scheme']: 'https', ['server.address']: 'example.com', ['url.same_origin']: true, + ['network.protocol.name']: 'http', + ['network.protocol.version']: '2', }, }), ); @@ -288,6 +297,7 @@ describe('_addResourceSpans', () => { transferSize: 2147483647, encodedBodySize: 2147483647, decodedBodySize: 2147483647, + nextHopProtocol: 'h3', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -301,6 +311,8 @@ describe('_addResourceSpans', () => { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https', + ['network.protocol.name']: 'http', + ['network.protocol.version']: '3', }, description: '/assets/to/css', timestamp: 468, @@ -325,6 +337,7 @@ describe('_addResourceSpans', () => { transferSize: null, encodedBodySize: null, decodedBodySize: null, + nextHopProtocol: 'h3', } as unknown as PerformanceResourceTiming; _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -338,6 +351,8 @@ describe('_addResourceSpans', () => { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https', + ['network.protocol.name']: 'http', + ['network.protocol.version']: '3', }, description: '/assets/to/css', timestamp: 468, @@ -365,6 +380,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 0, decodedBodySize: 0, deliveryType, + nextHopProtocol: 'h3', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); diff --git a/packages/browser-utils/test/browser/utils.test.ts b/packages/browser-utils/test/browser/utils.test.ts index bb7a757e4b6a..01fb5da605c4 100644 --- a/packages/browser-utils/test/browser/utils.test.ts +++ b/packages/browser-utils/test/browser/utils.test.ts @@ -1,5 +1,5 @@ import { SentrySpan, getCurrentScope, getIsolationScope, setCurrentClient, spanToJSON } from '@sentry/core'; -import { startAndEndSpan } from '../../src/metrics/utils'; +import { extractNetworkProtocol, startAndEndSpan } from '../../src/metrics/utils'; import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; describe('startAndEndSpan()', () => { @@ -54,3 +54,44 @@ describe('startAndEndSpan()', () => { expect(spanToJSON(parentSpan).start_timestamp).toEqual(123); }); }); + +describe('HTTPTimings', () => { + test.each([ + ['http/0.9', { name: 'http', version: '0.9' }], + ['http/1.0', { name: 'http', version: '1.0' }], + ['http/1.1', { name: 'http', version: '1.1' }], + ['spdy/1', { name: 'spdy', version: '1' }], + ['spdy/2', { name: 'spdy', version: '2' }], + ['spdy/3', { name: 'spdy', version: '3' }], + ['stun.turn', { name: 'stun.turn', version: 'unknown' }], + ['stun.nat-discovery', { name: 'stun.nat-discovery', version: 'unknown' }], + ['h2', { name: 'http', version: '2' }], + ['h2c', { name: 'http', version: '2c' }], + ['webrtc', { name: 'webrtc', version: 'unknown' }], + ['c-webrtc', { name: 'c-webrtc', version: 'unknown' }], + ['ftp', { name: 'ftp', version: 'unknown' }], + ['imap', { name: 'imap', version: 'unknown' }], + ['pop3', { name: 'pop', version: '3' }], + ['managesieve', { name: 'managesieve', version: 'unknown' }], + ['coap', { name: 'coap', version: 'unknown' }], + ['xmpp-client', { name: 'xmpp-client', version: 'unknown' }], + ['xmpp-server', { name: 'xmpp-server', version: 'unknown' }], + ['acme-tls/1', { name: 'acme-tls', version: '1' }], + ['mqtt', { name: 'mqtt', version: 'unknown' }], + ['dot', { name: 'dot', version: 'unknown' }], + ['ntske/1', { name: 'ntske', version: '1' }], + ['sunrpc', { name: 'sunrpc', version: 'unknown' }], + ['h3', { name: 'http', version: '3' }], + ['smb', { name: 'smb', version: 'unknown' }], + ['irc', { name: 'irc', version: 'unknown' }], + ['nntp', { name: 'nntp', version: 'unknown' }], + ['nnsp', { name: 'nnsp', version: 'unknown' }], + ['doq', { name: 'doq', version: 'unknown' }], + ['sip/2', { name: 'sip', version: '2' }], + ['tds/8.0', { name: 'tds', version: '8.0' }], + ['dicom', { name: 'dicom', version: 'unknown' }], + ['', { name: '', version: 'unknown' }], + ])('Extracting version from ALPN protocol %s', (protocol, expected) => { + expect(extractNetworkProtocol(protocol)).toMatchObject(expected); + }); +}); diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 92a8f2924084..368ea450b0d0 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -2,6 +2,7 @@ import { SENTRY_XHR_DATA_KEY, addPerformanceInstrumentationHandler, addXhrInstrumentationHandler, + extractNetworkProtocol, } from '@sentry-internal/browser-utils'; import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/core'; import { @@ -228,37 +229,6 @@ function addHTTPTimings(span: Span): void { }); } -/** - * Converts ALPN protocol ids to name and version. - * - * (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) - * @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol - */ -export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } { - let name = 'unknown'; - let version = 'unknown'; - let _name = ''; - for (const char of nextHopProtocol) { - // http/1.1 etc. - if (char === '/') { - [name, version] = nextHopProtocol.split('/') as [string, string]; - break; - } - // h2, h3 etc. - if (!isNaN(Number(char))) { - name = _name === 'h' ? 'http' : _name; - version = nextHopProtocol.split(_name)[1] as string; - break; - } - _name += char; - } - if (_name === nextHopProtocol) { - // webrtc, ftp, etc. - name = _name; - } - return { name, version }; -} - function getAbsoluteTime(time: number = 0): number { return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000; } diff --git a/packages/browser/test/tracing/request.test.ts b/packages/browser/test/tracing/request.test.ts index b262053190eb..67cd96ee6717 100644 --- a/packages/browser/test/tracing/request.test.ts +++ b/packages/browser/test/tracing/request.test.ts @@ -5,7 +5,7 @@ import * as browserUtils from '@sentry-internal/browser-utils'; import * as utils from '@sentry/core'; import type { Client } from '@sentry/core'; -import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request'; +import { instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request'; beforeAll(() => { // @ts-expect-error need to override global Request because it's not in the vi environment (even with an @@ -64,57 +64,6 @@ describe('instrumentOutgoingRequests', () => { }); }); -interface ProtocolInfo { - name: string; - version: string; -} - -describe('HTTPTimings', () => { - test('Extracting version from ALPN protocol', () => { - const nextHopToNetworkVersion: Record = { - 'http/0.9': { name: 'http', version: '0.9' }, - 'http/1.0': { name: 'http', version: '1.0' }, - 'http/1.1': { name: 'http', version: '1.1' }, - 'spdy/1': { name: 'spdy', version: '1' }, - 'spdy/2': { name: 'spdy', version: '2' }, - 'spdy/3': { name: 'spdy', version: '3' }, - 'stun.turn': { name: 'stun.turn', version: 'unknown' }, - 'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' }, - h2: { name: 'http', version: '2' }, - h2c: { name: 'http', version: '2c' }, - webrtc: { name: 'webrtc', version: 'unknown' }, - 'c-webrtc': { name: 'c-webrtc', version: 'unknown' }, - ftp: { name: 'ftp', version: 'unknown' }, - imap: { name: 'imap', version: 'unknown' }, - pop3: { name: 'pop', version: '3' }, - managesieve: { name: 'managesieve', version: 'unknown' }, - coap: { name: 'coap', version: 'unknown' }, - 'xmpp-client': { name: 'xmpp-client', version: 'unknown' }, - 'xmpp-server': { name: 'xmpp-server', version: 'unknown' }, - 'acme-tls/1': { name: 'acme-tls', version: '1' }, - mqtt: { name: 'mqtt', version: 'unknown' }, - dot: { name: 'dot', version: 'unknown' }, - 'ntske/1': { name: 'ntske', version: '1' }, - sunrpc: { name: 'sunrpc', version: 'unknown' }, - h3: { name: 'http', version: '3' }, - smb: { name: 'smb', version: 'unknown' }, - irc: { name: 'irc', version: 'unknown' }, - nntp: { name: 'nntp', version: 'unknown' }, - nnsp: { name: 'nnsp', version: 'unknown' }, - doq: { name: 'doq', version: 'unknown' }, - 'sip/2': { name: 'sip', version: '2' }, - 'tds/8.0': { name: 'tds', version: '8.0' }, - dicom: { name: 'dicom', version: 'unknown' }, - }; - - const protocols = Object.keys(nextHopToNetworkVersion); - for (const protocol of protocols) { - const expected = nextHopToNetworkVersion[protocol]!; - expect(extractNetworkProtocol(protocol)).toMatchObject(expected); - } - }); -}); - describe('shouldAttachHeaders', () => { describe('should prefer `tracePropagationTargets` over defaults', () => { it('should return `true` if the url matches the new tracePropagationTargets', () => { From e7176d53bac3b52b2d189b6b4e0f513fb2000dd3 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 29 Jan 2025 15:14:33 +0100 Subject: [PATCH 29/43] ci: Update runners to `ubuntu-22.04` (#15219) Bumping our images to ubuntu-22.04, except for the `ubuntu-20.04-large-js` ones, as I guess those ones are custom - should look into those too then eventually. In a follow up, we can try to bump to 24.04, possibly, but I figured we can do this piece by piece. --- .github/workflows/auto-release.yml | 2 +- .github/workflows/build.yml | 38 +++++++++---------- .github/workflows/canary.yml | 4 +- .github/workflows/clear-cache.yml | 2 +- .../workflows/enforce-license-compliance.yml | 2 +- .github/workflows/external-contributors.yml | 2 +- .github/workflows/flaky-test-detector.yml | 2 +- .github/workflows/gitflow-sync-develop.yml | 2 +- .github/workflows/release-comment-issues.yml | 2 +- .github/workflows/release-size-info.yml | 2 +- .github/workflows/release.yml | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index dd41d7d50e5e..39e73722dfa5 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -9,7 +9,7 @@ on: # This workflow tirggers a release when merging a branch with the pattern `prepare-release/VERSION` into master. jobs: release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Prepare a new version' steps: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b16742d7638..5972af74bb89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ env: jobs: job_get_metadata: name: Get Metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: pull-requests: read steps: @@ -118,7 +118,7 @@ jobs: job_build: name: Build needs: job_get_metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 if: | needs.job_get_metadata.outputs.changed_any_code == 'true' || @@ -196,7 +196,7 @@ jobs: job_check_branches: name: Check PR branches needs: job_get_metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.event_name == 'pull_request' permissions: pull-requests: write @@ -212,7 +212,7 @@ jobs: name: Size Check needs: [job_get_metadata, job_build] timeout-minutes: 15 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true' @@ -242,7 +242,7 @@ jobs: # inter-package dependencies resolve cleanly. needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -267,7 +267,7 @@ jobs: name: Check file formatting needs: [job_get_metadata] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -290,7 +290,7 @@ jobs: name: Circular Dependency Check needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -310,7 +310,7 @@ jobs: job_artifacts: name: Upload Artifacts needs: [job_get_metadata, job_build] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 # Build artifacts are only needed for releasing workflow. if: needs.job_get_metadata.outputs.is_release == 'true' steps: @@ -347,7 +347,7 @@ jobs: name: Browser Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) uses: actions/checkout@v4 @@ -386,7 +386,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_bun == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false steps: @@ -413,7 +413,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_deno == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false steps: @@ -443,7 +443,7 @@ jobs: name: Node (${{ matrix.node }}) Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -578,7 +578,7 @@ jobs: name: PW ${{ matrix.bundle }} Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 strategy: fail-fast: false @@ -638,7 +638,7 @@ jobs: job_check_for_faulty_dts: name: Check for faulty .d.ts files needs: [job_get_metadata, job_build] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -666,7 +666,7 @@ jobs: Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_node_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 strategy: fail-fast: false @@ -705,7 +705,7 @@ jobs: name: Remix (Node ${{ matrix.node }}) Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_remix == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 10 strategy: fail-fast: false @@ -801,7 +801,7 @@ jobs: # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix != '{"include":[]}' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -923,7 +923,7 @@ jobs: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} @@ -1043,7 +1043,7 @@ jobs: ] # Always run this, even if a dependent job failed if: always() - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check for failures if: contains(needs.*.result, 'failure') diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 24f25fd1ea9a..614a971623b3 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -27,7 +27,7 @@ permissions: jobs: job_e2e_prepare: name: Prepare E2E Canary tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 30 steps: - name: Check out current commit @@ -54,7 +54,7 @@ jobs: job_e2e_tests: name: E2E ${{ matrix.label }} Test needs: [job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 20 env: # We just use a dummy DSN here, only send to the tunnel anyhow diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml index 5c327553e3b8..78f1e3f66586 100644 --- a/.github/workflows/clear-cache.yml +++ b/.github/workflows/clear-cache.yml @@ -21,7 +21,7 @@ on: jobs: clear-caches: name: Delete all caches - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index 776f8135178d..f83a03a51b42 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -17,7 +17,7 @@ on: jobs: enforce-license-compliance: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: 'Enforce License Compliance' uses: getsentry/action-enforce-license-compliance@main diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index e9b1e05a2c92..cc2cbdb72774 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -12,7 +12,7 @@ jobs: permissions: pull-requests: write contents: write - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: | github.event.pull_request.merged == true && github.event.pull_request.author_association != 'COLLABORATOR' diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index 5d0d5af5d247..a232e1e735b1 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -23,7 +23,7 @@ concurrency: jobs: flaky-detector: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 name: 'Check tests for flakiness' # Also skip if PR is from master -> develop diff --git a/.github/workflows/gitflow-sync-develop.yml b/.github/workflows/gitflow-sync-develop.yml index 893dbbbf56fb..9bf8b6a556d6 100644 --- a/.github/workflows/gitflow-sync-develop.yml +++ b/.github/workflows/gitflow-sync-develop.yml @@ -17,7 +17,7 @@ env: jobs: main: name: Create PR master->develop - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: pull-requests: write contents: write diff --git a/.github/workflows/release-comment-issues.yml b/.github/workflows/release-comment-issues.yml index 4bbcb29aba21..ab4c45fc8f17 100644 --- a/.github/workflows/release-comment-issues.yml +++ b/.github/workflows/release-comment-issues.yml @@ -12,7 +12,7 @@ on: # This workflow is triggered when a release is published jobs: release-comment-issues: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Notify issues' steps: - name: Get version diff --git a/.github/workflows/release-size-info.yml b/.github/workflows/release-size-info.yml index 04e51e5ae14e..ea0cef636b8e 100644 --- a/.github/workflows/release-size-info.yml +++ b/.github/workflows/release-size-info.yml @@ -13,7 +13,7 @@ on: # It fetches the size-limit info from the release branch and adds it to the release jobs: release-size-info: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Add size-limit info to release' steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2768f18a5bc8..99569fd7f1aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ on: default: master jobs: release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Release a new version' steps: - name: Get auth token From 6e03449d5f27edc8e5689474995b445e00997568 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 29 Jan 2025 15:17:06 +0100 Subject: [PATCH 30/43] ref(core)!: Move `shutdownTimeout` option type from core to node (#15217) We used to have `shutdownTimeout` in the core client options, but this option only applies in node, so we can move it there. Also fixing a small unnecessary check I noticed along the way. This is technically breaking, but should not affect anybody as this option is not used (and documented) anywhere outside of node. --- packages/browser/src/sdk.ts | 4 +--- packages/core/src/types-hoist/options.ts | 9 --------- packages/node/src/types.ts | 8 ++++++++ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index d6fedeba769b..36dcade62859 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -53,9 +53,7 @@ export function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOpt release: typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value ? __SENTRY_RELEASE__ - : WINDOW.SENTRY_RELEASE?.id // This supports the variable that sentry-webpack-plugin injects - ? WINDOW.SENTRY_RELEASE.id - : undefined, + : WINDOW.SENTRY_RELEASE?.id, // This supports the variable that sentry-webpack-plugin injects sendClientReports: true, }; diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts index 49f3daa93b8e..58634930c993 100644 --- a/packages/core/src/types-hoist/options.ts +++ b/packages/core/src/types-hoist/options.ts @@ -138,15 +138,6 @@ export interface ClientOptions Date: Wed, 29 Jan 2025 15:36:07 +0100 Subject: [PATCH 31/43] chore: Add external contributor to CHANGELOG.md (#15223) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #15161 --------- Co-authored-by: Lms24 <8420481+Lms24@users.noreply.github.com> Co-authored-by: Lukas Stracke --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2232acfbb1..c7b41f50b30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf and @nathankleyn. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf, @Zen-cronic and @nathankleyn. Thank you for your contributions! ## 9.0.0-alpha.0 From 9ce4ad1a12150408d3514c093b0df02619f531b9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 29 Jan 2025 12:02:59 -0500 Subject: [PATCH 32/43] chore: Review copy/formatting for migration docs (#15178) Some more formatting changes to the migration docs, also remove todos where appropriate. --- docs/migration/v8-to-v9.md | 220 +++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 107 deletions(-) diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index e279fb0ebc86..538826a0cde5 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -1,7 +1,5 @@ # Upgrading from 8.x to 9.x -**DISCLAIMER: THIS MIGRATION GUIDE IS WORK IN PROGRESS** - Version 9 of the Sentry SDK concerns API cleanup and compatibility updates. This update contains behavioral changes that will not be caught by TypeScript or linters, so we recommend carefully reading the section on [Behavioral Changes](#2-behavior-changes). @@ -24,7 +22,7 @@ This includes features like Nullish Coalescing (`??`), Optional Chaining (`?.`), 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**, except for ESM-only SDKs (nuxt, solidstart, astro) which require Node **18.19.1** or up. +**Node.js:** The minimum supported Node.js version is **18.0.0**, except for ESM-only SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/sveltekit`) which require Node.js version **18.19.1** or higher. We no longer test against Node 14 and Node 16 and cannot guarantee that the SDK will work as expected on these versions. **Browsers:** Due to SDK code now including ES2020 features, the minimum supported browser list now looks as follows: @@ -52,7 +50,7 @@ Support for the following frameworks and library versions are dropped: ### TypeScript Version Policy -In preparation for the OpenTelemetry SDK v2, which will raise the minimum required TypeScript version, the minimum required TypeScript version is increased to version `5.0.4` (TBD https://github.com/open-telemetry/opentelemetry-js/pull/5145). +In preparation for the OpenTelemetry SDK v2, which will raise the minimum required TypeScript version, 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. @@ -71,7 +69,7 @@ Older Typescript versions _may_ still work, but we will not test them anymore an }); ``` -- Dropping spans in the `beforeSendSpan` hook is no longer possible. +- Dropping spans in the `beforeSendSpan` hook is no longer possible. This means you cannot return `null` from the `beforeSendSpan` hook anymore. - The `beforeSendSpan` hook now receives the root span as well as the child spans. - In previous versions, we determined if tracing is enabled (for Tracing Without Performance) by checking if either `tracesSampleRate` or `traceSampler` are _defined_ at all, in `Sentry.init()`. This means that e.g. the following config would lead to tracing without performance (=tracing being enabled, even if no spans would be started): @@ -154,7 +152,7 @@ Older Typescript versions _may_ still work, but we will not test them anymore an - The `sentry` property on the Next.js config object has officially been discontinued. Pass options to `withSentryConfig` directly. -### All Meta-Framework SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/solidstart`) +### All Meta-Framework SDKs (`@sentry/astro`, `@sentry/nextjs`, `@sentry/nuxt`, `@sentry/sveltekit`, `@sentry/solidstart`) - Updated source map generation to respect the user-provided value of your build config, such as `vite.build.sourcemap`: @@ -170,10 +168,6 @@ The `componentStack` field in the `ErrorBoundary` component is now typed as `str 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. -### Uncategorized (TODO) - -TODO - ## 3. Package Removals As part of an architectural cleanup, we deprecated the following packages: @@ -190,27 +184,34 @@ You may experience slight compatibility issues in the future by using it. We decided to keep this package around to temporarily lessen the upgrade burden. It will be removed in a future major version. -## 4. Removal of Deprecated APIs (TODO) +## 4. Removal of Deprecated 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 `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()`). + - 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'; + ```js + import * as Sentry from '@sentry/node'; -// before -Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation()); + // before + Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation()); -// after -Sentry.init({ - openTelemetryInstrumentations: [new GenericPoolInstrumentation()], -}); -``` + // after + Sentry.init({ + openTelemetryInstrumentations: [new GenericPoolInstrumentation()], + }); + ``` - The `DEFAULT_USER_INCLUDES` constant has been removed. + - The `getCurrentHub()`, `Hub` and `getCurrentHubShim()` APIs have been removed. They were on compatibility life support since the release of v8 and have now been fully removed from the SDK. ### `@sentry/browser` @@ -239,24 +240,23 @@ Sentry.init({ - 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) */ }) + export default defineConfig( + withSentry( + { + /* SolidStart config */ + }, + { + /* Sentry build-time config (like project and org) */ + }, + ), ); ``` -#### Other/Internal Changes - -The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior. - -- `client._prepareEvent()` now requires a currentScope & isolationScope to be passed as last arugments -- `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. - ### `@sentry/nestjs` - Removed `WithSentry` decorator. Use the `SentryExceptionCaptured` decorator instead. @@ -278,7 +278,7 @@ The following changes are unlikely to affect users of the SDK. They are listed h - The `wrapUseRoutes` method has been removed. Use the `wrapUseRoutesV6` or `wrapUseRoutesV7` methods instead depending on what version of react router you are using. - The `wrapCreateBrowserRouter` method has been removed. Use the `wrapCreateBrowserRouterV6` or `wrapCreateBrowserRouterV7` methods depending on what version of react router you are using. -## `@sentry/vue` +### `@sentry/vue` - The options `tracingOptions`, `trackComponents`, `timeout`, `hooks` have been removed everywhere except in the `tracingOptions` option of `vueIntegration()`. @@ -313,6 +313,13 @@ The following changes are unlikely to affect users of the SDK. They are listed h - 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. +#### 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 a currentScope & isolationScope to be passed as last arugments +- `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 Previously the CJS versions of the SDK code (wrongfully) contained compatibility statements for default exports in ESM: @@ -326,19 +333,18 @@ Let us know if this is causing issues in your setup by opening an issue on GitHu ### `@sentry/deno` -The minimum supported Deno version is now **2.0.0**. +- The minimum supported Deno version for the Deno SDK (`@sentry/deno`) is now **2.0.0**. -- `@sentry/deno` is no longer published on `deno.land` so you'll need to import - from npm: +- `@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'; + ```javascript + import * as Sentry from 'npm:@sentry/deno'; -Sentry.init({ - dsn: '__DSN__', - // ... -}); -``` + Sentry.init({ + dsn: '__DSN__', + // ... + }); + ``` ## 6. Type Changes @@ -383,49 +389,49 @@ The following outlines deprecations that were introduced in version 8 of the SDK - **Passing `undefined` to `tracesSampleRate` / `tracesSampler` / `enableTracing` will be handled differently in v9** -In v8, explicitly setting `tracesSampleRate` (even if it is set to `undefined`) resulted in tracing being _enabled_, although no spans were generated. + In v8, explicitly setting `tracesSampleRate` (even if it is set to `undefined`) resulted in tracing being _enabled_, although no spans were generated. -```ts -Sentry.init({ - tracesSampleRate: undefined, -}); -``` + ```ts + Sentry.init({ + tracesSampleRate: undefined, + }); + ``` -In v9, we will streamline this behavior so that passing `undefined` will result in tracing being disabled, the same as not passing the option at all. + In v9, we will streamline this behavior so that passing `undefined` will result in tracing being disabled, the same as not passing the option at all. -If you are relying on `undefined` being passed in and having tracing enabled because of this, you should update your config to set e.g. `tracesSampleRate: 0` instead, which will also enable tracing in v9. + If you are relying on `undefined` being passed in and having tracing enabled because of this, you should update your config to set e.g. `tracesSampleRate: 0` instead, which will also enable tracing in v9. -The `enableTracing` option was removed. In v9, to emulate `enableTracing: true`, set `tracesSampleRate: 1`. To emulate `enableTracing: false`, remove the `tracesSampleRate` and `tracesSampler` options (if configured). + The `enableTracing` option was removed. In v9, to emulate `enableTracing: true`, set `tracesSampleRate: 1`. To emulate `enableTracing: false`, remove the `tracesSampleRate` and `tracesSampler` options (if configured). - **The `autoSessionTracking` option is deprecated.** -To enable session tracking, it is recommended to unset `autoSessionTracking` and ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. + To enable session tracking, it is recommended to unset `autoSessionTracking` and ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. -To disable session tracking, it is recommended unset `autoSessionTracking` and to 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. + To disable session tracking, it is recommended unset `autoSessionTracking` and to 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 metrics API has been removed from the SDK.** +- **The metrics API is deprecated.** -The Sentry metrics beta has ended and the metrics API has been removed from the SDK. Learn more in [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th). + The Sentry metrics beta has ended and the metrics API has been deprecated in `8.x`. The entire API will be removed in `9.x` of 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). ## `@sentry/utils` - **The `@sentry/utils` package has been deprecated. Import everything from `@sentry/core` instead.** -- Deprecated `AddRequestDataToEventOptions.transaction`. This option effectively doesn't do anything anymore, and will be removed in v9. -- Deprecated `TransactionNamingScheme` type. -- Deprecated `validSeverityLevels`. Will not be replaced. -- Deprecated `urlEncode`. No replacements. -- Deprecated `addRequestDataToEvent`. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. -- Deprecated `extractRequestData`. Instead manually extract relevant data off request. -- Deprecated `arrayify`. No replacements. -- Deprecated `memoBuilder`. No replacements. -- Deprecated `getNumberOfUrlSegments`. No replacements. -- Deprecated `BAGGAGE_HEADER_NAME`. Use the `"baggage"` string constant directly instead. -- Deprecated `makeFifoCache`. No replacements. -- Deprecated `dynamicRequire`. No replacements. -- Deprecated `flatten`. No replacements. -- Deprecated `_browserPerformanceTimeOriginMode`. No replacements. + - Deprecated `AddRequestDataToEventOptions.transaction`. This option effectively doesn't do anything anymore, and will be removed in v9. + - Deprecated `TransactionNamingScheme` type. + - Deprecated `validSeverityLevels`. Will not be replaced. + - Deprecated `urlEncode`. No replacements. + - Deprecated `addRequestDataToEvent`. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. + - Deprecated `extractRequestData`. Instead manually extract relevant data off request. + - Deprecated `arrayify`. No replacements. + - Deprecated `memoBuilder`. No replacements. + - Deprecated `getNumberOfUrlSegments`. No replacements. + - Deprecated `BAGGAGE_HEADER_NAME`. Use the `"baggage"` string constant directly instead. + - Deprecated `makeFifoCache`. No replacements. + - Deprecated `dynamicRequire`. No replacements. + - Deprecated `flatten`. No replacements. + - Deprecated `_browserPerformanceTimeOriginMode`. No replacements. ## `@sentry/core` @@ -461,10 +467,10 @@ The Sentry metrics beta has ended and the metrics API has been removed from the - **The `@sentry/types` package has been deprecated. Import everything from `@sentry/core` instead.** -- Deprecated `Request` in favor of `RequestEventData`. -- Deprecated `RequestSession`. No replacements. -- Deprecated `RequestSessionStatus`. No replacements. -- Deprecated `SessionFlusherLike`. No replacements. + - Deprecated `Request` in favor of `RequestEventData`. + - Deprecated `RequestSession`. No replacements. + - Deprecated `RequestSessionStatus`. No replacements. + - Deprecated `SessionFlusherLike`. No replacements. ## `@sentry/nuxt` @@ -474,23 +480,23 @@ The Sentry metrics beta has ended and the metrics API has been removed from the - Deprecated `tracingOptions`, `trackComponents`, `timeout`, `hooks` options everywhere other than in the `tracingOptions` option of the `vueIntegration()`. -These options should now be set as follows: + These options should now be set as follows: -```ts -import * as Sentry from '@sentry/vue'; + ```ts + import * as Sentry from '@sentry/vue'; -Sentry.init({ - integrations: [ - Sentry.vueIntegration({ - tracingOptions: { - trackComponents: true, - timeout: 1000, - hooks: ['mount', 'update', 'unmount'], - }, - }), - ], -}); -``` + Sentry.init({ + integrations: [ + Sentry.vueIntegration({ + tracingOptions: { + trackComponents: true, + timeout: 1000, + hooks: ['mount', 'update', 'unmount'], + }, + }), + ], + }); + ``` - Deprecated `logErrors` in the `vueIntegration`. The Sentry Vue error handler will propagate the error to a user-defined error handler or just re-throw the error (which will log the error without modifying). @@ -499,24 +505,24 @@ Sentry.init({ - 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', // <-- - 'unmount', - ], - }, - }), - ], -}); -``` + 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/astro` From 0f254526a4fcac8d8ee857651902b4f91eab479c Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 30 Jan 2025 09:44:25 -0700 Subject: [PATCH 33/43] fix(react): From location can be undefined in Tanstack Router Instrumentation (#15235) fixes #15234 Due to a big bug in TanStack Router getting fixed, this needs an optional chain to not die. --- packages/react/src/tanstackrouter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts index 17ad3aff0a66..779528ec6d97 100644 --- a/packages/react/src/tanstackrouter.ts +++ b/packages/react/src/tanstackrouter.ts @@ -65,7 +65,7 @@ export function tanstackRouterBrowserTracingIntegration( // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected castRouterInstance.subscribe('onBeforeNavigate', onBeforeNavigateArgs => { // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments. - if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation.state) { + if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation?.state) { return; } From e3ee276fedf2bd14b7eed558ee91a59a139110c2 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 31 Jan 2025 09:02:50 +0100 Subject: [PATCH 34/43] chore: Add external contributor to CHANGELOG.md (#15236) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #15235 --------- Co-authored-by: Lms24 <8420481+Lms24@users.noreply.github.com> Co-authored-by: Lukas Stracke --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b41f50b30b..59271adf9976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf, @Zen-cronic and @nathankleyn. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf, @tannerlinsley, @Zen-cronic and @nathankleyn. Thank you for your contributions! ## 9.0.0-alpha.0 From 25e70715b7f13245a0548f32ef379f27c0613c53 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 31 Jan 2025 00:30:45 -0800 Subject: [PATCH 35/43] fix(react): Import default for hoistNonReactStatics (#15238) --- packages/react/src/errorboundary.tsx | 2 +- packages/react/src/hoist-non-react-statics.ts | 5 +++++ packages/react/src/profiler.tsx | 4 ++-- packages/react/src/reactrouter.tsx | 2 +- packages/react/src/reactrouterv6-compat-utils.tsx | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 packages/react/src/hoist-non-react-statics.ts diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index 9925ef1a8308..97f89357f38d 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -2,8 +2,8 @@ import type { ReportDialogOptions } from '@sentry/browser'; import { getClient, showReportDialog, withScope } from '@sentry/browser'; import { logger } from '@sentry/core'; import type { Scope } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import { DEBUG_BUILD } from './debug-build'; import { captureReactException } from './error'; diff --git a/packages/react/src/hoist-non-react-statics.ts b/packages/react/src/hoist-non-react-statics.ts new file mode 100644 index 000000000000..970928c80d23 --- /dev/null +++ b/packages/react/src/hoist-non-react-statics.ts @@ -0,0 +1,5 @@ +import * as hoistNonReactStaticsImport from 'hoist-non-react-statics'; + +// Ensure we use the default export from hoist-non-react-statics if available, +// falling back to the module itself. This handles both ESM and CJS usage. +export const hoistNonReactStatics = hoistNonReactStaticsImport.default || hoistNonReactStaticsImport; diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 2a147541b4b2..d7478ce6fb33 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -1,10 +1,10 @@ import { startInactiveSpan } from '@sentry/browser'; import type { Span } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON, timestampInSeconds, withActiveSpan } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; import { REACT_MOUNT_OP, REACT_RENDER_OP, REACT_UPDATE_OP } from './constants'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; export const UNKNOWN_COMPONENT = 'unknown'; @@ -236,4 +236,4 @@ function useProfiler( }, []); } -export { withProfiler, Profiler, useProfiler }; +export { Profiler, useProfiler, withProfiler }; diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index 42acef91522b..2692659de6c4 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -14,9 +14,9 @@ import { spanToJSON, } from '@sentry/core'; import type { Client, Integration, Span, TransactionSource } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; import type { ReactElement } from 'react'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import type { Action, Location } from './types'; diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index db65c32cee99..f9cf76cfc498 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -22,8 +22,8 @@ import { } from '@sentry/core'; import * as React from 'react'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import { DEBUG_BUILD } from './debug-build'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import type { Action, AgnosticDataRouteMatch, From 829d4da228cac70859d7f29d75ff252fc904ed77 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 31 Jan 2025 09:38:23 +0100 Subject: [PATCH 36/43] ref(react): Adapt tanstack router type (#15241) We adapted this here but did not adapt the types: https://github.com/getsentry/sentry-javascript/pull/15235 --- packages/react/src/vendor/tanstackrouter-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/vendor/tanstackrouter-types.ts b/packages/react/src/vendor/tanstackrouter-types.ts index e5eeba71aa87..417d2b1447b1 100644 --- a/packages/react/src/vendor/tanstackrouter-types.ts +++ b/packages/react/src/vendor/tanstackrouter-types.ts @@ -46,7 +46,7 @@ export interface VendoredTanstackRouter { eventType: 'onResolved' | 'onBeforeNavigate', callback: (stateUpdate: { toLocation: VendoredTanstackRouterLocation; - fromLocation: VendoredTanstackRouterLocation; + fromLocation?: VendoredTanstackRouterLocation; }) => void, ): () => void; } From d9b7543f6902f9d749f5fc7503b6269bb2993dc4 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 10:18:34 +0100 Subject: [PATCH 37/43] feat(sveltekit): Only inject fetch proxy script for SvelteKit < 2.16.0 (#15126) With v9 onwards, we'll only inject the fetch proxy script into SvelteKit versions where we actually need it. This is all kit versions below 2.16.0. I'll update docs in a follow-up PR. fixes #15007 --- .../.gitignore | 0 .../.npmrc | 0 .../README.md | 0 .../package.json | 4 +- .../playwright.config.mjs | 0 .../src/app.d.ts | 0 .../src/app.html | 0 .../src/hooks.client.ts | 0 .../src/hooks.server.ts | 0 .../src/routes/+layout.svelte | 0 .../src/routes/+page.svelte | 0 .../src/routes/errors/+page.server.ts | 0 .../src/routes/errors/+page.svelte | 0 .../start-event-proxy.mjs | 2 +- .../static/favicon.png | Bin .../svelte.config.js | 0 .../tests/errors.test.ts | 4 +- .../sveltekit-2.5.0-twp/tests/sdk.test.ts | 16 +++++++ .../tests/tracing.test.ts | 0 .../tsconfig.json | 0 .../vite.config.ts | 0 .../sveltekit-2/package.json | 2 +- .../sveltekit-2/tests/sdk.test.ts | 16 +++++++ packages/sveltekit/package.json | 4 +- packages/sveltekit/src/server/handle.ts | 40 +++++++++++++++--- packages/sveltekit/test/server/handle.test.ts | 19 ++++++++- 26 files changed, 91 insertions(+), 16 deletions(-) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/README.md (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/package.json (93%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/playwright.config.mjs (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/app.d.ts (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/app.html (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/hooks.client.ts (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/hooks.server.ts (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/routes/+layout.svelte (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/routes/+page.svelte (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/routes/errors/+page.server.ts (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/src/routes/errors/+page.svelte (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/start-event-proxy.mjs (72%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/static/favicon.png (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/svelte.config.js (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/tests/errors.test.ts (92%) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/tests/tracing.test.ts (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/tsconfig.json (100%) rename dev-packages/e2e-tests/test-applications/{sveltekit-2-twp => sveltekit-2.5.0-twp}/vite.config.ts (100%) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.gitignore b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.gitignore rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.npmrc b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.npmrc rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/README.md b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/README.md similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/README.md rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/README.md diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json similarity index 93% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json index f961077f9977..344eba5705fd 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json @@ -1,5 +1,5 @@ { - "name": "sveltekit-2-svelte-5", + "name": "sveltekit-2.5.0-twp", "version": "0.0.1", "private": true, "scripts": { @@ -22,7 +22,7 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", + "@sveltejs/kit": "2.5.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^5.0.0-next.115", "svelte-check": "^3.6.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/playwright.config.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/playwright.config.mjs rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/playwright.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.d.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.d.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.d.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.d.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.html b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.html similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.html rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.html diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.client.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.client.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.client.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.client.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.server.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.server.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.server.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+layout.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+layout.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+layout.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+page.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+page.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.server.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.server.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.server.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs similarity index 72% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs index 01e1095d6956..1ba4492f150a 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, - proxyServerName: 'sveltekit-2-twp', + proxyServerName: 'sveltekit-2.5.0-twp', }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/static/favicon.png b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/static/favicon.png similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/static/favicon.png rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/static/favicon.png diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/svelte.config.js similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/svelte.config.js rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/svelte.config.js diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts similarity index 92% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts index 0e16a2588982..984af2ec23a6 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts @@ -2,11 +2,11 @@ import { expect, test } from '@playwright/test'; import { waitForError } from '@sentry-internal/test-utils'; test('errors on frontend and backend are connected by the same trace', async ({ page }) => { - const clientErrorPromise = waitForError('sveltekit-2-twp', evt => { + const clientErrorPromise = waitForError('sveltekit-2.5.0-twp', evt => { return evt.exception?.values?.[0].value === 'Client Error'; }); - const serverErrorPromise = waitForError('sveltekit-2-twp', evt => { + const serverErrorPromise = waitForError('sveltekit-2.5.0-twp', evt => { return evt.exception?.values?.[0].value === 'No search query provided'; }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts new file mode 100644 index 000000000000..c1131913d057 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; + +test.describe('SDK-internal behavior', () => { + test('Injects fetch proxy script for SvelteKit<2.16.0', async ({ page }) => { + await page.goto('/'); + + const sentryCarrier = await page.evaluate('typeof window.__SENTRY__'); + const proxyHandle = await page.evaluate('typeof window._sentryFetchProxy'); + + // sanity check + expect(sentryCarrier).toBe('object'); + + // fetch proxy script ran + expect(proxyHandle).toBe('function'); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/tracing.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/tracing.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/tracing.test.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/tracing.test.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tsconfig.json b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tsconfig.json rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/vite.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/vite.config.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/vite.config.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json index f02253198aaa..91bc78290199 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -23,7 +23,7 @@ "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-node": "^2.0.0", - "@sveltejs/kit": "^2.5.0", + "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^4.2.8", "svelte-check": "^3.6.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts new file mode 100644 index 000000000000..f35a81e91238 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; +import { waitForInitialPageload } from './utils'; + +test.describe('SDK-internal behavior', () => { + test("Doesn't inject fetch proxy script for SvelteKit>=2.16.0", async ({ page }) => { + await waitForInitialPageload(page, { route: '/' }); + const sentryCarrier = await page.evaluate('typeof window.__SENTRY__'); + const proxyHandle = await page.evaluate('typeof window._sentryFetchProxy'); + + // sanity check + expect(sentryCarrier).toBe('object'); + + // fetch proxy script didn't run + expect(proxyHandle).toBe('undefined'); + }); +}); diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 077988a47bd1..94cb6fd32c39 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -9,9 +9,7 @@ "engines": { "node": ">=18" }, - "files": [ - "/build" - ], + "files": ["/build"], "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index d0e5e2e689f1..9bb9de9ce394 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -93,10 +93,10 @@ export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean } * ``` */ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { - const options: Required = { - handleUnknownRoutes: false, - injectFetchProxyScript: true, - ...handlerOptions, + const { handleUnknownRoutes, ...rest } = handlerOptions ?? {}; + const options = { + handleUnknownRoutes: handleUnknownRoutes ?? false, + ...rest, }; const sentryRequestHandler: Handle = input => { @@ -131,12 +131,24 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { async function instrumentHandle( { event, resolve }: Parameters[0], - options: Required, + options: SentryHandleOptions, ): Promise { if (!event.route?.id && !options.handleUnknownRoutes) { return resolve(event); } + // caching the result of the version check in `options.injectFetchProxyScript` + // to avoid doing the dynamic import on every request + if (options.injectFetchProxyScript == null) { + try { + // @ts-expect-error - the dynamic import is fine here + const { VERSION } = await import('@sveltejs/kit'); + options.injectFetchProxyScript = isFetchProxyRequired(VERSION); + } catch { + options.injectFetchProxyScript = true; + } + } + const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; if (getIsolationScope() !== getDefaultIsolationScope()) { @@ -161,7 +173,7 @@ async function instrumentHandle( normalizedRequest: winterCGRequestToRequestData(event.request.clone()), }); const res = await resolve(event, { - transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript }), + transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript ?? true }), }); if (span) { setHttpStatus(span, res.status); @@ -177,3 +189,19 @@ async function instrumentHandle( await flushIfServerless(); } } + +/** + * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. + * Exported only for testing. + */ +export function isFetchProxyRequired(version: string): boolean { + try { + const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); + if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { + return false; + } + } catch { + // ignore + } + return true; +} diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index f6556f8ddcea..150f59ae9bc8 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -14,7 +14,7 @@ import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, sentryHandle } from '../../src/server/handle'; +import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, isFetchProxyRequired, sentryHandle } from '../../src/server/handle'; import { getDefaultNodeClientOptions } from '../utils'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); @@ -462,3 +462,20 @@ describe('addSentryCodeToPage', () => { expect(transformed).not.toContain(``); }); }); + +describe('isFetchProxyRequired', () => { + it.each(['2.16.0', '2.16.1', '2.17.0', '3.0.0', '3.0.0-alpha.0'])( + 'returns false if the version is greater than or equal to 2.16.0 (%s)', + version => { + expect(isFetchProxyRequired(version)).toBe(false); + }, + ); + + it.each(['2.15.0', '2.15.1', '1.30.0', '1.0.0'])('returns true if the version is lower than 2.16.0 (%s)', version => { + expect(isFetchProxyRequired(version)).toBe(true); + }); + + it.each(['invalid', 'a.b.c'])('returns true for an invalid version (%s)', version => { + expect(isFetchProxyRequired(version)).toBe(true); + }); +}); From 3e31bdc405d2ef8514e98d000c7a916c51c0399f Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 31 Jan 2025 11:06:31 +0100 Subject: [PATCH 38/43] fix(node): Ensure `httpIntegration` propagates traces (#15233) Related to https://github.com/getsentry/sentry-javascript/pull/15231, I noticed that we today would not propagate traces in outgoing http requests if: 1. The user configures `httpIntegration({ spans: false })` 2. ..._or_ the user has a custom OTEL setup 3. _and_ the user does not add their own `HttpInstrumentation` Admittedly and edge case. More importantly, though, by actually adding distributed tracing information here, we are unblocked from potentially stopping to ship the http-instrumentation to users that do not need spans (and/or have a custom otel setup). --------- Co-authored-by: Lukas Stracke --- .../http-no-tracing-no-spans/scenario.ts | 61 +++++++++ .../requests/http-no-tracing-no-spans/test.ts | 95 ++++++++++++++ packages/core/src/utils-hoist/baggage.ts | 2 +- packages/core/src/utils-hoist/index.ts | 1 + .../http/SentryHttpInstrumentation.ts | 119 ++++++++++++++++-- 5 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts new file mode 100644 index 000000000000..77884dab80c7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts @@ -0,0 +1,61 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracePropagationTargets: [/\/v0/, 'v1'], + integrations: [Sentry.httpIntegration({ spans: false })], + transport: loggingTransport, + // Ensure this gets a correct hint + beforeBreadcrumb(breadcrumb, hint) { + breadcrumb.data = breadcrumb.data || {}; + const req = hint?.request as { path?: string }; + breadcrumb.data.ADDED_PATH = req?.path; + return breadcrumb; + }, +}); + +import * as http from 'http'; + +async function run(): Promise { + Sentry.addBreadcrumb({ message: 'manual breadcrumb' }); + + await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`); + await makeHttpGet(`${process.env.SERVER_URL}/api/v1`); + await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`); + await makeHttpRequest(`${process.env.SERVER_URL}/api/v3`); + + Sentry.captureException(new Error('foo')); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); + +function makeHttpRequest(url: string): Promise { + return new Promise(resolve => { + http + .request(url, httpRes => { + httpRes.on('data', () => { + // we don't care about data + }); + httpRes.on('end', () => { + resolve(); + }); + }) + .end(); + }); +} + +function makeHttpGet(url: string): Promise { + return new Promise(resolve => { + http.get(url, httpRes => { + httpRes.on('data', () => { + // we don't care about data + }); + httpRes.on('end', () => { + resolve(); + }); + }); + }); +} diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts new file mode 100644 index 000000000000..b85cc7913c2c --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts @@ -0,0 +1,95 @@ +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; + +test('outgoing http requests are correctly instrumented with tracing & spans disabled', done => { + expect.assertions(11); + + createTestServer(done) + .get('/api/v0', headers => { + expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); + expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); + expect(headers['baggage']).toEqual(expect.any(String)); + }) + .get('/api/v1', headers => { + expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); + expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); + expect(headers['baggage']).toEqual(expect.any(String)); + }) + .get('/api/v2', headers => { + expect(headers['baggage']).toBeUndefined(); + expect(headers['sentry-trace']).toBeUndefined(); + }) + .get('/api/v3', headers => { + expect(headers['baggage']).toBeUndefined(); + expect(headers['sentry-trace']).toBeUndefined(); + }) + .start() + .then(([SERVER_URL, closeTestServer]) => { + createRunner(__dirname, 'scenario.ts') + .withEnv({ SERVER_URL }) + .ensureNoErrorOutput() + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'foo', + }, + ], + }, + breadcrumbs: [ + { + message: 'manual breadcrumb', + timestamp: expect.any(Number), + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v0`, + status_code: 200, + ADDED_PATH: '/api/v0', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v1`, + status_code: 200, + ADDED_PATH: '/api/v1', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v2`, + status_code: 200, + ADDED_PATH: '/api/v2', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v3`, + status_code: 200, + ADDED_PATH: '/api/v3', + }, + timestamp: expect.any(Number), + type: 'http', + }, + ], + }, + }) + .start(closeTestServer); + }); +}); diff --git a/packages/core/src/utils-hoist/baggage.ts b/packages/core/src/utils-hoist/baggage.ts index 075dbf4389df..84d1078b7583 100644 --- a/packages/core/src/utils-hoist/baggage.ts +++ b/packages/core/src/utils-hoist/baggage.ts @@ -130,7 +130,7 @@ function baggageHeaderToObject(baggageHeader: string): Record { * @returns a baggage header string, or `undefined` if the object didn't have any values, since an empty baggage header * is not spec compliant. */ -function objectToBaggageHeader(object: Record): string | undefined { +export function objectToBaggageHeader(object: Record): string | undefined { if (Object.keys(object).length === 0) { // An empty baggage header is not spec compliant: We return undefined. return undefined; diff --git a/packages/core/src/utils-hoist/index.ts b/packages/core/src/utils-hoist/index.ts index a593b72e73ad..1b2032dbc4bc 100644 --- a/packages/core/src/utils-hoist/index.ts +++ b/packages/core/src/utils-hoist/index.ts @@ -128,6 +128,7 @@ export { baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader, parseBaggageHeader, + objectToBaggageHeader, } from './baggage'; export { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from './url'; diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index d645ac5c9ec2..60fec28a434c 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -1,26 +1,31 @@ -/* eslint-disable max-lines */ import type * as http from 'node:http'; import type { IncomingMessage, RequestOptions } from 'node:http'; import type * as https from 'node:https'; import type { EventEmitter } from 'node:stream'; +/* eslint-disable max-lines */ import { VERSION } from '@opentelemetry/core'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; import type { AggregationCounts, Client, RequestEventData, SanitizedRequestData, Scope } from '@sentry/core'; import { + LRUMap, addBreadcrumb, generateSpanId, getBreadcrumbLogLevelFromHttpStatusCode, getClient, getIsolationScope, getSanitizedUrlString, + getTraceData, httpRequestToRequestData, logger, + objectToBaggageHeader, + parseBaggageHeader, parseUrl, stripUrlQueryAndFragment, withIsolationScope, withScope, } from '@sentry/core'; +import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry'; import { DEBUG_BUILD } from '../../debug-build'; import { getRequestUrl } from '../../utils/getRequestUrl'; import { getRequestInfo } from './vendor/getRequestInfo'; @@ -28,6 +33,12 @@ import { getRequestInfo } from './vendor/getRequestInfo'; type Http = typeof http; type Https = typeof https; +type RequestArgs = + // eslint-disable-next-line @typescript-eslint/ban-types + | [url: string | URL, options?: RequestOptions, callback?: Function] + // eslint-disable-next-line @typescript-eslint/ban-types + | [options: RequestOptions, callback?: Function]; + type SentryHttpInstrumentationOptions = InstrumentationConfig & { /** * Whether breadcrumbs should be recorded for requests. @@ -80,8 +91,11 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024; * https://github.com/open-telemetry/opentelemetry-js/blob/f8ab5592ddea5cba0a3b33bf8d74f27872c0367f/experimental/packages/opentelemetry-instrumentation-http/src/http.ts */ export class SentryHttpInstrumentation extends InstrumentationBase { + private _propagationDecisionMap: LRUMap; + public constructor(config: SentryHttpInstrumentationOptions = {}) { super('@sentry/instrumentation-http', VERSION, config); + this._propagationDecisionMap = new LRUMap(100); } /** @inheritdoc */ @@ -208,22 +222,21 @@ export class SentryHttpInstrumentation extends InstrumentationBase; + const request = original.apply(this, [optionsParsed, ...requestArgs.slice(1)]) as ReturnType< + typeof http.request + >; request.prependListener('response', (response: http.IncomingMessage) => { const _breadcrumbs = instrumentation.getConfig().breadcrumbs; @@ -457,6 +470,44 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope): } } +/** + * Mutates the passed in `options` and adds `sentry-trace` / `baggage` headers, if they are not already set. + */ +function addSentryHeadersToRequestOptions( + url: string, + options: RequestOptions, + propagationDecisionMap: LRUMap, +): void { + // Manually add the trace headers, if it applies + // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span + // Which we do not have in this case + const tracePropagationTargets = getClient()?.getOptions().tracePropagationTargets; + const addedHeaders = shouldPropagateTraceForUrl(url, tracePropagationTargets, propagationDecisionMap) + ? getTraceData() + : undefined; + + if (!addedHeaders) { + return; + } + + if (!options.headers) { + options.headers = {}; + } + const headers = options.headers; + + const { 'sentry-trace': sentryTrace, baggage } = addedHeaders; + + // We do not want to overwrite existing header here, if it was already set + if (sentryTrace && !headers['sentry-trace']) { + headers['sentry-trace'] = sentryTrace; + } + + // For baggage, we make sure to merge this into a possibly existing header + if (baggage) { + headers['baggage'] = mergeBaggageHeaders(headers['baggage'], baggage); + } +} + /** * Starts a session and tracks it in the context of a given isolation scope. * When the passed response is finished, the session is put into a task and is @@ -531,3 +582,49 @@ const clientToRequestSessionAggregatesMap = new Map< Client, { [timestampRoundedToSeconds: string]: { exited: number; crashed: number; errored: number } } >(); + +function getAbsoluteUrl(origin: string, path: string = '/'): string { + try { + const url = new URL(path, origin); + return url.toString(); + } catch { + // fallback: Construct it on our own + const url = `${origin}`; + + if (url.endsWith('/') && path.startsWith('/')) { + return `${url}${path.slice(1)}`; + } + + if (!url.endsWith('/') && !path.startsWith('/')) { + return `${url}/${path.slice(1)}`; + } + + return `${url}${path}`; + } +} + +function mergeBaggageHeaders( + existing: string | string[] | number | undefined, + baggage: string, +): string | string[] | number | undefined { + if (!existing) { + return baggage; + } + + const existingBaggageEntries = parseBaggageHeader(existing); + const newBaggageEntries = parseBaggageHeader(baggage); + + if (!newBaggageEntries) { + return existing; + } + + // Existing entries take precedence, ensuring order remains stable for minimal changes + const mergedBaggageEntries = { ...existingBaggageEntries }; + Object.entries(newBaggageEntries).forEach(([key, value]) => { + if (!mergedBaggageEntries[key]) { + mergedBaggageEntries[key] = value; + } + }); + + return objectToBaggageHeader(mergedBaggageEntries); +} From aa38e37d2ca87adb9ab161855e4f275ca9be2012 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 31 Jan 2025 15:44:28 +0100 Subject: [PATCH 39/43] test(profiling-node): Ensure E2E tests run for profiling-node (#15248) This test was still skipped. I un-skipped it and actually split this up into dedicated tests for clarity. Can't get electron to run successfully on CI (works locally for me...) so skipping it for now, until maybe @timfish can figure it out :D --- .../.gitignore | 0 .../.npmrc | 0 .../build-cjs.mjs | 0 .../index.ts | 0 .../node-profiling-cjs/package.json | 22 ++++++++++++++ .../tsconfig.json | 0 .../node-profiling-electron/.gitignore | 1 + .../node-profiling-electron/.npmrc | 2 ++ .../__tests__/electron.spec.js | 1 + .../index.electron.js | 0 .../index.html | 0 .../node-profiling-electron/package.json | 24 +++++++++++++++ .../node-profiling-esm/.gitignore | 1 + .../node-profiling-esm/.npmrc | 2 ++ .../build-esm.mjs | 0 .../node-profiling-esm/index.ts | 15 ++++++++++ .../node-profiling-esm/package.json | 22 ++++++++++++++ .../node-profiling-esm/tsconfig.json | 13 +++++++++ .../node-profiling/package.json | 29 ------------------- 19 files changed, 103 insertions(+), 29 deletions(-) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-cjs}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-cjs}/.npmrc (100%) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-cjs}/build-cjs.mjs (100%) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-cjs}/index.ts (100%) create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-cjs}/tsconfig.json (100%) create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-electron}/__tests__/electron.spec.js (95%) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-electron}/index.electron.js (100%) rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-electron}/index.html (100%) create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc rename dev-packages/e2e-tests/test-applications/{node-profiling => node-profiling-esm}/build-esm.mjs (100%) create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-profiling/package.json diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/.gitignore rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/.npmrc rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/build-cjs.mjs b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/build-cjs.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/build-cjs.mjs rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/build-cjs.mjs diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.ts b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/index.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.ts rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/index.ts diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json new file mode 100644 index 000000000000..aa729a030a5d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-profiling-cjs", + "version": "1.0.0", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "node dist/cjs/index.js", + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install && node build-cjs.mjs", + "test:assert": "pnpm run typecheck && pnpm run test" + }, + "dependencies": { + "@playwright/test": "~1.50.0", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "esbuild": "0.20.0", + "typescript": "^5.7.3" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc new file mode 100644 index 000000000000..949fbddc2343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.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/node-profiling/__tests__/electron.spec.js b/dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js similarity index 95% rename from dev-packages/e2e-tests/test-applications/node-profiling/__tests__/electron.spec.js rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js index 4519220008d1..49c0666fc1f7 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling/__tests__/electron.spec.js +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js @@ -7,6 +7,7 @@ test('an h1 contains hello world"', async () => { process: { env: { ...process.env, + NODE_ENV: 'development', }, }, }); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.electron.js b/dev-packages/e2e-tests/test-applications/node-profiling-electron/index.electron.js similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.electron.js rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/index.electron.js diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.html b/dev-packages/e2e-tests/test-applications/node-profiling-electron/index.html similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.html rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/index.html diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json new file mode 100644 index 000000000000..dc176c847538 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json @@ -0,0 +1,24 @@ +{ + "name": "node-profiling-electron", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install", + "test:assert": "$(pnpm bin)/electron-rebuild && pnpm playwright test" + }, + "dependencies": { + "@electron/rebuild": "^3.7.0", + "@playwright/test": "~1.50.0", + "@sentry/electron": "latest || *", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "electron": "^33.2.0" + }, + "volta": { + "extends": "../../package.json" + }, + "sentryTest": { + "skip": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc new file mode 100644 index 000000000000..949fbddc2343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-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/node-profiling/build-esm.mjs b/dev-packages/e2e-tests/test-applications/node-profiling-esm/build-esm.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/build-esm.mjs rename to dev-packages/e2e-tests/test-applications/node-profiling-esm/build-esm.mjs diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts b/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts new file mode 100644 index 000000000000..e956a1d9de33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node'; +import { nodeProfilingIntegration } from '@sentry/profiling-node'; + +const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +Sentry.init({ + dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + integrations: [nodeProfilingIntegration()], + tracesSampleRate: 1.0, + profilesSampleRate: 1.0, +}); + +Sentry.startSpan({ name: 'Precompile test' }, async () => { + await wait(500); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json new file mode 100644 index 000000000000..b633df2df172 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-profiling-esm", + "version": "1.0.0", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "node dist/esm/index.mjs", + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install && node build-esm.mjs", + "test:assert": "pnpm run typecheck && pnpm run test" + }, + "dependencies": { + "@playwright/test": "~1.50.0", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "esbuild": "0.20.0", + "typescript": "^5.7.3" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json new file mode 100644 index 000000000000..1308ed76609c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["es2018"], + "strict": true, + "outDir": "dist", + "target": "ESNext", + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["index.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/package.json b/dev-packages/e2e-tests/test-applications/node-profiling/package.json deleted file mode 100644 index c48ab9c3d42d..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-profiling/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "node-profiling", - "version": "1.0.0", - "private": true, - "scripts": { - "typecheck": "tsc --noEmit", - "build": "node build-cjs.mjs && node build-esm.mjs", - "test": "node dist/cjs/index.js && node --experimental-require-module dist/cjs/index.js && node dist/esm/index.mjs", - "clean": "npx rimraf node_modules dist", - "test:electron": "$(pnpm bin)/electron-rebuild && playwright test", - "test:build": "pnpm run typecheck && pnpm run build", - "test:assert": "pnpm run test && pnpm run test:electron" - }, - "dependencies": { - "@electron/rebuild": "^3.7.0", - "@playwright/test": "~1.50.0", - "@sentry/electron": "latest || *", - "@sentry/node": "latest || *", - "@sentry/profiling-node": "latest || *", - "electron": "^33.2.0", - "esbuild": "0.20.0" - }, - "volta": { - "extends": "../../package.json" - }, - "sentryTest": { - "skip": true - } -} From 5731cacbd525ebcf27527c19eb2979293f81a5eb Mon Sep 17 00:00:00 2001 From: Catherine Lee <55311782+c298lee@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:42:03 -0800 Subject: [PATCH 40/43] chore: Add profilesSampler in migration docs (#15253) --- docs/migration/v8-to-v9.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index 538826a0cde5..487dbee7cf4c 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -361,7 +361,7 @@ This should not affect most users unless you relied on passing things with a sim - The `Request` type has been removed. Use `RequestEventData` type instead. - The `IntegrationClass` type is no longer exported - it was not used anymore. Instead, use `Integration` or `IntegrationFn`. - The `samplingContext.request` attribute in the `tracesSampler` has been removed. Use `samplingContext.normalizedRequest` instead. Note that the type of `normalizedRequest` differs from `request`. -- The `samplingContext.transactionContext` object in the `tracesSampler` has been removed. All object attributes are available in the top-level of `samplingContext`. +- The `samplingContext.transactionContext` object in the `tracesSampler` and `profilesSampler` has been removed. All object attributes are available in the top-level of `samplingContext`. - `Client` now always expects the `BaseClient` class - there is no more abstract `Client` that can be implemented! Any `Client` class has to extend from `BaseClient`. - `ReportDialogOptions` now extends `Record` instead of `Record` - this should not affect most users. - The `RequestDataIntegrationOptions` type has been removed. There is no replacement. From e4333e5ce2d65be319ee6a5a5976f7c93983a417 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 3 Feb 2025 10:30:30 +0100 Subject: [PATCH 41/43] ref(core): Move log message about invalid sample rate (#15215) --- packages/core/src/tracing/sampling.ts | 7 ++++++- packages/core/src/utils/hasTracingEnabled.ts | 2 +- packages/core/src/utils/parseSampleRate.ts | 9 --------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index eb19643164aa..70c62cd20992 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -41,7 +41,12 @@ export function sampleSpan( const parsedSampleRate = parseSampleRate(sampleRate); if (parsedSampleRate === undefined) { - DEBUG_BUILD && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.'); + DEBUG_BUILD && + logger.warn( + `[Tracing] Discarding root span because of invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( + sampleRate, + )} of type ${JSON.stringify(typeof sampleRate)}.`, + ); return [false]; } diff --git a/packages/core/src/utils/hasTracingEnabled.ts b/packages/core/src/utils/hasTracingEnabled.ts index f00bf10ff367..a125c7a0cc9e 100644 --- a/packages/core/src/utils/hasTracingEnabled.ts +++ b/packages/core/src/utils/hasTracingEnabled.ts @@ -20,7 +20,7 @@ export function hasTracingEnabled( const options = maybeOptions || client?.getOptions(); return ( !!options && - // Note: This check is `!= null`, meaning "nullish" + // Note: This check is `!= null`, meaning "nullish". `0` is not "nullish", `undefined` and `null` are. (This comment was brought to you by 15 minutes of questioning life) (options.tracesSampleRate != null || !!options.tracesSampler) ); } diff --git a/packages/core/src/utils/parseSampleRate.ts b/packages/core/src/utils/parseSampleRate.ts index 3e297025b52e..acd22509e568 100644 --- a/packages/core/src/utils/parseSampleRate.ts +++ b/packages/core/src/utils/parseSampleRate.ts @@ -1,6 +1,3 @@ -import { DEBUG_BUILD } from '../debug-build'; -import { logger } from '../utils-hoist/logger'; - /** * Parse a sample rate from a given value. * This will either return a boolean or number sample rate, if the sample rate is valid (between 0 and 1). @@ -15,12 +12,6 @@ export function parseSampleRate(sampleRate: unknown): number | undefined { const rate = typeof sampleRate === 'string' ? parseFloat(sampleRate) : sampleRate; if (typeof rate !== 'number' || isNaN(rate) || rate < 0 || rate > 1) { - DEBUG_BUILD && - logger.warn( - `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( - sampleRate, - )} of type ${JSON.stringify(typeof sampleRate)}.`, - ); return undefined; } From 406dd8458d55b1ff50287fcd85bb22ee796cd4ae Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 3 Feb 2025 13:29:54 +0100 Subject: [PATCH 42/43] update changelog for 9.0.0-alpha.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59271adf9976..6ddc359b36af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf, @tannerlinsley, @Zen-cronic and @nathankleyn. Thank you for your contributions! +## 9.0.0-alpha.1 + +- fix(react): Import default for hoistNonReactStatics ([#15238](https://github.com/getsentry/sentry-javascript/pull/15238)) + ## 9.0.0-alpha.0 This is an alpha release of the upcoming major release of version 9. From a7f4938678d7c6a2df6759c7171abeefa5c830eb Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 3 Feb 2025 14:13:53 +0100 Subject: [PATCH 43/43] chore(changelog): Add v9 snippet to changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ddc359b36af..54ee78937f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,11 @@ Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK198 ## 9.0.0-alpha.1 -- fix(react): Import default for hoistNonReactStatics ([#15238](https://github.com/getsentry/sentry-javascript/pull/15238)) +This is an alpha release of the upcoming major release of version 9. +This release does not yet entail a comprehensive changelog as version 9 is not yet stable. + +For this release's iteration of the migration guide, see the [Migration Guide as per `9.0.0-alpha.1`](https://github.com/getsentry/sentry-javascript/blob/e4333e5ce2d65be319ee6a5a5976f7c93983a417/docs/migration/v8-to-v9.md). +Please note that the migration guide is work in progress and subject to change. ## 9.0.0-alpha.0