From 821537c7516dd32ee584f4367a0de3e4b37142b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:23:02 +0000 Subject: [PATCH 001/173] feat(deps): bump @sentry/cli from 2.26.0 to 2.28.0 (#10496) --- packages/remix/package.json | 2 +- yarn.lock | 90 ++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index d6f5fe987392..2d5a5e3eb9ec 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -34,7 +34,7 @@ "access": "public" }, "dependencies": { - "@sentry/cli": "^2.23.0", + "@sentry/cli": "^2.28.0", "@sentry/core": "7.100.0", "@sentry/node": "7.100.0", "@sentry/react": "7.100.0", diff --git a/yarn.lock b/yarn.lock index f3e927b72635..66dd13126498 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5497,40 +5497,40 @@ magic-string "0.27.0" unplugin "1.0.1" -"@sentry/cli-darwin@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.26.0.tgz#6b44500dd549415c5f8b7228a3f4aef18a2e6766" - integrity sha512-SJ4ts9VELoLdOx1g034Tv2nGqhjutBYNAI3WMsjBaQG3tqNPJkQJKGrOqfpL6kTdO2tqQIAYeVw60yqWuHU3FA== - -"@sentry/cli-linux-arm64@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.26.0.tgz#dfe28a7caeffd8bd68476b709ba5f5f50fa74244" - integrity sha512-tAsK5pWrLyU+zqoW0uwylfLB7udOV8FtU8xqcfMsYGxt44zviiuxzKeDnaUdHsZcvk03aTAyf1Dxqn0u32A0MA== - -"@sentry/cli-linux-arm@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.26.0.tgz#cae4cf7db31a0cb4dba5243fce826abfa71dc404" - integrity sha512-qNqKLf3eGowhm+4gg47jGLfova5SLgC0wvWX181U+w94oVGp4onuSjbqpy7wbSA9nsfTXllMhEFI5jA4CMmZVw== - -"@sentry/cli-linux-i686@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.26.0.tgz#29cd6617d2e764dec4cff377ede58762b87e9a66" - integrity sha512-+dSFR9rK6o6F0gBxoU0mrHw18qVgF1t27Y0jvdItMtDuCuduBuXTffmsbBwbPFWBgWuLPG+ojB1LuoBt5qVMng== - -"@sentry/cli-linux-x64@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.26.0.tgz#a54dbcdd5c377ba97f7d3cc8115f25e68c3aded4" - integrity sha512-oY86ECWVQuk434K+enUVZnn28T8qxjJTpxN079xvz7SIWOxQ609tMva91Ywo0gExcu07AZ0pg71XFsEQ9WhZgA== - -"@sentry/cli-win32-i686@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.26.0.tgz#42fdf7006e9e420cb0b3e6b70d9f44f6807906e9" - integrity sha512-vLju9NFl4venKEVpuFJpxaCwa2NdG6C9mhYNqxRvZAPrXWMdMd697qBDOMepAPT7CI8EWiyXUwMli0WjGXGMeQ== - -"@sentry/cli-win32-x64@2.26.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.26.0.tgz#805cb184652d4a608128b70f9e19c1117f25c6f6" - integrity sha512-r3ZaxdHGC6OyJhOxO5ADzAitpGcgT/PkqQzOzKXBOebHj5jzwY27JWjdNhpT6sJZDII13HxqwISRedVWftZgRw== +"@sentry/cli-darwin@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.0.tgz#88ed01f709afb717bc6ce5e9744c29683fe06ffd" + integrity sha512-GgpayUQcGjT55Dc7oojjbqIYIUaBAr4za7D9yU5foMTJ6QjMTovmtE1bVj4bVKzK+0aIiZvZ2dg2g6jF0iGqfg== + +"@sentry/cli-linux-arm64@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.0.tgz#8055f2423c253afcb76c57e0be4d0870d2adb5d3" + integrity sha512-QZtl4dyVMrsWEuRCN8h3RMQSjekM6LmdAWiEIxCgVMvTueau31EQz1jokGpaYotAsWK2GyzFALiCA3QwMCTtnA== + +"@sentry/cli-linux-arm@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.0.tgz#a4c8ab3efac2742822b69245728dc69786c9e3c9" + integrity sha512-hjCRyZBNri+gNoMO22g2qevKcUOnDGhTjmyq14q2rXT0KHb4LjyMpebSgE63YTLDj/qxq4MSq8kcjD/jDzSpLw== + +"@sentry/cli-linux-i686@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.0.tgz#289965689ec8af12ee49984fe46d6df934e928c0" + integrity sha512-fgT0G6b1OCBHtrIClNrFfO8w5pVw7yIqtVsq4Bf+FJOwkD2buaPx1Qt66aGP+3+AexXO5pXfagN4+ykSsKqKZA== + +"@sentry/cli-linux-x64@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.0.tgz#330b94640377fc2484169ac8e0b7c04ae2def718" + integrity sha512-mrqbxpo6dF8iC4nz0+TS8ymIeNKy6gngcmlRVfOBuVEP9+Ry8HAeIzuKwbt4QAA6lwKCbPsEwK5ZLsrJEJIC6A== + +"@sentry/cli-win32-i686@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.0.tgz#061c065928d6498af6c149d6af500e87de2693de" + integrity sha512-yzji557eqz4XW7z8k0LF4LiIwFAqxPlpVnoeN8ntk8hi/ehXm9AdvPqA+bw7cRK5iu4/Tqr4OJeGPbcI5iKpgQ== + +"@sentry/cli-win32-x64@2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.0.tgz#8f251b63d38a62cdd2a3188ea91a46777729bb49" + integrity sha512-5frag3uV+niuMVYQ3ME5Nwlv5uftV88xDUyaCe1UD9jfM8WqJPgvQYUNPgBQKynxwLAUp5zXII+47Vnn8mriOA== "@sentry/cli@^1.74.4", "@sentry/cli@^1.77.1": version "1.77.1" @@ -5544,10 +5544,10 @@ proxy-from-env "^1.1.0" which "^2.0.2" -"@sentry/cli@^2.17.0", "@sentry/cli@^2.21.2", "@sentry/cli@^2.23.0": - version "2.26.0" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.26.0.tgz#94cee60c89f457318540f74f8f1158357c2dd706" - integrity sha512-WRrY9nkjLLUvyo+l8KE0x0Q+0NtCd2U8HYJzh3kyJHyyfKWiSH7ZhExcsb2MoSIjlzbKjjrIJzxhklZABkidDw== +"@sentry/cli@^2.17.0", "@sentry/cli@^2.21.2", "@sentry/cli@^2.28.0": + version "2.28.0" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.0.tgz#3a857815da52144c67f5819f9229983c228bd868" + integrity sha512-0vdMTeN3Ip1wI9T7F6GupuaOocIrfyHpAN3iUztsO7PY2j7e/+m69DRkU99aPTlmUgQikZjtVaHkTsEMLt3lgA== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -5555,13 +5555,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.26.0" - "@sentry/cli-linux-arm" "2.26.0" - "@sentry/cli-linux-arm64" "2.26.0" - "@sentry/cli-linux-i686" "2.26.0" - "@sentry/cli-linux-x64" "2.26.0" - "@sentry/cli-win32-i686" "2.26.0" - "@sentry/cli-win32-x64" "2.26.0" + "@sentry/cli-darwin" "2.28.0" + "@sentry/cli-linux-arm" "2.28.0" + "@sentry/cli-linux-arm64" "2.28.0" + "@sentry/cli-linux-i686" "2.28.0" + "@sentry/cli-linux-x64" "2.28.0" + "@sentry/cli-win32-i686" "2.28.0" + "@sentry/cli-win32-x64" "2.28.0" "@sentry/core@7.93.0": version "7.93.0" From 81a5b4f61d4bb4e5794133f5c06db46e61b5beec Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 6 Feb 2024 17:29:54 +0100 Subject: [PATCH 002/173] build(test): Skip codecov upload for bun & deno (#10523) We don't generate coverage report for these, so we should not try to upload them to codecov, as the action warns about this. --- .github/workflows/build.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b30882983ddb..2d3c2706872f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -477,10 +477,6 @@ jobs: - name: Run tests run: | yarn test-ci-bun - - name: Compute test coverage - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} job_deno_unit_tests: name: Deno Unit Tests @@ -512,10 +508,6 @@ jobs: cd packages/deno yarn build yarn test - - name: Compute test coverage - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} job_node_unit_tests: name: Node (${{ matrix.node }}) Unit Tests From f01e8844e06e013da617a8358000e670fb71689d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 6 Feb 2024 18:18:33 -0500 Subject: [PATCH 003/173] build(profiling-node): make sure debug build plugin is used (#10534) Fixes https://github.com/getsentry/sentry-javascript/issues/10525 When writing the rollup config, we didn't include the debug build plugin. This led to things not be replaced properly as `profiling-node` bundles everything into a single file. This was also causing issues in our CI: https://github.com/getsentry/sentry-javascript/actions/runs/7804351046/job/21287026518?pr=10527 Backporting this fix to v7 so we can do a `7.100.1` release after we merge this in. --- .github/workflows/build.yml | 12 +- dev-packages/e2e-tests/package.json | 3 +- packages/profiling-node/rollup.npm.config.mjs | 31 ++-- yarn.lock | 144 ++++++++++++++++++ 4 files changed, 161 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d3c2706872f..c258e5066239 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,10 +87,12 @@ jobs: id: changed with: filters: | + workflow: &workflow + - '.github/**' shared: &shared + - *workflow - '*.{js,ts,json,yml,lock}' - 'CHANGELOG.md' - - '.github/**' - 'jest/**' - 'scripts/**' - 'packages/core/**' @@ -137,8 +139,11 @@ jobs: - *shared - 'packages/node/**' - 'packages/profiling-node/**' + - 'dev-packages/e2e-tests/test-applications/node-profiling/**' profiling_node_bindings: + - *workflow - 'packages/profiling-node/bindings/**' + - 'dev-packages/e2e-tests/test-applications/node-profiling/**' deno: - *shared - *browser @@ -1120,11 +1125,6 @@ jobs: - name: Build Profiling tarball run: yarn build:tarball --scope @sentry/profiling-node - - - name: Install esbuild - if: ${{ matrix.test-application == 'node-profiling' }} - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - run: yarn add esbuild@0.19.11 # End rebuild profiling - name: Restore tarball cache diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 9e0808b52693..1a583e39b848 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -22,7 +22,8 @@ "dotenv": "16.0.3", "glob": "8.0.3", "ts-node": "10.9.1", - "yaml": "2.2.2" + "yaml": "2.2.2", + "esbuild": "0.20.0" }, "volta": { "node": "18.17.1", diff --git a/packages/profiling-node/rollup.npm.config.mjs b/packages/profiling-node/rollup.npm.config.mjs index 51e812488bb1..057d5b8c60a6 100644 --- a/packages/profiling-node/rollup.npm.config.mjs +++ b/packages/profiling-node/rollup.npm.config.mjs @@ -1,25 +1,12 @@ import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; -import { makeBaseNPMConfig, makeNPMConfigVariants, plugins } from '@sentry-internal/rollup-utils'; - -const configs = makeNPMConfigVariants(makeBaseNPMConfig()); -const cjsConfig = configs.find(config => config.output.format === 'cjs'); - -if (!cjsConfig) { - throw new Error('CJS config is required for profiling-node.'); -} - -const config = { - ...cjsConfig, - input: 'src/index.ts', - output: { ...cjsConfig.output, file: 'lib/index.js', format: 'cjs', dir: undefined, preserveModules: false }, - plugins: [ - plugins.makeLicensePlugin('Sentry Node Profiling'), - resolve(), - commonjs(), - typescript({ tsconfig: './tsconfig.json' }), - ], -}; - -export default config; +import { makeBaseNPMConfig } from '@sentry-internal/rollup-utils'; + +export default makeBaseNPMConfig({ + packageSpecificConfig: { + input: 'src/index.ts', + output: { file: 'lib/index.js', format: 'cjs', dir: undefined, preserveModules: false }, + plugins: [resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json' })], + }, +}); diff --git a/yarn.lock b/yarn.lock index 66dd13126498..ddf84ad0e320 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2729,6 +2729,11 @@ broccoli-funnel "^3.0.5" ember-cli-babel "^7.26.11" +"@esbuild/aix-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" + integrity sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw== + "@esbuild/android-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23" @@ -2749,6 +2754,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz#683794bdc3d27222d3eced7b74cad15979548031" integrity sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ== +"@esbuild/android-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz#109a6fdc4a2783fc26193d2687827045d8fef5ab" + integrity sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q== + "@esbuild/android-arm@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2" @@ -2769,6 +2779,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.9.tgz#21a4de41f07b2af47401c601d64dfdefd056c595" integrity sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA== +"@esbuild/android-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.0.tgz#1397a2c54c476c4799f9b9073550ede496c94ba5" + integrity sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g== + "@esbuild/android-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e" @@ -2789,6 +2804,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.9.tgz#e2d7674bc025ddc8699f0cc76cb97823bb63c252" integrity sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA== +"@esbuild/android-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.0.tgz#2b615abefb50dc0a70ac313971102f4ce2fdb3ca" + integrity sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ== + "@esbuild/darwin-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220" @@ -2809,6 +2829,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz#ae7a582289cc5c0bac15d4b9020a90cb7288f1e9" integrity sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw== +"@esbuild/darwin-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz#5c122ed799eb0c35b9d571097f77254964c276a2" + integrity sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ== + "@esbuild/darwin-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4" @@ -2829,6 +2854,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz#8a216c66dcf51addeeb843d8cfaeff712821d12b" integrity sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ== +"@esbuild/darwin-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz#9561d277002ba8caf1524f209de2b22e93d170c1" + integrity sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw== + "@esbuild/freebsd-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27" @@ -2849,6 +2879,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz#63d4f603e421252c3cd836b18d01545be7c6c440" integrity sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g== +"@esbuild/freebsd-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz#84178986a3138e8500d17cc380044868176dd821" + integrity sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ== + "@esbuild/freebsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72" @@ -2869,6 +2904,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz#a3db52595be65360eae4de1d1fa3c1afd942e1e4" integrity sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA== +"@esbuild/freebsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz#3f9ce53344af2f08d178551cd475629147324a83" + integrity sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ== + "@esbuild/linux-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca" @@ -2889,6 +2929,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz#4ae5811ce9f8d7df5eb9edd9765ea9401a534f13" integrity sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ== +"@esbuild/linux-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz#24efa685515689df4ecbc13031fa0a9dda910a11" + integrity sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw== + "@esbuild/linux-arm@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196" @@ -2909,6 +2954,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz#9807e92cfd335f46326394805ad488e646e506f2" integrity sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw== +"@esbuild/linux-arm@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz#6b586a488e02e9b073a75a957f2952b3b6e87b4c" + integrity sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg== + "@esbuild/linux-ia32@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54" @@ -2929,6 +2979,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz#18892c10f3106652b16f9da88a0362dc95ed46c7" integrity sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q== +"@esbuild/linux-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz#84ce7864f762708dcebc1b123898a397dea13624" + integrity sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w== + "@esbuild/linux-loong64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8" @@ -2949,6 +3004,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz#dc2ebf9a125db0a1bba18c2bbfd4fbdcbcaf61c2" integrity sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA== +"@esbuild/linux-loong64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz#1922f571f4cae1958e3ad29439c563f7d4fd9037" + integrity sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw== + "@esbuild/linux-mips64el@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726" @@ -2969,6 +3029,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz#4c2f7c5d901015e3faf1563c4a89a50776cb07fd" integrity sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw== +"@esbuild/linux-mips64el@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz#7ca1bd9df3f874d18dbf46af009aebdb881188fe" + integrity sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ== + "@esbuild/linux-ppc64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8" @@ -2989,6 +3054,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz#8385332713b4e7812869622163784a5633f76fc4" integrity sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ== +"@esbuild/linux-ppc64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz#8f95baf05f9486343bceeb683703875d698708a4" + integrity sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw== + "@esbuild/linux-riscv64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9" @@ -3009,6 +3079,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz#23f1db24fa761be311874f32036c06249aa20cba" integrity sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg== +"@esbuild/linux-riscv64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz#ca63b921d5fe315e28610deb0c195e79b1a262ca" + integrity sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA== + "@esbuild/linux-s390x@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87" @@ -3029,6 +3104,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz#2dffe497726b897c9f0109e774006e25b33b4fd0" integrity sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw== +"@esbuild/linux-s390x@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz#cb3d069f47dc202f785c997175f2307531371ef8" + integrity sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ== + "@esbuild/linux-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f" @@ -3049,6 +3129,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz#ceb1d62cd830724ff5b218e5d3172a8bad59420e" integrity sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A== +"@esbuild/linux-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz#ac617e0dc14e9758d3d7efd70288c14122557dc7" + integrity sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg== + "@esbuild/netbsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775" @@ -3069,6 +3154,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz#0cbca65e9ef4d3fc41502d3e055e6f49479a8f18" integrity sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug== +"@esbuild/netbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz#6cc778567f1513da6e08060e0aeb41f82eb0f53c" + integrity sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ== + "@esbuild/openbsd-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35" @@ -3089,6 +3179,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz#1f57adfbee09c743292c6758a3642e875bcad1cf" integrity sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw== +"@esbuild/openbsd-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz#76848bcf76b4372574fb4d06cd0ed1fb29ec0fbe" + integrity sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA== + "@esbuild/sunos-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c" @@ -3109,6 +3204,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz#116be6adbd2c7479edeeb5f6ea0441002ab4cb9c" integrity sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw== +"@esbuild/sunos-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz#ea4cd0639bf294ad51bc08ffbb2dac297e9b4706" + integrity sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g== + "@esbuild/win32-arm64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a" @@ -3129,6 +3229,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz#2be22131ab18af4693fd737b161d1ef34de8ca9d" integrity sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg== +"@esbuild/win32-arm64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz#a5c171e4a7f7e4e8be0e9947a65812c1535a7cf0" + integrity sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ== + "@esbuild/win32-ia32@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09" @@ -3149,6 +3254,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz#e10ead5a55789b167b4225d2469324538768af7c" integrity sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg== +"@esbuild/win32-ia32@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz#f8ac5650c412d33ea62d7551e0caf82da52b7f85" + integrity sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg== + "@esbuild/win32-x64@0.16.17": version "0.16.17" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091" @@ -3169,6 +3279,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz#b2da6219b603e3fa371a78f53f5361260d0c5585" integrity sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ== +"@esbuild/win32-x64@0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz#2efddf82828aac85e64cef62482af61c29561bee" + integrity sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg== + "@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -14740,6 +14855,35 @@ esbuild@0.13.8: esbuild-windows-64 "0.13.8" esbuild-windows-arm64 "0.13.8" +esbuild@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" + integrity sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.0" + "@esbuild/android-arm" "0.20.0" + "@esbuild/android-arm64" "0.20.0" + "@esbuild/android-x64" "0.20.0" + "@esbuild/darwin-arm64" "0.20.0" + "@esbuild/darwin-x64" "0.20.0" + "@esbuild/freebsd-arm64" "0.20.0" + "@esbuild/freebsd-x64" "0.20.0" + "@esbuild/linux-arm" "0.20.0" + "@esbuild/linux-arm64" "0.20.0" + "@esbuild/linux-ia32" "0.20.0" + "@esbuild/linux-loong64" "0.20.0" + "@esbuild/linux-mips64el" "0.20.0" + "@esbuild/linux-ppc64" "0.20.0" + "@esbuild/linux-riscv64" "0.20.0" + "@esbuild/linux-s390x" "0.20.0" + "@esbuild/linux-x64" "0.20.0" + "@esbuild/netbsd-x64" "0.20.0" + "@esbuild/openbsd-x64" "0.20.0" + "@esbuild/sunos-x64" "0.20.0" + "@esbuild/win32-arm64" "0.20.0" + "@esbuild/win32-ia32" "0.20.0" + "@esbuild/win32-x64" "0.20.0" + esbuild@^0.16.14, esbuild@^0.16.3: version "0.16.17" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259" From 8976d6140b8088ac22334d2b7e55265e6606154b Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 03:04:56 -0500 Subject: [PATCH 004/173] build: Only run profiling e2e test if bindings have changed (#10542) In CI currently on develop, we are stuck in a situation where we don't build bindings which means that the e2e tests always fail. Let's only run the profiling e2e tests whenever we change bindings, and make it a little more liberal for when we do run CI for changing bindings. --- .github/workflows/build.yml | 129 +++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c258e5066239..ec2490183b5a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -117,6 +117,11 @@ jobs: - *shared - *browser - 'packages/ember/**' + node: + - *shared + - 'packages/node/**' + - 'packages/node-experimental/**' + - 'dev-packages/node-integration-tests/**' nextjs: - *shared - *browser @@ -129,20 +134,15 @@ jobs: - 'packages/remix/**' - 'packages/node/**' - 'packages/react/**' - node: - - *shared - - 'packages/node/**' - - 'packages/node-experimental/**' - - 'packages/profiling-node/**' - - 'dev-packages/node-integration-tests/**' profiling_node: - *shared - 'packages/node/**' + - 'packages/node-experimental/**' - 'packages/profiling-node/**' - 'dev-packages/e2e-tests/test-applications/node-profiling/**' profiling_node_bindings: - *workflow - - 'packages/profiling-node/bindings/**' + - 'packages/profiling-node/**' - 'dev-packages/e2e-tests/test-applications/node-profiling/**' deno: - *shared @@ -551,7 +551,7 @@ jobs: job_profiling_node_unit_tests: name: Node Profiling Unit Tests needs: [job_get_metadata, job_build] - if: needs.job_get_metadata.outputs.changed_node =='true' || needs.job_get_metadata.outputs.changed_profiling_node == 'true' || github.event_name != 'pull_request' + if: needs.job_get_metadata.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: @@ -1061,7 +1061,6 @@ jobs: 'node-experimental-fastify-app', 'node-hapi-app', 'node-exports-test-app', - 'node-profiling', 'vue-3' ] build-command: @@ -1083,7 +1082,6 @@ jobs: - test-application: 'nextjs-app-dir' build-command: 'test:build-13' label: 'nextjs-app-dir (next@13)' - steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -1104,29 +1102,6 @@ jobs: env: DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} - # Rebuild profiling by compiling TS and pull the precompiled binary artifacts - - name: Build Profiling Node - if: | - (needs.job_get_metadata.outputs.changed_profiling_node_bindings == '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 - # @TODO: v4 breaks convenient merging of same name artifacts - # https://github.com/actions/upload-artifact/issues/478 - if: | - (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || - (github.event_name != 'pull_request') - uses: actions/download-artifact@v3 - with: - name: profiling-node-binaries-${{ github.sha }} - path: ${{ github.workspace }}/packages/profiling-node/lib/ - - - name: Build Profiling tarball - run: yarn build:tarball --scope @sentry/profiling-node - # End rebuild profiling - - name: Restore tarball cache uses: actions/cache/restore@v4 with: @@ -1168,6 +1143,93 @@ 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 PRs sadly also don't have access to secrets, so we skip them as well + # 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) && + github.actor != 'dependabot[bot]' && ( + (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (needs.job_get_metadata.outputs.is_release == 'true') || + (github.event_name != 'pull_request') + ) + needs: [job_get_metadata, job_build, job_e2e_prepare] + runs-on: ubuntu-20.04 + timeout-minutes: 10 + 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_TEST_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@v2 + with: + version: 8.3.1 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'dev-packages/e2e-tests/package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + 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@v3 + with: + name: profiling-node-binaries-${{ github.sha }} + path: ${{ github.workspace }}/packages/profiling-node/lib/ + - name: Build Profiling tarball + run: yarn build:tarball --scope @sentry/profiling-node + - name: Restore tarball cache + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/packages/*/*.tgz + key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + + - 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: Build E2E app + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn ${{ matrix.build-command || 'test:build' }} + + - name: Run E2E test + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn test:assert + job_required_jobs_passed: name: All required jobs passed or were skipped needs: @@ -1187,6 +1249,7 @@ jobs: job_browser_loader_tests, job_remix_integration_tests, job_e2e_tests, + job_profiling_e2e_tests, job_artifacts, job_lint, job_check_format, From fa4669a688a350663c0093ef6f7eb7b688d2c631 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Feb 2024 04:55:46 -0330 Subject: [PATCH 005/173] fix(feedback): Replay breadcrumb for feedback events was incorrect (#10536) We are creating a replay breadcrumb when user feedback was submitted, however, the it was not typed correctly, which the timestamp not being included in the proper location. --- .../replay/src/coreHandlers/util/addFeedbackBreadcrumb.ts | 7 ++++--- packages/replay/src/types/replayFrame.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/replay/src/coreHandlers/util/addFeedbackBreadcrumb.ts b/packages/replay/src/coreHandlers/util/addFeedbackBreadcrumb.ts index 0e08b459d3ca..39fc2923cabd 100644 --- a/packages/replay/src/coreHandlers/util/addFeedbackBreadcrumb.ts +++ b/packages/replay/src/coreHandlers/util/addFeedbackBreadcrumb.ts @@ -1,7 +1,7 @@ import { EventType } from '@sentry-internal/rrweb'; import type { FeedbackEvent } from '@sentry/types'; -import type { ReplayContainer } from '../../types'; +import type { ReplayBreadcrumbFrameEvent, ReplayContainer } from '../../types'; /** * Add a feedback breadcrumb event to replay. @@ -21,16 +21,17 @@ export function addFeedbackBreadcrumb(replay: ReplayContainer, event: FeedbackEv type: EventType.Custom, timestamp: event.timestamp * 1000, data: { - timestamp: event.timestamp, tag: 'breadcrumb', payload: { + timestamp: event.timestamp, + type: 'default', category: 'sentry.feedback', data: { feedbackId: event.event_id, }, }, }, - }); + } as ReplayBreadcrumbFrameEvent); return false; }); diff --git a/packages/replay/src/types/replayFrame.ts b/packages/replay/src/types/replayFrame.ts index 3a595e47a4cf..48dc4aa72a2a 100644 --- a/packages/replay/src/types/replayFrame.ts +++ b/packages/replay/src/types/replayFrame.ts @@ -128,7 +128,7 @@ interface ReplayOptionFrame { } interface ReplayFeedbackFrameData { - feedback_id: string; + feedbackId: string; } interface ReplayFeedbackFrame extends ReplayBaseBreadcrumbFrame { From b1b704fa533681b72f66207ae0712b1162716bc7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 7 Feb 2024 13:29:20 +0100 Subject: [PATCH 006/173] feat(replay): Add `getReplay` utility function (#10510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As pointed out here, and I also did notice that myself, it is not super nice - as you need to provide a generic integration to `getIntegrationByName`, which is annoying to do in a type safe way, esp. if you want to avoid deprecations: ```ts const client = getClient(); const replay = client && client.getIntegrationByName && client.getIntegrationByName>('Replay'); ``` So IMHO a small utility `Sentry.getReplay()` is not unreasonable for this 🤔 --- packages/browser/src/index.ts | 1 + packages/replay/src/index.ts | 2 + packages/replay/src/util/getReplay.ts | 13 ++++++ .../replay/test/unit/util/getReplay.test.ts | 42 +++++++++++++++++++ packages/replay/test/utils/TestClient.ts | 2 +- 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 packages/replay/src/util/getReplay.ts create mode 100644 packages/replay/test/unit/util/getReplay.test.ts diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 2be5c71c4518..59ef74cdbfb5 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -27,6 +27,7 @@ export { // eslint-disable-next-line deprecation/deprecation Replay, replayIntegration, + getReplay, } from '@sentry/replay'; export type { ReplayEventType, diff --git a/packages/replay/src/index.ts b/packages/replay/src/index.ts index 6471ff9e87ce..d16fd7733c20 100644 --- a/packages/replay/src/index.ts +++ b/packages/replay/src/index.ts @@ -19,5 +19,7 @@ export type { CanvasManagerOptions, } from './types'; +export { getReplay } from './util/getReplay'; + // TODO (v8): Remove deprecated types export * from './types/deprecated'; diff --git a/packages/replay/src/util/getReplay.ts b/packages/replay/src/util/getReplay.ts new file mode 100644 index 000000000000..278505f15338 --- /dev/null +++ b/packages/replay/src/util/getReplay.ts @@ -0,0 +1,13 @@ +import { getClient } from '@sentry/core'; +import type { replayIntegration } from '../integration'; + +/** + * This is a small utility to get a type-safe instance of the Replay integration. + */ +// eslint-disable-next-line deprecation/deprecation +export function getReplay(): ReturnType | undefined { + const client = getClient(); + return ( + client && client.getIntegrationByName && client.getIntegrationByName>('Replay') + ); +} diff --git a/packages/replay/test/unit/util/getReplay.test.ts b/packages/replay/test/unit/util/getReplay.test.ts new file mode 100644 index 000000000000..7f614d4fdc33 --- /dev/null +++ b/packages/replay/test/unit/util/getReplay.test.ts @@ -0,0 +1,42 @@ +import { getCurrentScope } from '@sentry/core'; +import { replayIntegration } from '../../../src/integration'; +import { getReplay } from '../../../src/util/getReplay'; +import { getDefaultClientOptions, init } from '../../utils/TestClient'; + +describe('getReplay', () => { + beforeEach(() => { + getCurrentScope().setClient(undefined); + }); + + it('works without a client', () => { + const actual = getReplay(); + expect(actual).toBeUndefined(); + }); + + it('works with a client without Replay', () => { + init( + getDefaultClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + }), + ); + + const actual = getReplay(); + expect(actual).toBeUndefined(); + }); + + it('works with a client with Replay xxx', () => { + const replay = replayIntegration(); + init( + getDefaultClientOptions({ + integrations: [replay], + replaysOnErrorSampleRate: 0, + replaysSessionSampleRate: 0, + }), + ); + + const actual = getReplay(); + expect(actual).toBeDefined(); + expect(actual === replay).toBe(true); + expect(replay.getReplayId()).toBe(undefined); + }); +}); diff --git a/packages/replay/test/utils/TestClient.ts b/packages/replay/test/utils/TestClient.ts index da131aec8fd2..26a14f2a9795 100644 --- a/packages/replay/test/utils/TestClient.ts +++ b/packages/replay/test/utils/TestClient.ts @@ -39,7 +39,7 @@ export function init(options: TestClientOptions): void { initAndBind(TestClient, options); } -export function getDefaultClientOptions(options: Partial = {}): ClientOptions { +export function getDefaultClientOptions(options: Partial = {}): ClientOptions { return { integrations: [], dsn: 'https://username@domain/123', From c23715af65e48c04bace17832faa88c0c5ea350c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 7 Feb 2024 13:42:00 +0100 Subject: [PATCH 007/173] test(nextjs): Don't run Next.js integration tests for Node.js < 14 (#10547) --- .github/workflows/build.yml | 6 +++--- packages/nextjs/test/run-integration-tests.sh | 12 ------------ 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec2490183b5a..095133bd64c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -585,7 +585,7 @@ jobs: strategy: fail-fast: false matrix: - node: [10, 12, 14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 21] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -612,12 +612,12 @@ jobs: path: ${{ steps.npm-cache-dir.outputs.dir }} key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} - name: Install Playwright browser if not cached - if: steps.playwright-cache.outputs.cache-hit != 'true' && matrix.node >= 14 + if: steps.playwright-cache.outputs.cache-hit != 'true' run: npx playwright install --with-deps env: PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} - name: Install OS dependencies of Playwright if cache hit - if: steps.playwright-cache.outputs.cache-hit == 'true' && matrix.node >= 14 + if: steps.playwright-cache.outputs.cache-hit == 'true' run: npx playwright install-deps - name: Run tests env: diff --git a/packages/nextjs/test/run-integration-tests.sh b/packages/nextjs/test/run-integration-tests.sh index 0eff88612e95..79adf0016075 100755 --- a/packages/nextjs/test/run-integration-tests.sh +++ b/packages/nextjs/test/run-integration-tests.sh @@ -45,18 +45,6 @@ for NEXTJS_VERSION in 10 11 12 13; do export NODE_MAJOR=$NODE_MAJOR export USE_APPDIR=$USE_APPDIR - # Next 10 requires at least Node v10 - if [ "$NODE_MAJOR" -lt "10" ]; then - echo "[nextjs] Next.js is not compatible with versions of Node older than v10. Current version $NODE_VERSION" - exit 0 - fi - - # Next.js v11 requires at least Node v12 - if [ "$NODE_MAJOR" -lt "12" ] && [ "$NEXTJS_VERSION" -ge "11" ]; then - echo "[nextjs@$NEXTJS_VERSION] Not compatible with Node $NODE_MAJOR" - exit 0 - fi - # Next.js v13 requires at least Node v16 if [ "$NODE_MAJOR" -lt "16" ] && [ "$NEXTJS_VERSION" -ge "13" ]; then echo "[nextjs@$NEXTJS_VERSION] Not compatible with Node $NODE_MAJOR" From 09e8dd8048e28c853b58b1695724e5fcc3552200 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 08:46:54 -0500 Subject: [PATCH 008/173] feat(v8): Bump minimum Node Version to 14 (#10527) ref https://github.com/getsentry/sentry-javascript/issues/9827 Drops support for node 8, 10, 12. Does not change Next.js tests, as we can look at that in https://github.com/getsentry/sentry-javascript/issues/9838 --- .github/ISSUE_TEMPLATE/flaky.yml | 2 +- .github/workflows/build.yml | 8 +- .../express/tracing-experimental/test.ts | 3 +- .../apollo-graphql/test.ts | 3 +- .../suites/tracing-experimental/hapi/test.ts | 3 +- .../tracing-experimental/mongodb/test.ts | 3 +- .../tracing-experimental/mongoose/test.ts | 3 +- .../suites/tracing-experimental/mysql/test.ts | 3 +- .../tracing-experimental/mysql2/test.ts | 3 +- .../tracing-experimental/postgres/test.ts | 3 +- .../suites/tracing-new/apollo-graphql/test.ts | 6 +- .../auto-instrument/mongodb/test.ts | 4 +- .../suites/tracing-new/prisma-orm/test.ts | 4 +- .../suites/tracing/apollo-graphql/test.ts | 6 +- .../tracing/auto-instrument/mongodb/test.ts | 4 +- .../suites/tracing/prisma-orm/test.ts | 4 +- packages/angular/package.json | 2 +- packages/browser/package.json | 2 +- packages/bun/package.json | 2 +- packages/core/package.json | 2 +- packages/eslint-config-sdk/package.json | 2 +- packages/eslint-plugin-sdk/package.json | 2 +- packages/gatsby/package.json | 2 +- packages/hub/package.json | 2 +- packages/integrations/package.json | 2 +- packages/nextjs/package.json | 2 +- .../server/doubleEndMethodOnVercel.test.ts | 4 - packages/node/README.md | 2 +- packages/node/package.json | 2 +- packages/node/test/async/hooks.test.ts | 3 +- packages/node/test/integrations/http.test.ts | 8 +- .../node/test/integrations/undici.test.ts | 11 +- .../manual/webpack-async-context/npm-build.js | 5 - packages/opentelemetry-node/package.json | 2 +- packages/profiling-node/package.json | 2 +- packages/react/package.json | 2 +- packages/replay-canvas/README.md | 2 +- packages/replay/README.md | 2 +- packages/svelte/package.json | 2 +- packages/tracing-internal/package.json | 2 +- .../test/browser/backgroundtab.test.ts | 4 +- .../test/browser/browsertracing.test.ts | 4 +- packages/tracing/package.json | 2 +- packages/types/package.json | 2 +- packages/utils/package.json | 2 +- packages/utils/src/requestdata.ts | 24 ++-- .../utils/src/vendor/escapeStringForRegex.ts | 2 +- packages/utils/test/normalize.test.ts | 5 +- packages/vercel-edge/package.json | 2 +- packages/vue/package.json | 2 +- packages/wasm/package.json | 2 +- scripts/node-unit-tests.ts | 132 ++---------------- 52 files changed, 81 insertions(+), 233 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/flaky.yml b/.github/ISSUE_TEMPLATE/flaky.yml index 9cc9046a6ece..bff560770675 100644 --- a/.github/ISSUE_TEMPLATE/flaky.yml +++ b/.github/ISSUE_TEMPLATE/flaky.yml @@ -18,7 +18,7 @@ body: id: job-name attributes: label: Name of Job - placeholder: Build & Test / Nextjs (Node 10) Tests + placeholder: Build & Test / Nextjs (Node 14) Tests description: name of job as reported in the status report validations: required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 095133bd64c1..ccda1b605e7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -523,7 +523,7 @@ jobs: strategy: fail-fast: false matrix: - node: [8, 10, 12, 14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 21] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -540,9 +540,7 @@ jobs: - name: Run tests env: NODE_VERSION: ${{ matrix.node }} - run: | - [[ $NODE_VERSION == 8 ]] && yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ts-node@8.10.2 - yarn test-ci-node + run: yarn test-ci-node - name: Compute test coverage uses: codecov/codecov-action@v4 with: @@ -874,7 +872,7 @@ jobs: strategy: fail-fast: false matrix: - node: [10, 12, 14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 21] typescript: - false include: diff --git a/dev-packages/node-integration-tests/suites/express/tracing-experimental/test.ts b/dev-packages/node-integration-tests/suites/express/tracing-experimental/test.ts index 526a211033a0..ca3ff93435c8 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing-experimental/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing-experimental/test.ts @@ -1,7 +1,6 @@ -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; -conditionalTest({ min: 14 })('express tracing experimental', () => { +describe('express tracing experimental', () => { afterAll(() => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts index 230c4cd4dac3..ad32799a5e79 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts @@ -1,7 +1,6 @@ -import { conditionalTest } from '../../../utils'; import { createRunner } from '../../../utils/runner'; -conditionalTest({ min: 14 })('GraphQL/Apollo Tests', () => { +describe('GraphQL/Apollo Tests', () => { test('CJS - should instrument GraphQL queries used from Apollo Server.', done => { const EXPECTED_TRANSACTION = { transaction: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/test.ts index 148bf83bb397..93e3203f6470 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/test.ts @@ -1,9 +1,8 @@ -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; jest.setTimeout(20000); -conditionalTest({ min: 14 })('hapi auto-instrumentation', () => { +describe('hapi auto-instrumentation', () => { afterAll(async () => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/test.ts index 83018f4c9a3d..b8c16862c34c 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/test.ts @@ -1,11 +1,10 @@ import { MongoMemoryServer } from 'mongodb-memory-server-global'; -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; jest.setTimeout(20000); -conditionalTest({ min: 14 })('MongoDB experimental Test', () => { +describe('MongoDB experimental Test', () => { let mongoServer: MongoMemoryServer; beforeAll(async () => { diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts index 1a246d8ec5b9..050a3ffc9e12 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts @@ -1,11 +1,10 @@ import { MongoMemoryServer } from 'mongodb-memory-server-global'; -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; jest.setTimeout(20000); -conditionalTest({ min: 14 })('Mongoose experimental Test', () => { +describe('Mongoose experimental Test', () => { let mongoServer: MongoMemoryServer; beforeAll(async () => { diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/test.ts index 84c63a30ff68..530349ae3fb7 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/test.ts @@ -1,7 +1,6 @@ -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; -conditionalTest({ min: 14 })('mysql auto instrumentation', () => { +describe('mysql auto instrumentation', () => { afterAll(() => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/test.ts index 28209009b03e..db056fd222e8 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/test.ts @@ -1,7 +1,6 @@ -import { conditionalTest } from '../../../utils'; import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; -conditionalTest({ min: 14 })('mysql2 auto instrumentation', () => { +describe('mysql2 auto instrumentation', () => { afterAll(() => { cleanupChildProcesses(); }); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/test.ts index 117a5d80ac02..d83f992638d1 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/test.ts @@ -1,7 +1,6 @@ -import { conditionalTest } from '../../../utils'; import { createRunner } from '../../../utils/runner'; -conditionalTest({ min: 14 })('postgres auto instrumentation', () => { +describe('postgres auto instrumentation', () => { test('should auto-instrument `pg` package', done => { const EXPECTED_TRANSACTION = { transaction: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts index bcddfd588447..bcf16ca1dfb4 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts @@ -1,8 +1,6 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../utils'; -// Node 10 is not supported by `graphql-js` -// Ref: https://github.com/graphql/graphql-js/blob/main/package.json -conditionalTest({ min: 12 })('GraphQL/Apollo Tests', () => { +describe('GraphQL/Apollo Tests', () => { test('should instrument GraphQL and Apollo Server.', async () => { const env = await TestEnv.init(__dirname); const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts index d2ce56f314ee..76ae4706eeb0 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts @@ -1,11 +1,11 @@ import { MongoMemoryServer } from 'mongodb-memory-server-global'; -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../../utils'; // This test can take longer. jest.setTimeout(15000); -conditionalTest({ min: 12 })('MongoDB Test', () => { +describe('MongoDB Test', () => { let mongoServer: MongoMemoryServer; beforeAll(async () => { diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts index 4a76f328dd34..0d969c262413 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts @@ -1,6 +1,6 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../utils'; -conditionalTest({ min: 12 })('Prisma ORM Integration', () => { +describe('Prisma ORM Integration', () => { test('should instrument Prisma client for tracing.', async () => { const env = await TestEnv.init(__dirname); const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts index bcddfd588447..bcf16ca1dfb4 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts @@ -1,8 +1,6 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../utils'; -// Node 10 is not supported by `graphql-js` -// Ref: https://github.com/graphql/graphql-js/blob/main/package.json -conditionalTest({ min: 12 })('GraphQL/Apollo Tests', () => { +describe('GraphQL/Apollo Tests', () => { test('should instrument GraphQL and Apollo Server.', async () => { const env = await TestEnv.init(__dirname); const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts index d2ce56f314ee..76ae4706eeb0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts @@ -1,11 +1,11 @@ import { MongoMemoryServer } from 'mongodb-memory-server-global'; -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../../utils'; // This test can take longer. jest.setTimeout(15000); -conditionalTest({ min: 12 })('MongoDB Test', () => { +describe('MongoDB Test', () => { let mongoServer: MongoMemoryServer; beforeAll(async () => { diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts index 4a76f328dd34..0d969c262413 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts @@ -1,6 +1,6 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../utils'; -conditionalTest({ min: 12 })('Prisma ORM Integration', () => { +describe('Prisma ORM Integration', () => { test('should instrument Prisma client for tracing.', async () => { const env = await TestEnv.init(__dirname); const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); diff --git a/packages/angular/package.json b/packages/angular/package.json index d6668d7c2af0..69073a5e1e07 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "main": "build/bundles/sentry-angular.umd.js", "module": "build/fesm2015/sentry-angular.js", diff --git a/packages/browser/package.json b/packages/browser/package.json index db4a7315a346..7dbe81061ad8 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/bun/package.json b/packages/bun/package.json index 74ccdc24611e..bac554fee6dd 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/core/package.json b/packages/core/package.json index 24b1157174e6..3bd1182f7931 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 0849cfc388b1..74a0159579d3 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "src" diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index e5c16a64c307..ec06cadca350 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "src" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 4fca577e0bc8..998efebd193d 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -11,7 +11,7 @@ "gatsby-plugin" ], "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/hub/package.json b/packages/hub/package.json index b3dcf9f3045f..1d51f3adb0be 100644 --- a/packages/hub/package.json +++ b/packages/hub/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/integrations/package.json b/packages/integrations/package.json index c232487a6e95..b016aaf1d9e8 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "publishConfig": { "access": "public" diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 59a83f0b80a9..3f6ef36ca15f 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", diff --git a/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.test.ts b/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.test.ts index 07b70e61e036..5bc1aed6536e 100644 --- a/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.test.ts +++ b/packages/nextjs/test/integration/test/server/doubleEndMethodOnVercel.test.ts @@ -4,10 +4,6 @@ import { NextTestEnv } from './utils/helpers'; // `res.send` multiple times in one request handler. // https://github.com/getsentry/sentry-javascript/issues/6670 it.skip('should not break API routes on Vercel if people call res.json or res.send multiple times in one request handler', async () => { - if (process.env.NODE_MAJOR === '10') { - console.log('not running doubleEndMethodOnVercel test on Node 10'); - return; - } const env = await NextTestEnv.init(); const url = `${env.url}/api/doubleEndMethodOnVercel`; const response = await env.getAPIResponse(url); diff --git a/packages/node/README.md b/packages/node/README.md index cab92a553d25..0fdf96a4aa34 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -18,7 +18,7 @@ ## Usage To use this SDK, call `init(options)` as early as possible in the main entry module. This will initialize the SDK and -hook into the environment. Note that you can turn off almost all side effects using the respective options. +hook into the environment. Note that you can turn off almost all side effects using the respective options. Minimum supported Node version is Node 14. ```javascript // CJS syntax diff --git a/packages/node/package.json b/packages/node/package.json index 6b538d1a2246..28d51122a57c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/node/test/async/hooks.test.ts b/packages/node/test/async/hooks.test.ts index f016a7087e4f..513abd2b0ccb 100644 --- a/packages/node/test/async/hooks.test.ts +++ b/packages/node/test/async/hooks.test.ts @@ -10,9 +10,8 @@ import { } from '@sentry/core'; import { setHooksAsyncContextStrategy } from '../../src/async/hooks'; -import { conditionalTest } from '../utils'; -conditionalTest({ min: 12 })('setHooksAsyncContextStrategy()', () => { +describe('setHooksAsyncContextStrategy()', () => { beforeEach(() => { const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 0b1d81edd29c..527ef48c3f0f 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -682,17 +682,11 @@ describe('default protocols', () => { it('makes https request over http proxy', async () => { const key = 'catcatchers'; const p = captureBreadcrumb(key); - let nockProtocol = 'https'; const proxy = 'http://some.url:3128'; const agent = new HttpsProxyAgent(proxy); - // TODO (v8): No longer needed once we drop Node 8 support - if (NODE_VERSION.major < 9) { - nockProtocol = 'http'; - } - - nock(`${nockProtocol}://${key}.ingest.sentry.io`).get('/api/123122332/store/').reply(200); + nock(`https://${key}.ingest.sentry.io`).get('/api/123122332/store/').reply(200); https.get({ host: `${key}.ingest.sentry.io`, diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index f280b3d4018a..1decb76006fd 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -2,7 +2,7 @@ import * as http from 'http'; import { Transaction, getActiveSpan, getClient, getCurrentScope, setCurrentClient, startSpan } from '@sentry/core'; import { spanToTraceHeader } from '@sentry/core'; import { Hub, makeMain, runWithAsyncContext } from '@sentry/core'; -import type { fetch as FetchType } from 'undici'; +import { fetch } from 'undici'; import { NodeClient } from '../../src/client'; import type { Undici, UndiciOptions } from '../../src/integrations/undici'; @@ -13,17 +13,16 @@ import { conditionalTest } from '../utils'; const SENTRY_DSN = 'https://0@0.ingest.sentry.io/0'; let hub: Hub; -let fetch: typeof FetchType; beforeAll(async () => { try { await setupTestServer(); - // need to conditionally require `undici` because it's not available in Node 10 - // eslint-disable-next-line @typescript-eslint/no-var-requires - fetch = require('undici').fetch; } catch (e) { // eslint-disable-next-line no-console - console.warn('Undici integration tests are skipped because undici is not installed.'); + const error = new Error('Undici integration tests are skipped because test server could not be set up.'); + // This needs lib es2022 and newer so marking as any + (error as any).cause = e; + throw e; } }); diff --git a/packages/node/test/manual/webpack-async-context/npm-build.js b/packages/node/test/manual/webpack-async-context/npm-build.js index 9d9c687981bb..eac357b10f36 100644 --- a/packages/node/test/manual/webpack-async-context/npm-build.js +++ b/packages/node/test/manual/webpack-async-context/npm-build.js @@ -7,11 +7,6 @@ if (Number(process.versions.node.split('.')[0]) >= 18) { process.exit(0); } -// Webpack test does not work in Node 8 and below. -if (Number(process.versions.node.split('.')[0]) <= 8) { - process.exit(0); -} - // biome-ignore format: Follow-up for prettier webpack( { diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index 95e6bc8da8fd..9574933a1c86 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index 8118230deb07..fd999c68b3ba 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -19,7 +19,7 @@ "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" }, "engines": { - "node": ">=8.0.0" + "node": ">=14" }, "publishConfig": { "access": "public" diff --git a/packages/react/package.json b/packages/react/package.json index f4898621fd4a..d43bef08cef7 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/replay-canvas/README.md b/packages/replay-canvas/README.md index eac14facc5cc..21af46bf2512 100644 --- a/packages/replay-canvas/README.md +++ b/packages/replay-canvas/README.md @@ -8,7 +8,7 @@ ## Pre-requisites -Replay with canvas requires Node 12+, and browsers newer than IE11. +Replay with canvas requires Node 14+, and browsers newer than IE11. ## Installation diff --git a/packages/replay/README.md b/packages/replay/README.md index 091f51d785bf..e87aa7861b94 100644 --- a/packages/replay/README.md +++ b/packages/replay/README.md @@ -12,7 +12,7 @@ ## Pre-requisites -`@sentry/replay` requires Node 12+, and browsers newer than IE11. +`@sentry/replay` requires Node 14+, and browsers newer than IE11. ## Installation diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 6406acf463e5..10c6dac63b7d 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/tracing-internal/package.json b/packages/tracing-internal/package.json index 1783c2dc075b..a69ff69f4cbe 100644 --- a/packages/tracing-internal/package.json +++ b/packages/tracing-internal/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index 27cba1d934fa..38dbc04bae37 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -2,11 +2,11 @@ import { Hub, makeMain, spanToJSON, startSpan } from '@sentry/core'; import { JSDOM } from 'jsdom'; import { addExtensionMethods } from '../../../tracing/src'; -import { conditionalTest, getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; +import { getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; import { registerBackgroundTabDetection } from '../../src/browser/backgroundtab'; import { TestClient } from '../utils/TestClient'; -conditionalTest({ min: 10 })('registerBackgroundTabDetection', () => { +describe('registerBackgroundTabDetection', () => { let events: Record = {}; let hub: Hub; beforeEach(() => { diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index b9830b8d754c..8144f4b0621b 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -7,7 +7,7 @@ import { JSDOM } from 'jsdom'; import { timestampInSeconds } from '@sentry/utils'; import type { IdleTransaction } from '../../../tracing/src'; import { getActiveTransaction } from '../../../tracing/src'; -import { conditionalTest, getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; +import { getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; import type { BrowserTracingOptions } from '../../src/browser/browsertracing'; import { BrowserTracing, getMetaContent } from '../../src/browser/browsertracing'; import { defaultRequestInstrumentationOptions } from '../../src/browser/request'; @@ -55,7 +55,7 @@ beforeAll(() => { WINDOW.location = dom.window.location; }); -conditionalTest({ min: 10 })('BrowserTracing', () => { +describe('BrowserTracing', () => { let hub: Hub; beforeEach(() => { jest.useFakeTimers(); diff --git a/packages/tracing/package.json b/packages/tracing/package.json index cbcbb217493e..aa8b44ecdc5c 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/types/package.json b/packages/types/package.json index bd2250fe145e..0d09fcee6cd5 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/utils/package.json b/packages/utils/package.json index f3ad380a7c4d..b4ceaf186c6d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/utils/src/requestdata.ts b/packages/utils/src/requestdata.ts index aaa1898e1f55..85b748dadaba 100644 --- a/packages/utils/src/requestdata.ts +++ b/packages/utils/src/requestdata.ts @@ -69,7 +69,8 @@ export type TransactionNamingScheme = 'path' | 'methodPath' | 'handler'; export function addRequestDataToTransaction( transaction: Transaction | undefined, req: PolymorphicRequest, - deps?: InjectedNodeDeps, + // TODO(v8): Remove this parameter in v8 + _deps?: InjectedNodeDeps, ): void { if (!transaction) return; // eslint-disable-next-line deprecation/deprecation @@ -87,7 +88,7 @@ export function addRequestDataToTransaction( } // TODO: We need to rewrite this to a flat format? // eslint-disable-next-line deprecation/deprecation - transaction.setData('query', extractQueryParams(req, deps)); + transaction.setData('query', extractQueryParams(req)); } /** @@ -188,10 +189,11 @@ export function extractRequestData( req: PolymorphicRequest, options?: { include?: string[]; + // TODO(v8): Remove this paramater deps?: InjectedNodeDeps; }, ): ExtractedNodeRequestData { - const { include = DEFAULT_REQUEST_INCLUDES, deps } = options || {}; + const { include = DEFAULT_REQUEST_INCLUDES } = options || {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any const requestData: { [key: string]: any } = {}; @@ -257,7 +259,7 @@ export function extractRequestData( // node: req.url (raw) // express, koa, nextjs: req.query // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - requestData.query_string = extractQueryParams(req, deps); + requestData.query_string = extractQueryParams(req); break; } case 'data': { @@ -349,10 +351,7 @@ export function addRequestDataToEvent( return event; } -function extractQueryParams( - req: PolymorphicRequest, - deps?: InjectedNodeDeps, -): string | Record | undefined { +function extractQueryParams(req: PolymorphicRequest): string | Record | undefined { // url (including path and query string): // node, express: req.originalUrl // koa, nextjs: req.url @@ -369,13 +368,8 @@ function extractQueryParams( } try { - return ( - req.query || - (typeof URL !== 'undefined' && new URL(originalUrl).search.slice(1)) || - // In Node 8, `URL` isn't in the global scope, so we have to use the built-in module from Node - (deps && deps.url && deps.url.parse(originalUrl).query) || - undefined - ); + const queryParams = req.query || new URL(originalUrl).search.slice(1); + return queryParams.length ? queryParams : undefined; } catch { return undefined; } diff --git a/packages/utils/src/vendor/escapeStringForRegex.ts b/packages/utils/src/vendor/escapeStringForRegex.ts index 2b55452802ad..229c3da707c1 100644 --- a/packages/utils/src/vendor/escapeStringForRegex.ts +++ b/packages/utils/src/vendor/escapeStringForRegex.ts @@ -1,6 +1,6 @@ // Based on https://github.com/sindresorhus/escape-string-regexp but with modifications to: // a) reduce the size by skipping the runtime type - checking -// b) ensure it gets down - compiled for old versions of Node(the published package only supports Node 12+). +// b) ensure it gets down - compiled for old versions of Node(the published package only supports Node 14+). // // MIT License // diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index fda1798c3792..b01c887abedf 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -406,10 +406,7 @@ describe('normalize()', () => { test('primitive values', () => { expect(normalize(NaN)).toEqual('[NaN]'); expect(normalize(Symbol('dogs'))).toEqual('[Symbol(dogs)]'); - // `BigInt` doesn't exist in Node 8 - if (Number(process.versions.node.split('.')[0]) >= 10) { - expect(normalize(BigInt(1121201212312012))).toEqual('[BigInt: 1121201212312012]'); - } + expect(normalize(BigInt(1121201212312012))).toEqual('[BigInt: 1121201212312012]'); }); test('functions', () => { diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 4257a3a83713..2ab2a932a753 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/vue/package.json b/packages/vue/package.json index f2d93043dd94..e316ec970924 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 8f77b463c7c5..7507cb607757 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14" }, "files": [ "cjs", diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index 9c9312e76571..c876552d2d8d 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -1,12 +1,9 @@ import * as childProcess from 'child_process'; -import * as fs from 'fs'; -type NodeVersion = '8' | '10' | '12' | '14' | '16'; +type NodeVersion = '14' | '16' | '18' | '20' | '21'; interface VersionConfig { ignoredPackages: Array<`@${'sentry' | 'sentry-internal'}/${string}`>; - legacyDeps: Array<`${string}@${string}`>; - shouldES6Utils: boolean; } const CURRENT_NODE_VERSION = process.version.replace('v', '').split('.')[0] as NodeVersion; @@ -29,78 +26,23 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [ ]; const SKIP_TEST_PACKAGES: Record = { - '8': { - ignoredPackages: [ - '@sentry/gatsby', - '@sentry/serverless', - '@sentry/nextjs', - '@sentry/remix', - '@sentry/sveltekit', - '@sentry-internal/replay-worker', - '@sentry/node-experimental', - '@sentry/opentelemetry', - '@sentry/vercel-edge', - '@sentry/astro', - ], - legacyDeps: [ - 'jsdom@15.x', - 'jest@25.x', - 'jest-environment-jsdom@25.x', - 'jest-environment-node@25.x', - 'ts-jest@25.x', - 'lerna@3.13.4', - ], - shouldES6Utils: true, - }, - '10': { - ignoredPackages: [ - '@sentry/remix', - '@sentry/sveltekit', - '@sentry-internal/replay-worker', - '@sentry/node-experimental', - '@sentry/opentelemetry', - '@sentry/vercel-edge', - '@sentry/astro', - ], - legacyDeps: ['jsdom@16.x', 'lerna@3.13.4'], - shouldES6Utils: true, - }, - '12': { - ignoredPackages: [ - '@sentry/remix', - '@sentry/sveltekit', - '@sentry/node-experimental', - '@sentry/opentelemetry', - '@sentry/vercel-edge', - '@sentry/astro', - ], - legacyDeps: ['lerna@3.13.4'], - shouldES6Utils: true, - }, '14': { ignoredPackages: ['@sentry/sveltekit', '@sentry/vercel-edge', '@sentry/astro'], - legacyDeps: [], - shouldES6Utils: false, }, '16': { ignoredPackages: ['@sentry/vercel-edge', '@sentry/astro'], - legacyDeps: [], - shouldES6Utils: false, + }, + '18': { + ignoredPackages: [], + }, + '20': { + ignoredPackages: [], + }, + '21': { + ignoredPackages: [], }, }; -type JSONValue = string | number | boolean | null | JSONArray | JSONObject; - -type JSONObject = { - [key: string]: JSONValue; -}; - -type JSONArray = Array; - -interface TSConfigJSON extends JSONObject { - compilerOptions: { lib: string[]; target: string }; -} - /** * Run the given shell command, piping the shell process's `stdin`, `stdout`, and `stderr` to that of the current * process. Returns contents of `stdout`. @@ -109,50 +51,6 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): void { childProcess.execSync(cmd, { stdio: 'inherit', ...options }); } -/** - * Install the given legacy dependencies, for compatibility with tests run in older versions of Node. - */ -function installLegacyDeps(legacyDeps: string[] = []): void { - // Ignoring engines and scripts lets us get away with having incompatible things installed for SDK packages we're not - // testing in the current node version, and ignoring the root check lets us install things at the repo root. - run(`yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ${legacyDeps.join(' ')}`); -} - -/** - * Modify a json file on disk. - * - * @param filepath The path to the file to be modified - * @param transformer A function which takes the JSON data as input and returns a mutated version. It may mutate the - * JSON data in place, but it isn't required to do so. - */ -export function modifyJSONFile(filepath: string, transformer: (json: T) => T): void { - const fileContents = fs - .readFileSync(filepath) - .toString() - // get rid of comments, which the `jsonc` format allows, but which will crash `JSON.parse` - .replace(/\/\/.*\n/g, ''); - const json = JSON.parse(fileContents); - const newJSON = transformer(json); - fs.writeFileSync(filepath, JSON.stringify(newJSON, null, 2)); -} - -const es6ifyTestTSConfig = (pkg: string): void => { - const filepath = `packages/${pkg}/tsconfig.test.json`; - const transformer = (tsconfig: TSConfigJSON): TSConfigJSON => { - tsconfig.compilerOptions.target = 'es6'; - return tsconfig; - }; - modifyJSONFile(filepath, transformer); -}; - -/** - * Skip tests which don't run in Node 8. - * We're forced to skip these tests for compatibility reasons. - */ -function skipNodeV8Tests(): void { - run('rm -rf packages/tracing/test/browser'); -} - /** * Run tests, ignoring the given packages */ @@ -169,19 +67,9 @@ function runTests(): void { DEFAULT_SKIP_TESTS_PACKAGES.forEach(pkg => ignores.add(pkg)); - if (CURRENT_NODE_VERSION === '8') { - skipNodeV8Tests(); - } - const versionConfig = SKIP_TEST_PACKAGES[CURRENT_NODE_VERSION]; if (versionConfig) { versionConfig.ignoredPackages.forEach(dep => ignores.add(dep)); - if (versionConfig.legacyDeps.length > 0) { - installLegacyDeps(versionConfig.legacyDeps); - } - if (versionConfig.shouldES6Utils) { - es6ifyTestTSConfig('utils'); - } } runWithIgnores(Array.from(ignores)); From b6954f9df717272585976a5b41771ff15edd94fd Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 08:47:44 -0500 Subject: [PATCH 009/173] feat(v8/ember): Remove deprecated exports (#10535) ref https://github.com/getsentry/sentry-javascript/issues/10100 --- packages/ember/addon/index.ts | 11 ----------- .../addon/instance-initializers/sentry-performance.ts | 11 ++--------- packages/ember/addon/types.ts | 6 ++---- packages/ember/tests/helpers/utils.ts | 7 +++---- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index b3ccfffa404f..f403df54284e 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -9,7 +9,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata } from '@sentry/core import { GLOBAL_OBJ } from '@sentry/utils'; import Ember from 'ember'; -import type { Transaction } from '@sentry/types'; import type { EmberSentryConfig, GlobalConfig, OwnConfig } from './types'; function _getSentryInitConfig(): EmberSentryConfig['sentry'] { @@ -60,16 +59,6 @@ export function init(_runtimeConfig?: BrowserOptions): void { } } -/** - * Grabs active transaction off scope. - * - * @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead. - */ -export const getActiveTransaction = (): Transaction | undefined => { - // eslint-disable-next-line deprecation/deprecation - return Sentry.getCurrentScope().getTransaction(); -}; - type RouteConstructor = new (...args: ConstructorParameters) => Route; export const instrumentRoutePerformance = (BaseRoute: T): T => { diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index f4c47998ea90..5b7ee172542a 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -109,11 +109,7 @@ export function _instrumentEmberRouter( return; } - if ( - url && - browserTracingOptions.startTransactionOnPageLoad !== false && - browserTracingOptions.instrumentPageLoad !== false - ) { + if (url && browserTracingOptions.instrumentPageLoad !== false) { const routeInfo = routerService.recognize(url); Sentry.startBrowserTracingPageLoadSpan(client, { name: `route:${routeInfo.name}`, @@ -136,10 +132,7 @@ export function _instrumentEmberRouter( getBackburner().off('end', finishActiveTransaction); }; - if ( - browserTracingOptions.startTransactionOnLocationChange === false && - browserTracingOptions.instrumentNavigation === false - ) { + if (browserTracingOptions.instrumentNavigation === false) { return; } diff --git a/packages/ember/addon/types.ts b/packages/ember/addon/types.ts index bf333740ee5b..1b6825442be1 100644 --- a/packages/ember/addon/types.ts +++ b/packages/ember/addon/types.ts @@ -1,9 +1,7 @@ -import type { BrowserOptions, BrowserTracing, browserTracingIntegration } from '@sentry/browser'; +import type { BrowserOptions, browserTracingIntegration } from '@sentry/browser'; import type { Transaction, TransactionContext } from '@sentry/types'; -type BrowserTracingOptions = Parameters[0] & - // eslint-disable-next-line deprecation/deprecation - ConstructorParameters[0]; +type BrowserTracingOptions = Parameters[0]; export type EmberSentryConfig = { sentry: BrowserOptions & { browserTracingOptions?: BrowserTracingOptions }; diff --git a/packages/ember/tests/helpers/utils.ts b/packages/ember/tests/helpers/utils.ts index 0be2c3d2f422..7e7715fc57f5 100644 --- a/packages/ember/tests/helpers/utils.ts +++ b/packages/ember/tests/helpers/utils.ts @@ -72,13 +72,12 @@ export function assertSentryTransactions( return !op?.startsWith('ui.ember.runloop.') && !op?.startsWith('ui.long-task'); }) .map(s => { - // eslint-disable-next-line deprecation/deprecation - return `${s.op} | ${spanToJSON(s).description}`; + const spanJson = spanToJSON(s); + return `${spanJson.op} | ${spanJson.description}`; }); assert.true( - // eslint-disable-next-line deprecation/deprecation - spans.some(span => span.op?.startsWith('ui.ember.runloop.')), + spans.some(span => spanToJSON(span).op?.startsWith('ui.ember.runloop.')), 'it captures runloop spans', ); assert.deepEqual(filteredSpans, options.spans, 'Has correct spans'); From 5022f2f6a898ea63260d9ccbe75b88fa0bcc6fd0 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Feb 2024 10:25:11 -0330 Subject: [PATCH 010/173] test(browser-integration): Attempt to improve potentially flakey `slowClick -> windowOpen` test (#10538) Maybe closes https://github.com/getsentry/sentry-javascript/issues/10377 --- .../suites/replay/slowClick/windowOpen/test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts index c029f19b8c28..bb2c91018d94 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts @@ -28,11 +28,14 @@ sentryTest('window.open() is considered for slow click', async ({ getLocalTestUr // Ensure window.open() still works as expected const context = browser.contexts()[0]; - const waitForNewPage = context.waitForEvent('page'); - await page.locator('#windowOpenButton').click(); + const [reqResponse1] = await Promise.all([ + reqPromise1, + context.waitForEvent('page'), + page.locator('#windowOpenButton').click(), + ]); - const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1); + const { breadcrumbs } = getCustomRecordingEvents(reqResponse1); // Filter out potential blur breadcrumb, as otherwise this can be flaky const filteredBreadcrumb = breadcrumbs.filter(breadcrumb => breadcrumb.category !== 'ui.blur'); @@ -57,8 +60,6 @@ sentryTest('window.open() is considered for slow click', async ({ getLocalTestUr }, ]); - await waitForNewPage; - const pages = context.pages(); expect(pages.length).toBe(2); From 949358067487b7c798f9f7c5f31e0282facda614 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 10:03:10 -0500 Subject: [PATCH 011/173] feat(v8/react): Delete react router exports (#10532) ref https://github.com/getsentry/sentry-javascript/issues/10100 --- .../src/index.tsx | 15 +- packages/react/src/index.ts | 12 +- packages/react/src/reactrouter.tsx | 28 +- packages/react/src/reactrouterv3.ts | 9 +- packages/react/test/reactrouterv3.test.tsx | 214 ------ packages/react/test/reactrouterv4.test.tsx | 302 +-------- packages/react/test/reactrouterv5.test.tsx | 302 +-------- packages/react/test/reactrouterv6.4.test.tsx | 439 +----------- packages/react/test/reactrouterv6.test.tsx | 640 +----------------- 9 files changed, 14 insertions(+), 1947 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/src/index.tsx b/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/src/index.tsx index 9a26b58079e1..319290d010ce 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/src/index.tsx @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/react'; -import { BrowserTracing } from '@sentry/tracing'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { @@ -18,14 +17,12 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.REACT_APP_E2E_TEST_DSN, integrations: [ - new BrowserTracing({ - routingInstrumentation: Sentry.reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - ), + Sentry.reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, }), ], // We recommend adjusting this value in production, or using tracesSampler diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2fa3e32e67d6..ef6627cf0c5e 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -5,23 +5,13 @@ export { Profiler, withProfiler, useProfiler } from './profiler'; export type { ErrorBoundaryProps, FallbackRender } from './errorboundary'; export { ErrorBoundary, withErrorBoundary } from './errorboundary'; export { createReduxEnhancer } from './redux'; +export { reactRouterV3BrowserTracingIntegration } from './reactrouterv3'; export { - // eslint-disable-next-line deprecation/deprecation - reactRouterV3Instrumentation, - reactRouterV3BrowserTracingIntegration, -} from './reactrouterv3'; -export { - // eslint-disable-next-line deprecation/deprecation - reactRouterV4Instrumentation, - // eslint-disable-next-line deprecation/deprecation - reactRouterV5Instrumentation, withSentryRouting, reactRouterV4BrowserTracingIntegration, reactRouterV5BrowserTracingIntegration, } from './reactrouter'; export { - // eslint-disable-next-line deprecation/deprecation - reactRouterV6Instrumentation, reactRouterV6BrowserTracingIntegration, withSentryReactRouterV6Routing, wrapUseRoutes, diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index ba6fc523ee58..fba359e75e79 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -75,8 +75,7 @@ export function reactRouterV4BrowserTracingIntegration( return undefined; }; - // eslint-disable-next-line deprecation/deprecation - const instrumentation = reactRouterV4Instrumentation(history, routes, matchPath); + const instrumentation = createReactRouterInstrumentation(history, 'reactrouter_v4', routes, matchPath); // Now instrument page load & navigation with correct settings instrumentation(startPageloadCallback, instrumentPageLoad, false); @@ -115,8 +114,7 @@ export function reactRouterV5BrowserTracingIntegration( return undefined; }; - // eslint-disable-next-line deprecation/deprecation - const instrumentation = reactRouterV5Instrumentation(history, routes, matchPath); + const instrumentation = createReactRouterInstrumentation(history, 'reactrouter_v5', routes, matchPath); // Now instrument page load & navigation with correct settings instrumentation(startPageloadCallback, options.instrumentPageLoad, false); @@ -125,28 +123,6 @@ export function reactRouterV5BrowserTracingIntegration( }; } -/** - * @deprecated Use `browserTracingReactRouterV4()` instead. - */ -export function reactRouterV4Instrumentation( - history: RouterHistory, - routes?: RouteConfig[], - matchPath?: MatchPath, -): ReactRouterInstrumentation { - return createReactRouterInstrumentation(history, 'reactrouter_v4', routes, matchPath); -} - -/** - * @deprecated Use `browserTracingReactRouterV5()` instead. - */ -export function reactRouterV5Instrumentation( - history: RouterHistory, - routes?: RouteConfig[], - matchPath?: MatchPath, -): ReactRouterInstrumentation { - return createReactRouterInstrumentation(history, 'reactrouter_v5', routes, matchPath); -} - function createReactRouterInstrumentation( history: RouterHistory, instrumentationName: string, diff --git a/packages/react/src/reactrouterv3.ts b/packages/react/src/reactrouterv3.ts index 905ebec13897..459589d91b93 100644 --- a/packages/react/src/reactrouterv3.ts +++ b/packages/react/src/reactrouterv3.ts @@ -74,7 +74,6 @@ export function reactRouterV3BrowserTracingIntegration( return undefined; }; - // eslint-disable-next-line deprecation/deprecation const instrumentation = reactRouterV3Instrumentation(history, routes, match); // Now instrument page load & navigation with correct settings @@ -91,14 +90,8 @@ export function reactRouterV3BrowserTracingIntegration( * @param history object from the `history` library * @param routes a list of all routes, should be * @param match `Router.match` utility - * - * @deprecated Use `reactRouterV3BrowserTracingIntegration()` instead */ -export function reactRouterV3Instrumentation( - history: HistoryV3, - routes: Route[], - match: Match, -): ReactRouterInstrumentation { +function reactRouterV3Instrumentation(history: HistoryV3, routes: Route[], match: Match): ReactRouterInstrumentation { return ( startTransaction: (context: TransactionContext) => Transaction | undefined, startTransactionOnPageLoad: boolean = true, diff --git a/packages/react/test/reactrouterv3.test.tsx b/packages/react/test/reactrouterv3.test.tsx index c9926567cea4..964fe7e47b3d 100644 --- a/packages/react/test/reactrouterv3.test.tsx +++ b/packages/react/test/reactrouterv3.test.tsx @@ -13,7 +13,6 @@ import { IndexRoute, Route, Router, createMemoryHistory, createRoutes, match } f import type { Match, Route as RouteType } from '../src/reactrouterv3'; import { reactRouterV3BrowserTracingIntegration } from '../src/reactrouterv3'; -import { reactRouterV3Instrumentation } from '../src/reactrouterv3'; // Have to manually set types because we are using package-alias declare module 'react-router-3' { @@ -26,219 +25,6 @@ declare module 'react-router-3' { export const createRoutes: (routes: any) => RouteType[]; } -function createMockStartTransaction(opts: { finish?: jest.FunctionLike; setMetadata?: jest.FunctionLike } = {}) { - const { finish = jest.fn(), setMetadata = jest.fn() } = opts; - return jest.fn().mockReturnValue({ - end: finish, - setMetadata, - }); -} - -describe('reactRouterV3Instrumentation', () => { - const routes = ( -
{children}
}> -
Home
} /> -
About
} /> -
Features
} /> - }) =>
{params.userid}
} - /> - -
OrgId
} /> -
Team
} /> -
-
- ); - const history = createMemoryHistory(); - - const instrumentationRoutes = createRoutes(routes); - // eslint-disable-next-line deprecation/deprecation - const instrumentation = reactRouterV3Instrumentation(history, instrumentationRoutes, match); - - it('starts a pageload transaction when instrumentation is started', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - }, - }); - }); - - it('does not start pageload transaction if option is false', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction, false); - expect(mockStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('starts a navigation transaction', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - render({routes}); - - act(() => { - history.push('/about'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/features'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/features', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('does not start a transaction if option is false', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction, true, false); - render({routes}); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('only starts a navigation transaction on push', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - render({routes}); - - act(() => { - history.replace('hello'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('finishes a transaction on navigation', () => { - const mockFinish = jest.fn(); - const mockStartTransaction = createMockStartTransaction({ finish: mockFinish }); - instrumentation(mockStartTransaction); - render({routes}); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - - act(() => { - history.push('/features'); - }); - expect(mockFinish).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - }); - - it('normalizes transaction names', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - const { container } = render({routes}); - - act(() => { - history.push('/users/123'); - }); - expect(container.innerHTML).toContain('123'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/users/:userid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('normalizes nested transaction names', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - const { container } = render({routes}); - - act(() => { - history.push('/organizations/1234/v1/758'); - }); - expect(container.innerHTML).toContain('Team'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid/v1/:teamid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/organizations/543'); - }); - expect(container.innerHTML).toContain('OrgId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('sets metadata to url if on an unknown route', () => { - const mockStartTransaction = createMockStartTransaction(); - instrumentation(mockStartTransaction); - render({routes}); - - act(() => { - history.push('/organizations/1234/some/other/route'); - }); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/1234/some/other/route', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('sets metadata to url if no routes are provided', () => { - const fakeRoutes =
hello
; - const mockStartTransaction = createMockStartTransaction(); - // eslint-disable-next-line deprecation/deprecation - const mockInstrumentation = reactRouterV3Instrumentation(history, createRoutes(fakeRoutes), match); - mockInstrumentation(mockStartTransaction); - // We render here with `routes` instead of `fakeRoutes` from above to validate the case - // where users provided the instrumentation with a bad set of routes. - render({routes}); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v3', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - }, - }); - }); -}); - const mockStartBrowserTracingPageLoadSpan = jest.fn(); const mockStartBrowserTracingNavigationSpan = jest.fn(); diff --git a/packages/react/test/reactrouterv4.test.tsx b/packages/react/test/reactrouterv4.test.tsx index 973bda75d273..1028131eb0b3 100644 --- a/packages/react/test/reactrouterv4.test.tsx +++ b/packages/react/test/reactrouterv4.test.tsx @@ -12,309 +12,9 @@ import { createMemoryHistory } from 'history-4'; import * as React from 'react'; import { Route, Router, Switch, matchPath } from 'react-router-4'; -import { - BrowserClient, - reactRouterV4BrowserTracingIntegration, - reactRouterV4Instrumentation, - withSentryRouting, -} from '../src'; +import { BrowserClient, reactRouterV4BrowserTracingIntegration, withSentryRouting } from '../src'; import type { RouteConfig } from '../src/reactrouter'; -describe('reactRouterV4Instrumentation', () => { - function createInstrumentation(_opts?: { - startTransactionOnPageLoad?: boolean; - startTransactionOnLocationChange?: boolean; - routes?: RouteConfig[]; - }): [jest.Mock, any, { mockUpdateName: jest.Mock; mockFinish: jest.Mock; mockSetAttribute: jest.Mock }] { - const options = { - matchPath: _opts && _opts.routes !== undefined ? matchPath : undefined, - routes: undefined, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ..._opts, - }; - const history = createMemoryHistory(); - const mockFinish = jest.fn(); - const mockUpdateName = jest.fn(); - const mockSetAttribute = jest.fn(); - const mockStartTransaction = jest - .fn() - .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); - // eslint-disable-next-line deprecation/deprecation - reactRouterV4Instrumentation(history, options.routes, options.matchPath)( - mockStartTransaction, - options.startTransactionOnPageLoad, - options.startTransactionOnLocationChange, - ); - return [mockStartTransaction, history, { mockUpdateName, mockFinish, mockSetAttribute }]; - } - - it('starts a pageload transaction when instrumentation is started', () => { - const [mockStartTransaction] = createInstrumentation(); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - }, - }); - }); - - it('does not start pageload transaction if option is false', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnPageLoad: false }); - expect(mockStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('starts a navigation transaction', () => { - const [mockStartTransaction, history] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/about'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/features'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/features', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('does not start a transaction if option is false', () => { - const [mockStartTransaction, history] = createInstrumentation({ startTransactionOnLocationChange: false }); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('only starts a navigation transaction on push', () => { - const [mockStartTransaction, history] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.replace('hello'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('finishes a transaction on navigation', () => { - const [mockStartTransaction, history, { mockFinish }] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - - act(() => { - history.push('/features'); - }); - expect(mockFinish).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - }); - - it('does not normalize transaction name ', () => { - const [mockStartTransaction, history] = createInstrumentation(); - const { getByText } = render( - - -
UserId
} /> -
Users
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/users/123'); - }); - getByText('UserId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/users/123', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('normalizes transaction name with custom Route', () => { - const [mockStartTransaction, history, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const SentryRoute = withSentryRouting(Route); - const { getByText } = render( - - -
UserId
} /> -
Users
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/users/123'); - }); - getByText('UserId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/users/123', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(2); - expect(mockUpdateName).toHaveBeenLastCalledWith('/users/:userid'); - expect(mockSetAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('normalizes nested transaction names with custom Route', () => { - const [mockStartTransaction, history, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const SentryRoute = withSentryRouting(Route); - const { getByText } = render( - - -
Team
} /> -
OrgId
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/organizations/1234/v1/758'); - }); - getByText('Team'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/1234/v1/758', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(2); - expect(mockUpdateName).toHaveBeenLastCalledWith('/organizations/:orgid/v1/:teamid'); - expect(mockSetAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - - act(() => { - history.push('/organizations/543'); - }); - getByText('OrgId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/543', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(3); - expect(mockUpdateName).toHaveBeenLastCalledWith('/organizations/:orgid'); - expect(mockSetAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('matches with route object', () => { - const routes: RouteConfig[] = [ - { - path: '/organizations/:orgid/v1/:teamid', - }, - { path: '/organizations/:orgid' }, - { path: '/' }, - ]; - const [mockStartTransaction, history] = createInstrumentation({ routes }); - render( - - -
Team
} /> -
OrgId
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/organizations/1234/v1/758'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid/v1/:teamid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/organizations/1234'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v4', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); -}); - const mockStartBrowserTracingPageLoadSpan = jest.fn(); const mockStartBrowserTracingNavigationSpan = jest.fn(); diff --git a/packages/react/test/reactrouterv5.test.tsx b/packages/react/test/reactrouterv5.test.tsx index b08f7de702a1..0678f65652e4 100644 --- a/packages/react/test/reactrouterv5.test.tsx +++ b/packages/react/test/reactrouterv5.test.tsx @@ -12,309 +12,9 @@ import { createMemoryHistory } from 'history-4'; import * as React from 'react'; import { Route, Router, Switch, matchPath } from 'react-router-5'; -import { - BrowserClient, - reactRouterV5BrowserTracingIntegration, - reactRouterV5Instrumentation, - withSentryRouting, -} from '../src'; +import { BrowserClient, reactRouterV5BrowserTracingIntegration, withSentryRouting } from '../src'; import type { RouteConfig } from '../src/reactrouter'; -describe('reactRouterV5Instrumentation', () => { - function createInstrumentation(_opts?: { - startTransactionOnPageLoad?: boolean; - startTransactionOnLocationChange?: boolean; - routes?: RouteConfig[]; - }): [jest.Mock, any, { mockUpdateName: jest.Mock; mockFinish: jest.Mock; mockSetAttribute: jest.Mock }] { - const options = { - matchPath: _opts && _opts.routes !== undefined ? matchPath : undefined, - routes: undefined, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ..._opts, - }; - const history = createMemoryHistory(); - const mockFinish = jest.fn(); - const mockUpdateName = jest.fn(); - const mockSetAttribute = jest.fn(); - const mockStartTransaction = jest - .fn() - .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); - // eslint-disable-next-line deprecation/deprecation - reactRouterV5Instrumentation(history, options.routes, options.matchPath)( - mockStartTransaction, - options.startTransactionOnPageLoad, - options.startTransactionOnLocationChange, - ); - return [mockStartTransaction, history, { mockUpdateName, mockFinish, mockSetAttribute }]; - } - - it('starts a pageload transaction when instrumentation is started', () => { - const [mockStartTransaction] = createInstrumentation(); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - }, - }); - }); - - it('does not start pageload transaction if option is false', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnPageLoad: false }); - expect(mockStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('starts a navigation transaction', () => { - const [mockStartTransaction, history] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/about'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/features'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/features', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('does not start a transaction if option is false', () => { - const [mockStartTransaction, history] = createInstrumentation({ startTransactionOnLocationChange: false }); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('only starts a navigation transaction on push', () => { - const [mockStartTransaction, history] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.replace('hello'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - }); - - it('finishes a transaction on navigation', () => { - const [mockStartTransaction, history, { mockFinish }] = createInstrumentation(); - render( - - -
Features
} /> -
About
} /> -
Home
} /> -
-
, - ); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - - act(() => { - history.push('/features'); - }); - expect(mockFinish).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - }); - - it('does not normalize transaction name ', () => { - const [mockStartTransaction, history] = createInstrumentation(); - const { getByText } = render( - - -
UserId
} /> -
Users
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/users/123'); - }); - getByText('UserId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/users/123', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); - - it('normalizes transaction name with custom Route', () => { - const [mockStartTransaction, history, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const SentryRoute = withSentryRouting(Route); - const { getByText } = render( - - -
UserId
} /> -
Users
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/users/123'); - }); - getByText('UserId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/users/123', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(2); - expect(mockUpdateName).toHaveBeenLastCalledWith('/users/:userid'); - expect(mockSetAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('normalizes nested transaction names with custom Route', () => { - const [mockStartTransaction, history, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const SentryRoute = withSentryRouting(Route); - const { getByText } = render( - - -
Team
} /> -
OrgId
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/organizations/1234/v1/758'); - }); - getByText('Team'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/1234/v1/758', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(2); - expect(mockUpdateName).toHaveBeenLastCalledWith('/organizations/:orgid/v1/:teamid'); - expect(mockSetAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - - act(() => { - history.push('/organizations/543'); - }); - getByText('OrgId'); - - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/543', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - expect(mockUpdateName).toHaveBeenCalledTimes(3); - expect(mockUpdateName).toHaveBeenLastCalledWith('/organizations/:orgid'); - expect(mockSetAttribute).toHaveBeenLastCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('matches with route object', () => { - const routes: RouteConfig[] = [ - { - path: '/organizations/:orgid/v1/:teamid', - }, - { path: '/organizations/:orgid' }, - { path: '/' }, - ]; - const [mockStartTransaction, history] = createInstrumentation({ routes }); - render( - - -
Team
} /> -
OrgId
} /> -
Home
} /> -
-
, - ); - - act(() => { - history.push('/organizations/1234/v1/758'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid/v1/:teamid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - - act(() => { - history.push('/organizations/1234'); - }); - expect(mockStartTransaction).toHaveBeenCalledTimes(3); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/organizations/:orgid', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v5', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - }, - }); - }); -}); - const mockStartBrowserTracingPageLoadSpan = jest.fn(); const mockStartBrowserTracingNavigationSpan = jest.fn(); diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index f534d02f97e2..2013524d5185 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -14,13 +14,12 @@ import { RouterProvider, createMemoryRouter, createRoutesFromChildren, - matchPath, matchRoutes, useLocation, useNavigationType, } from 'react-router-6.4'; -import { BrowserClient, reactRouterV6Instrumentation, wrapCreateBrowserRouter } from '../src'; +import { BrowserClient, wrapCreateBrowserRouter } from '../src'; import { reactRouterV6BrowserTracingIntegration } from '../src/reactrouterv6'; import type { CreateRouterFunction } from '../src/types'; @@ -30,442 +29,6 @@ beforeAll(() => { global.Request = Request; }); -describe('reactRouterV6Instrumentation (v6.4)', () => { - function createInstrumentation(_opts?: { - startTransactionOnPageLoad?: boolean; - startTransactionOnLocationChange?: boolean; - stripBasename?: boolean; - }): [jest.Mock, { mockUpdateName: jest.Mock; mockFinish: jest.Mock; mockSetAttribute: jest.Mock }] { - const options = { - matchPath: _opts ? matchPath : undefined, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ..._opts, - }; - const mockFinish = jest.fn(); - const mockUpdateName = jest.fn(); - const mockSetAttribute = jest.fn(); - const mockStartTransaction = jest - .fn() - .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); - - // eslint-disable-next-line deprecation/deprecation - reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - options.stripBasename, - )(mockStartTransaction, options.startTransactionOnPageLoad, options.startTransactionOnLocationChange); - return [mockStartTransaction, { mockUpdateName, mockFinish, mockSetAttribute }]; - } - - describe('wrapCreateBrowserRouter', () => { - it('starts a pageload transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element:
TEST
, - }, - ], - { - initialEntries: ['/'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - }); - - it('starts a navigation transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'about', - element:
About
, - }, - ], - { - initialEntries: ['/'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with nested routes', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'about', - element:
About
, - children: [ - { - path: 'us', - element:
Us
, - }, - ], - }, - ], - { - initialEntries: ['/'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/us', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with parameterized paths', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'about', - element:
About
, - children: [ - { - path: ':page', - element:
Page
, - }, - ], - }, - ], - { - initialEntries: ['/'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/:page', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with paths with multiple parameters', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'stores', - element:
Stores
, - children: [ - { - path: ':storeId', - element:
Store
, - children: [ - { - path: 'products', - element:
Products
, - children: [ - { - path: ':productId', - element:
Product
, - }, - ], - }, - ], - }, - ], - }, - ], - { - initialEntries: ['/'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/stores/:storeId/products/:productId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('updates pageload transaction to a parameterized route', () => { - const [mockStartTransaction, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: 'about', - element:
About
, - children: [ - { - path: ':page', - element:
page
, - }, - ], - }, - ], - { - initialEntries: ['/about/us'], - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockUpdateName).toHaveBeenLastCalledWith('/about/:page'); - expect(mockSetAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('works with `basename` option', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'about', - element:
About
, - children: [ - { - path: 'us', - element:
Us
, - }, - ], - }, - ], - { - initialEntries: ['/app'], - basename: '/app', - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/app/about/us', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with parameterized paths and `basename`', () => { - const [mockStartTransaction] = createInstrumentation(); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: ':orgId', - children: [ - { - path: 'users', - children: [ - { - path: ':userId', - element:
User
, - }, - ], - }, - ], - }, - ], - { - initialEntries: ['/admin'], - basename: '/admin', - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/admin/:orgId/users/:userId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('strips `basename` from transaction names of parameterized paths', () => { - const [mockStartTransaction] = createInstrumentation({ - stripBasename: true, - }); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: ':orgId', - children: [ - { - path: 'users', - children: [ - { - path: ':userId', - element:
User
, - }, - ], - }, - ], - }, - ], - { - initialEntries: ['/admin'], - basename: '/admin', - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/:orgId/users/:userId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('strips `basename` from transaction names of non-parameterized paths', () => { - const [mockStartTransaction] = createInstrumentation({ - stripBasename: true, - }); - const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction); - - const router = sentryCreateBrowserRouter( - [ - { - path: '/', - element: , - }, - { - path: 'about', - element:
About
, - children: [ - { - path: 'us', - element:
Us
, - }, - ], - }, - ], - { - initialEntries: ['/app'], - basename: '/app', - }, - ); - - // @ts-expect-error router is fine - render(); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/us', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - }); -}); - const mockStartBrowserTracingPageLoadSpan = jest.fn(); const mockStartBrowserTracingNavigationSpan = jest.fn(); diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index f2ec3fb3a4b9..65355099476b 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -15,657 +15,19 @@ import { Route, Routes, createRoutesFromChildren, - matchPath, matchRoutes, useLocation, useNavigationType, useRoutes, } from 'react-router-6'; -import { BrowserClient, reactRouterV6Instrumentation } from '../src'; +import { BrowserClient } from '../src'; import { reactRouterV6BrowserTracingIntegration, withSentryReactRouterV6Routing, wrapUseRoutes, } from '../src/reactrouterv6'; -describe('reactRouterV6Instrumentation', () => { - function createInstrumentation(_opts?: { - startTransactionOnPageLoad?: boolean; - startTransactionOnLocationChange?: boolean; - }): [jest.Mock, { mockUpdateName: jest.Mock; mockFinish: jest.Mock; mockSetAttribute: jest.Mock }] { - const options = { - matchPath: _opts ? matchPath : undefined, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ..._opts, - }; - const mockFinish = jest.fn(); - const mockUpdateName = jest.fn(); - const mockSetAttribute = jest.fn(); - const mockStartTransaction = jest - .fn() - .mockReturnValue({ updateName: mockUpdateName, end: mockFinish, setAttribute: mockSetAttribute }); - - // eslint-disable-next-line deprecation/deprecation - reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - )(mockStartTransaction, options.startTransactionOnPageLoad, options.startTransactionOnLocationChange); - return [mockStartTransaction, { mockUpdateName, mockFinish, mockSetAttribute }]; - } - - describe('withSentryReactRouterV6Routing', () => { - it('starts a pageload transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - Home} /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - }); - - it('skips pageload transaction with `startTransactionOnPageLoad: false`', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnPageLoad: false }); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - Home} /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('skips navigation transaction, with `startTransactionOnLocationChange: false`', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnLocationChange: false }); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - About} /> - } /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - }); - - it('starts a navigation transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - About} /> - } /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with nested routes', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - About}> - us} /> - - } /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/us', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with paramaterized paths', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - About}> - page} /> - - } /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/:page', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with paths with multiple parameters', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - Stores}> - Store}> - Product} /> - - - } /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/stores/:storeId/products/:productId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with nested paths with parameters', () => { - const [mockStartTransaction] = createInstrumentation(); - const SentryRoutes = withSentryReactRouterV6Routing(Routes); - - render( - - - } /> - Account Page} /> - - Project Index} /> - Project Page}> - Project Page Root} /> - Editor}> - View Canvas} /> - Space Canvas} /> - - - - - No Match Page} /> - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/projects/:projectId/views/:viewId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - }); - - describe('wrapUseRoutes', () => { - it('starts a pageload transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element:
Home
, - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - }); - - it('skips pageload transaction with `startTransactionOnPageLoad: false`', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnPageLoad: false }); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element:
Home
, - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('skips navigation transaction, with `startTransactionOnLocationChange: false`', () => { - const [mockStartTransaction] = createInstrumentation({ startTransactionOnLocationChange: false }); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: , - }, - { - path: '/about', - element:
About
, - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react.reactrouter_v6', - }, - }); - }); - - it('starts a navigation transaction', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: , - }, - { - path: '/about', - element:
About
, - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with nested routes', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: , - }, - { - path: '/about', - element:
About
, - children: [ - { - path: '/about/us', - element:
us
, - }, - ], - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/us', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with paramaterized paths', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: , - }, - { - path: '/about', - element:
About
, - children: [ - { - path: '/about/:page', - element:
page
, - }, - ], - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/about/:page', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with paths with multiple parameters', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: , - }, - { - path: '/stores', - element:
Stores
, - children: [ - { - path: '/stores/:storeId', - element:
Store
, - children: [ - { - path: '/stores/:storeId/products/:productId', - element:
Product
, - }, - ], - }, - ], - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/stores/:storeId/products/:productId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('works with nested paths with parameters', () => { - const [mockStartTransaction] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - index: true, - element: , - }, - { - path: 'account', - element:
Account Page
, - }, - { - path: 'projects', - children: [ - { - index: true, - element:
Project Index
, - }, - { - path: ':projectId', - element:
Project Page
, - children: [ - { - index: true, - element:
Project Page Root
, - }, - { - element:
Editor
, - children: [ - { - path: 'views/:viewId', - element:
View Canvas
, - }, - { - path: 'spaces/:spaceId', - element:
Space Canvas
, - }, - ], - }, - ], - }, - ], - }, - { - path: '*', - element:
No Match Page
, - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/projects/:projectId/views/:viewId', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', - }, - }); - }); - - it('does not add double slashes to URLS', () => { - const [mockStartTransaction, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: ( -
- -
- ), - children: [ - { - path: 'tests', - children: [ - { index: true, element:
Main Test
}, - { path: ':testId/*', element:
Test Component
}, - ], - }, - { path: '/', element: }, - { path: '*', element: }, - ], - }, - { - path: '/', - element:
, - children: [ - { path: '404', element:
Error
}, - { path: '*', element: }, - ], - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - // should be /tests not //tests - expect(mockUpdateName).toHaveBeenLastCalledWith('/tests'); - expect(mockSetAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it('handles wildcard routes properly', () => { - const [mockStartTransaction, { mockUpdateName, mockSetAttribute }] = createInstrumentation(); - const wrappedUseRoutes = wrapUseRoutes(useRoutes); - - const Routes = () => - wrappedUseRoutes([ - { - path: '/', - element: ( -
- -
- ), - children: [ - { - path: 'tests', - children: [ - { index: true, element:
Main Test
}, - { path: ':testId/*', element:
Test Component
}, - ], - }, - { path: '/', element: }, - { path: '*', element: }, - ], - }, - { - path: '/', - element:
, - children: [ - { path: '404', element:
Error
}, - { path: '*', element: }, - ], - }, - ]); - - render( - - - , - ); - - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockUpdateName).toHaveBeenLastCalledWith('/tests/:testId/*'); - expect(mockSetAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - }); -}); - const mockStartBrowserTracingPageLoadSpan = jest.fn(); const mockStartBrowserTracingNavigationSpan = jest.fn(); From f34503fb0627ed818305fa3e07e6933dee5ae2e9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 10:28:46 -0500 Subject: [PATCH 012/173] feat(v8/wasm): Remove deprecated exports (#10552) ref https://github.com/getsentry/sentry-javascript/issues/10100 --- .../suites/wasm/init.js | 4 ++-- packages/wasm/src/index.ts | 23 ++----------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/wasm/init.js b/dev-packages/browser-integration-tests/suites/wasm/init.js index c4826ccd8dba..9b9aad7b911f 100644 --- a/dev-packages/browser-integration-tests/suites/wasm/init.js +++ b/dev-packages/browser-integration-tests/suites/wasm/init.js @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { Wasm } from '@sentry/wasm'; +import { wasmIntegration } from '@sentry/wasm'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Wasm()], + integrations: [wasmIntegration()], beforeSend: event => { window.events.push(event); return null; diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index e84d4c880bd5..c3cf09fbbcd8 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; import { patchWebAssembly } from './patchWebAssembly'; import { getImage, getImages } from './registry'; @@ -35,25 +35,6 @@ const _wasmIntegration = (() => { export const wasmIntegration = defineIntegration(_wasmIntegration); -/** - * Process WASM stack traces to support server-side symbolication. - * - * This also hooks the WebAssembly loading browser API so that module - * registrations are intercepted. - * - * @deprecated Use `wasmIntegration` export instead - * - * import { wasmIntegration } from '@sentry/wasm'; - * - * ``` - * Sentry.init({ integrations: [wasmIntegration()] }); - * ``` - */ -// eslint-disable-next-line deprecation/deprecation -export const Wasm = convertIntegrationFnToClass(INTEGRATION_NAME, wasmIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; - /** * Patches a list of stackframes with wasm data needed for server-side symbolication * if applicable. Returns true if any frames were patched. From be0cc01828e6e829f17d3e4ac67efd0f2d968ca6 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 10:40:26 -0500 Subject: [PATCH 013/173] feat(v8): Remove Severity enum (#10551) --- MIGRATION.md | 8 ++++++++ packages/browser/src/client.ts | 4 +--- packages/browser/src/eventbuilder.ts | 4 +--- packages/browser/src/exports.ts | 2 -- packages/bun/src/index.ts | 2 -- packages/core/src/baseclient.ts | 7 ++----- packages/core/src/exports.ts | 7 +------ packages/core/src/hub.ts | 8 +------- packages/core/src/scope.ts | 9 ++------- packages/core/src/server-runtime-client.ts | 4 +--- packages/core/test/mocks/client.ts | 7 +------ packages/deno/src/index.ts | 2 -- packages/node-experimental/src/sdk/api.ts | 7 +------ packages/node-experimental/src/sdk/hub.ts | 8 +------- packages/node-experimental/src/sdk/scope.ts | 9 ++------- packages/node/src/index.ts | 2 -- packages/types/src/breadcrumb.ts | 5 ++--- packages/types/src/client.ts | 17 +++-------------- packages/types/src/event.ts | 5 ++--- packages/types/src/hub.ts | 9 ++------- packages/types/src/index.ts | 3 +-- packages/types/src/scope.ts | 10 +++------- packages/types/src/severity.ts | 19 ------------------- packages/utils/src/eventbuilder.ts | 4 +--- packages/utils/src/severity.ts | 15 +-------------- packages/vercel-edge/src/index.ts | 2 -- 26 files changed, 37 insertions(+), 142 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index e315bf77ccfd..626d95bb88cd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,11 @@ +# Upgrading from 7.x to 8.x + +## Removal of Severity Enum + +In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type. In v8 we removed the `Severity` +enum. If you were using the `Severity` enum, you should replace it with the `SeverityLevel` type. See +[below](#severity-severitylevel-and-severitylevels) for code snippet examples + # Deprecations in 7.x You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index 2f1c5ba2fdbb..97ea8e3b5e0c 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -9,7 +9,6 @@ import type { EventHint, Options, ParameterizedString, - Severity, SeverityLevel, UserFeedback, } from '@sentry/types'; @@ -76,8 +75,7 @@ export class BrowserClient extends BaseClient { */ public eventFromMessage( message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', + level: SeverityLevel = 'info', hint?: EventHint, ): PromiseLike { return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace); diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index 9e72fb288cb4..2956006b0ac7 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -4,7 +4,6 @@ import type { EventHint, Exception, ParameterizedString, - Severity, SeverityLevel, StackFrame, StackParser, @@ -178,8 +177,7 @@ export function eventFromException( export function eventFromMessage( stackParser: StackParser, message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', + level: SeverityLevel = 'info', hint?: EventHint, attachStacktrace?: boolean, ): PromiseLike { diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index e9e947de8559..496fbc3a9644 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -6,8 +6,6 @@ export type { Event, EventHint, Exception, - // eslint-disable-next-line deprecation/deprecation - Severity, SeverityLevel, StackFrame, Stacktrace, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index b51083052c8b..f1958c53404d 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -8,8 +8,6 @@ export type { EventHint, Exception, Session, - // eslint-disable-next-line deprecation/deprecation - Severity, SeverityLevel, Span, StackFrame, diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index b7f00e14baef..2c08ba45e20d 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -23,7 +23,6 @@ import type { SdkMetadata, Session, SessionAggregates, - Severity, SeverityLevel, StartSpanOptions, Transaction, @@ -186,8 +185,7 @@ export abstract class BaseClient implements Client { */ public captureMessage( message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, + level?: SeverityLevel, hint?: EventHint, scope?: Scope, ): string | undefined { @@ -876,8 +874,7 @@ export abstract class BaseClient implements Client { */ public abstract eventFromMessage( _message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - _level?: Severity | SeverityLevel, + _level?: SeverityLevel, _hint?: EventHint, ): PromiseLike; } diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index e1fc9a6102ac..22dd5f26a3ba 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -15,7 +15,6 @@ import type { Scope as ScopeInterface, Session, SessionContext, - Severity, SeverityLevel, Span, TransactionContext, @@ -56,11 +55,7 @@ export function captureException( * @param captureContext Define the level of the message or pass in additional data to attach to the message. * @returns the id of the captured message. */ -export function captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - captureContext?: CaptureContext | Severity | SeverityLevel, -): string { +export function captureMessage(message: string, captureContext?: CaptureContext | SeverityLevel): string { // This is necessary to provide explicit scopes upgrade, without changing the original // arity of the `captureMessage(message, level)` method. const level = typeof captureContext === 'string' ? captureContext : undefined; diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index d0f7c1cf4430..2a343b320f1b 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -14,7 +14,6 @@ import type { Primitive, Session, SessionContext, - Severity, SeverityLevel, Transaction, TransactionContext, @@ -320,12 +319,7 @@ export class Hub implements HubInterface { * * @deprecated Use `Sentry.captureMessage()` instead. */ - public captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - ): string { + public captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string { const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4()); const syntheticException = new Error(message); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 01546a84be25..9298637fae68 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -18,7 +18,6 @@ import type { ScopeContext, ScopeData, Session, - Severity, SeverityLevel, Span, Transaction, @@ -86,8 +85,7 @@ export class Scope implements ScopeInterface { protected _fingerprint?: string[]; /** Severity */ - // eslint-disable-next-line deprecation/deprecation - protected _level?: Severity | SeverityLevel; + protected _level?: SeverityLevel; /** * Transaction Name @@ -283,10 +281,7 @@ export class Scope implements ScopeInterface { /** * @inheritDoc */ - public setLevel( - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel, - ): this { + public setLevel(level: SeverityLevel): this { this._level = level; this._notifyScopeListeners(); return this; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index aed69369fc5d..b415a392d4cc 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -8,7 +8,6 @@ import type { MonitorConfig, ParameterizedString, SerializedCheckIn, - Severity, SeverityLevel, TraceContext, } from '@sentry/types'; @@ -70,8 +69,7 @@ export class ServerRuntimeClient< */ public eventFromMessage( message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', + level: SeverityLevel = 'info', hint?: EventHint, ): PromiseLike { return resolvedSyncPromise( diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 7cb1e08cecba..ad47a50fceb8 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -7,7 +7,6 @@ import type { Outcome, ParameterizedString, Session, - Severity, SeverityLevel, } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; @@ -76,11 +75,7 @@ export class TestClient extends BaseClient { return resolvedSyncPromise(event); } - public eventFromMessage( - message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', - ): PromiseLike { + public eventFromMessage(message: ParameterizedString, level: SeverityLevel = 'info'): PromiseLike { return resolvedSyncPromise({ message, level }); } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index d42ad97fedb8..82ce6004ece6 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -8,8 +8,6 @@ export type { EventHint, Exception, Session, - // eslint-disable-next-line deprecation/deprecation - Severity, SeverityLevel, Span, StackFrame, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index ae8450d58a7c..c4a0f3858111 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -12,7 +12,6 @@ import type { Extra, Extras, Primitive, - Severity, SeverityLevel, User, } from '@sentry/types'; @@ -120,11 +119,7 @@ export function captureException(exception: unknown, hint?: ExclusiveEventHintOr } /** Record a message and send it to Sentry. */ -export function captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - captureContext?: CaptureContext | Severity | SeverityLevel, -): string { +export function captureMessage(message: string, captureContext?: CaptureContext | SeverityLevel): string { // This is necessary to provide explicit scopes upgrade, without changing the original // arity of the `captureMessage(message, level)` method. const level = typeof captureContext === 'string' ? captureContext : undefined; diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index b2ffaa16b364..cce73a81d71f 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -5,7 +5,6 @@ import type { Hub, Integration, IntegrationClass, - Severity, SeverityLevel, TransactionContext, } from '@sentry/types'; @@ -68,12 +67,7 @@ export function getCurrentHub(): Hub { captureException: (exception: unknown, hint?: EventHint) => { return getCurrentScope().captureException(exception, hint); }, - captureMessage: ( - message: string, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - ) => { + captureMessage: (message: string, level?: SeverityLevel, hint?: EventHint) => { return getCurrentScope().captureMessage(message, level, hint); }, captureEvent, diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index e55667992839..dfe19e63fdbe 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -1,6 +1,6 @@ import { getGlobalScope as _getGlobalScope, setGlobalScope } from '@sentry/core'; import { OpenTelemetryScope } from '@sentry/opentelemetry'; -import type { Breadcrumb, Client, Event, EventHint, Severity, SeverityLevel } from '@sentry/types'; +import type { Breadcrumb, Client, Event, EventHint, SeverityLevel } from '@sentry/types'; import { uuid4 } from '@sentry/utils'; import { getGlobalCarrier } from './globals'; @@ -140,12 +140,7 @@ export class Scope extends OpenTelemetryScope implements ScopeInterface { } /** Capture a message for this scope. */ - public captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - ): string { + public captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string { const eventId = hint && hint.event_id ? hint.event_id : uuid4(); const syntheticException = new Error(message); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 8d0a82ecbfe6..44630d871260 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -8,8 +8,6 @@ export type { EventHint, Exception, Session, - // eslint-disable-next-line deprecation/deprecation - Severity, SeverityLevel, Span, StackFrame, diff --git a/packages/types/src/breadcrumb.ts b/packages/types/src/breadcrumb.ts index 9f9b36bd6dcb..21a6a481fbec 100644 --- a/packages/types/src/breadcrumb.ts +++ b/packages/types/src/breadcrumb.ts @@ -1,10 +1,9 @@ -import type { Severity, SeverityLevel } from './severity'; +import type { SeverityLevel } from './severity'; /** JSDoc */ export interface Breadcrumb { type?: string; - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel; + level?: SeverityLevel; event_id?: string; category?: string; message?: string; diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 5db008b0ba37..6c4409185e2d 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -14,7 +14,7 @@ import type { ParameterizedString } from './parameterize'; import type { Scope } from './scope'; import type { SdkMetadata } from './sdkmetadata'; import type { Session, SessionAggregates } from './session'; -import type { Severity, SeverityLevel } from './severity'; +import type { SeverityLevel } from './severity'; import type { StartSpanOptions } from './startSpanOptions'; import type { Transaction } from './transaction'; import type { Transport, TransportMakeRequestResponse } from './transport'; @@ -48,13 +48,7 @@ export interface Client { * @param scope An optional scope containing event metadata. * @returns The event id */ - captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - scope?: Scope, - ): string | undefined; + captureMessage(message: string, level?: SeverityLevel, hint?: EventHint, scope?: Scope): string | undefined; /** * Captures a manually created event and sends it to Sentry. @@ -175,12 +169,7 @@ export interface Client { eventFromException(exception: any, hint?: EventHint): PromiseLike; /** Creates an {@link Event} from primitive inputs to `captureMessage`. */ - eventFromMessage( - message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - ): PromiseLike; + eventFromMessage(message: ParameterizedString, level?: SeverityLevel, hint?: EventHint): PromiseLike; /** Submits the event to Sentry */ sendEvent(event: Event, hint?: EventHint): void; diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index 50322f18fbc6..cfddd6bef471 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -10,7 +10,7 @@ import type { Primitive } from './misc'; import type { Request } from './request'; import type { CaptureContext } from './scope'; import type { SdkInfo } from './sdkinfo'; -import type { Severity, SeverityLevel } from './severity'; +import type { SeverityLevel } from './severity'; import type { Span, SpanJSON } from './span'; import type { Thread } from './thread'; import type { TransactionSource } from './transaction'; @@ -26,8 +26,7 @@ export interface Event { }; timestamp?: number; start_timestamp?: number; - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel; + level?: SeverityLevel; platform?: string; logger?: string; server_name?: string; diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index 0656d55644f8..3f46b88da498 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -6,7 +6,7 @@ import type { Integration, IntegrationClass } from './integration'; import type { Primitive } from './misc'; import type { Scope } from './scope'; import type { Session } from './session'; -import type { Severity, SeverityLevel } from './severity'; +import type { SeverityLevel } from './severity'; import type { CustomSamplingContext, Transaction, TransactionContext } from './transaction'; import type { User } from './user'; @@ -116,12 +116,7 @@ export interface Hub { * * @deprecated Use `Sentry.captureMessage()` instead. */ - captureMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - level?: Severity | SeverityLevel, - hint?: EventHint, - ): string; + captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string; /** * Captures a manually created event and sends it to Sentry. diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5970383febc3..0504803c49a1 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -87,8 +87,7 @@ export type { SerializedSession, } from './session'; -// eslint-disable-next-line deprecation/deprecation -export type { Severity, SeverityLevel } from './severity'; +export type { SeverityLevel } from './severity'; export type { Span, SpanContext, diff --git a/packages/types/src/scope.ts b/packages/types/src/scope.ts index fd51eae8e5c4..2a6d01f7caa0 100644 --- a/packages/types/src/scope.ts +++ b/packages/types/src/scope.ts @@ -7,7 +7,7 @@ import type { EventProcessor } from './eventprocessor'; import type { Extra, Extras } from './extra'; import type { Primitive } from './misc'; import type { RequestSession, Session } from './session'; -import type { Severity, SeverityLevel } from './severity'; +import type { SeverityLevel } from './severity'; import type { Span } from './span'; import type { PropagationContext } from './tracing'; import type { Transaction } from './transaction'; @@ -19,8 +19,7 @@ export type CaptureContext = Scope | Partial | ((scope: Scope) => /** JSDocs */ export interface ScopeContext { user: User; - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel; + level: SeverityLevel; extra: Extras; contexts: Contexts; tags: { [key: string]: Primitive }; @@ -119,10 +118,7 @@ export interface Scope { * Sets the level on the scope for future events. * @param level string {@link SeverityLevel} */ - setLevel( - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel, - ): this; + setLevel(level: SeverityLevel): this; /** * Sets the transaction name on the scope for future events. diff --git a/packages/types/src/severity.ts b/packages/types/src/severity.ts index de13f2a02b99..78d1a6f5dd73 100644 --- a/packages/types/src/severity.ts +++ b/packages/types/src/severity.ts @@ -1,22 +1,3 @@ -/** - * @deprecated Please use a `SeverityLevel` string instead of the `Severity` enum. Acceptable values are 'fatal', - * 'error', 'warning', 'log', 'info', and 'debug'. - */ -export enum Severity { - /** JSDoc */ - Fatal = 'fatal', - /** JSDoc */ - Error = 'error', - /** JSDoc */ - Warning = 'warning', - /** JSDoc */ - Log = 'log', - /** JSDoc */ - Info = 'info', - /** JSDoc */ - Debug = 'debug', -} - // Note: If this is ever changed, the `validSeverityLevels` array in `@sentry/utils` needs to be changed, also. (See // note there for why we can't derive one from the other.) export type SeverityLevel = 'fatal' | 'error' | 'warning' | 'log' | 'info' | 'debug'; diff --git a/packages/utils/src/eventbuilder.ts b/packages/utils/src/eventbuilder.ts index 74b5aff6538e..5391723dfe57 100644 --- a/packages/utils/src/eventbuilder.ts +++ b/packages/utils/src/eventbuilder.ts @@ -7,7 +7,6 @@ import type { Hub, Mechanism, ParameterizedString, - Severity, SeverityLevel, StackFrame, StackParser, @@ -133,8 +132,7 @@ export function eventFromUnknownInput( export function eventFromMessage( stackParser: StackParser, message: ParameterizedString, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', + level: SeverityLevel = 'info', hint?: EventHint, attachStacktrace?: boolean, ): Event { diff --git a/packages/utils/src/severity.ts b/packages/utils/src/severity.ts index 6d631b590a88..983ec1ae08ae 100644 --- a/packages/utils/src/severity.ts +++ b/packages/utils/src/severity.ts @@ -1,5 +1,4 @@ -/* eslint-disable deprecation/deprecation */ -import type { Severity, SeverityLevel } from '@sentry/types'; +import type { SeverityLevel } from '@sentry/types'; // Note: Ideally the `SeverityLevel` type would be derived from `validSeverityLevels`, but that would mean either // @@ -13,18 +12,6 @@ import type { Severity, SeverityLevel } from '@sentry/types'; export const validSeverityLevels = ['fatal', 'error', 'warning', 'log', 'info', 'debug']; -/** - * Converts a string-based level into a member of the deprecated {@link Severity} enum. - * - * @deprecated `severityFromString` is deprecated. Please use `severityLevelFromString` instead. - * - * @param level String representation of Severity - * @returns Severity - */ -export function severityFromString(level: Severity | SeverityLevel | string): Severity { - return severityLevelFromString(level) as Severity; -} - /** * Converts a string-based level into a `SeverityLevel`, normalizing it along the way. * diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 8288c8ca5374..98910cbd754a 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -8,8 +8,6 @@ export type { EventHint, Exception, Session, - // eslint-disable-next-line deprecation/deprecation - Severity, SeverityLevel, Span, StackFrame, From 9307308e4b3fed18645889838983083315697b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Wed, 7 Feb 2024 17:15:01 +0100 Subject: [PATCH 014/173] ref(integrations): Remove offline integration (#9456) Co-authored-by: Luca Forstner --- MIGRATION.md | 5 + .../rollup-utils/plugins/bundlePlugins.mjs | 6 - packages/integrations/package.json | 3 +- .../integrations/rollup.bundle.config.mjs | 8 +- packages/integrations/src/index.ts | 1 - packages/integrations/src/offline.ts | 181 -------------- packages/integrations/test/offline.test.ts | 221 ------------------ yarn.lock | 19 -- 8 files changed, 7 insertions(+), 437 deletions(-) delete mode 100644 packages/integrations/src/offline.ts delete mode 100644 packages/integrations/test/offline.test.ts diff --git a/MIGRATION.md b/MIGRATION.md index 626d95bb88cd..f975b13c0f4d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -6,6 +6,11 @@ In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` ty enum. If you were using the `Severity` enum, you should replace it with the `SeverityLevel` type. See [below](#severity-severitylevel-and-severitylevels) for code snippet examples +## Removal of the `Offline` integration + +The `Offline` integration has been removed in favor of the offline transport wrapper: +http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching + # Deprecations in 7.x You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update diff --git a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs index b0a1c806ef98..e2a96c9697de 100644 --- a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs +++ b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs @@ -116,12 +116,6 @@ export function makeTerserPlugin() { reserved: [ // ...except for `_experiments`, which we want to remain usable from the outside '_experiments', - // ...except for some localforage internals, which if we replaced them would break the localforage package - // with the error "Error: Custom driver not compliant": https://github.com/getsentry/sentry-javascript/issues/5527. - // Reference for which fields are affected: https://localforage.github.io/localForage/ (ctrl-f for "_") - '_driver', - '_initStorage', - '_support', // We want to keep some replay fields unmangled to enable integration tests to access them '_replay', '_canvas', diff --git a/packages/integrations/package.json b/packages/integrations/package.json index b016aaf1d9e8..46f3485228cd 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -31,8 +31,7 @@ "dependencies": { "@sentry/core": "7.100.0", "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", - "localforage": "^1.8.1" + "@sentry/utils": "7.100.0" }, "devDependencies": { "@sentry/browser": "7.100.0", diff --git a/packages/integrations/rollup.bundle.config.mjs b/packages/integrations/rollup.bundle.config.mjs index 366585b8abe3..4ad9d857b496 100644 --- a/packages/integrations/rollup.bundle.config.mjs +++ b/packages/integrations/rollup.bundle.config.mjs @@ -1,6 +1,4 @@ -import commonjs from '@rollup/plugin-commonjs'; - -import { insertAt, makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; +import { makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; const builds = []; @@ -15,10 +13,6 @@ const baseBundleConfig = makeBaseBundleConfig({ outputFileBase: ({ name: entrypoint }) => `bundles/${entrypoint}${jsVersion === 'es5' ? '.es5' : ''}`, }); -// TODO We only need `commonjs` for localforage (used in the offline plugin). Once that's fixed, this can come out. -// CommonJS plugin docs: https://github.com/rollup/plugins/tree/master/packages/commonjs -baseBundleConfig.plugins = insertAt(baseBundleConfig.plugins, -2, commonjs()); - // this makes non-minified, minified, and minified-with-debug-logging versions of each bundle builds.push(...makeBundleConfigVariants(baseBundleConfig)); diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 445e10fa463e..a73b14257ed7 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -3,7 +3,6 @@ export { CaptureConsole, captureConsoleIntegration } from './captureconsole'; export { Debug, debugIntegration } from './debug'; export { Dedupe, dedupeIntegration } from './dedupe'; export { ExtraErrorData, extraErrorDataIntegration } from './extraerrordata'; -export { Offline } from './offline'; export { ReportingObserver, reportingObserverIntegration } from './reportingobserver'; export { RewriteFrames, rewriteFramesIntegration } from './rewriteframes'; export { SessionTiming, sessionTimingIntegration } from './sessiontiming'; diff --git a/packages/integrations/src/offline.ts b/packages/integrations/src/offline.ts deleted file mode 100644 index 67a9df76eca7..000000000000 --- a/packages/integrations/src/offline.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable deprecation/deprecation */ -import type { Event, EventProcessor, Hub, Integration } from '@sentry/types'; -import { GLOBAL_OBJ, logger, normalize, uuid4 } from '@sentry/utils'; -import localForage from 'localforage'; - -import { DEBUG_BUILD } from './debug-build'; - -const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; - -type LocalForage = { - setItem(key: string, value: T, callback?: (err: any, value: T) => void): Promise; - iterate( - iteratee: (value: T, key: string, iterationNumber: number) => U, - callback?: (err: any, result: U) => void, - ): Promise; - removeItem(key: string, callback?: (err: any) => void): Promise; - length(): Promise; -}; - -export type Item = { key: string; value: Event }; - -/** - * cache offline errors and send when connected - * @deprecated The offline integration has been deprecated in favor of the offline transport wrapper. - * - * http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching - */ -export class Offline implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Offline'; - - /** - * @inheritDoc - */ - public readonly name: string; - - /** - * the current hub instance - */ - public hub?: Hub; - - /** - * maximum number of events to store while offline - */ - public maxStoredEvents: number; - - /** - * event cache - */ - public offlineEventStore: LocalForage; - - /** - * @inheritDoc - */ - public constructor(options: { maxStoredEvents?: number } = {}) { - this.name = Offline.id; - - this.maxStoredEvents = options.maxStoredEvents || 30; // set a reasonable default - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - this.offlineEventStore = localForage.createInstance({ - name: 'sentry/offlineEventStore', - }); - } - - /** - * @inheritDoc - */ - public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - this.hub = getCurrentHub(); - - if ('addEventListener' in WINDOW) { - WINDOW.addEventListener('online', () => { - void this._sendEvents().catch(() => { - DEBUG_BUILD && logger.warn('could not send cached events'); - }); - }); - } - - const eventProcessor: EventProcessor = event => { - // eslint-disable-next-line deprecation/deprecation - if (this.hub && this.hub.getIntegration(Offline)) { - // cache if we are positively offline - if ('navigator' in WINDOW && 'onLine' in WINDOW.navigator && !WINDOW.navigator.onLine) { - DEBUG_BUILD && logger.log('Event dropped due to being a offline - caching instead'); - - void this._cacheEvent(event) - .then((_event: Event): Promise => this._enforceMaxEvents()) - .catch((_error): void => { - DEBUG_BUILD && logger.warn('could not cache event while offline'); - }); - - // return null on success or failure, because being offline will still result in an error - return null; - } - } - - return event; - }; - - eventProcessor.id = this.name; - addGlobalEventProcessor(eventProcessor); - - // if online now, send any events stored in a previous offline session - if ('navigator' in WINDOW && 'onLine' in WINDOW.navigator && WINDOW.navigator.onLine) { - void this._sendEvents().catch(() => { - DEBUG_BUILD && logger.warn('could not send cached events'); - }); - } - } - - /** - * cache an event to send later - * @param event an event - */ - private async _cacheEvent(event: Event): Promise { - return this.offlineEventStore.setItem(uuid4(), normalize(event)); - } - - /** - * purge excess events if necessary - */ - private async _enforceMaxEvents(): Promise { - const events: Array<{ event: Event; cacheKey: string }> = []; - - return this.offlineEventStore - .iterate((event: Event, cacheKey: string, _index: number): void => { - // aggregate events - events.push({ cacheKey, event }); - }) - .then( - (): Promise => - // this promise resolves when the iteration is finished - this._purgeEvents( - // purge all events past maxStoredEvents in reverse chronological order - events - .sort((a, b) => (b.event.timestamp || 0) - (a.event.timestamp || 0)) - .slice(this.maxStoredEvents < events.length ? this.maxStoredEvents : events.length) - .map(event => event.cacheKey), - ), - ) - .catch((_error): void => { - DEBUG_BUILD && logger.warn('could not enforce max events'); - }); - } - - /** - * purge event from cache - */ - private async _purgeEvent(cacheKey: string): Promise { - return this.offlineEventStore.removeItem(cacheKey); - } - - /** - * purge events from cache - */ - private async _purgeEvents(cacheKeys: string[]): Promise { - // trail with .then to ensure the return type as void and not void|void[] - return Promise.all(cacheKeys.map(cacheKey => this._purgeEvent(cacheKey))).then(); - } - - /** - * send all events - */ - private async _sendEvents(): Promise { - return this.offlineEventStore.iterate((event: Event, cacheKey: string, _index: number): void => { - if (this.hub) { - this.hub.captureEvent(event); - - void this._purgeEvent(cacheKey).catch((_error): void => { - DEBUG_BUILD && logger.warn('could not purge event from cache'); - }); - } else { - DEBUG_BUILD && logger.warn('no hub found - could not send cached event'); - } - }); - } -} diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts deleted file mode 100644 index 1dac96bc1e53..000000000000 --- a/packages/integrations/test/offline.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import { WINDOW } from '@sentry/browser'; -import type { Event, EventProcessor, Hub, Integration, IntegrationClass } from '@sentry/types'; - -import type { Item } from '../src/offline'; -import { Offline } from '../src/offline'; - -// mock localforage methods -jest.mock('localforage', () => ({ - createInstance(_options: { name: string }): any { - let items: Item[] = []; - - return { - async getItem(key: string): Promise { - return items.find(item => item.key === key); - }, - async iterate(callback: (event: Event, key: string, index: number) => void): Promise { - items.forEach((item, index) => { - callback(item.value, item.key, index); - }); - }, - async length(): Promise { - return items.length; - }, - async removeItem(key: string): Promise { - items = items.filter(item => item.key !== key); - }, - async setItem(key: string, value: Event): Promise { - items.push({ - key, - value, - }); - }, - }; - }, -})); - -let integration: Offline; - -// We need to mock the WINDOW object so we can modify 'navigator.online' which is readonly -jest.mock('@sentry/utils', () => { - const originalModule = jest.requireActual('@sentry/utils'); - - return { - ...originalModule, - get GLOBAL_OBJ() { - return { - addEventListener: (_windowEvent: any, callback: any) => { - eventListeners.push(callback); - }, - navigator: { - onLine: false, - }, - }; - }, - }; -}); - -describe('Offline', () => { - describe('when app is online', () => { - beforeEach(() => { - (WINDOW.navigator as any).onLine = true; - - initIntegration(); - }); - - it('does not store events in offline store', async () => { - setupOnce(); - processEvents(); - - expect(await integration.offlineEventStore.length()).toEqual(0); - }); - - describe('when there are already events in the cache from a previous offline session', () => { - beforeEach(async () => { - const event = { message: 'previous event' }; - - await integration.offlineEventStore.setItem('previous', event); - }); - - it('sends stored events', async () => { - expect(await integration.offlineEventStore.length()).toEqual(1); - - setupOnce(); - processEvents(); - - expect(await integration.offlineEventStore.length()).toEqual(0); - }); - }); - }); - - describe('when app is offline', () => { - beforeEach(() => { - (WINDOW.navigator as any).onLine = false; - }); - - it('stores events in offline store', async () => { - initIntegration(); - setupOnce(); - prepopulateEvents(1); - processEvents(); - - expect(await integration.offlineEventStore.length()).toEqual(1); - }); - - it('enforces a default of 30 maxStoredEvents', done => { - initIntegration(); - setupOnce(); - prepopulateEvents(50); - processEvents(); - - setImmediate(async () => { - // allow background promises to finish resolving - expect(await integration.offlineEventStore.length()).toEqual(30); - done(); - }); - }); - - it('does not purge events when below the maxStoredEvents threshold', done => { - initIntegration(); - setupOnce(); - prepopulateEvents(5); - processEvents(); - - setImmediate(async () => { - // allow background promises to finish resolving - expect(await integration.offlineEventStore.length()).toEqual(5); - done(); - }); - }); - - describe('when maxStoredEvents is supplied', () => { - it('respects the configuration', done => { - initIntegration({ maxStoredEvents: 5 }); - setupOnce(); - prepopulateEvents(50); - processEvents(); - - setImmediate(async () => { - // allow background promises to finish resolving - expect(await integration.offlineEventStore.length()).toEqual(5); - done(); - }); - }); - }); - - describe('when connectivity is restored', () => { - it('sends stored events', async () => { - initIntegration(); - setupOnce(); - prepopulateEvents(1); - processEvents(); - processEventListeners(); - - expect(await integration.offlineEventStore.length()).toEqual(0); - }); - }); - }); -}); - -let eventListeners: any[]; -let eventProcessors: EventProcessor[]; -let events: Event[]; - -/** JSDoc */ -function addGlobalEventProcessor(callback: EventProcessor): void { - eventProcessors.push(callback); -} - -/** JSDoc */ -function getCurrentHub(): Hub { - return { - captureEvent(_event: Event): string { - return 'an-event-id'; - }, - getIntegration(_integration: IntegrationClass): T | null { - // pretend integration is enabled - return {} as T; - }, - } as Hub; -} - -/** JSDoc */ -function initIntegration(options: { maxStoredEvents?: number } = {}): void { - eventListeners = []; - eventProcessors = []; - events = []; - - integration = new Offline(options); -} - -/** JSDoc */ -function prepopulateEvents(count: number = 1): void { - for (let i = 0; i < count; i++) { - events.push({ - message: 'There was an error!', - timestamp: Date.now(), - }); - } -} - -/** JSDoc */ -function processEventListeners(): void { - eventListeners.forEach(listener => { - listener(); - }); -} - -/** JSDoc */ -function processEvents(): void { - eventProcessors.forEach(processor => { - events.forEach(event => { - processor(event, {}) as Event | null; - }); - }); -} - -/** JSDoc */ -function setupOnce(): void { - integration.setupOnce(addGlobalEventProcessor, getCurrentHub); -} diff --git a/yarn.lock b/yarn.lock index ddf84ad0e320..9f5971f8838d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18176,11 +18176,6 @@ image-size@~0.5.0: resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= - immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -20653,13 +20648,6 @@ license-webpack-plugin@2.3.20: "@types/webpack-sources" "^0.1.5" webpack-sources "^1.2.0" -lie@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= - dependencies: - immediate "~3.0.5" - lilconfig@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" @@ -20805,13 +20793,6 @@ local-pkg@^0.4.2: resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963" integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g== -localforage@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" - integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== - dependencies: - lie "3.1.1" - locate-character@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-3.0.0.tgz#0305c5b8744f61028ef5d01f444009e00779f974" From ca6010bbd65f65b287802e611f702d000e5f4f56 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 7 Feb 2024 14:23:32 -0500 Subject: [PATCH 015/173] feat(v8/node): Remove LocalVariables class integration (#10558) Works toward https://github.com/getsentry/sentry-javascript/issues/8844 in a more incremental way. --- .../create-next-app/sentry.server.config.ts | 7 +------ .../LocalVariables/local-variables-memory-test.js | 2 +- packages/bun/src/sdk.ts | 1 - packages/node-experimental/src/integrations/index.ts | 12 +----------- packages/node/src/integrations/index.ts | 1 - .../node/src/integrations/local-variables/index.ts | 12 +----------- 6 files changed, 4 insertions(+), 31 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts index 4f8004c021b3..3750d0f5c5fd 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/sentry.server.config.ts @@ -15,12 +15,7 @@ Sentry.init({ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1.0, - // ... - // Note: if you want to override the automatic release value, do not set a - // `release` value here - use the environment variable `SENTRY_RELEASE`, so - // that it will also get attached to your source maps - - integrations: [new Sentry.Integrations.LocalVariables()], + integrations: [Sentry.localVariablesIntegration()], }); Sentry.addEventProcessor(event => { diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-memory-test.js b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-memory-test.js index 7aa9feb9ae2a..58d838ae08cf 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-memory-test.js +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-memory-test.js @@ -7,7 +7,7 @@ Sentry.init({ includeLocalVariables: true, transport: loggingTransport, // Stop the rate limiting from kicking in - integrations: [new Sentry.Integrations.LocalVariables({ maxExceptionsPerSecond: 10000000 })], + integrations: [Sentry.localVariablesIntegration({ maxExceptionsPerSecond: 10000000 })], }); class Some { diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index f8dbcb99c0df..2054c833fd01 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -37,7 +37,6 @@ export const defaultIntegrations = [ // new NodeIntegrations.OnUnhandledRejection(), // Event Info contextLinesIntegration(), - // new NodeIntegrations.LocalVariables(), # does't work with Bun nodeContextIntegration(), modulesIntegration(), // Bun Specific diff --git a/packages/node-experimental/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts index a37c0f3b615e..ac9c0775bead 100644 --- a/packages/node-experimental/src/integrations/index.ts +++ b/packages/node-experimental/src/integrations/index.ts @@ -8,20 +8,10 @@ const { ContextLines, Context, RequestData, - LocalVariables, // eslint-disable-next-line deprecation/deprecation } = NodeIntegrations; -export { - Console, - OnUncaughtException, - OnUnhandledRejection, - Modules, - ContextLines, - Context, - RequestData, - LocalVariables, -}; +export { Console, OnUncaughtException, OnUnhandledRejection, Modules, ContextLines, Context, RequestData }; /* eslint-disable deprecation/deprecation */ export { Express } from './express'; diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts index 12f57116c533..72f49efbc95d 100644 --- a/packages/node/src/integrations/index.ts +++ b/packages/node/src/integrations/index.ts @@ -7,7 +7,6 @@ export { Modules } from './modules'; export { ContextLines } from './contextlines'; export { Context } from './context'; export { RequestData } from '@sentry/core'; -export { LocalVariables } from './local-variables'; export { Undici } from './undici'; export { Spotlight } from './spotlight'; export { Anr } from './anr'; diff --git a/packages/node/src/integrations/local-variables/index.ts b/packages/node/src/integrations/local-variables/index.ts index 79cf3b0a7d67..60649b03118f 100644 --- a/packages/node/src/integrations/local-variables/index.ts +++ b/packages/node/src/integrations/local-variables/index.ts @@ -1,13 +1,3 @@ -import { LocalVariablesSync, localVariablesSyncIntegration } from './local-variables-sync'; - -/** - * Adds local variables to exception frames. - * - * @deprecated Use `localVariablesIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const LocalVariables = LocalVariablesSync; -// eslint-disable-next-line deprecation/deprecation -export type LocalVariables = LocalVariablesSync; +import { localVariablesSyncIntegration } from './local-variables-sync'; export const localVariablesIntegration = localVariablesSyncIntegration; From a985487e8efea129eb903a0605bd05defba7633b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 8 Feb 2024 09:09:22 +0100 Subject: [PATCH 016/173] feat(core): Streamline custom hub creation for node-experimental (#10555) This is an intermediate step to streamline what we need to customize in OTEL. This should make it easier to slowly remove customizations there. The main change is that you can set a `createHub` method on the main carrier, which will be used if set. This way, all the code we have to duplicate in node-experimental right now just to create the correct `OpenTelemetryHub` can mostly go away. We will remove this again during then v8 cycle. --- packages/core/src/hub.ts | 114 ++++++++----- packages/opentelemetry/src/contextManager.ts | 3 +- packages/opentelemetry/src/custom/hub.ts | 150 +++--------------- packages/opentelemetry/src/index.ts | 6 +- packages/opentelemetry/src/propagator.ts | 3 +- packages/opentelemetry/src/spanExporter.ts | 4 +- packages/opentelemetry/src/spanProcessor.ts | 4 +- packages/opentelemetry/src/trace.ts | 3 +- .../test/asyncContextStrategy.test.ts | 16 +- .../opentelemetry/test/custom/hub.test.ts | 9 +- .../test/custom/hubextensions.test.ts | 3 +- .../test/custom/transaction.test.ts | 8 +- .../opentelemetry/test/helpers/initOtel.ts | 3 +- .../test/integration/breadcrumbs.test.ts | 6 +- .../test/integration/otelTimedEvents.test.ts | 2 +- .../test/integration/scope.test.ts | 6 +- .../test/integration/transactions.test.ts | 3 +- packages/opentelemetry/test/trace.test.ts | 3 +- 18 files changed, 148 insertions(+), 198 deletions(-) diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 2a343b320f1b..79359657cc8b 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -19,15 +19,7 @@ import type { TransactionContext, User, } from '@sentry/types'; -import { - GLOBAL_OBJ, - consoleSandbox, - dateTimestampInSeconds, - getGlobalSingleton, - isThenable, - logger, - uuid4, -} from '@sentry/utils'; +import { GLOBAL_OBJ, consoleSandbox, dateTimestampInSeconds, isThenable, logger, uuid4 } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from './constants'; import { DEBUG_BUILD } from './debug-build'; @@ -86,21 +78,41 @@ export interface Layer { * @hidden */ export interface Carrier { - __SENTRY__?: { - hub?: Hub; - acs?: AsyncContextStrategy; - /** - * Extra Hub properties injected by various SDKs - */ - integrations?: Integration[]; - extensions?: { - /** Extension methods for the hub, which are bound to the current Hub instance */ - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: Function; - }; + __SENTRY__?: SentryCarrier; +} + +type CreateHub = (...options: ConstructorParameters) => Hub; + +interface SentryCarrier { + hub?: Hub; + createHub?: CreateHub; + acs?: AsyncContextStrategy; + /** + * Extra Hub properties injected by various SDKs + */ + integrations?: Integration[]; + extensions?: { + /** Extension methods for the hub, which are bound to the current Hub instance */ + // eslint-disable-next-line @typescript-eslint/ban-types + [key: string]: Function; }; } +/** + * Create a hub. If a custom `createHub` is registered on the main carrier, use that instead. + * This only exists to make POTEL migration easier. + */ +function createHub(...options: ConstructorParameters): Hub { + const carrier = getMainCarrier(); + const sentry = getSentryCarrier(carrier); + + if (sentry.createHub) { + return sentry.createHub(...options); + } + + return new Hub(...options); +} + /** * @inheritDoc */ @@ -663,8 +675,8 @@ Sentry.init({...}); // eslint-disable-next-line @typescript-eslint/no-explicit-any private _callExtensionMethod(method: string, ...args: any[]): T { const carrier = getMainCarrier(); - const sentry = carrier.__SENTRY__; - if (sentry && sentry.extensions && typeof sentry.extensions[method] === 'function') { + const sentry = getSentryCarrier(carrier); + if (sentry.extensions && typeof sentry.extensions[method] === 'function') { return sentry.extensions[method].apply(this, args); } DEBUG_BUILD && logger.warn(`Extension method ${method} couldn't be found, doing nothing.`); @@ -679,10 +691,8 @@ Sentry.init({...}); * at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there. **/ export function getMainCarrier(): Carrier { - GLOBAL_OBJ.__SENTRY__ = GLOBAL_OBJ.__SENTRY__ || { - extensions: {}, - hub: undefined, - }; + // This ensures a Sentry carrier exists + getSentryCarrier(GLOBAL_OBJ); return GLOBAL_OBJ; } @@ -711,10 +721,11 @@ export function makeMain(hub: Hub): Hub { */ export function getCurrentHub(): Hub { // Get main carrier (global for every environment) - const registry = getMainCarrier(); + const carrier = getMainCarrier(); + const sentry = getSentryCarrier(carrier); - if (registry.__SENTRY__ && registry.__SENTRY__.acs) { - const hub = registry.__SENTRY__.acs.getCurrentHub(); + if (sentry.acs) { + const hub = sentry.acs.getCurrentHub(); if (hub) { return hub; @@ -722,7 +733,7 @@ export function getCurrentHub(): Hub { } // Return hub that lives on a global object - return getGlobalHub(registry); + return getGlobalHub(); } /** @@ -735,7 +746,9 @@ export function getIsolationScope(): Scope { return getCurrentHub().getIsolationScope(); } -function getGlobalHub(registry: Carrier = getMainCarrier()): Hub { +function getGlobalHub(): Hub { + const registry = getMainCarrier(); + // If there's no hub, or its an old API, assign a new one if ( @@ -743,7 +756,7 @@ function getGlobalHub(registry: Carrier = getMainCarrier()): Hub { // eslint-disable-next-line deprecation/deprecation getHubFromCarrier(registry).isOlderThan(API_VERSION) ) { - setHubOnCarrier(registry, new Hub()); + setHubOnCarrier(registry, createHub()); } // Return hub that lives on a global object @@ -768,7 +781,7 @@ export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub( const scope = parent.getScope(); // eslint-disable-next-line deprecation/deprecation const isolationScope = parent.getIsolationScope(); - setHubOnCarrier(carrier, new Hub(client, scope.clone(), isolationScope.clone())); + setHubOnCarrier(carrier, createHub(client, scope.clone(), isolationScope.clone())); } } @@ -780,8 +793,8 @@ export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub( export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void { // Get main carrier (global for every environment) const registry = getMainCarrier(); - registry.__SENTRY__ = registry.__SENTRY__ || {}; - registry.__SENTRY__.acs = strategy; + const sentry = getSentryCarrier(registry); + sentry.acs = strategy; } /** @@ -793,9 +806,10 @@ export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefin */ export function runWithAsyncContext(callback: () => T, options: RunWithAsyncContextOptions = {}): T { const registry = getMainCarrier(); + const sentry = getSentryCarrier(registry); - if (registry.__SENTRY__ && registry.__SENTRY__.acs) { - return registry.__SENTRY__.acs.runWithAsyncContext(callback, options); + if (sentry.acs) { + return sentry.acs.runWithAsyncContext(callback, options); } // if there was no strategy, fallback to just calling the callback @@ -807,7 +821,7 @@ export function runWithAsyncContext(callback: () => T, options: RunWithAsyncC * @param carrier object */ function hasHubOnCarrier(carrier: Carrier): boolean { - return !!(carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub); + return !!getSentryCarrier(carrier).hub; } /** @@ -817,7 +831,12 @@ function hasHubOnCarrier(carrier: Carrier): boolean { * @hidden */ export function getHubFromCarrier(carrier: Carrier): Hub { - return getGlobalSingleton('hub', () => new Hub(), carrier); + const sentry = getSentryCarrier(carrier); + if (!sentry.hub) { + sentry.hub = createHub(); + } + + return sentry.hub; } /** @@ -828,7 +847,18 @@ export function getHubFromCarrier(carrier: Carrier): Hub { */ export function setHubOnCarrier(carrier: Carrier, hub: Hub): boolean { if (!carrier) return false; - const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {}); - __SENTRY__.hub = hub; + const sentry = getSentryCarrier(carrier); + sentry.hub = hub; return true; } + +/** Will either get the existing sentry carrier, or create a new one. */ +function getSentryCarrier(carrier: Carrier): SentryCarrier { + if (!carrier.__SENTRY__) { + carrier.__SENTRY__ = { + extensions: {}, + hub: undefined, + }; + } + return carrier.__SENTRY__; +} diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index 3b3a6a280928..d904a4847a06 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -1,8 +1,8 @@ import type { Context, ContextManager } from '@opentelemetry/api'; import type { Carrier, Hub } from '@sentry/core'; +import { getCurrentHub, getHubFromCarrier } from '@sentry/core'; import { ensureHubOnCarrier } from '@sentry/core'; -import { getCurrentHub, getHubFromCarrier } from './custom/hub'; import { setHubOnContext } from './utils/contextData'; function createNewHub(parent: Hub | undefined): Hub { @@ -46,6 +46,7 @@ export function wrapContextManagerClass, ...args: A ): ReturnType { + // eslint-disable-next-line deprecation/deprecation const existingHub = getCurrentHub(); const newHub = createNewHub(existingHub); diff --git a/packages/opentelemetry/src/custom/hub.ts b/packages/opentelemetry/src/custom/hub.ts index 2e0d120486dd..6f1554eda7d2 100644 --- a/packages/opentelemetry/src/custom/hub.ts +++ b/packages/opentelemetry/src/custom/hub.ts @@ -1,7 +1,7 @@ import type { Carrier, Scope } from '@sentry/core'; +import { getMainCarrier } from '@sentry/core'; import { Hub } from '@sentry/core'; import type { Client } from '@sentry/types'; -import { GLOBAL_OBJ, getGlobalSingleton } from '@sentry/utils'; import { OpenTelemetryScope } from './scope'; @@ -15,144 +15,34 @@ export class OpenTelemetryHub extends Hub { } } -/** Custom getClient method that uses the custom hub. */ -export function getClient(): C | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getClient(); -} - -/** - * ******************************************************************************* - * Everything below here is a copy of the stuff from core's hub.ts, - * only that we make sure to create our custom NodeExperimentalScope instead of the default Scope. - * This is necessary to get the correct breadcrumbs behavior. - * - * Basically, this overwrites all places that do `new Scope()` with `new NodeExperimentalScope()`. - * Which in turn means overwriting all places that do `new Hub()` and make sure to pass in a NodeExperimentalScope instead. - * ******************************************************************************* - */ - -/** - * API compatibility version of this hub. - * - * WARNING: This number should only be increased when the global interface - * changes and new methods are introduced. - * - * @hidden - */ -const API_VERSION = 4; - -/** - * Returns the default hub instance. - * - * If a hub is already registered in the global carrier but this module - * contains a more recent version, it replaces the registered version. - * Otherwise, the currently registered hub will be returned. - */ -export function getCurrentHub(): Hub { - // Get main carrier (global for every environment) - const registry = getMainCarrier(); - - if (registry.__SENTRY__ && registry.__SENTRY__.acs) { - const hub = registry.__SENTRY__.acs.getCurrentHub(); - - if (hub) { - return hub; - } - } - - // Return hub that lives on a global object - return getGlobalHub(registry); -} - /** * Ensure the global hub is an OpenTelemetryHub. */ export function setupGlobalHub(): void { - const globalRegistry = getMainCarrier(); + const carrier = getMainCarrier(); + const sentry = getSentryCarrier(carrier); - if (getGlobalHub(globalRegistry) instanceof OpenTelemetryHub) { - return; - } - - // If the current global hub is not correct, ensure we overwrite it - setHubOnCarrier(globalRegistry, new OpenTelemetryHub()); -} + // We register a custom hub creator + sentry.createHub = (...options: ConstructorParameters) => { + return new OpenTelemetryHub(...options); + }; -/** - * This will create a new {@link Hub} and add to the passed object on - * __SENTRY__.hub. - * @param carrier object - * @hidden - */ -export function getHubFromCarrier(carrier: Carrier): Hub { - return getGlobalSingleton('hub', () => new OpenTelemetryHub(), carrier); -} + const hub = sentry.hub; -/** - * @private Private API with no semver guarantees! - * - * If the carrier does not contain a hub, a new hub is created with the global hub client and scope. - */ -export function ensureHubOnCarrier(carrier: Carrier, parent: Hub = getGlobalHub()): void { - // If there's no hub on current domain, or it's an old API, assign a new one - if ( - !hasHubOnCarrier(carrier) || - // eslint-disable-next-line deprecation/deprecation - getHubFromCarrier(carrier).isOlderThan(API_VERSION) - ) { - // eslint-disable-next-line deprecation/deprecation - const globalHubTopStack = parent.getStackTop(); - setHubOnCarrier(carrier, new OpenTelemetryHub(globalHubTopStack.client, globalHubTopStack.scope.clone())); + if (hub && hub instanceof OpenTelemetryHub) { + return; } + // If the current global hub is not correct, ensure we overwrite it + sentry.hub = new OpenTelemetryHub(); } -function getGlobalHub(registry: Carrier = getMainCarrier()): Hub { - // If there's no hub, or its an old API, assign a new one - if ( - !hasHubOnCarrier(registry) || - // eslint-disable-next-line deprecation/deprecation - getHubFromCarrier(registry).isOlderThan(API_VERSION) - ) { - setHubOnCarrier(registry, new OpenTelemetryHub()); +/** Will either get the existing sentry carrier, or create a new one. */ +function getSentryCarrier(carrier: Carrier): Carrier['__SENTRY__'] & object { + if (!carrier.__SENTRY__) { + carrier.__SENTRY__ = { + extensions: {}, + hub: undefined, + }; } - - // Return hub that lives on a global object - return getHubFromCarrier(registry); -} - -/** - * This will tell whether a carrier has a hub on it or not - * @param carrier object - */ -function hasHubOnCarrier(carrier: Carrier): boolean { - return !!(carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub); -} - -/** - * Returns the global shim registry. - * - * FIXME: This function is problematic, because despite always returning a valid Carrier, - * it has an optional `__SENTRY__` property, which then in turn requires us to always perform an unnecessary check - * at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there. - **/ -function getMainCarrier(): Carrier { - GLOBAL_OBJ.__SENTRY__ = GLOBAL_OBJ.__SENTRY__ || { - extensions: {}, - hub: undefined, - }; - return GLOBAL_OBJ; -} - -/** - * This will set passed {@link Hub} on the passed object's __SENTRY__.hub attribute - * @param carrier object - * @param hub Hub - * @returns A boolean indicating success or failure - */ -function setHubOnCarrier(carrier: Carrier, hub: Hub): boolean { - if (!carrier) return false; - const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {}); - __SENTRY__.hub = hub; - return true; + return carrier.__SENTRY__; } diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index 534c99c95fb4..602dcdda6234 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -31,7 +31,7 @@ export { isSentryRequestSpan } from './utils/isSentryRequest'; export { getActiveSpan, getRootSpan } from './utils/getActiveSpan'; export { startSpan, startSpanManual, startInactiveSpan } from './trace'; -export { getCurrentHub, setupGlobalHub, getClient } from './custom/hub'; +export { setupGlobalHub } from './custom/hub'; export { OpenTelemetryScope } from './custom/scope'; export { addTracingExtensions } from './custom/hubextensions'; export { setupEventContextTrace } from './setupEventContextTrace'; @@ -42,6 +42,10 @@ export { SentryPropagator } from './propagator'; export { SentrySpanProcessor } from './spanProcessor'; export { SentrySampler } from './sampler'; +// Legacy +// eslint-disable-next-line deprecation/deprecation +export { getCurrentHub, getClient } from '@sentry/core'; + /** * The following internal utils are not considered public API and are subject to change. * @hidden diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 0f53876f7240..5ab31e3f4804 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -1,12 +1,11 @@ import type { Baggage, Context, SpanContext, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; import { TraceFlags, propagation, trace } from '@opentelemetry/api'; import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; -import { getDynamicSamplingContextFromClient } from '@sentry/core'; +import { getClient, getDynamicSamplingContextFromClient } from '@sentry/core'; import type { DynamicSamplingContext, PropagationContext } from '@sentry/types'; import { SENTRY_BAGGAGE_KEY_PREFIX, generateSentryTraceHeader, propagationContextFromHeaders } from '@sentry/utils'; import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from './constants'; -import { getClient } from './custom/hub'; import { getPropagationContextFromContext, setPropagationContextOnContext } from './utils/contextData'; import { getSpanScope } from './utils/spanData'; diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index f4bf4bc3aec4..97a7a77c26dc 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -3,11 +3,10 @@ import type { ExportResult } from '@opentelemetry/core'; import { ExportResultCode } from '@opentelemetry/core'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentHub, getCurrentScope } from '@sentry/core'; import type { Scope, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { getCurrentHub } from './custom/hub'; import type { OpenTelemetryTransaction } from './custom/transaction'; import { startTransaction } from './custom/transaction'; import { DEBUG_BUILD } from './debug-build'; @@ -151,6 +150,7 @@ function parseSpan(span: ReadableSpan): { op?: string; origin?: SpanOrigin; sour function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransaction { const scope = getSpanScope(span); + // eslint-disable-next-line deprecation/deprecation const hub = getSpanHub(span) || getCurrentHub(); const spanContext = span.spanContext(); const spanId = spanContext.spanId; diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index ea2a2d0c0e75..7cb452a03d98 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -2,9 +2,9 @@ import type { Context } from '@opentelemetry/api'; import { ROOT_CONTEXT, trace } from '@opentelemetry/api'; import type { Span, SpanProcessor as SpanProcessorInterface } from '@opentelemetry/sdk-trace-base'; import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { getCurrentHub } from '@sentry/core'; import { logger } from '@sentry/utils'; -import { getCurrentHub } from './custom/hub'; import { OpenTelemetryScope } from './custom/scope'; import { DEBUG_BUILD } from './debug-build'; import { SentrySpanExporter } from './spanExporter'; @@ -26,6 +26,7 @@ function onSpanStart(span: Span, parentContext: Context, _ScopeClass: typeof Ope // We do this instead of just falling back to `getCurrentHub` to avoid attaching the wrong hub let actualHub = hub; if (parentContext === ROOT_CONTEXT) { + // eslint-disable-next-line deprecation/deprecation actualHub = getCurrentHub(); } @@ -45,6 +46,7 @@ function onSpanStart(span: Span, parentContext: Context, _ScopeClass: typeof Ope function onSpanEnd(span: Span): void { // Capture exceptions as events + // eslint-disable-next-line deprecation/deprecation const hub = getSpanHub(span) || getCurrentHub(); span.events.forEach(event => { maybeCaptureExceptionForTimedEvent(hub, event, span); diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 1047d77f39fd..ed53b02a7c17 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -2,10 +2,9 @@ import type { Span, Tracer } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; import { SpanStatusCode, trace } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { SDK_VERSION, handleCallbackErrors } from '@sentry/core'; +import { SDK_VERSION, getClient, handleCallbackErrors } from '@sentry/core'; import type { Client } from '@sentry/types'; -import { getClient } from './custom/hub'; import { InternalSentrySemanticAttributes } from './semanticAttributes'; import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; import { setSpanMetadata } from './utils/spanData'; diff --git a/packages/opentelemetry/test/asyncContextStrategy.test.ts b/packages/opentelemetry/test/asyncContextStrategy.test.ts index 92856234bf80..c71e23dfecac 100644 --- a/packages/opentelemetry/test/asyncContextStrategy.test.ts +++ b/packages/opentelemetry/test/asyncContextStrategy.test.ts @@ -1,9 +1,9 @@ import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Hub } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; import { runWithAsyncContext, setAsyncContextStrategy } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../src/asyncContextStrategy'; -import { getCurrentHub } from '../src/custom/hub'; import { TestClient, getDefaultTestClientOptions } from './helpers/TestClient'; import { setupOtel } from './helpers/initOtel'; import { cleanupOtel } from './helpers/mockSdkInit'; @@ -28,11 +28,13 @@ describe('asyncContextStrategy', () => { }); test('hub scope inheritance', () => { + // eslint-disable-next-line deprecation/deprecation const globalHub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation globalHub.setExtra('a', 'b'); runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); expect(hub1).toEqual(globalHub); @@ -41,6 +43,7 @@ describe('asyncContextStrategy', () => { expect(hub1).not.toEqual(globalHub); runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); @@ -63,10 +66,12 @@ describe('asyncContextStrategy', () => { }); } + // eslint-disable-next-line deprecation/deprecation const globalHub = getCurrentHub(); await addRandomExtra(globalHub, 'a'); await runWithAsyncContext(async () => { + // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); expect(hub1).toEqual(globalHub); @@ -74,6 +79,7 @@ describe('asyncContextStrategy', () => { expect(hub1).not.toEqual(globalHub); await runWithAsyncContext(async () => { + // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); @@ -85,16 +91,20 @@ describe('asyncContextStrategy', () => { }); test('context single instance', () => { + // eslint-disable-next-line deprecation/deprecation const globalHub = getCurrentHub(); runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation expect(globalHub).not.toBe(getCurrentHub()); }); }); test('context within a context not reused', () => { runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub1).not.toBe(hub2); }); @@ -103,9 +113,11 @@ describe('asyncContextStrategy', () => { test('context within a context reused when requested', () => { runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); runWithAsyncContext( () => { + // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub1).toBe(hub2); }, @@ -119,6 +131,7 @@ describe('asyncContextStrategy', () => { let d2done = false; runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation hub.getStack().push({ client: 'process' } as any); @@ -135,6 +148,7 @@ describe('asyncContextStrategy', () => { }); runWithAsyncContext(() => { + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation hub.getStack().push({ client: 'local' } as any); diff --git a/packages/opentelemetry/test/custom/hub.test.ts b/packages/opentelemetry/test/custom/hub.test.ts index fac2405a4a90..f3b62e2d1507 100644 --- a/packages/opentelemetry/test/custom/hub.test.ts +++ b/packages/opentelemetry/test/custom/hub.test.ts @@ -1,12 +1,19 @@ -import { OpenTelemetryHub, getCurrentHub } from '../../src/custom/hub'; +import { getCurrentHub } from '@sentry/core'; +import { OpenTelemetryHub, setupGlobalHub } from '../../src/custom/hub'; import { OpenTelemetryScope } from '../../src/custom/scope'; describe('OpenTelemetryHub', () => { + beforeEach(() => { + setupGlobalHub(); + }); + it('getCurrentHub() returns the correct hub', () => { + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); expect(hub).toBeDefined(); expect(hub).toBeInstanceOf(OpenTelemetryHub); + // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub2).toBe(hub); diff --git a/packages/opentelemetry/test/custom/hubextensions.test.ts b/packages/opentelemetry/test/custom/hubextensions.test.ts index dea308d99f3a..9d8f72cafb1f 100644 --- a/packages/opentelemetry/test/custom/hubextensions.test.ts +++ b/packages/opentelemetry/test/custom/hubextensions.test.ts @@ -1,5 +1,4 @@ -import { setCurrentClient } from '@sentry/core'; -import { getCurrentHub } from '../../src/custom/hub'; +import { getCurrentHub, setCurrentClient } from '@sentry/core'; import { addTracingExtensions } from '../../src/custom/hubextensions'; import { TestClient, getDefaultTestClientOptions } from '../helpers/TestClient'; diff --git a/packages/opentelemetry/test/custom/transaction.test.ts b/packages/opentelemetry/test/custom/transaction.test.ts index cb0cd2efd332..ff76e2653d90 100644 --- a/packages/opentelemetry/test/custom/transaction.test.ts +++ b/packages/opentelemetry/test/custom/transaction.test.ts @@ -1,5 +1,4 @@ -import { setCurrentClient, spanToJSON } from '@sentry/core'; -import { getCurrentHub } from '../../src/custom/hub'; +import { getCurrentHub, setCurrentClient, spanToJSON } from '@sentry/core'; import { OpenTelemetryScope } from '../../src/custom/scope'; import { OpenTelemetryTransaction, startTransaction } from '../../src/custom/transaction'; import { TestClient, getDefaultTestClientOptions } from '../helpers/TestClient'; @@ -14,6 +13,7 @@ describe('NodeExperimentalTransaction', () => { const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); client.init(); @@ -65,6 +65,7 @@ describe('NodeExperimentalTransaction', () => { const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); client.init(); @@ -91,6 +92,7 @@ describe('NodeExperimentalTransaction', () => { const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); client.init(); @@ -152,6 +154,7 @@ describe('startTranscation', () => { it('creates a NodeExperimentalTransaction', () => { const client = new TestClient(getDefaultTestClientOptions()); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); client.init(); @@ -182,6 +185,7 @@ describe('startTranscation', () => { it('allows to pass data to transaction', () => { const client = new TestClient(getDefaultTestClientOptions()); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); client.init(); diff --git a/packages/opentelemetry/test/helpers/initOtel.ts b/packages/opentelemetry/test/helpers/initOtel.ts index ba42f221b3d8..fb0ac135e015 100644 --- a/packages/opentelemetry/test/helpers/initOtel.ts +++ b/packages/opentelemetry/test/helpers/initOtel.ts @@ -3,11 +3,10 @@ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-ho import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { SDK_VERSION } from '@sentry/core'; +import { SDK_VERSION, getClient } from '@sentry/core'; import { logger } from '@sentry/utils'; import { wrapContextManagerClass } from '../../src/contextManager'; -import { getClient } from '../../src/custom/hub'; import { DEBUG_BUILD } from '../../src/debug-build'; import { SentryPropagator } from '../../src/propagator'; import { SentrySampler } from '../../src/sampler'; diff --git a/packages/opentelemetry/test/integration/breadcrumbs.test.ts b/packages/opentelemetry/test/integration/breadcrumbs.test.ts index bfb40a348ff9..65baab5b95a9 100644 --- a/packages/opentelemetry/test/integration/breadcrumbs.test.ts +++ b/packages/opentelemetry/test/integration/breadcrumbs.test.ts @@ -1,6 +1,6 @@ -import { addBreadcrumb, captureException, withScope } from '@sentry/core'; +import { addBreadcrumb, captureException, getClient, getCurrentHub, withScope } from '@sentry/core'; -import { OpenTelemetryHub, getClient, getCurrentHub } from '../../src/custom/hub'; +import { OpenTelemetryHub } from '../../src/custom/hub'; import { startSpan } from '../../src/trace'; import type { TestClientInterface } from '../helpers/TestClient'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; @@ -19,6 +19,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb }); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const client = getClient() as TestClientInterface; @@ -59,6 +60,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb }); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const client = getClient() as TestClientInterface; diff --git a/packages/opentelemetry/test/integration/otelTimedEvents.test.ts b/packages/opentelemetry/test/integration/otelTimedEvents.test.ts index 8611f366d04a..ab360d74b9e1 100644 --- a/packages/opentelemetry/test/integration/otelTimedEvents.test.ts +++ b/packages/opentelemetry/test/integration/otelTimedEvents.test.ts @@ -1,6 +1,6 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { getClient } from '../../src/custom/hub'; +import { getClient } from '@sentry/core'; import { startSpan } from '../../src/trace'; import type { TestClientInterface } from '../helpers/TestClient'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index d0234b27a140..791f93d75c54 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -1,6 +1,6 @@ -import { captureException, getCurrentScope, setTag, withScope } from '@sentry/core'; +import { captureException, getClient, getCurrentHub, getCurrentScope, setTag, withScope } from '@sentry/core'; -import { OpenTelemetryHub, getClient, getCurrentHub } from '../../src/custom/hub'; +import { OpenTelemetryHub } from '../../src/custom/hub'; import { OpenTelemetryScope } from '../../src/custom/scope'; import { startSpan } from '../../src/trace'; import { getSpanScope } from '../../src/utils/spanData'; @@ -22,6 +22,7 @@ describe('Integration | Scope', () => { mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const client = getClient() as TestClientInterface; @@ -129,6 +130,7 @@ describe('Integration | Scope', () => { mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); + // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const client = getClient() as TestClientInterface; const rootScope = getCurrentScope(); diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 5c4742df5f97..d131f91ba0dc 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -1,11 +1,10 @@ import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { addBreadcrumb, setTag } from '@sentry/core'; +import { addBreadcrumb, getClient, setTag } from '@sentry/core'; import type { PropagationContext, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; import { spanToJSON } from '@sentry/core'; -import { getClient } from '../../src/custom/hub'; import { SentrySpanProcessor } from '../../src/spanProcessor'; import { startInactiveSpan, startSpan } from '../../src/trace'; import { setPropagationContextOnContext } from '../../src/utils/contextData'; diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 942a46057521..5785eb7fdb00 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -3,10 +3,9 @@ import { SpanKind } from '@opentelemetry/api'; import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { Span as SpanClass } from '@opentelemetry/sdk-trace-base'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, getClient } from '@sentry/core'; import type { PropagationContext } from '@sentry/types'; -import { getClient } from '../src/custom/hub'; import { InternalSentrySemanticAttributes } from '../src/semanticAttributes'; import { startInactiveSpan, startSpan, startSpanManual } from '../src/trace'; import type { AbstractSpan } from '../src/types'; From 465bd2954a48838e37efa2c34bd12aea2925bb2d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 8 Feb 2024 09:02:54 -0500 Subject: [PATCH 017/173] feat(v8): Delete @sentry/hub (#10530) closes https://github.com/getsentry/sentry-javascript/issues/5665 Goodbye hub package, I've known you my whole (sentry) life. Moved tests into core where applicable, otherwise deleted tests that were redundant. --- .craft.yml | 3 - .../utils/generatePlugin.ts | 2 +- .../test-applications/generic-ts3.8/index.ts | 2 - .../generic-ts3.8/package.json | 1 - .../e2e-tests/verdaccio-config/config.yaml | 6 - .../rollup-utils/plugins/bundlePlugins.mjs | 1 - package.json | 1 - packages/core/test/lib/exports.test.ts | 335 -------- .../test => core/test/lib}/global.test.ts | 4 +- .../test => core/test/lib}/session.test.ts | 2 +- .../test/lib}/sessionflusher.test.ts | 2 +- packages/hub/.eslintrc.js | 3 - packages/hub/LICENSE | 14 - packages/hub/README.md | 20 - packages/hub/jest.config.js | 1 - packages/hub/package.json | 60 -- packages/hub/rollup.npm.config.mjs | 3 - packages/hub/src/index.ts | 159 ---- packages/hub/test/exports.test.ts | 313 ------- packages/hub/test/hub.test.ts | 556 ------------- packages/hub/test/scope.test.ts | 783 ------------------ packages/hub/tsconfig.json | 9 - packages/hub/tsconfig.test.json | 12 - packages/hub/tsconfig.types.json | 10 - .../serverless/scripts/buildLambdaLayer.ts | 2 +- packages/tracing/README.md | 2 +- packages/tracing/test/testutils.ts | 5 +- scripts/ensure-bundle-deps.ts | 3 +- 28 files changed, 11 insertions(+), 2303 deletions(-) delete mode 100644 packages/core/test/lib/exports.test.ts rename packages/{hub/test => core/test/lib}/global.test.ts (87%) rename packages/{hub/test => core/test/lib}/session.test.ts (98%) rename packages/{hub/test => core/test/lib}/sessionflusher.test.ts (98%) delete mode 100644 packages/hub/.eslintrc.js delete mode 100644 packages/hub/LICENSE delete mode 100644 packages/hub/README.md delete mode 100644 packages/hub/jest.config.js delete mode 100644 packages/hub/package.json delete mode 100644 packages/hub/rollup.npm.config.mjs delete mode 100644 packages/hub/src/index.ts delete mode 100644 packages/hub/test/exports.test.ts delete mode 100644 packages/hub/test/hub.test.ts delete mode 100644 packages/hub/test/scope.test.ts delete mode 100644 packages/hub/tsconfig.json delete mode 100644 packages/hub/tsconfig.test.json delete mode 100644 packages/hub/tsconfig.types.json diff --git a/.craft.yml b/.craft.yml index 99efaf3d095e..f795d317b05e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -132,9 +132,6 @@ targets: includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ ## 8. Deprecated packages we still release (but no packages depend on them anymore) - - name: npm - id: '@sentry/hub' - includeNames: /^sentry-hub-\d.*\.tgz$/ - name: npm id: '@sentry/tracing' includeNames: /^sentry-tracing-\d.*\.tgz$/ diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index cf2816ab0033..fe583e271ca3 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -115,7 +115,7 @@ export const LOADER_CONFIGS: Record; * When using compiled versions of the tracing and browser packages, their aliases look for example like * '@sentry/browser': 'path/to/sentry-javascript/packages/browser/esm/index.js' * and all other monorepo packages' aliases look for example like - * '@sentry/hub': 'path/to/sentry-javascript/packages/hub' + * '@sentry/react': 'path/to/sentry-javascript/packages/react' * * When using bundled versions of the tracing and browser packages, all aliases look for example like * '@sentry/browser': false diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts index 46c0608d641d..3ec4677b17bc 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts @@ -3,8 +3,6 @@ import * as _SentryBrowser from '@sentry/browser'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryCore from '@sentry/core'; // biome-ignore lint/nursery/noUnusedImports: -import * as _SentryHub from '@sentry/hub'; -// biome-ignore lint/nursery/noUnusedImports: import * as _SentryIntegrations from '@sentry/integrations'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryNode from '@sentry/node'; diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json index dd4a2b22544d..919d2fb99e84 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json @@ -15,7 +15,6 @@ "dependencies": { "@sentry/browser": "latest || *", "@sentry/core": "latest || *", - "@sentry/hub": "latest || *", "@sentry/integrations": "latest || *", "@sentry/node": "latest || *", "@sentry/opentelemetry-node": "latest || *", diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 2ed138f1cdcc..143596b74849 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -86,12 +86,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/hub': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/integrations': access: $all publish: $all diff --git a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs index e2a96c9697de..1df1e2ac917d 100644 --- a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs +++ b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs @@ -157,7 +157,6 @@ export function makeTSPlugin(jsVersion) { paths: { '@sentry/browser': ['../browser/src'], '@sentry/core': ['../core/src'], - '@sentry/hub': ['../hub/src'], '@sentry/types': ['../types/src'], '@sentry/utils': ['../utils/src'], '@sentry-internal/integration-shims': ['../integration-shims/src'], diff --git a/package.json b/package.json index 9ecacd34c252..74fbfe2a650f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "packages/eslint-plugin-sdk", "packages/feedback", "packages/gatsby", - "packages/hub", "packages/integrations", "packages/integration-shims", "packages/nextjs", diff --git a/packages/core/test/lib/exports.test.ts b/packages/core/test/lib/exports.test.ts deleted file mode 100644 index cb4c9fbdd16d..000000000000 --- a/packages/core/test/lib/exports.test.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { GLOBAL_OBJ } from '@sentry/utils'; -import { - Hub, - Scope, - captureSession, - endSession, - getCurrentScope, - getIsolationScope, - makeMain, - setContext, - setExtra, - setExtras, - setTag, - setTags, - setUser, - startSession, - withIsolationScope, - withScope, -} from '../../src'; -import { isInitialized } from '../../src/exports'; -import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; - -function getTestClient(): TestClient { - return new TestClient( - getDefaultTestClientOptions({ - dsn: 'https://username@domain/123', - }), - ); -} - -describe('withScope', () => { - beforeEach(() => { - const client = getTestClient(); - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - }); - - it('works without a return value', () => { - const scope1 = getCurrentScope(); - expect(scope1).toBeInstanceOf(Scope); - - scope1.setTag('foo', 'bar'); - - const res = withScope(scope => { - expect(scope).toBeInstanceOf(Scope); - expect(scope).not.toBe(scope1); - expect(scope['_tags']).toEqual({ foo: 'bar' }); - - expect(getCurrentScope()).toBe(scope); - }); - - expect(getCurrentScope()).toBe(scope1); - expect(res).toBe(undefined); - }); - - it('works with a return value', () => { - const res = withScope(() => { - return 'foo'; - }); - - expect(res).toBe('foo'); - }); - - it('works with an async function return value', async () => { - const res = withScope(async () => { - return 'foo'; - }); - - expect(res).toBeInstanceOf(Promise); - expect(await res).toBe('foo'); - }); - - it('correctly sets & resets the current scope', () => { - const scope1 = getCurrentScope(); - - withScope(scope2 => { - expect(scope2).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope2); - }); - - withScope(scope3 => { - expect(scope3).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope3); - }); - - expect(getCurrentScope()).toBe(scope1); - }); - - it('correctly sets & resets the current scope with async functions', async () => { - const scope1 = getCurrentScope(); - - await withScope(async scope2 => { - expect(scope2).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope2); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(getCurrentScope()).toBe(scope2); - }); - - await withScope(async scope3 => { - expect(scope3).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope3); - - await new Promise(resolve => setTimeout(resolve, 10)); - - expect(getCurrentScope()).toBe(scope3); - }); - - expect(getCurrentScope()).toBe(scope1); - }); - - it('correctly sets & resets the current scope when an error happens', () => { - const scope1 = getCurrentScope(); - - const error = new Error('foo'); - - expect(() => - withScope(scope2 => { - expect(scope2).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope2); - - throw error; - }), - ).toThrow(error); - - expect(getCurrentScope()).toBe(scope1); - }); - - it('correctly sets & resets the current scope with async functions & errors', async () => { - const scope1 = getCurrentScope(); - - const error = new Error('foo'); - - await expect( - withScope(async scope2 => { - expect(scope2).not.toBe(scope1); - expect(getCurrentScope()).toBe(scope2); - - throw error; - }), - ).rejects.toBe(error); - - expect(getCurrentScope()).toBe(scope1); - }); - - it('allows to pass a custom scope', () => { - const scope1 = getCurrentScope(); - scope1.setExtra('x1', 'x1'); - - const customScope = new Scope(); - customScope.setExtra('x2', 'x2'); - - withScope(customScope, scope2 => { - expect(scope2).not.toBe(scope1); - expect(scope2).toBe(customScope); - expect(getCurrentScope()).toBe(scope2); - expect(scope2['_extra']).toEqual({ x2: 'x2' }); - }); - - withScope(customScope, scope3 => { - expect(scope3).not.toBe(scope1); - expect(scope3).toBe(customScope); - expect(getCurrentScope()).toBe(scope3); - expect(scope3['_extra']).toEqual({ x2: 'x2' }); - }); - - expect(getCurrentScope()).toBe(scope1); - }); -}); - -describe('session APIs', () => { - beforeEach(() => { - const client = getTestClient(); - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - }); - - describe('startSession', () => { - it('starts a session', () => { - const session = startSession(); - - expect(session).toMatchObject({ - status: 'ok', - errors: 0, - init: true, - environment: 'production', - ignoreDuration: false, - sid: expect.any(String), - did: undefined, - timestamp: expect.any(Number), - started: expect.any(Number), - duration: expect.any(Number), - toJSON: expect.any(Function), - }); - }); - - it('ends a previously active session and removes it from the scope', () => { - const session1 = startSession(); - - expect(session1.status).toBe('ok'); - expect(getIsolationScope().getSession()).toBe(session1); - - const session2 = startSession(); - - expect(session2.status).toBe('ok'); - expect(session1.status).toBe('exited'); - expect(getIsolationScope().getSession()).toBe(session2); - }); - }); - - describe('endSession', () => { - it('ends a session and removes it from the scope', () => { - const session = startSession(); - - expect(session.status).toBe('ok'); - expect(getIsolationScope().getSession()).toBe(session); - - endSession(); - - expect(session.status).toBe('exited'); - expect(getIsolationScope().getSession()).toBe(undefined); - }); - }); - - describe('captureSession', () => { - it('captures a session without ending it by default', () => { - const session = startSession({ release: '1.0.0' }); - - expect(session.status).toBe('ok'); - expect(session.init).toBe(true); - expect(getIsolationScope().getSession()).toBe(session); - - captureSession(); - - // this flag indicates the session was sent via BaseClient - expect(session.init).toBe(false); - - // session is still active and on the scope - expect(session.status).toBe('ok'); - expect(getIsolationScope().getSession()).toBe(session); - }); - - it('captures a session and ends it if end is `true`', () => { - const session = startSession({ release: '1.0.0' }); - - expect(session.status).toBe('ok'); - expect(session.init).toBe(true); - expect(getIsolationScope().getSession()).toBe(session); - - captureSession(true); - - // this flag indicates the session was sent via BaseClient - expect(session.init).toBe(false); - - // session is still active and on the scope - expect(session.status).toBe('exited'); - expect(getIsolationScope().getSession()).toBe(undefined); - }); - }); - - describe('setUser', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setUser({ id: 'foo' }); - expect(isolationScope.getScopeData().user.id).toBe('foo'); - }); - }); - }); - - describe('setTags', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setTags({ wee: true, woo: false }); - expect(isolationScope.getScopeData().tags['wee']).toBe(true); - expect(isolationScope.getScopeData().tags['woo']).toBe(false); - }); - }); - }); - - describe('setTag', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setTag('foo', true); - expect(isolationScope.getScopeData().tags['foo']).toBe(true); - }); - }); - }); - - describe('setExtras', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setExtras({ wee: { foo: 'bar' }, woo: { foo: 'bar' } }); - expect(isolationScope.getScopeData().extra?.wee).toEqual({ foo: 'bar' }); - expect(isolationScope.getScopeData().extra?.woo).toEqual({ foo: 'bar' }); - }); - }); - }); - - describe('setExtra', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setExtra('foo', { bar: 'baz' }); - expect(isolationScope.getScopeData().extra?.foo).toEqual({ bar: 'baz' }); - }); - }); - }); - - describe('setContext', () => { - it('should write to the isolation scope', () => { - withIsolationScope(isolationScope => { - setContext('foo', { bar: 'baz' }); - expect(isolationScope.getScopeData().contexts?.foo?.bar).toBe('baz'); - }); - }); - }); -}); - -describe('isInitialized', () => { - it('returns false if no client is setup', () => { - GLOBAL_OBJ.__SENTRY__.hub = undefined; - expect(isInitialized()).toBe(false); - }); - - it('returns true if client is setup', () => { - const client = getTestClient(); - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - - expect(isInitialized()).toBe(true); - }); -}); diff --git a/packages/hub/test/global.test.ts b/packages/core/test/lib/global.test.ts similarity index 87% rename from packages/hub/test/global.test.ts rename to packages/core/test/lib/global.test.ts index 23bc51193a29..7e6bdf58ac9e 100644 --- a/packages/hub/test/global.test.ts +++ b/packages/core/test/lib/global.test.ts @@ -2,7 +2,7 @@ import { GLOBAL_OBJ } from '@sentry/utils'; -import { Hub, getCurrentHub, getHubFromCarrier } from '../src'; +import { Hub, getCurrentHub, getHubFromCarrier } from '../../src/hub'; describe('global', () => { test('getGlobalHub', () => { @@ -28,7 +28,7 @@ describe('global', () => { const newestHub = new Hub(undefined, undefined, undefined, 999999); GLOBAL_OBJ.__SENTRY__.hub = newestHub; const fn = jest.fn().mockImplementation(function (...args: []) { - // @ts-expect-error typescript complains that this can be `any` + // @ts-expect-error 'this' implicitly has type 'any' because it does not have a type annotation expect(this).toBe(newestHub); expect(args).toEqual([1, 2, 3]); }); diff --git a/packages/hub/test/session.test.ts b/packages/core/test/lib/session.test.ts similarity index 98% rename from packages/hub/test/session.test.ts rename to packages/core/test/lib/session.test.ts index fd8a1a58c359..fcded43eb740 100644 --- a/packages/hub/test/session.test.ts +++ b/packages/core/test/lib/session.test.ts @@ -3,7 +3,7 @@ import type { SessionContext } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; -import { closeSession, makeSession, updateSession } from '../src'; +import { closeSession, makeSession, updateSession } from '../../src/session'; describe('Session', () => { it('initializes with the proper defaults', () => { diff --git a/packages/hub/test/sessionflusher.test.ts b/packages/core/test/lib/sessionflusher.test.ts similarity index 98% rename from packages/hub/test/sessionflusher.test.ts rename to packages/core/test/lib/sessionflusher.test.ts index 0079e56087b4..f2c019839bcd 100644 --- a/packages/hub/test/sessionflusher.test.ts +++ b/packages/core/test/lib/sessionflusher.test.ts @@ -2,7 +2,7 @@ import type { Client } from '@sentry/types'; -import { SessionFlusher } from '../src'; +import { SessionFlusher } from '../../src/sessionflusher'; describe('Session Flusher', () => { let sendSession: jest.Mock; diff --git a/packages/hub/.eslintrc.js b/packages/hub/.eslintrc.js deleted file mode 100644 index 5a2cc7f1ec08..000000000000 --- a/packages/hub/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], -}; diff --git a/packages/hub/LICENSE b/packages/hub/LICENSE deleted file mode 100644 index 535ef0561e1b..000000000000 --- a/packages/hub/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. All rights reserved. - -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 the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/hub/README.md b/packages/hub/README.md deleted file mode 100644 index 24e11a114d67..000000000000 --- a/packages/hub/README.md +++ /dev/null @@ -1,20 +0,0 @@ -

- - Sentry - -

- -# Sentry JavaScript SDK Hub - -[![npm version](https://img.shields.io/npm/v/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) -[![npm dm](https://img.shields.io/npm/dm/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) -[![npm dt](https://img.shields.io/npm/dt/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -## General - -This package provides the `Hub` and `Scope` for all JavaScript related SDKs. diff --git a/packages/hub/jest.config.js b/packages/hub/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/hub/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/hub/package.json b/packages/hub/package.json deleted file mode 100644 index 1d51f3adb0be..000000000000 --- a/packages/hub/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "@sentry/hub", - "version": "7.100.0", - "description": "Sentry hub which handles global state managment.", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/hub", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "files": [ - "cjs", - "esm", - "types", - "types-ts3.8" - ], - "main": "build/cjs/index.js", - "module": "build/esm/index.js", - "types": "build/types/index.d.ts", - "typesVersions": { - "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "yarn build", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", - "build:watch": "run-p build:transpile:watch build:types:watch", - "build:dev:watch": "yarn build:watch", - "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", - "circularDepCheck": "madge --circular src/index.ts", - "clean": "rimraf build coverage sentry-hub-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", - "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": false -} diff --git a/packages/hub/rollup.npm.config.mjs b/packages/hub/rollup.npm.config.mjs deleted file mode 100644 index 84a06f2fb64a..000000000000 --- a/packages/hub/rollup.npm.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/hub/src/index.ts b/packages/hub/src/index.ts deleted file mode 100644 index f865df997d29..000000000000 --- a/packages/hub/src/index.ts +++ /dev/null @@ -1,159 +0,0 @@ -export type { Carrier, Layer } from '@sentry/core'; - -import { - Hub as HubCore, - Scope as ScopeCore, - SessionFlusher as SessionFlusherCore, - addBreadcrumb as addBreadcrumbCore, - addGlobalEventProcessor as addGlobalEventProcessorCore, - captureEvent as captureEventCore, - captureException as captureExceptionCore, - captureMessage as captureMessageCore, - closeSession as closeSessionCore, - configureScope as configureScopeCore, - getCurrentHub as getCurrentHubCore, - getHubFromCarrier as getHubFromCarrierCore, - getMainCarrier as getMainCarrierCore, - makeMain as makeMainCore, - makeSession as makeSessionCore, - setContext as setContextCore, - setExtra as setExtraCore, - setExtras as setExtrasCore, - setHubOnCarrier as setHubOnCarrierCore, - setTag as setTagCore, - setTags as setTagsCore, - setUser as setUserCore, - startTransaction as startTransactionCore, - updateSession as updateSessionCore, - withScope as withScopeCore, -} from '@sentry/core'; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8 - */ -export class Hub extends HubCore {} - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8 - */ -export class Scope extends ScopeCore {} - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const getCurrentHub = getCurrentHubCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ - -export const addGlobalEventProcessor = addGlobalEventProcessorCore; // eslint-disable-line deprecation/deprecation - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const getHubFromCarrier = getHubFromCarrierCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const getMainCarrier = getMainCarrierCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const makeMain = makeMainCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setHubOnCarrier = setHubOnCarrierCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const SessionFlusher = SessionFlusherCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const closeSession = closeSessionCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const makeSession = makeSessionCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const updateSession = updateSessionCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const addBreadcrumb = addBreadcrumbCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const captureException = captureExceptionCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const captureEvent = captureEventCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const captureMessage = captureMessageCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const configureScope = configureScopeCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const startTransaction = startTransactionCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setContext = setContextCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setExtra = setExtraCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setExtras = setExtrasCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setTag = setTagCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setTags = setTagsCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const setUser = setUserCore; - -/** - * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. - */ -export const withScope = withScopeCore; diff --git a/packages/hub/test/exports.test.ts b/packages/hub/test/exports.test.ts deleted file mode 100644 index 0b48e38a9c49..000000000000 --- a/packages/hub/test/exports.test.ts +++ /dev/null @@ -1,313 +0,0 @@ -/* eslint-disable deprecation/deprecation */ - -import type { Scope } from '../src'; -import { - captureEvent, - captureException, - captureMessage, - configureScope, - getCurrentHub, - getHubFromCarrier, - setContext, - setExtra, - setExtras, - setTag, - setTags, - setUser, - withScope, -} from '../src'; - -export class TestClient { - public static instance?: TestClient; - - public constructor(public options: Record) { - TestClient.instance = this; - } - - public mySecretPublicMethod(str: string): string { - return `secret: ${str}`; - } -} - -export class TestClient2 {} - -export function init(options: Record): void { - getCurrentHub().bindClient(new TestClient(options) as any); -} - -// eslint-disable-next-line no-var -declare var global: any; - -describe('Top Level API', () => { - beforeEach(() => { - global.__SENTRY__ = { - hub: undefined, - }; - }); - - describe('Capture', () => { - test('Return an event_id', () => { - const client: any = { - captureException: jest.fn(async () => Promise.resolve()), - }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const e = new Error('test exception'); - const eventId = captureException(e); - expect(eventId).toBeTruthy(); - }); - }); - - test('Exception', () => { - const client: any = { - captureException: jest.fn(async () => Promise.resolve()), - }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const e = new Error('test exception'); - captureException(e); - expect(client.captureException.mock.calls[0][0]).toBe(e); - }); - }); - - test('Exception with explicit scope', () => { - const client: any = { - captureException: jest.fn(async () => Promise.resolve()), - }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const e = new Error('test exception'); - const captureContext = { extra: { foo: 'wat' } }; - captureException(e, captureContext); - expect(client.captureException.mock.calls[0][0]).toBe(e); - expect(client.captureException.mock.calls[0][1].captureContext).toEqual(captureContext); - }); - }); - - test('Message', () => { - const client: any = { captureMessage: jest.fn(async () => Promise.resolve()) }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const message = 'yo'; - captureMessage(message); - expect(client.captureMessage.mock.calls[0][0]).toBe(message); - }); - }); - - test('Message with explicit scope', () => { - const client: any = { captureMessage: jest.fn(async () => Promise.resolve()) }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const message = 'yo'; - const captureContext = { extra: { foo: 'wat' } }; - captureMessage(message, captureContext); - expect(client.captureMessage.mock.calls[0][0]).toBe(message); - // Skip the level if explicit content is provided - expect(client.captureMessage.mock.calls[0][1]).toBe(undefined); - expect(client.captureMessage.mock.calls[0][2].captureContext).toBe(captureContext); - }); - }); - - // NOTE: We left custom level as 2nd argument to not break the API. Should be removed and unified in v6. - // TODO: Before we release v8, check if this is still a thing - test('Message with custom level', () => { - const client: any = { captureMessage: jest.fn(async () => Promise.resolve()) }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const message = 'yo'; - const level = 'warning'; - captureMessage(message, level); - expect(client.captureMessage.mock.calls[0][0]).toBe(message); - expect(client.captureMessage.mock.calls[0][1]).toBe('warning'); - }); - }); - - test('Event', () => { - const client: any = { captureEvent: jest.fn(async () => Promise.resolve()) }; - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - const e = { message: 'test' }; - captureEvent(e); - expect(client.captureEvent.mock.calls[0][0]).toBe(e); - }); - }); - }); - - describe('configureScope', () => { - test('User Context', () => { - const client: any = new TestClient({}); - getCurrentHub().pushScope(); - getCurrentHub().bindClient(client); - configureScope((scope: Scope) => { - scope.setUser({ id: '1234' }); - }); - expect(global.__SENTRY__.hub._stack[1].scope._user).toEqual({ - id: '1234', - }); - getCurrentHub().popScope(); - }); - - test('Extra Context', () => { - const client: any = new TestClient({}); - getCurrentHub().pushScope(); - getCurrentHub().bindClient(client); - configureScope((scope: Scope) => { - scope.setExtra('id', '1234'); - }); - expect(global.__SENTRY__.hub._stack[1].scope._extra).toEqual({ - id: '1234', - }); - getCurrentHub().popScope(); - }); - - test('Tags Context', () => { - init({}); - configureScope((scope: Scope) => { - scope.setTag('id', '1234'); - }); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ - id: '1234', - }); - }); - - test('Fingerprint', () => { - const client: any = new TestClient({}); - getCurrentHub().pushScope(); - getCurrentHub().bindClient(client); - configureScope((scope: Scope) => { - scope.setFingerprint(['abcd']); - }); - expect(global.__SENTRY__.hub._stack[1].scope._fingerprint).toEqual(['abcd']); - }); - - test('Level', () => { - const client: any = new TestClient({}); - const scope = getCurrentHub().pushScope(); - getCurrentHub().bindClient(client); - scope.setLevel('warning'); - expect(global.__SENTRY__.hub._stack[1].scope._level).toEqual('warning'); - }); - }); - - test('Clear Scope', () => { - const client: any = new TestClient({}); - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(client); - expect(global.__SENTRY__.hub._stack.length).toBe(2); - configureScope((scope: Scope) => { - scope.setUser({ id: '1234' }); - }); - expect(global.__SENTRY__.hub._stack[1].scope._user).toEqual({ - id: '1234', - }); - configureScope((scope: Scope) => { - scope.clear(); - }); - expect(global.__SENTRY__.hub._stack[1].scope._user).toEqual({}); - }); - }); - - test('returns undefined before binding a client', () => { - expect(getCurrentHub().getClient()).toBeUndefined(); - }); - - test('returns the bound client', () => { - init({}); - expect(getCurrentHub().getClient()).toBe(TestClient.instance); - }); - - test('does not throw an error when pushing different clients', () => { - init({}); - expect(() => { - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(new TestClient2() as any); - }); - }).not.toThrow(); - }); - - test('does not throw an error when pushing same clients', () => { - init({}); - expect(() => { - getCurrentHub().withScope(() => { - getCurrentHub().bindClient(new TestClient({}) as any); - }); - }).not.toThrow(); - }); - - test('custom carrier', () => { - const iAmSomeGlobalVarTheUserHasToManage = { - state: {}, - }; - const hub = getHubFromCarrier(iAmSomeGlobalVarTheUserHasToManage.state); - hub.pushScope(); - hub.bindClient(new TestClient({}) as any); - hub.configureScope((scope: Scope) => { - scope.setUser({ id: '1234' }); - }); - expect((iAmSomeGlobalVarTheUserHasToManage.state as any).__SENTRY__.hub._stack[1].scope._user).toEqual({ - id: '1234', - }); - hub.popScope(); - expect((iAmSomeGlobalVarTheUserHasToManage.state as any).__SENTRY__.hub._stack[1]).toBeUndefined(); - }); - - test('withScope', () => { - withScope(scope => { - scope.setLevel('warning'); - scope.setFingerprint(['1']); - withScope(scope2 => { - scope2.setLevel('info'); - scope2.setFingerprint(['2']); - withScope(scope3 => { - scope3.clear(); - expect(global.__SENTRY__.hub._stack[1].scope._level).toEqual('warning'); - expect(global.__SENTRY__.hub._stack[1].scope._fingerprint).toEqual(['1']); - expect(global.__SENTRY__.hub._stack[2].scope._level).toEqual('info'); - expect(global.__SENTRY__.hub._stack[2].scope._fingerprint).toEqual(['2']); - expect(global.__SENTRY__.hub._stack[3].scope._level).toBeUndefined(); - }); - expect(global.__SENTRY__.hub._stack).toHaveLength(3); - }); - expect(global.__SENTRY__.hub._stack).toHaveLength(2); - }); - expect(global.__SENTRY__.hub._stack).toHaveLength(1); - }); - - test('setExtras', () => { - init({}); - setExtras({ a: 'b' }); - expect(global.__SENTRY__.hub._stack[0].scope._extra).toEqual({ a: 'b' }); - }); - - test('setTags', () => { - init({}); - setTags({ a: 'b' }); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ a: 'b' }); - }); - - test('setExtra', () => { - init({}); - setExtra('a', 'b'); - // biome-ignore format: Follow-up for prettier - expect(global.__SENTRY__.hub._stack[0].scope._extra).toEqual({ 'a': 'b' }); - }); - - test('setTag', () => { - init({}); - setTag('a', 'b'); - // biome-ignore format: Follow-up for prettier - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ 'a': 'b' }); - }); - - test('setUser', () => { - init({}); - setUser({ id: 'b' }); - expect(global.__SENTRY__.hub._stack[0].scope._user).toEqual({ id: 'b' }); - }); - - test('setContext', () => { - init({}); - setContext('test', { id: 'b' }); - expect(global.__SENTRY__.hub._stack[0].scope._contexts).toEqual({ test: { id: 'b' } }); - }); -}); diff --git a/packages/hub/test/hub.test.ts b/packages/hub/test/hub.test.ts deleted file mode 100644 index 08ec6a22130a..000000000000 --- a/packages/hub/test/hub.test.ts +++ /dev/null @@ -1,556 +0,0 @@ -/* eslint-disable @typescript-eslint/unbound-method */ -/* eslint-disable deprecation/deprecation */ - -import type { Client, Event, EventType } from '@sentry/types'; - -import { getCurrentScope, makeMain } from '@sentry/core'; -import { Hub, Scope, getCurrentHub } from '../src'; - -const clientFn: any = jest.fn(); - -function makeClient() { - return { - getOptions: jest.fn(), - captureEvent: jest.fn(), - captureException: jest.fn(), - close: jest.fn(), - flush: jest.fn(), - getDsn: jest.fn(), - getIntegration: jest.fn(), - setupIntegrations: jest.fn(), - captureMessage: jest.fn(), - captureSession: jest.fn(), - } as unknown as Client; -} - -/** - * Return an array containing the arguments passed to the given mocked or spied-upon function. - * - * By default, the args passed to the first call of the function are returned, but it is also possible to retrieve the - * nth call by passing `callIndex`. If the function wasn't called, an error message is returned instead. - */ -function getPassedArgs(mock: (...args: any[]) => any, callIndex: number = 0): any[] { - const asMock = mock as jest.MockedFunction<(...args: any[]) => any>; - return asMock.mock.calls[callIndex] || ["Error: Function wasn't called."]; -} - -describe('Hub', () => { - afterEach(() => { - jest.restoreAllMocks(); - jest.useRealTimers(); - }); - - test('call bindClient with provided client when constructing new instance', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - expect(hub.getClient()).toBe(testClient); - }); - - test('push process into stack', () => { - const hub = new Hub(); - expect(hub.getStack()).toHaveLength(1); - }); - - test('pass in filled layer', () => { - const hub = new Hub(clientFn); - expect(hub.getStack()).toHaveLength(1); - }); - - test('isOlderThan', () => { - const hub = new Hub(); - expect(hub.isOlderThan(0)).toBeFalsy(); - }); - - describe('pushScope', () => { - test('simple', () => { - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - const hub = new Hub(undefined, localScope); - hub.pushScope(); - expect(hub.getStack()).toHaveLength(2); - expect(hub.getStack()[1].scope).not.toBe(localScope); - expect((hub.getStack()[1].scope as Scope as any)._extra).toEqual({ a: 'b' }); - }); - - test('inherit client', () => { - const testClient: any = { bla: 'a' }; - const hub = new Hub(testClient); - hub.pushScope(); - expect(hub.getStack()).toHaveLength(2); - expect(hub.getStack()[1].client).toBe(testClient); - }); - - describe('bindClient', () => { - test('should override current client', () => { - const testClient = makeClient(); - const nextClient = makeClient(); - const hub = new Hub(testClient); - hub.bindClient(nextClient); - expect(hub.getStack()).toHaveLength(1); - expect(hub.getStack()[0].client).toBe(nextClient); - }); - - test('should bind client to the top-most layer', () => { - const testClient: any = { bla: 'a' }; - const nextClient: any = { foo: 'bar' }; - const hub = new Hub(testClient); - hub.pushScope(); - hub.bindClient(nextClient); - expect(hub.getStack()).toHaveLength(2); - expect(hub.getStack()[0].client).toBe(testClient); - expect(hub.getStack()[1].client).toBe(nextClient); - }); - - test('should call setupIntegration method of passed client', () => { - const testClient = makeClient(); - const nextClient = makeClient(); - const hub = new Hub(testClient); - hub.bindClient(nextClient); - expect(testClient.setupIntegrations).toHaveBeenCalled(); - expect(nextClient.setupIntegrations).toHaveBeenCalled(); - }); - }); - - test('inherit processors', async () => { - expect.assertions(1); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - const hub = new Hub({ a: 'b' } as any, localScope); - - localScope.addEventProcessor(async (processedEvent: Event) => { - processedEvent.dist = '1'; - return processedEvent; - }); - - hub.pushScope(); - const pushedScope = hub.getStackTop().scope; - - return pushedScope!.applyToEvent(event).then(final => { - expect(final!.dist).toEqual('1'); - }); - }); - }); - - test('popScope', () => { - const hub = new Hub(); - hub.pushScope(); - expect(hub.getStack()).toHaveLength(2); - hub.popScope(); - expect(hub.getStack()).toHaveLength(1); - }); - - describe('withScope', () => { - let hub: Hub; - - beforeEach(() => { - hub = new Hub(); - }); - - test('simple', () => { - hub.withScope(() => { - expect(hub.getStack()).toHaveLength(2); - }); - expect(hub.getStack()).toHaveLength(1); - }); - - test('bindClient', () => { - const testClient: any = { bla: 'a' }; - hub.withScope(() => { - hub.bindClient(testClient); - expect(hub.getStack()).toHaveLength(2); - expect(hub.getStack()[1].client).toBe(testClient); - }); - expect(hub.getStack()).toHaveLength(1); - }); - - test('should bubble up exceptions', () => { - const error = new Error('test'); - expect(() => { - hub.withScope(() => { - throw error; - }); - }).toThrow(error); - }); - }); - - test('getCurrentClient', () => { - const testClient: any = { bla: 'a' }; - const hub = new Hub(testClient); - expect(hub.getClient()).toBe(testClient); - }); - - test('getStack', () => { - const client: any = { a: 'b' }; - const hub = new Hub(client); - expect(hub.getStack()[0].client).toBe(client); - }); - - test('getStackTop', () => { - const testClient: any = { bla: 'a' }; - const hub = new Hub(); - hub.pushScope(); - hub.pushScope(); - hub.bindClient(testClient); - expect(hub.getStackTop().client).toEqual({ bla: 'a' }); - }); - - describe('configureScope', () => { - test('should have an access to provide scope', () => { - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - const hub = new Hub({} as any, localScope); - const cb = jest.fn(); - hub.configureScope(cb); - expect(cb).toHaveBeenCalledWith(localScope); - }); - - test('should not invoke without client and scope', () => { - const hub = new Hub(); - const cb = jest.fn(); - hub.configureScope(cb); - expect(cb).not.toHaveBeenCalled(); - }); - }); - - describe('captureException', () => { - test('simple', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureException('a'); - const args = getPassedArgs(testClient.captureException); - - expect(args[0]).toBe('a'); - }); - - test('should set event_id in hint', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureException('a'); - const args = getPassedArgs(testClient.captureException); - - expect(args[1].event_id).toBeTruthy(); - }); - - test('should keep event_id from hint', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - const id = Math.random().toString(); - - hub.captureException('a', { event_id: id }); - const args = getPassedArgs(testClient.captureException); - - expect(args[1].event_id).toBe(id); - }); - - test('should generate hint if not provided in the call', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - const ex = new Error('foo'); - - hub.captureException(ex); - const args = getPassedArgs(testClient.captureException); - - expect(args[1].originalException).toBe(ex); - expect(args[1].syntheticException).toBeInstanceOf(Error); - expect(args[1].syntheticException.message).toBe('Sentry syntheticException'); - }); - }); - - describe('captureMessage', () => { - test('simple', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureMessage('a'); - const args = getPassedArgs(testClient.captureMessage); - - expect(args[0]).toBe('a'); - }); - - test('should set event_id in hint', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureMessage('a'); - const args = getPassedArgs(testClient.captureMessage); - - expect(args[2].event_id).toBeTruthy(); - }); - - test('should keep event_id from hint', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - const id = Math.random().toString(); - - hub.captureMessage('a', undefined, { event_id: id }); - const args = getPassedArgs(testClient.captureMessage); - - expect(args[2].event_id).toBe(id); - }); - - test('should generate hint if not provided in the call', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureMessage('foo'); - const args = getPassedArgs(testClient.captureMessage); - - expect(args[2].originalException).toBe('foo'); - expect(args[2].syntheticException).toBeInstanceOf(Error); - expect(args[2].syntheticException.message).toBe('foo'); - }); - }); - - describe('captureEvent', () => { - test('simple', () => { - const event: Event = { - extra: { b: 3 }, - }; - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureEvent(event); - const args = getPassedArgs(testClient.captureEvent); - - expect(args[0]).toBe(event); - }); - - test('should set event_id in hint', () => { - const event: Event = { - extra: { b: 3 }, - }; - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureEvent(event); - const args = getPassedArgs(testClient.captureEvent); - - expect(args[1].event_id).toBeTruthy(); - }); - - test('should keep event_id from hint', () => { - const event: Event = { - extra: { b: 3 }, - }; - const testClient = makeClient(); - const hub = new Hub(testClient); - const id = Math.random().toString(); - - hub.captureEvent(event, { event_id: id }); - const args = getPassedArgs(testClient.captureEvent); - - expect(args[1].event_id).toBe(id); - }); - - test('sets lastEventId', () => { - const event: Event = { - extra: { b: 3 }, - }; - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureEvent(event); - const args = getPassedArgs(testClient.captureEvent); - - expect(args[1].event_id).toEqual(hub.lastEventId()); - }); - - const eventTypesToIgnoreLastEventId: EventType[] = ['transaction', 'replay_event']; - it.each(eventTypesToIgnoreLastEventId)('eventType %s does not set lastEventId', eventType => { - const event: Event = { - extra: { b: 3 }, - type: eventType, - }; - const testClient = makeClient(); - const hub = new Hub(testClient); - - hub.captureEvent(event); - const args = getPassedArgs(testClient.captureEvent); - - expect(args[1].event_id).not.toEqual(hub.lastEventId()); - }); - }); - - test('lastEventId should be the same as last created', () => { - const event: Event = { - extra: { b: 3 }, - }; - const hub = new Hub(); - const eventId = hub.captureEvent(event); - expect(eventId).toBe(hub.lastEventId()); - }); - - describe('run', () => { - test('simple', () => { - const currentHub = getCurrentHub(); - const myScope = new Scope(); - const myClient: any = { a: 'b' }; - myScope.setExtra('a', 'b'); - const myHub = new Hub(myClient, myScope); - myHub.run(hub => { - expect(hub.getScope()).toBe(myScope); - expect(hub.getClient()).toBe(myClient); - expect(hub).toBe(getCurrentHub()); - }); - expect(currentHub).toBe(getCurrentHub()); - }); - - test('should bubble up exceptions', () => { - const hub = new Hub(); - const error = new Error('test'); - expect(() => { - hub.run(() => { - throw error; - }); - }).toThrow(error); - }); - }); - - describe('breadcrumbs', () => { - test('withScope', () => { - expect.assertions(6); - const hub = new Hub(clientFn); - hub.addBreadcrumb({ message: 'My Breadcrumb' }); - hub.withScope(scope => { - scope.addBreadcrumb({ message: 'scope breadcrumb' }); - const event: Event = {}; - void scope - .applyToEvent(event) - .then((appliedEvent: Event | null) => { - expect(appliedEvent).toBeTruthy(); - expect(appliedEvent!.breadcrumbs).toHaveLength(2); - expect(appliedEvent!.breadcrumbs![0].message).toEqual('My Breadcrumb'); - expect(appliedEvent!.breadcrumbs![0]).toHaveProperty('timestamp'); - expect(appliedEvent!.breadcrumbs![1].message).toEqual('scope breadcrumb'); - expect(appliedEvent!.breadcrumbs![1]).toHaveProperty('timestamp'); - }) - .then(null, e => { - // eslint-disable-next-line no-console - console.error(e); - }); - }); - }); - }); - - describe('shouldSendDefaultPii()', () => { - test('return false if sendDefaultPii is not set', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - expect(hub.shouldSendDefaultPii()).toBe(false); - }); - - test('return true if sendDefaultPii is set in the client options', () => { - const testClient = makeClient(); - testClient.getOptions = () => { - return { - sendDefaultPii: true, - } as unknown as any; - }; - const hub = new Hub(testClient); - expect(hub.shouldSendDefaultPii()).toBe(true); - }); - }); - - describe('session APIs', () => { - beforeEach(() => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - }); - - describe('startSession', () => { - it('starts a session', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - const session = hub.startSession(); - - expect(session).toMatchObject({ - status: 'ok', - errors: 0, - init: true, - environment: 'production', - ignoreDuration: false, - sid: expect.any(String), - did: undefined, - timestamp: expect.any(Number), - started: expect.any(Number), - duration: expect.any(Number), - toJSON: expect.any(Function), - }); - }); - - it('ends a previously active session and removes it from the scope', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - - const session1 = hub.startSession(); - - expect(session1.status).toBe('ok'); - expect(getCurrentScope().getSession()).toBe(session1); - - const session2 = hub.startSession(); - - expect(session2.status).toBe('ok'); - expect(session1.status).toBe('exited'); - expect(getCurrentHub().getScope().getSession()).toBe(session2); - }); - }); - - describe('endSession', () => { - it('ends a session and removes it from the scope', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - - const session = hub.startSession(); - - expect(session.status).toBe('ok'); - expect(getCurrentScope().getSession()).toBe(session); - - hub.endSession(); - - expect(session.status).toBe('exited'); - expect(getCurrentHub().getScope().getSession()).toBe(undefined); - }); - }); - - describe('captureSession', () => { - it('captures a session without ending it by default', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - - const session = hub.startSession(); - - expect(session.status).toBe('ok'); - expect(getCurrentScope().getSession()).toBe(session); - - hub.captureSession(); - - expect(testClient.captureSession).toHaveBeenCalledWith(expect.objectContaining({ status: 'ok' })); - }); - - it('captures a session and ends it if end is `true`', () => { - const testClient = makeClient(); - const hub = new Hub(testClient); - makeMain(hub); - - const session = hub.startSession(); - - expect(session.status).toBe('ok'); - expect(hub.getScope().getSession()).toBe(session); - - hub.captureSession(true); - - expect(testClient.captureSession).toHaveBeenCalledWith(expect.objectContaining({ status: 'exited' })); - }); - }); - }); -}); diff --git a/packages/hub/test/scope.test.ts b/packages/hub/test/scope.test.ts deleted file mode 100644 index 08f4a73abcb2..000000000000 --- a/packages/hub/test/scope.test.ts +++ /dev/null @@ -1,783 +0,0 @@ -/* eslint-disable deprecation/deprecation */ - -import type { Event, EventHint, RequestSessionStatus } from '@sentry/types'; -import { GLOBAL_OBJ } from '@sentry/utils'; - -import { Scope, addGlobalEventProcessor } from '../src'; - -describe('Scope', () => { - afterEach(() => { - jest.resetAllMocks(); - jest.useRealTimers(); - GLOBAL_OBJ.__SENTRY__ = GLOBAL_OBJ.__SENTRY__ || {}; - GLOBAL_OBJ.__SENTRY__.globalEventProcessors = undefined; - }); - - describe('init', () => { - test('it creates a propagation context', () => { - const scope = new Scope(); - - // @ts-expect-error asserting on private properties - expect(scope._propagationContext).toEqual({ - traceId: expect.any(String), - spanId: expect.any(String), - sampled: undefined, - dsc: undefined, - parentSpanId: undefined, - }); - }); - }); - - describe('attributes modification', () => { - test('setFingerprint', () => { - const scope = new Scope(); - scope.setFingerprint(['abcd']); - expect((scope as any)._fingerprint).toEqual(['abcd']); - }); - - test('setExtra', () => { - const scope = new Scope(); - scope.setExtra('a', 1); - expect((scope as any)._extra).toEqual({ a: 1 }); - }); - - test('setExtras', () => { - const scope = new Scope(); - scope.setExtras({ a: 1 }); - expect((scope as any)._extra).toEqual({ a: 1 }); - }); - - test('setExtras with undefined overrides the value', () => { - const scope = new Scope(); - scope.setExtra('a', 1); - scope.setExtras({ a: undefined }); - expect((scope as any)._extra).toEqual({ a: undefined }); - }); - - test('setTag', () => { - const scope = new Scope(); - scope.setTag('a', 'b'); - expect((scope as any)._tags).toEqual({ a: 'b' }); - }); - - test('setTags', () => { - const scope = new Scope(); - scope.setTags({ a: 'b' }); - expect((scope as any)._tags).toEqual({ a: 'b' }); - }); - - test('setUser', () => { - const scope = new Scope(); - scope.setUser({ id: '1' }); - expect((scope as any)._user).toEqual({ id: '1' }); - }); - - test('setUser with null unsets the user', () => { - const scope = new Scope(); - scope.setUser({ id: '1' }); - scope.setUser(null); - expect((scope as any)._user).toEqual({}); - }); - - test('addBreadcrumb', () => { - const scope = new Scope(); - scope.addBreadcrumb({ message: 'test' }); - expect((scope as any)._breadcrumbs[0]).toHaveProperty('message', 'test'); - }); - - test('addBreadcrumb can be limited to hold up to N breadcrumbs', () => { - const scope = new Scope(); - for (let i = 0; i < 10; i++) { - scope.addBreadcrumb({ message: 'test' }, 5); - } - expect((scope as any)._breadcrumbs).toHaveLength(5); - }); - - test('addBreadcrumb can go over DEFAULT_MAX_BREADCRUMBS value', () => { - const scope = new Scope(); - for (let i = 0; i < 120; i++) { - scope.addBreadcrumb({ message: 'test' }, 111); - } - expect((scope as any)._breadcrumbs).toHaveLength(111); - }); - - test('setLevel', () => { - const scope = new Scope(); - scope.setLevel('fatal'); - expect((scope as any)._level).toEqual('fatal'); - }); - - test('setTransactionName', () => { - const scope = new Scope(); - scope.setTransactionName('/abc'); - expect((scope as any)._transactionName).toEqual('/abc'); - }); - - test('setTransactionName with no value unsets it', () => { - const scope = new Scope(); - scope.setTransactionName('/abc'); - scope.setTransactionName(); - expect((scope as any)._transactionName).toBeUndefined(); - }); - - test('setContext', () => { - const scope = new Scope(); - scope.setContext('os', { id: '1' }); - expect((scope as any)._contexts.os).toEqual({ id: '1' }); - }); - - test('setContext with null unsets it', () => { - const scope = new Scope(); - scope.setContext('os', { id: '1' }); - scope.setContext('os', null); - expect((scope as any)._user).toEqual({}); - }); - - test('setSpan', () => { - const scope = new Scope(); - const span = { fake: 'span' } as any; - scope.setSpan(span); - expect((scope as any)._span).toEqual(span); - }); - - test('setSpan with no value unsets it', () => { - const scope = new Scope(); - scope.setSpan({ fake: 'span' } as any); - scope.setSpan(); - expect((scope as any)._span).toEqual(undefined); - }); - - test('setProcessingMetadata', () => { - const scope = new Scope(); - scope.setSDKProcessingMetadata({ dogs: 'are great!' }); - expect((scope as any)._sdkProcessingMetadata.dogs).toEqual('are great!'); - }); - - test('set and get propagation context', () => { - const scope = new Scope(); - const oldPropagationContext = scope.getPropagationContext(); - scope.setPropagationContext({ - traceId: '86f39e84263a4de99c326acab3bfe3bd', - spanId: '6e0c63257de34c92', - sampled: true, - }); - expect(scope.getPropagationContext()).not.toEqual(oldPropagationContext); - expect(scope.getPropagationContext()).toEqual({ - traceId: '86f39e84263a4de99c326acab3bfe3bd', - spanId: '6e0c63257de34c92', - sampled: true, - }); - }); - - test('chaining', () => { - const scope = new Scope(); - scope.setLevel('fatal').setUser({ id: '1' }); - expect((scope as any)._level).toEqual('fatal'); - expect((scope as any)._user).toEqual({ id: '1' }); - }); - }); - - describe('clone', () => { - test('basic inheritance', () => { - const parentScope = new Scope(); - parentScope.setExtra('a', 1); - const scope = Scope.clone(parentScope); - expect((parentScope as any)._extra).toEqual((scope as any)._extra); - }); - - test('_requestSession clone', () => { - const parentScope = new Scope(); - parentScope.setRequestSession({ status: 'errored' }); - const scope = Scope.clone(parentScope); - expect(parentScope.getRequestSession()).toEqual(scope.getRequestSession()); - }); - - test('parent changed inheritance', () => { - const parentScope = new Scope(); - const scope = Scope.clone(parentScope); - parentScope.setExtra('a', 2); - expect((scope as any)._extra).toEqual({}); - expect((parentScope as any)._extra).toEqual({ a: 2 }); - }); - - test('child override inheritance', () => { - const parentScope = new Scope(); - parentScope.setExtra('a', 1); - - const scope = Scope.clone(parentScope); - scope.setExtra('a', 2); - expect((parentScope as any)._extra).toEqual({ a: 1 }); - expect((scope as any)._extra).toEqual({ a: 2 }); - }); - - test('child override should set the value of parent _requestSession', () => { - // Test that ensures if the status value of `status` of `_requestSession` is changed in a child scope - // that it should also change in parent scope because we are copying the reference to the object - const parentScope = new Scope(); - parentScope.setRequestSession({ status: 'errored' }); - - const scope = Scope.clone(parentScope); - const requestSession = scope.getRequestSession(); - if (requestSession) { - requestSession.status = 'ok'; - } - - expect(parentScope.getRequestSession()).toEqual({ status: 'ok' }); - expect(scope.getRequestSession()).toEqual({ status: 'ok' }); - }); - - test('should clone propagation context', () => { - const parentScope = new Scope(); - const scope = Scope.clone(parentScope); - - // @ts-expect-error accessing private property for test - expect(scope._propagationContext).toEqual(parentScope._propagationContext); - }); - }); - - describe('applyToEvent', () => { - test('basic usage', async () => { - const scope = new Scope(); - scope.setExtra('a', 2); - scope.setTag('a', 'b'); - scope.setUser({ id: '1' }); - scope.setFingerprint(['abcd']); - scope.setLevel('warning'); - scope.setTransactionName('/abc'); - scope.addBreadcrumb({ message: 'test' }); - scope.setContext('os', { id: '1' }); - scope.setSDKProcessingMetadata({ dogs: 'are great!' }); - - const event: Event = {}; - const processedEvent = await scope.applyToEvent(event); - - expect(processedEvent!.extra).toEqual({ a: 2 }); - expect(processedEvent!.tags).toEqual({ a: 'b' }); - expect(processedEvent!.user).toEqual({ id: '1' }); - expect(processedEvent!.fingerprint).toEqual(['abcd']); - expect(processedEvent!.level).toEqual('warning'); - expect(processedEvent!.transaction).toEqual('/abc'); - expect(processedEvent!.breadcrumbs![0]).toHaveProperty('message', 'test'); - expect(processedEvent!.contexts).toEqual({ os: { id: '1' } }); - expect(processedEvent!.sdkProcessingMetadata).toEqual({ - dogs: 'are great!', - }); - }); - - test('merge with existing event data', async () => { - expect.assertions(8); - const scope = new Scope(); - scope.setExtra('a', 2); - scope.setTag('a', 'b'); - scope.setUser({ id: '1' }); - scope.setFingerprint(['abcd']); - scope.addBreadcrumb({ message: 'test' }); - scope.setContext('server', { id: '2' }); - const event: Event = { - breadcrumbs: [{ message: 'test1' }], - contexts: { os: { id: '1' } }, - extra: { b: 3 }, - fingerprint: ['efgh'], - tags: { b: 'c' }, - user: { id: '3' }, - }; - return scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.extra).toEqual({ a: 2, b: 3 }); - expect(processedEvent!.tags).toEqual({ a: 'b', b: 'c' }); - expect(processedEvent!.user).toEqual({ id: '3' }); - expect(processedEvent!.fingerprint).toEqual(['efgh', 'abcd']); - expect(processedEvent!.breadcrumbs).toHaveLength(2); - expect(processedEvent!.breadcrumbs![0]).toHaveProperty('message', 'test1'); - expect(processedEvent!.breadcrumbs![1]).toHaveProperty('message', 'test'); - expect(processedEvent!.contexts).toEqual({ - os: { id: '1' }, - server: { id: '2' }, - }); - }); - }); - - test('should make sure that fingerprint is always array', async () => { - const scope = new Scope(); - const event: Event = {}; - - // @ts-expect-error we want to be able to assign string value - event.fingerprint = 'foo'; - await scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.fingerprint).toEqual(['foo']); - }); - - // @ts-expect-error we want to be able to assign string value - event.fingerprint = 'bar'; - await scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.fingerprint).toEqual(['bar']); - }); - }); - - test('should merge fingerprint from event and scope', async () => { - const scope = new Scope(); - scope.setFingerprint(['foo']); - const event: Event = { - fingerprint: ['bar'], - }; - - await scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.fingerprint).toEqual(['bar', 'foo']); - }); - }); - - test('should remove default empty fingerprint array if theres no data available', async () => { - const scope = new Scope(); - const event: Event = {}; - await scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.fingerprint).toEqual(undefined); - }); - }); - - test('scope level should have priority over event level', async () => { - expect.assertions(1); - const scope = new Scope(); - scope.setLevel('warning'); - const event: Event = {}; - event.level = 'fatal'; - return scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.level).toEqual('warning'); - }); - }); - - test('scope transaction should have priority over event transaction', async () => { - expect.assertions(1); - const scope = new Scope(); - scope.setTransactionName('/abc'); - const event: Event = {}; - event.transaction = '/cdf'; - return scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.transaction).toEqual('/abc'); - }); - }); - - test('adds trace context', async () => { - const scope = new Scope(); - const span = { - fake: 'span', - spanContext: () => ({}), - toJSON: () => ({ origin: 'manual' }), - } as any; - scope.setSpan(span); - const event: Event = {}; - const processedEvent = await scope.applyToEvent(event); - expect(processedEvent!.contexts!.trace as any).toEqual({ origin: 'manual' }); - }); - - test('existing trace context in event should take precedence', async () => { - expect.assertions(1); - const scope = new Scope(); - const span = { - fake: 'span', - spanContext: () => ({}), - toJSON: () => ({ a: 'b' }), - } as any; - scope.setSpan(span); - const event: Event = { - contexts: { - trace: { a: 'c' } as any, - }, - }; - return scope.applyToEvent(event).then(processedEvent => { - expect((processedEvent!.contexts!.trace as any).a).toEqual('c'); - }); - }); - - test('adds `transaction` tag when transaction on scope', async () => { - expect.assertions(1); - const scope = new Scope(); - const transaction = { - fake: 'span', - spanContext: () => ({}), - toJSON: () => ({ a: 'b', description: 'fake transaction' }), - getDynamicSamplingContext: () => ({}), - } as any; - transaction.transaction = transaction; // because this is a transaction, its `transaction` pointer points to itself - scope.setSpan(transaction); - const event: Event = {}; - return scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.tags!.transaction).toEqual('fake transaction'); - }); - }); - - test('adds `transaction` tag when span on scope', async () => { - expect.assertions(1); - const scope = new Scope(); - const transaction = { - spanContext: () => ({}), - toJSON: () => ({ description: 'fake transaction' }), - getDynamicSamplingContext: () => ({}), - }; - const span = { - fake: 'span', - spanContext: () => ({}), - toJSON: () => ({ a: 'b' }), - transaction, - } as any; - scope.setSpan(span); - const event: Event = {}; - return scope.applyToEvent(event).then(processedEvent => { - expect(processedEvent!.tags!.transaction).toEqual('fake transaction'); - }); - }); - }); - - test('clear', () => { - const scope = new Scope(); - // @ts-expect-error accessing private property - const oldPropagationContext = scope._propagationContext; - scope.setExtra('a', 2); - scope.setTag('a', 'b'); - scope.setUser({ id: '1' }); - scope.setFingerprint(['abcd']); - scope.addBreadcrumb({ message: 'test' }); - scope.setRequestSession({ status: 'ok' }); - expect((scope as any)._extra).toEqual({ a: 2 }); - scope.clear(); - expect((scope as any)._extra).toEqual({}); - expect((scope as any)._requestSession).toEqual(undefined); - // @ts-expect-error accessing private property - expect(scope._propagationContext).toEqual({ - traceId: expect.any(String), - spanId: expect.any(String), - sampled: undefined, - }); - // @ts-expect-error accessing private property - expect(scope._propagationContext).not.toEqual(oldPropagationContext); - }); - - test('clearBreadcrumbs', () => { - const scope = new Scope(); - scope.addBreadcrumb({ message: 'test' }); - expect((scope as any)._breadcrumbs).toHaveLength(1); - scope.clearBreadcrumbs(); - expect((scope as any)._breadcrumbs).toHaveLength(0); - }); - - describe('update', () => { - let scope: Scope; - - beforeEach(() => { - scope = new Scope(); - scope.setTags({ foo: '1', bar: '2' }); - scope.setExtras({ foo: '1', bar: '2' }); - scope.setContext('foo', { id: '1' }); - scope.setContext('bar', { id: '2' }); - scope.setUser({ id: '1337' }); - scope.setLevel('info'); - scope.setFingerprint(['foo']); - scope.setRequestSession({ status: 'ok' }); - }); - - test('given no data, returns the original scope', () => { - const updatedScope = scope.update(); - expect(updatedScope).toEqual(scope); - }); - - test('given neither function, Scope or plain object, returns original scope', () => { - // @ts-expect-error we want to be able to update scope with string - const updatedScope = scope.update('wat'); - expect(updatedScope).toEqual(scope); - }); - - test('given callback function, pass it the scope and returns original or modified scope', () => { - const cb = jest - .fn() - .mockImplementationOnce(v => v) - .mockImplementationOnce(v => { - v.setTag('foo', 'bar'); - return v; - }); - - let updatedScope = scope.update(cb); - expect(cb).toHaveBeenNthCalledWith(1, scope); - expect(updatedScope).toEqual(scope); - - updatedScope = scope.update(cb); - expect(cb).toHaveBeenNthCalledWith(2, scope); - expect(updatedScope).toEqual(scope); - }); - - test('given callback function, when it doesnt return instanceof Scope, ignore it and return original scope', () => { - const cb = jest.fn().mockImplementationOnce(_v => 'wat'); - const updatedScope = scope.update(cb); - expect(cb).toHaveBeenCalledWith(scope); - expect(updatedScope).toEqual(scope); - }); - - test('given another instance of Scope, it should merge two together, with the passed scope having priority', () => { - const localScope = new Scope(); - localScope.setTags({ bar: '3', baz: '4' }); - localScope.setExtras({ bar: '3', baz: '4' }); - localScope.setContext('bar', { id: '3' }); - localScope.setContext('baz', { id: '4' }); - localScope.setUser({ id: '42' }); - localScope.setLevel('warning'); - localScope.setFingerprint(['bar']); - (localScope as any)._requestSession = { status: 'ok' }; - - const updatedScope = scope.update(localScope) as any; - - expect(updatedScope._tags).toEqual({ - bar: '3', - baz: '4', - foo: '1', - }); - expect(updatedScope._extra).toEqual({ - bar: '3', - baz: '4', - foo: '1', - }); - expect(updatedScope._contexts).toEqual({ - bar: { id: '3' }, - baz: { id: '4' }, - foo: { id: '1' }, - }); - expect(updatedScope._user).toEqual({ id: '42' }); - expect(updatedScope._level).toEqual('warning'); - expect(updatedScope._fingerprint).toEqual(['bar']); - expect(updatedScope._requestSession.status).toEqual('ok'); - // @ts-expect-error accessing private property for test - expect(updatedScope._propagationContext).toEqual(localScope._propagationContext); - }); - - test('given an empty instance of Scope, it should preserve all the original scope data', () => { - const updatedScope = scope.update(new Scope()) as any; - - expect(updatedScope._tags).toEqual({ - bar: '2', - foo: '1', - }); - expect(updatedScope._extra).toEqual({ - bar: '2', - foo: '1', - }); - expect(updatedScope._contexts).toEqual({ - bar: { id: '2' }, - foo: { id: '1' }, - }); - expect(updatedScope._user).toEqual({ id: '1337' }); - expect(updatedScope._level).toEqual('info'); - expect(updatedScope._fingerprint).toEqual(['foo']); - expect(updatedScope._requestSession.status).toEqual('ok'); - }); - - test('given a plain object, it should merge two together, with the passed object having priority', () => { - const localAttributes = { - contexts: { bar: { id: '3' }, baz: { id: '4' } }, - extra: { bar: '3', baz: '4' }, - fingerprint: ['bar'], - level: 'warning' as const, - tags: { bar: '3', baz: '4' }, - user: { id: '42' }, - requestSession: { status: 'errored' as RequestSessionStatus }, - propagationContext: { - traceId: '8949daf83f4a4a70bee4c1eb9ab242ed', - spanId: 'a024ad8fea82680e', - sampled: true, - }, - }; - - const updatedScope = scope.update(localAttributes) as any; - - expect(updatedScope._tags).toEqual({ - bar: '3', - baz: '4', - foo: '1', - }); - expect(updatedScope._extra).toEqual({ - bar: '3', - baz: '4', - foo: '1', - }); - expect(updatedScope._contexts).toEqual({ - bar: { id: '3' }, - baz: { id: '4' }, - foo: { id: '1' }, - }); - expect(updatedScope._user).toEqual({ id: '42' }); - expect(updatedScope._level).toEqual('warning'); - expect(updatedScope._fingerprint).toEqual(['bar']); - expect(updatedScope._requestSession).toEqual({ status: 'errored' }); - expect(updatedScope._propagationContext).toEqual({ - traceId: '8949daf83f4a4a70bee4c1eb9ab242ed', - spanId: 'a024ad8fea82680e', - sampled: true, - }); - }); - }); - - describe('addEventProcessor', () => { - test('should allow for basic event manipulation', async () => { - expect.assertions(3); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - localScope.addEventProcessor((processedEvent: Event) => { - expect(processedEvent.extra).toEqual({ a: 'b', b: 3 }); - return processedEvent; - }); - localScope.addEventProcessor((processedEvent: Event) => { - processedEvent.dist = '1'; - return processedEvent; - }); - localScope.addEventProcessor((processedEvent: Event) => { - expect(processedEvent.dist).toEqual('1'); - return processedEvent; - }); - - return localScope.applyToEvent(event).then(final => { - expect(final!.dist).toEqual('1'); - }); - }); - - test('should work alongside global event processors', async () => { - expect.assertions(3); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - - // eslint-disable-next-line deprecation/deprecation - addGlobalEventProcessor((processedEvent: Event) => { - processedEvent.dist = '1'; - return processedEvent; - }); - - localScope.addEventProcessor((processedEvent: Event) => { - expect(processedEvent.extra).toEqual({ a: 'b', b: 3 }); - return processedEvent; - }); - - localScope.addEventProcessor((processedEvent: Event) => { - expect(processedEvent.dist).toEqual('1'); - return processedEvent; - }); - - return localScope.applyToEvent(event).then(final => { - expect(final!.dist).toEqual('1'); - }); - }); - - test('should allow for async callbacks', async () => { - jest.useFakeTimers(); - expect.assertions(6); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - const callCounter = jest.fn(); - localScope.addEventProcessor((processedEvent: Event) => { - callCounter(1); - expect(processedEvent.extra).toEqual({ a: 'b', b: 3 }); - return processedEvent; - }); - localScope.addEventProcessor( - async (processedEvent: Event) => - new Promise(resolve => { - callCounter(2); - setTimeout(() => { - callCounter(3); - processedEvent.dist = '1'; - resolve(processedEvent); - }, 1); - jest.runAllTimers(); - }), - ); - localScope.addEventProcessor((processedEvent: Event) => { - callCounter(4); - return processedEvent; - }); - - return localScope.applyToEvent(event).then(processedEvent => { - expect(callCounter.mock.calls[0][0]).toBe(1); - expect(callCounter.mock.calls[1][0]).toBe(2); - expect(callCounter.mock.calls[2][0]).toBe(3); - expect(callCounter.mock.calls[3][0]).toBe(4); - expect(processedEvent!.dist).toEqual('1'); - }); - }); - - test('should correctly handle async rejections', async () => { - jest.useFakeTimers(); - expect.assertions(2); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - const callCounter = jest.fn(); - localScope.addEventProcessor((processedEvent: Event) => { - callCounter(1); - expect(processedEvent.extra).toEqual({ a: 'b', b: 3 }); - return processedEvent; - }); - localScope.addEventProcessor( - async (_processedEvent: Event) => - new Promise((_, reject) => { - setTimeout(() => { - reject('bla'); - }, 1); - jest.runAllTimers(); - }), - ); - localScope.addEventProcessor((processedEvent: Event) => { - callCounter(4); - return processedEvent; - }); - - return localScope.applyToEvent(event).then(null, reason => { - expect(reason).toEqual('bla'); - }); - }); - - test('should drop an event when any of processors return null', async () => { - expect.assertions(1); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - localScope.addEventProcessor(async (_: Event) => null); - return localScope.applyToEvent(event).then(processedEvent => { - expect(processedEvent).toBeNull(); - }); - }); - - test('should have an access to the EventHint', async () => { - expect.assertions(3); - const event: Event = { - extra: { b: 3 }, - }; - const localScope = new Scope(); - localScope.setExtra('a', 'b'); - localScope.addEventProcessor(async (internalEvent: Event, hint?: EventHint) => { - expect(hint).toBeTruthy(); - expect(hint!.syntheticException).toBeTruthy(); - return internalEvent; - }); - return localScope.applyToEvent(event, { syntheticException: new Error('what') }).then(processedEvent => { - expect(processedEvent).toEqual(event); - }); - }); - - test('should notify all the listeners about the changes', () => { - jest.useFakeTimers(); - const scope = new Scope(); - const listener = jest.fn(); - scope.addScopeListener(listener); - scope.setExtra('a', 2); - jest.runAllTimers(); - expect(listener).toHaveBeenCalled(); - expect(listener.mock.calls[0][0]._extra).toEqual({ a: 2 }); - }); - }); -}); diff --git a/packages/hub/tsconfig.json b/packages/hub/tsconfig.json deleted file mode 100644 index bf45a09f2d71..000000000000 --- a/packages/hub/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - // package-specific options - } -} diff --git a/packages/hub/tsconfig.test.json b/packages/hub/tsconfig.test.json deleted file mode 100644 index af7e36ec0eda..000000000000 --- a/packages/hub/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "include": ["test/**/*"], - - "compilerOptions": { - // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["jest"] - - // other package-specific, test-specific options - } -} diff --git a/packages/hub/tsconfig.types.json b/packages/hub/tsconfig.types.json deleted file mode 100644 index 65455f66bd75..000000000000 --- a/packages/hub/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "outDir": "build/types" - } -} diff --git a/packages/serverless/scripts/buildLambdaLayer.ts b/packages/serverless/scripts/buildLambdaLayer.ts index 9b6912898386..a462a33c6804 100644 --- a/packages/serverless/scripts/buildLambdaLayer.ts +++ b/packages/serverless/scripts/buildLambdaLayer.ts @@ -18,7 +18,7 @@ async function buildLambdaLayer(): Promise { // Create the main SDK bundle // TODO: Check if we can get rid of this, after the lerna 6/nx update?? await ensureBundleBuildPrereqs({ - dependencies: ['@sentry/utils', '@sentry/hub', '@sentry/core', '@sentry/node'], + dependencies: ['@sentry/utils', '@sentry/core', '@sentry/node'], }); run('yarn rollup --config rollup.aws.config.mjs'); diff --git a/packages/tracing/README.md b/packages/tracing/README.md index 462f2da52ff8..09bf79eb2e89 100644 --- a/packages/tracing/README.md +++ b/packages/tracing/README.md @@ -19,7 +19,7 @@ ## General -This package contains extensions to the `@sentry/hub` to enable Sentry AM related functionality. It also provides integrations for Browser and Node that provide a good experience out of the box. +This package contains extensions to the Sentry SDKs to enable Sentry AM related functionality. It also provides integrations for Browser and Node that provide a good experience out of the box. ## Migrating from @sentry/apm to @sentry/tracing diff --git a/packages/tracing/test/testutils.ts b/packages/tracing/test/testutils.ts index 484e3bf3cc35..5ce79099d38f 100644 --- a/packages/tracing/test/testutils.ts +++ b/packages/tracing/test/testutils.ts @@ -6,9 +6,8 @@ import { JSDOM } from 'jsdom'; /** * Injects DOM properties into node global object. * - * Useful for running tests where some of the tested code applies to @sentry/node and some applies to @sentry/browser - * (e.g. tests in @sentry/tracing or @sentry/hub). Note that not all properties from the browser `window` object are - * available. + * Useful for running tests where some of the tested code is isomorphic (apply to both node and browser). + * Note that not all properties from the browser `window` object are available. * * @param properties The names of the properties to add */ diff --git a/scripts/ensure-bundle-deps.ts b/scripts/ensure-bundle-deps.ts index 4e61a6ecf993..189614bbb044 100644 --- a/scripts/ensure-bundle-deps.ts +++ b/scripts/ensure-bundle-deps.ts @@ -147,12 +147,13 @@ function run(cmd: string, options?: childProcess.ExecSyncOptions): string { // TODO: Not ideal that we're hard-coding this, and it's easy to get when we're in a package directory, but would take // more work to get from the repo level. Fortunately this list is unlikely to change very often, and we're the only ones // we'll break if it gets out of date. -const dependencies = ['@sentry/utils', '@sentry/hub', '@sentry/core']; +const dependencies = ['@sentry/utils', '@sentry/core']; if (['sentry-javascript', 'tracing', 'wasm'].includes(path.basename(process.cwd()))) { dependencies.push('@sentry/browser'); } +// eslint-disable-next-line @typescript-eslint/no-floating-promises void ensureBundleBuildPrereqs({ dependencies, }); From 6142ef18285bbd2f141241833762e898eace9525 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 8 Feb 2024 09:03:32 -0500 Subject: [PATCH 018/173] feat(v8/browser): Remove `_eventFromIncompleteOnError` usage (#10553) closes https://github.com/getsentry/sentry-javascript/issues/5842 Also deletes `GlobalHandlers` integration deprecated export while I'm here. --- packages/browser/src/exports.ts | 2 +- .../src/integrations/globalhandlers.ts | 86 +++---------------- packages/browser/src/integrations/index.ts | 1 - 3 files changed, 15 insertions(+), 74 deletions(-) diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 496fbc3a9644..f0f717f084cc 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -109,4 +109,4 @@ export { linkedErrorsIntegration } from './integrations/linkederrors'; export { browserApiErrorsIntegration } from './integrations/trycatch'; // eslint-disable-next-line deprecation/deprecation -export { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; +export { TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index c1096e7c2132..e2aa0b6116f0 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -1,19 +1,9 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { captureEvent, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core'; -import type { - Client, - Event, - Integration, - IntegrationClass, - IntegrationFn, - Primitive, - StackParser, -} from '@sentry/types'; +import { captureEvent, defineIntegration, getClient } from '@sentry/core'; +import type { Client, Event, IntegrationFn, Primitive, StackParser } from '@sentry/types'; import { addGlobalErrorInstrumentationHandler, addGlobalUnhandledRejectionInstrumentationHandler, getLocationHref, - isErrorEvent, isPrimitive, isString, logger, @@ -57,18 +47,6 @@ const _globalHandlersIntegration = ((options: Partial void }> & { - new (options?: Partial): Integration; -}; - function _installGlobalOnErrorHandler(client: Client): void { addGlobalErrorInstrumentationHandler(data => { const { stackParser, attachStacktrace } = getOptions(); @@ -79,15 +57,12 @@ function _installGlobalOnErrorHandler(client: Client): void { const { msg, url, line, column, error } = data; - const event = - error === undefined && isString(msg) - ? _eventFromIncompleteOnError(msg, url, line, column) - : _enhanceEventWithInitialFrame( - eventFromUnknownInput(stackParser, error || msg, undefined, attachStacktrace, false), - url, - line, - column, - ); + const event = _enhanceEventWithInitialFrame( + eventFromUnknownInput(stackParser, error || msg, undefined, attachStacktrace, false), + url, + line, + column, + ); event.level = 'error'; @@ -132,24 +107,23 @@ function _getUnhandledRejectionError(error: unknown): unknown { return error; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const e = error as any; - // dig the object of the rejection out of known event types try { + type ErrorWithReason = { reason: unknown }; // PromiseRejectionEvents store the object of the rejection under 'reason' // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent - if ('reason' in e) { - return e.reason; + if ('reason' in (error as ErrorWithReason)) { + return (error as ErrorWithReason).reason; } + type CustomEventWithDetail = { detail: { reason: unknown } }; // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and // https://github.com/getsentry/sentry-javascript/issues/2380 - else if ('detail' in e && 'reason' in e.detail) { - return e.detail.reason; + if ('detail' in (error as CustomEventWithDetail) && 'reason' in (error as CustomEventWithDetail).detail) { + return (error as CustomEventWithDetail).detail.reason; } } catch {} // eslint-disable-line no-empty @@ -176,38 +150,6 @@ function _eventFromRejectionWithPrimitive(reason: Primitive): Event { }; } -/** - * This function creates a stack from an old, error-less onerror handler. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function _eventFromIncompleteOnError(msg: any, url: any, line: any, column: any): Event { - const ERROR_TYPES_RE = - /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i; - - // If 'message' is ErrorEvent, get real message from inside - let message = isErrorEvent(msg) ? msg.message : msg; - let name = 'Error'; - - const groups = message.match(ERROR_TYPES_RE); - if (groups) { - name = groups[1]; - message = groups[2]; - } - - const event = { - exception: { - values: [ - { - type: name, - value: message, - }, - ], - }, - }; - - return _enhanceEventWithInitialFrame(event, url, line, column); -} - // eslint-disable-next-line @typescript-eslint/no-explicit-any function _enhanceEventWithInitialFrame(event: Event, url: any, line: any, column: any): Event { // event.exception diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts index 17ac85b31232..977cdabba8a4 100644 --- a/packages/browser/src/integrations/index.ts +++ b/packages/browser/src/integrations/index.ts @@ -1,5 +1,4 @@ /* eslint-disable deprecation/deprecation */ -export { GlobalHandlers } from './globalhandlers'; export { TryCatch } from './trycatch'; export { Breadcrumbs } from './breadcrumbs'; export { LinkedErrors } from './linkederrors'; From 8b2569bd8d42472b8d3d51df8a7833404b1d9885 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 8 Feb 2024 09:04:05 -0500 Subject: [PATCH 019/173] feat(v8/node): Remove deepReadDirSync export (#10564) ref https://github.com/getsentry/sentry-javascript/issues/10100 Deprecated in #10016 --- packages/astro/src/index.server.ts | 2 - packages/bun/src/index.ts | 2 - packages/node-experimental/src/index.ts | 2 - packages/node/src/index.ts | 2 - packages/node/src/utils.ts | 40 ----------------- packages/node/test/utils.test.ts | 57 ------------------------- packages/remix/src/index.server.ts | 2 - packages/serverless/src/index.ts | 2 - packages/sveltekit/src/server/index.ts | 2 - 9 files changed, 111 deletions(-) delete mode 100644 packages/node/src/utils.ts delete mode 100644 packages/node/test/utils.test.ts diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 98e5486894db..bbaae475e068 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -68,8 +68,6 @@ export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData, - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, Integrations, consoleIntegration, onUncaughtExceptionIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index f1958c53404d..e48be07a948d 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -89,8 +89,6 @@ export { } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, // eslint-disable-next-line deprecation/deprecation enableAnrDetection, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index b5ffeb6de0c9..171af5251107 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -66,8 +66,6 @@ export { DEFAULT_USER_INCLUDES, extractRequestData, // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, - // eslint-disable-next-line deprecation/deprecation getModuleFromFilename, createGetModuleFromFilename, close, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 44630d871260..aaaf23dc290f 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -102,8 +102,6 @@ export { getSentryRelease, } from './sdk'; export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils'; -// eslint-disable-next-line deprecation/deprecation -export { deepReadDirSync } from './utils'; import { createGetModuleFromFilename } from './module'; /** diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts deleted file mode 100644 index 475709208ed9..000000000000 --- a/packages/node/src/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; - -/** - * Recursively read the contents of a directory. - * - * @param targetDir Absolute or relative path of the directory to scan. All returned paths will be relative to this - * directory. - * @returns Array holding all relative paths - * @deprecated This function will be removed in the next major version. - */ -export function deepReadDirSync(targetDir: string): string[] { - const targetDirAbsPath = path.resolve(targetDir); - - if (!fs.existsSync(targetDirAbsPath)) { - throw new Error(`Cannot read contents of ${targetDirAbsPath}. Directory does not exist.`); - } - - if (!fs.statSync(targetDirAbsPath).isDirectory()) { - throw new Error(`Cannot read contents of ${targetDirAbsPath}, because it is not a directory.`); - } - - // This does the same thing as its containing function, `deepReadDirSync` (except that - purely for convenience - it - // deals in absolute paths rather than relative ones). We need this to be separate from the outer function to preserve - // the difference between `targetDirAbsPath` and `currentDirAbsPath`. - const deepReadCurrentDir = (currentDirAbsPath: string): string[] => { - return fs.readdirSync(currentDirAbsPath).reduce((absPaths: string[], itemName: string) => { - const itemAbsPath = path.join(currentDirAbsPath, itemName); - - if (fs.statSync(itemAbsPath).isDirectory()) { - return absPaths.concat(deepReadCurrentDir(itemAbsPath)); - } - - absPaths.push(itemAbsPath); - return absPaths; - }, []); - }; - - return deepReadCurrentDir(targetDirAbsPath).map(absPath => path.relative(targetDirAbsPath, absPath)); -} diff --git a/packages/node/test/utils.test.ts b/packages/node/test/utils.test.ts deleted file mode 100644 index bcee236c070c..000000000000 --- a/packages/node/test/utils.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import { deepReadDirSync } from '../src/utils'; - -// The parent directory for the new temporary directory - -describe('deepReadDirSync', () => { - it('handles nested files', () => { - const expected = [ - // root level - 'debra.txt', - // one level deep - 'cats/eddy.txt', - 'cats/persephone.txt', - 'cats/piper.txt', - 'cats/sassafras.txt', - 'cats/teaberry.txt', - // two levels deep - 'dogs/theBigs/charlie.txt', - 'dogs/theBigs/maisey.txt', - 'dogs/theSmalls/bodhi.txt', - 'dogs/theSmalls/cory.txt', - ].map(p => (process.platform === 'win32' ? p.replace(/\//g, '\\') : p)); - - // compare sets so that order doesn't matter - // eslint-disable-next-line deprecation/deprecation - expect(new Set(deepReadDirSync('./test/fixtures/testDeepReadDirSync'))).toEqual(new Set(expected)); - }); - - it('handles empty target directory', (done: (error?: Error) => void) => { - expect.assertions(1); - const tmpDir = os.tmpdir(); - - fs.mkdtemp(`${tmpDir}${path.sep}`, (err, dirPath) => { - if (err) throw err; - try { - // eslint-disable-next-line deprecation/deprecation - expect(deepReadDirSync(dirPath)).toEqual([]); - done(); - } catch (error) { - done(error as Error); - } - }); - }); - - it('errors if directory does not exist', () => { - // eslint-disable-next-line deprecation/deprecation - expect(() => deepReadDirSync('./IDontExist')).toThrowError('Directory does not exist.'); - }); - - it('errors if given path is not a directory', () => { - // eslint-disable-next-line deprecation/deprecation - expect(() => deepReadDirSync('package.json')).toThrowError('it is not a directory'); - }); -}); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index a34250100287..3abfc76c4372 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -72,8 +72,6 @@ export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData, - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, Integrations, consoleIntegration, onUncaughtExceptionIntegration, diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 24ee21115f0b..8c1e732bfda8 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -70,8 +70,6 @@ export { DEFAULT_USER_INCLUDES, addRequestDataToEvent, extractRequestData, - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, Handlers, // eslint-disable-next-line deprecation/deprecation Integrations, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 7a886334cfbc..105da4026c06 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -66,8 +66,6 @@ export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData, - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, Integrations, consoleIntegration, onUncaughtExceptionIntegration, From 737fb0e5774d5e261614dab79e454ee854aef6e9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 8 Feb 2024 09:05:19 -0500 Subject: [PATCH 020/173] feat(v8): Remove deprecated spanStatusfromHttpCode export (#10563) Small removal for `spanStatusfromHttpCode`! --- MIGRATION.md | 4 ++++ packages/astro/src/index.server.ts | 2 -- packages/browser/src/index.ts | 2 -- packages/bun/src/index.ts | 2 -- packages/core/src/tracing/index.ts | 2 -- packages/core/src/tracing/spanstatus.ts | 11 ----------- packages/deno/src/index.ts | 2 -- packages/node-experimental/src/index.ts | 2 -- packages/node/src/index.ts | 2 -- packages/remix/src/index.server.ts | 2 -- packages/serverless/src/index.ts | 2 -- packages/sveltekit/src/server/index.ts | 2 -- packages/tracing-internal/src/exports/index.ts | 2 -- packages/tracing/src/index.ts | 9 --------- packages/vercel-edge/src/index.ts | 2 -- 15 files changed, 4 insertions(+), 44 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index f975b13c0f4d..1e9c993e7cd1 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -11,6 +11,10 @@ enum. If you were using the `Severity` enum, you should replace it with the `Sev The `Offline` integration has been removed in favor of the offline transport wrapper: http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching +## Other changes + +- Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) + # Deprecations in 7.x You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index bbaae475e068..6ba4f76f78a2 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -46,8 +46,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 59ef74cdbfb5..39a2d32fd0d8 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -71,8 +71,6 @@ export { extractTraceparentData, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index e48be07a948d..e8a4738b9bba 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -64,8 +64,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index d1e1c7f65b44..c45389da53c6 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -9,8 +9,6 @@ export { extractTraceparentData, getActiveTransaction } from './utils'; export { SpanStatus } from './spanstatus'; export { setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, } from './spanstatus'; export type { SpanStatusType } from './spanstatus'; diff --git a/packages/core/src/tracing/spanstatus.ts b/packages/core/src/tracing/spanstatus.ts index aa0d1639a70c..7ccb4883169c 100644 --- a/packages/core/src/tracing/spanstatus.ts +++ b/packages/core/src/tracing/spanstatus.ts @@ -123,17 +123,6 @@ export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatusType { return 'unknown_error'; } -/** - * Converts a HTTP status code into a {@link SpanStatusType}. - * - * @deprecated Use {@link spanStatusFromHttpCode} instead. - * This export will be removed in v8 as the signature contains a typo. - * - * @param httpStatus The HTTP response status code. - * @returns The span status or unknown_error. - */ -export const spanStatusfromHttpCode = getSpanStatusFromHttpCode; - /** * Sets the Http status attributes on the current span based on the http code. * Additionally, the span's status is updated, depending on the http code. diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 82ce6004ece6..0d0e357737ac 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -63,8 +63,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 171af5251107..680e45d1282a 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -76,8 +76,6 @@ export { Hub, runWithAsyncContext, SDK_VERSION, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index aaaf23dc290f..3e91aae28d14 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -63,8 +63,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 3abfc76c4372..fbf41d811b6f 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -50,8 +50,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 8c1e732bfda8..7af085aac698 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -38,8 +38,6 @@ export { getGlobalScope, getIsolationScope, getHubFromCarrier, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 105da4026c06..614ec4998969 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -44,8 +44,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/tracing-internal/src/exports/index.ts b/packages/tracing-internal/src/exports/index.ts index 96cd3b85ac89..5111048de02a 100644 --- a/packages/tracing-internal/src/exports/index.ts +++ b/packages/tracing-internal/src/exports/index.ts @@ -8,8 +8,6 @@ export { Span, // eslint-disable-next-line deprecation/deprecation SpanStatus, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, startIdleTransaction, Transaction, } from '@sentry/core'; diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts index 24b1241af764..4f6b7fc41e87 100644 --- a/packages/tracing/src/index.ts +++ b/packages/tracing/src/index.ts @@ -23,7 +23,6 @@ import { getActiveTransaction as getActiveTransactionT, hasTracingEnabled as hasTracingEnabledT, instrumentOutgoingRequests as instrumentOutgoingRequestsT, - spanStatusfromHttpCode as spanStatusfromHttpCodeT, startIdleTransaction as startIdleTransactionT, stripUrlQueryAndFragment as stripUrlQueryAndFragmentT, } from '@sentry-internal/tracing'; @@ -75,14 +74,6 @@ export const getActiveTransaction = getActiveTransactionT; // eslint-disable-next-line deprecation/deprecation export const extractTraceparentData = extractTraceparentDataT; -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `spanStatusfromHttpCode` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -// eslint-disable-next-line deprecation/deprecation -export const spanStatusfromHttpCode = spanStatusfromHttpCodeT; - /** * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. * diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 98910cbd754a..8a27be0f1156 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -63,8 +63,6 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation From 245a9fa3be52b8b1db01b094e2b69429e5dbf5fe Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 9 Feb 2024 09:23:50 +0100 Subject: [PATCH 021/173] ref: Remove user segment (#10575) --- .../suites/replay/dsc/test.ts | 12 ++++-------- .../envelope-header-transaction-name/init.js | 2 +- .../envelope-header-transaction-name/test.ts | 1 - .../suites/tracing/envelope-header/init.js | 2 +- .../suites/tracing/envelope-header/test.ts | 1 - .../sentry-trace/baggage-header-out/server.ts | 2 +- .../sentry-trace/baggage-header-out/test.ts | 2 +- .../baggage-transaction-name/server.ts | 2 +- packages/astro/src/server/meta.ts | 2 +- packages/core/src/baseclient.ts | 2 +- packages/core/src/scope.ts | 1 - packages/core/src/server-runtime-client.ts | 2 +- .../core/src/tracing/dynamicSamplingContext.ts | 16 ++++------------ packages/core/test/lib/envelope.test.ts | 2 -- packages/node/src/integrations/http.ts | 2 +- packages/node/src/integrations/undici/index.ts | 5 +---- packages/node/test/integrations/http.test.ts | 11 +++++------ packages/opentelemetry/src/propagator.ts | 8 ++------ packages/profiling-node/clang-format.js | 8 +++++++- packages/sveltekit/test/server/handle.test.ts | 3 +-- packages/sveltekit/test/server/load.test.ts | 4 +--- packages/sveltekit/test/server/utils.test.ts | 3 +-- packages/tracing-internal/src/browser/request.ts | 3 +-- packages/tracing-internal/src/common/fetch.ts | 3 +-- packages/types/src/envelope.ts | 4 ---- packages/types/src/user.ts | 4 ---- 26 files changed, 37 insertions(+), 70 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts index 0588e165604e..c6483e2cdea0 100644 --- a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts @@ -36,7 +36,7 @@ sentryTest( await page.evaluate(() => { const scope = (window as unknown as TestWindow).Sentry.getCurrentScope(); - scope.setUser({ id: 'user123', segment: 'segmentB' }); + scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; @@ -53,7 +53,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', trace_id: expect.any(String), public_key: 'public', @@ -84,7 +83,7 @@ sentryTest( await page.evaluate(() => { const scope = (window as unknown as TestWindow).Sentry.getCurrentScope(); - scope.setUser({ id: 'user123', segment: 'segmentB' }); + scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; @@ -101,7 +100,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', trace_id: expect.any(String), public_key: 'public', @@ -144,7 +142,7 @@ sentryTest( await page.evaluate(() => { const scope = (window as unknown as TestWindow).Sentry.getCurrentScope(); - scope.setUser({ id: 'user123', segment: 'segmentB' }); + scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; @@ -162,7 +160,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', trace_id: expect.any(String), public_key: 'public', @@ -195,7 +192,7 @@ sentryTest( await page.evaluate(async () => { const scope = (window as unknown as TestWindow).Sentry.getCurrentScope(); - scope.setUser({ id: 'user123', segment: 'segmentB' }); + scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; @@ -213,7 +210,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', trace_id: expect.any(String), public_key: 'public', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index c2fcbb33a24c..e28325fcf78e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -12,7 +12,7 @@ Sentry.init({ }); const scope = Sentry.getCurrentScope(); -scope.setUser({ id: 'user123', segment: 'segmentB' }); +scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts index b244768f7f82..6126a6728ca5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts @@ -22,7 +22,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', transaction: expect.stringContaining('/index.html'), trace_id: expect.any(String), diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js index 1528306fcbde..1ecbb8351cb9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js @@ -12,7 +12,7 @@ Sentry.init({ }); const scope = Sentry.getCurrentScope(); -scope.setUser({ id: 'user123', segment: 'segmentB' }); +scope.setUser({ id: 'user123' }); scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts index 71adc007385c..0d7e2b793553 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts @@ -26,7 +26,6 @@ sentryTest( expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ environment: 'production', - user_segment: 'segmentB', sample_rate: '1', trace_id: expect.any(String), public_key: 'public', diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts index 7e9ee87a1a2a..8669c5bb21b8 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts @@ -20,7 +20,7 @@ Sentry.init({ transport: loggingTransport, }); -Sentry.setUser({ id: 'user123', segment: 'SegmentA' }); +Sentry.setUser({ id: 'user123' }); app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts index 395fd4861076..3cd2e9984685 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/test.ts @@ -15,7 +15,7 @@ test('should attach a baggage header to an outgoing request.', async () => { test_data: { host: 'somewhere.not.sentry', baggage: - 'sentry-environment=prod,sentry-release=1.0,sentry-user_segment=SegmentA,sentry-public_key=public' + + 'sentry-environment=prod,sentry-release=1.0,sentry-public_key=public' + ',sentry-trace_id=86f39e84263a4de99c326acab3bfe3bd,sentry-sample_rate=1,sentry-transaction=GET%20%2Ftest%2Fexpress' + ',sentry-sampled=true', }, diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts index 153b7351b594..92d2f8a0195a 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts @@ -24,7 +24,7 @@ Sentry.init({ transport: loggingTransport, }); -Sentry.setUser({ id: 'user123', segment: 'SegmentA' }); +Sentry.setUser({ id: 'user123' }); app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); diff --git a/packages/astro/src/server/meta.ts b/packages/astro/src/server/meta.ts index dd591c15d966..42d50c9d865d 100644 --- a/packages/astro/src/server/meta.ts +++ b/packages/astro/src/server/meta.ts @@ -42,7 +42,7 @@ export function getTracingMetaTags( : dsc ? dsc : client - ? getDynamicSamplingContextFromClient(traceId, client, scope) + ? getDynamicSamplingContextFromClient(traceId, client) : undefined; const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 2c08ba45e20d..b54e1887fd2b 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -679,7 +679,7 @@ export abstract class BaseClient implements Client { ...evt.contexts, }; - const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope); + const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this); evt.sdkProcessingMetadata = { dynamicSamplingContext, diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 9298637fae68..43e200ede516 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -193,7 +193,6 @@ export class Scope implements ScopeInterface { email: undefined, id: undefined, ip_address: undefined, - segment: undefined, username: undefined, }; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index b415a392d4cc..e4dfb135abdf 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -275,6 +275,6 @@ export class ServerRuntimeClient< return [dsc, traceContext]; } - return [getDynamicSamplingContextFromClient(traceId, this, scope), traceContext]; + return [getDynamicSamplingContextFromClient(traceId, this), traceContext]; } } diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index a51c2362c437..93acfe77d10a 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -1,8 +1,8 @@ -import type { Client, DynamicSamplingContext, Scope, Span, Transaction } from '@sentry/types'; +import type { Client, DynamicSamplingContext, Span, Transaction } from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; -import { getClient, getCurrentScope } from '../exports'; +import { getClient } from '../exports'; import { getRootSpan } from '../utils/getRootSpan'; import { spanIsSampled, spanToJSON } from '../utils/spanUtils'; @@ -11,22 +11,14 @@ import { spanIsSampled, spanToJSON } from '../utils/spanUtils'; * * Dispatches the `createDsc` lifecycle hook as a side effect. */ -export function getDynamicSamplingContextFromClient( - trace_id: string, - client: Client, - scope?: Scope, -): DynamicSamplingContext { +export function getDynamicSamplingContextFromClient(trace_id: string, client: Client): DynamicSamplingContext { const options = client.getOptions(); const { publicKey: public_key } = client.getDsn() || {}; - // TODO(v8): Remove segment from User - // eslint-disable-next-line deprecation/deprecation - const { segment: user_segment } = (scope && scope.getUser()) || {}; const dsc = dropUndefinedKeys({ environment: options.environment || DEFAULT_ENVIRONMENT, release: options.release, - user_segment, public_key, trace_id, }) as DynamicSamplingContext; @@ -55,7 +47,7 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly { environment: 'prod', release: '1.0.0', transaction: 'TX', - user_segment: 'segmentA', sample_rate: '0.95', public_key: 'pubKey123', trace_id: '1234', @@ -52,7 +51,6 @@ describe('createEventEnvelope', () => { environment: 'prod', release: '1.0.0', transaction: 'TX', - user_segment: 'segmentA', sample_rate: '0.95', public_key: 'pubKey123', trace_id: '1234', diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index f572fcc160f2..407f7da6a43d 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -354,7 +354,7 @@ function _createWrappedRequestMethodFactory( dsc || (requestSpan ? getDynamicSamplingContextFromSpan(requestSpan) - : getDynamicSamplingContextFromClient(traceId, client, scope)), + : getDynamicSamplingContextFromClient(traceId, client)), ); addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader); diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index a2616deb920b..ed34a3fc0fb7 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -214,10 +214,7 @@ export class Undici implements Integration { const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || - (span - ? getDynamicSamplingContextFromSpan(span) - : getDynamicSamplingContextFromClient(traceId, client, scope)), + dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), ); setHeadersOnRequest(request, sentryTraceHeader, sentryBaggageHeader); diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 527ef48c3f0f..dc2fbc69a37f 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -39,7 +39,6 @@ describe('tracing', () => { setUser({ id: 'uid123', - segment: 'segmentA', }); const transaction = startInactiveSpan({ @@ -139,7 +138,7 @@ describe('tracing', () => { expect(baggageHeader).toEqual( 'sentry-environment=production,sentry-release=1.0.0,' + - 'sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,' + + 'sentry-public_key=dogsarebadatkeepingsecrets,' + 'sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,' + 'sentry-transaction=dogpark,sentry-sampled=true', ); @@ -155,7 +154,7 @@ describe('tracing', () => { expect(baggageHeader[0]).toEqual('dog=great'); expect(baggageHeader[1]).toEqual( - 'sentry-environment=production,sentry-release=1.0.0,sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-transaction=dogpark,sentry-sampled=true', + 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-transaction=dogpark,sentry-sampled=true', ); }); @@ -169,7 +168,7 @@ describe('tracing', () => { expect(baggageHeader).toEqual([ 'dog=great', - 'sentry-environment=production,sentry-release=1.0.0,sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-transaction=dogpark,sentry-sampled=true', + 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-transaction=dogpark,sentry-sampled=true', ]); }); @@ -183,7 +182,7 @@ describe('tracing', () => { expect(baggageHeader).toEqual([ 'dog=great', - 'sentry-environment=production,sentry-release=1.0.0,sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-sampled=true', + 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=12312012123120121231201212312012,sentry-sample_rate=1,sentry-sampled=true', ]); }); @@ -220,7 +219,7 @@ describe('tracing', () => { expect(parts[1]).toEqual(expect.any(String)); expect(baggageHeader).toEqual( - `sentry-environment=production,sentry-release=1.0.0,sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=${traceId}`, + `sentry-environment=production,sentry-release=1.0.0,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=${traceId}`, ); }); diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 5ab31e3f4804..80c71c7cac3c 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -7,7 +7,6 @@ import { SENTRY_BAGGAGE_KEY_PREFIX, generateSentryTraceHeader, propagationContex import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from './constants'; import { getPropagationContextFromContext, setPropagationContextOnContext } from './utils/contextData'; -import { getSpanScope } from './utils/spanData'; /** * Injects and extracts `sentry-trace` and `baggage` headers from carriers. @@ -25,7 +24,7 @@ export class SentryPropagator extends W3CBaggagePropagator { const propagationContext = getPropagationContextFromContext(context); const { spanId, traceId, sampled } = getSentryTraceData(context, propagationContext); - const dynamicSamplingContext = propagationContext ? getDsc(context, propagationContext, traceId) : undefined; + const dynamicSamplingContext = propagationContext ? getDsc(propagationContext, traceId) : undefined; if (dynamicSamplingContext) { baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => { @@ -79,7 +78,6 @@ export class SentryPropagator extends W3CBaggagePropagator { } function getDsc( - context: Context, propagationContext: PropagationContext, traceId: string | undefined, ): Partial | undefined { @@ -90,11 +88,9 @@ function getDsc( // Else, we try to generate a new one const client = getClient(); - const activeSpan = trace.getSpan(context); - const scope = activeSpan ? getSpanScope(activeSpan) : undefined; if (client) { - return getDynamicSamplingContextFromClient(traceId || propagationContext.traceId, client, scope); + return getDynamicSamplingContextFromClient(traceId || propagationContext.traceId, client); } return undefined; diff --git a/packages/profiling-node/clang-format.js b/packages/profiling-node/clang-format.js index 7deb9fb97993..dd001cf28ad7 100644 --- a/packages/profiling-node/clang-format.js +++ b/packages/profiling-node/clang-format.js @@ -3,7 +3,13 @@ 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(' ')}`; -child_process.execSync(cmd); +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...'); diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 6465cbfe5da5..53dad5a23c0c 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -228,7 +228,7 @@ describe('handleSentry', () => { if (key === 'baggage') { return ( 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,' + - 'sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,' + + 'sentry-public_key=dogsarebadatkeepingsecrets,' + 'sentry-trace_id=1234567890abcdef1234567890abcdef,sentry-sample_rate=1' ); } @@ -258,7 +258,6 @@ describe('handleSentry', () => { sample_rate: '1', trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', - user_segment: 'segmentA', }); }); diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 2656b22f685a..388541a8d884 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -56,7 +56,7 @@ function getServerOnlyArgs() { if (key === 'baggage') { return ( 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,' + - 'sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,' + + 'sentry-public_key=dogsarebadatkeepingsecrets,' + 'sentry-trace_id=1234567890abcdef1234567890abcdef,sentry-sample_rate=1' ); } @@ -274,7 +274,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { sample_rate: '1', trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', - user_segment: 'segmentA', }, source: 'route', }, @@ -365,7 +364,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { sample_rate: '1', trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', - user_segment: 'segmentA', }, source: 'url', }, diff --git a/packages/sveltekit/test/server/utils.test.ts b/packages/sveltekit/test/server/utils.test.ts index 8e5c064c338c..3543a1128835 100644 --- a/packages/sveltekit/test/server/utils.test.ts +++ b/packages/sveltekit/test/server/utils.test.ts @@ -11,7 +11,7 @@ const MOCK_REQUEST_EVENT: any = { if (key === 'baggage') { return ( 'sentry-environment=production,sentry-release=1.0.0,sentry-transaction=dogpark,' + - 'sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,' + + 'sentry-public_key=dogsarebadatkeepingsecrets,' + 'sentry-trace_id=1234567890abcdef1234567890abcdef,sentry-sample_rate=1' ); } @@ -41,7 +41,6 @@ describe('getTracePropagationData', () => { sample_rate: '1', trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', - user_segment: 'segmentA', }); }); diff --git a/packages/tracing-internal/src/browser/request.ts b/packages/tracing-internal/src/browser/request.ts index d072188bb2af..57307917cbef 100644 --- a/packages/tracing-internal/src/browser/request.ts +++ b/packages/tracing-internal/src/browser/request.ts @@ -309,8 +309,7 @@ export function xhrCallback( const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || - (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client, scope)), + dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), ); setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader); diff --git a/packages/tracing-internal/src/common/fetch.ts b/packages/tracing-internal/src/common/fetch.ts index e12ca3cf1b97..e3650c13e788 100644 --- a/packages/tracing-internal/src/common/fetch.ts +++ b/packages/tracing-internal/src/common/fetch.ts @@ -145,8 +145,7 @@ export function addTracingHeadersToFetchRequest( const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( - dsc || - (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client, scope)), + dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), ); const headers = diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index 98bb9145a547..05db8f118c70 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -20,10 +20,6 @@ export type DynamicSamplingContext = { release?: string; environment?: string; transaction?: string; - /** - * @deprecated Functonality for segment has been removed. - */ - user_segment?: string; replay_id?: string; sampled?: string; }; diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index c9eeac18ce51..afbb42261ab3 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -7,10 +7,6 @@ export interface User { ip_address?: string; email?: string; username?: string; - /** - * @deprecated Functonality for segment has been removed. Use a custom tag or context instead to capture this information. - */ - segment?: string; } export interface UserFeedback { From 5a2b67640a563b42a24471ec5ea35f41b17b8ad4 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 9 Feb 2024 11:30:32 +0100 Subject: [PATCH 022/173] feat(opentelemetry): Fix & align isolation scope usage in node-experimental (#10570) Co-authored-by: Luca Forstner --- packages/core/src/tracing/trace.ts | 1 + packages/node-experimental/src/sdk/client.ts | 6 +- packages/node-experimental/src/sdk/scope.ts | 3 - .../src/sdk/spanProcessor.ts | 16 +- packages/node-experimental/src/sdk/types.ts | 2 - .../test/integration/scope.test.ts | 8 +- .../test/integration/transactions.test.ts | 157 ++++++++++++++++- .../opentelemetry/src/asyncContextStrategy.ts | 7 +- packages/opentelemetry/src/constants.ts | 2 + packages/opentelemetry/src/contextManager.ts | 39 ++++- packages/opentelemetry/src/custom/client.ts | 4 +- packages/opentelemetry/src/custom/hub.ts | 4 +- .../opentelemetry/src/custom/transaction.ts | 31 +--- packages/opentelemetry/src/index.ts | 3 +- packages/opentelemetry/src/spanExporter.ts | 57 +++--- packages/opentelemetry/src/spanProcessor.ts | 11 +- packages/opentelemetry/src/utils/spanData.ts | 48 ++++-- .../test/custom/transaction.test.ts | 155 +---------------- .../test/integration/scope.test.ts | 163 +++++++++++++++++- .../test/integration/transactions.test.ts | 61 ++++--- 20 files changed, 476 insertions(+), 302 deletions(-) diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 832180ef3c72..a1327d0f737c 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -399,6 +399,7 @@ type SpanWithScopes = Span & { [ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope; }; +/** Store the scope & isolation scope for a span, which can the be used when it is finished. */ function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { if (span) { addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope); diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index 15d5720e4ac9..5e4ccafaa5d3 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -5,7 +5,7 @@ import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { applySdkMetadata } from '@sentry/core'; import type { CaptureContext, Event, EventHint } from '@sentry/types'; -import { Scope, getIsolationScope } from './scope'; +import { Scope } from './scope'; /** A client for using Sentry with Node & OpenTelemetry. */ export class NodeExperimentalClient extends NodeClient { @@ -54,7 +54,7 @@ export class NodeExperimentalClient extends NodeClient { event: Event, hint: EventHint, scope?: Scope, - _isolationScope?: Scope, + isolationScope?: Scope, ): PromiseLike { let actualScope = scope; @@ -64,8 +64,6 @@ export class NodeExperimentalClient extends NodeClient { delete hint.captureContext; } - const isolationScope = _isolationScope || (scope && scope.isolationScope) || getIsolationScope(); - return super._prepareEvent(event, hint, actualScope, isolationScope); } } diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index dfe19e63fdbe..2f8091586fbf 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -72,9 +72,6 @@ export function isInitialized(): boolean { /** A fork of the classic scope with some otel specific stuff. */ export class Scope extends OpenTelemetryScope implements ScopeInterface { - // Overwrite this if you want to use a specific isolation scope here - public isolationScope: Scope | undefined; - protected _client: Client | undefined; protected _lastEventId: string | undefined; diff --git a/packages/node-experimental/src/sdk/spanProcessor.ts b/packages/node-experimental/src/sdk/spanProcessor.ts index a85085077e94..f298d049f94b 100644 --- a/packages/node-experimental/src/sdk/spanProcessor.ts +++ b/packages/node-experimental/src/sdk/spanProcessor.ts @@ -1,13 +1,11 @@ -import type { Context } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import type { Span } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SentrySpanProcessor, getClient, getSpanFinishScope } from '@sentry/opentelemetry'; +import { SentrySpanProcessor, getClient } from '@sentry/opentelemetry'; import type { Http } from '../integrations/http'; import type { NodeFetch } from '../integrations/node-fetch'; import type { NodeExperimentalClient } from '../types'; -import { getIsolationScope } from './api'; import { Scope } from './scope'; /** @@ -18,18 +16,6 @@ export class NodeExperimentalSentrySpanProcessor extends SentrySpanProcessor { super({ scopeClass: Scope }); } - /** @inheritDoc */ - public onStart(span: Span, parentContext: Context): void { - super.onStart(span, parentContext); - - // We need to make sure that we use the correct isolation scope when finishing the span - // so we store it on the span finish scope for later use - const scope = getSpanFinishScope(span) as Scope | undefined; - if (scope) { - scope.isolationScope = getIsolationScope(); - } - } - /** @inheritDoc */ protected _shouldSendSpanToSentry(span: Span): boolean { const client = getClient(); diff --git a/packages/node-experimental/src/sdk/types.ts b/packages/node-experimental/src/sdk/types.ts index 57ad321a5470..37fd80bfab1a 100644 --- a/packages/node-experimental/src/sdk/types.ts +++ b/packages/node-experimental/src/sdk/types.ts @@ -28,8 +28,6 @@ export interface ScopeData { } export interface Scope extends BaseScope { - // @ts-expect-error typeof this is what we want here - isolationScope: typeof this | undefined; // @ts-expect-error typeof this is what we want here clone(scope?: Scope): typeof this; /** diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index 6dfcad34ff81..163bed572f24 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -1,5 +1,5 @@ import { getCurrentScope, setGlobalScope } from '@sentry/core'; -import { getClient, getSpanScope } from '@sentry/opentelemetry'; +import { getClient, getSpanScopes } from '@sentry/opentelemetry'; import * as Sentry from '../../src/'; import type { NodeExperimentalClient } from '../../src/types'; @@ -41,7 +41,11 @@ describe('Integration | Scope', () => { scope2.setTag('tag3', 'val3'); Sentry.startSpan({ name: 'outer' }, span => { - expect(getSpanScope(span)).toBe(enableTracing ? scope2 : undefined); + // TODO: This is "incorrect" until we stop cloning the current scope for setSpanScopes + // Once we change this, the scopes _should_ be the same again + if (enableTracing) { + expect(getSpanScopes(span)?.scope).not.toBe(scope2); + } spanId = span.spanContext().spanId; traceId = span.spanContext().traceId; diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index d379070c4ee1..7de51c7811e6 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -99,7 +99,7 @@ describe('Integration | Transactions', () => { environment: 'production', event_id: expect.any(String), platform: 'node', - sdkProcessingMetadata: { + sdkProcessingMetadata: expect.objectContaining({ dynamicSamplingContext: expect.objectContaining({ environment: 'production', public_key: expect.any(String), @@ -112,7 +112,7 @@ describe('Integration | Transactions', () => { source: 'task', spanMetadata: expect.any(Object), requestPath: 'test-path', - }, + }), server_name: expect.any(String), // spans are circular (they have a reference to the transaction), which leads to jest choking on this // instead we compare them in detail below @@ -329,6 +329,159 @@ describe('Integration | Transactions', () => { ); }); + it('correctly creates concurrent transaction & spans when using native OTEL tracer', async () => { + const beforeSendTransaction = jest.fn(() => null); + + mockSdkInit({ enableTracing: true, beforeSendTransaction }); + + const client = Sentry.getClient(); + + Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); + + Sentry.withIsolationScope(() => { + client.tracer.startActiveSpan('test name', span => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); + + span.setAttributes({ + 'test.outer': 'test value', + }); + + const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); + + Sentry.setTag('test.tag', 'test value'); + + client.tracer.startActiveSpan('inner span 2', innerSpan => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); + + innerSpan.setAttributes({ + 'test.inner': 'test value', + }); + + innerSpan.end(); + }); + + span.end(); + }); + }); + + Sentry.withIsolationScope(() => { + client.tracer.startActiveSpan('test name b', span => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 }); + + span.setAttributes({ + 'test.outer': 'test value b', + }); + + const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1b' }); + subSpan.end(); + + Sentry.setTag('test.tag', 'test value b'); + + client.tracer.startActiveSpan('inner span 2b', innerSpan => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 }); + + innerSpan.setAttributes({ + 'test.inner': 'test value b', + }); + + innerSpan.end(); + }); + + span.end(); + }); + }); + + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(2); + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + breadcrumbs: [ + { message: 'test breadcrumb 1', timestamp: 123456 }, + { message: 'test breadcrumb 2', timestamp: 123456 }, + { message: 'test breadcrumb 3', timestamp: 123456 }, + ], + contexts: expect.objectContaining({ + otel: expect.objectContaining({ + attributes: { + 'test.outer': 'test value', + }, + }), + trace: { + data: { + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }, + span_id: expect.any(String), + status: 'ok', + trace_id: expect.any(String), + origin: 'manual', + }, + }), + spans: [ + expect.objectContaining({ + description: 'inner span 1', + }), + expect.objectContaining({ + description: 'inner span 2', + }), + ], + start_timestamp: expect.any(Number), + tags: { 'test.tag': 'test value' }, + timestamp: expect.any(Number), + transaction: 'test name', + type: 'transaction', + }), + { + event_id: expect.any(String), + }, + ); + + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + breadcrumbs: [ + { message: 'test breadcrumb 1', timestamp: 123456 }, + { message: 'test breadcrumb 2b', timestamp: 123456 }, + { message: 'test breadcrumb 3b', timestamp: 123456 }, + ], + contexts: expect.objectContaining({ + otel: expect.objectContaining({ + attributes: { + 'test.outer': 'test value b', + }, + }), + trace: { + data: { + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }, + span_id: expect.any(String), + status: 'ok', + trace_id: expect.any(String), + origin: 'manual', + }, + }), + spans: [ + expect.objectContaining({ + description: 'inner span 1b', + }), + expect.objectContaining({ + description: 'inner span 2b', + }), + ], + start_timestamp: expect.any(Number), + tags: { 'test.tag': 'test value b' }, + timestamp: expect.any(Number), + transaction: 'test name b', + type: 'transaction', + }), + { + event_id: expect.any(String), + }, + ); + }); + it('correctly creates transaction & spans with a trace header data', async () => { const beforeSendTransaction = jest.fn(() => null); diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index c4cc48c1cfb5..f7523a557766 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -1,6 +1,7 @@ import * as api from '@opentelemetry/api'; import type { Hub, RunWithAsyncContextOptions } from '@sentry/core'; import { setAsyncContextStrategy } from '@sentry/core'; +import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY } from './constants'; import { getHubFromContext } from './utils/contextData'; @@ -30,7 +31,11 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { const ctx = api.context.active(); // We depend on the otelContextManager to handle the context/hub - return api.context.with(ctx, () => { + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not + // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` + return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { return callback(); }); } diff --git a/packages/opentelemetry/src/constants.ts b/packages/opentelemetry/src/constants.ts index 36c8a36f886c..d38f8a91f121 100644 --- a/packages/opentelemetry/src/constants.ts +++ b/packages/opentelemetry/src/constants.ts @@ -8,3 +8,5 @@ export const SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY = createContextKey('SENTRY_P /** Context Key to hold a Hub. */ export const SENTRY_HUB_CONTEXT_KEY = createContextKey('sentry_hub'); + +export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope'); diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index d904a4847a06..14c682da9fb4 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -1,14 +1,28 @@ import type { Context, ContextManager } from '@opentelemetry/api'; -import type { Carrier, Hub } from '@sentry/core'; -import { getCurrentHub, getHubFromCarrier } from '@sentry/core'; -import { ensureHubOnCarrier } from '@sentry/core'; +import type { Hub } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY } from './constants'; +import { OpenTelemetryHub } from './custom/hub'; import { setHubOnContext } from './utils/contextData'; -function createNewHub(parent: Hub | undefined): Hub { - const carrier: Carrier = {}; - ensureHubOnCarrier(carrier, parent); - return getHubFromCarrier(carrier); +function createNewHub(parent: Hub | undefined, shouldForkIsolationScope: boolean): Hub { + if (parent) { + // eslint-disable-next-line deprecation/deprecation + const client = parent.getClient(); + // eslint-disable-next-line deprecation/deprecation + const scope = parent.getScope(); + // eslint-disable-next-line deprecation/deprecation + const isolationScope = parent.getIsolationScope(); + + return new OpenTelemetryHub( + client, + scope.clone(), + shouldForkIsolationScope ? isolationScope.clone() : isolationScope, + ); + } + + return new OpenTelemetryHub(); } // Typescript complains if we do not use `...args: any[]` for the mixin, with: @@ -46,11 +60,18 @@ export function wrapContextManagerClass, ...args: A ): ReturnType { + const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true; + // eslint-disable-next-line deprecation/deprecation const existingHub = getCurrentHub(); - const newHub = createNewHub(existingHub); + const newHub = createNewHub(existingHub, shouldForkIsolationScope); - return super.with(setHubOnContext(context, newHub), fn, thisArg, ...args); + return super.with( + setHubOnContext(context.deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY), newHub), + fn, + thisArg, + ...args, + ); } } diff --git a/packages/opentelemetry/src/custom/client.ts b/packages/opentelemetry/src/custom/client.ts index 5bf39208a79f..62088f8295f1 100644 --- a/packages/opentelemetry/src/custom/client.ts +++ b/packages/opentelemetry/src/custom/client.ts @@ -71,7 +71,7 @@ export function wrapClientClass< event: Event, hint: EventHint, scope?: Scope, - _isolationScope?: Scope, + isolationScope?: Scope, ): PromiseLike { let actualScope = scope; @@ -81,7 +81,7 @@ export function wrapClientClass< delete hint.captureContext; } - return super._prepareEvent(event, hint, actualScope); + return super._prepareEvent(event, hint, actualScope, isolationScope); } } diff --git a/packages/opentelemetry/src/custom/hub.ts b/packages/opentelemetry/src/custom/hub.ts index 6f1554eda7d2..74e4c3c7b90e 100644 --- a/packages/opentelemetry/src/custom/hub.ts +++ b/packages/opentelemetry/src/custom/hub.ts @@ -10,8 +10,8 @@ import { OpenTelemetryScope } from './scope'; * Exported only for testing */ export class OpenTelemetryHub extends Hub { - public constructor(client?: Client, scope: Scope = new OpenTelemetryScope()) { - super(client, scope); + public constructor(client?: Client, scope: Scope = new OpenTelemetryScope(), isolationScope?: Scope) { + super(client, scope, isolationScope); } } diff --git a/packages/opentelemetry/src/custom/transaction.ts b/packages/opentelemetry/src/custom/transaction.ts index fe5001f8b050..ceb950bc610d 100644 --- a/packages/opentelemetry/src/custom/transaction.ts +++ b/packages/opentelemetry/src/custom/transaction.ts @@ -1,7 +1,6 @@ import type { Hub } from '@sentry/core'; import { Transaction } from '@sentry/core'; -import type { ClientOptions, Hub as HubInterface, Scope, TransactionContext } from '@sentry/types'; -import { uuid4 } from '@sentry/utils'; +import type { ClientOptions, Hub as HubInterface, TransactionContext } from '@sentry/types'; /** * This is a fork of core's tracing/hubextensions.ts _startTransaction, @@ -13,7 +12,7 @@ export function startTransaction(hub: HubInterface, transactionContext: Transact const options: Partial = (client && client.getOptions()) || {}; // eslint-disable-next-line deprecation/deprecation - const transaction = new OpenTelemetryTransaction(transactionContext, hub as Hub); + const transaction = new Transaction(transactionContext, hub as Hub); // Since we do not do sampling here, we assume that this is _always_ sampled // Any sampling decision happens in OpenTelemetry's sampler transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); @@ -23,29 +22,3 @@ export function startTransaction(hub: HubInterface, transactionContext: Transact } return transaction; } - -/** - * This is a fork of the base Transaction with OTEL specific stuff added. - */ -export class OpenTelemetryTransaction extends Transaction { - /** - * Finish the transaction, but apply the given scope instead of the current one. - */ - public finishWithScope(endTimestamp?: number, scope?: Scope): string | undefined { - const event = this._finishTransaction(endTimestamp); - - if (!event) { - return undefined; - } - - // eslint-disable-next-line deprecation/deprecation - const client = this._hub.getClient(); - - if (!client) { - return undefined; - } - - const eventId = uuid4(); - return client.captureEvent(event, { event_id: eventId }, scope); - } -} diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index 602dcdda6234..f0b43ab8a386 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -10,9 +10,8 @@ export { getSpanHub, getSpanMetadata, getSpanParent, - getSpanScope, setSpanMetadata, - getSpanFinishScope, + getSpanScopes, } from './utils/spanData'; export { getPropagationContextFromContext, setPropagationContextOnContext, setHubOnContext } from './utils/contextData'; diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 97a7a77c26dc..1f6792932dcd 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -3,12 +3,12 @@ import type { ExportResult } from '@opentelemetry/core'; import { ExportResultCode } from '@opentelemetry/core'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentHub, getCurrentScope } from '@sentry/core'; +import type { Transaction } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentHub } from '@sentry/core'; import type { Scope, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import type { OpenTelemetryTransaction } from './custom/transaction'; +import { addNonEnumerableProperty, logger } from '@sentry/utils'; import { startTransaction } from './custom/transaction'; + import { DEBUG_BUILD } from './debug-build'; import { InternalSentrySemanticAttributes } from './semanticAttributes'; import { convertOtelTimeToSeconds } from './utils/convertOtelTimeToSeconds'; @@ -17,7 +17,7 @@ import type { SpanNode } from './utils/groupSpansWithParents'; import { groupSpansWithParents } from './utils/groupSpansWithParents'; import { mapStatus } from './utils/mapStatus'; import { parseSpanDescription } from './utils/parseSpanDescription'; -import { getSpanFinishScope, getSpanHub, getSpanMetadata, getSpanScope } from './utils/spanData'; +import { getSpanHub, getSpanMetadata, getSpanScopes } from './utils/spanData'; type SpanNodeCompleted = SpanNode & { span: ReadableSpan }; @@ -107,10 +107,7 @@ function maybeSend(spans: ReadableSpan[]): ReadableSpan[] { createAndFinishSpanForOtelSpan(child, transaction, remaining); }); - // Now finish the transaction, which will send it together with all the spans - // We make sure to use the finish scope - const scope = getScopeForTransactionFinish(span); - transaction.finishWithScope(convertOtelTimeToSeconds(span.endTime), scope); + transaction.end(span.endTime); }); return Array.from(remaining) @@ -118,17 +115,6 @@ function maybeSend(spans: ReadableSpan[]): ReadableSpan[] { .filter((span): span is ReadableSpan => !!span); } -function getScopeForTransactionFinish(span: ReadableSpan): Scope { - // The finish scope should normally always be there (and it is already a clone), - // but for the sake of type safety we fall back to a clone of the current scope - const scope = getSpanFinishScope(span) || getCurrentScope().clone(); - scope.setContext('otel', { - attributes: removeSentryAttributes(span.attributes), - resource: span.resource.attributes, - }); - return scope; -} - function getCompletedRootNodes(nodes: SpanNode[]): SpanNodeCompleted[] { return nodes.filter((node): node is SpanNodeCompleted => !!node.span && !node.parentNode); } @@ -148,8 +134,7 @@ function parseSpan(span: ReadableSpan): { op?: string; origin?: SpanOrigin; sour return { origin, op, source }; } -function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransaction { - const scope = getSpanScope(span); +function createTransactionForOtelSpan(span: ReadableSpan): Transaction { // eslint-disable-next-line deprecation/deprecation const hub = getSpanHub(span) || getCurrentHub(); const spanContext = span.spanContext(); @@ -158,10 +143,10 @@ function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransact const parentSpanId = span.parentSpanId; const parentSampled = span.attributes[InternalSentrySemanticAttributes.PARENT_SAMPLED] as boolean | undefined; - const dynamicSamplingContext = scope ? scope.getPropagationContext().dsc : undefined; const { op, description, tags, data, origin, source } = getSpanData(span); const metadata = getSpanMetadata(span); + const capturedSpanScopes = getSpanScopes(span); const transaction = startTransaction(hub, { spanId, @@ -174,7 +159,6 @@ function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransact status: mapStatus(span), startTimestamp: convertOtelTimeToSeconds(span.startTime), metadata: { - dynamicSamplingContext, source, sampleRate: span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] as number | undefined, ...metadata, @@ -183,7 +167,19 @@ function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransact origin, tags, sampled: true, - }) as OpenTelemetryTransaction; + }); + + // We currently don't want to write this to the scope because it would mutate it. + // In the future we will likely have some sort of transaction payload factory where we can pass this context in directly + // eslint-disable-next-line deprecation/deprecation + transaction.setContext('otel', { + attributes: removeSentryAttributes(span.attributes), + resource: span.resource.attributes, + }); + + if (capturedSpanScopes) { + setCapturedScopesOnTransaction(transaction, capturedSpanScopes.scope, capturedSpanScopes.isolationScope); + } return transaction; } @@ -311,3 +307,14 @@ function getData(span: ReadableSpan): Record { return data; } + +const SCOPE_ON_START_SPAN_FIELD = '_sentryScope'; +const ISOLATION_SCOPE_ON_START_SPAN_FIELD = '_sentryIsolationScope'; + +/** Sets the scope and isolation scope to be used for when the transaction is finished. */ +function setCapturedScopesOnTransaction(span: Transaction, scope: Scope, isolationScope: Scope): void { + if (span) { + addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope); + addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope); + } +} diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index 7cb452a03d98..83aac51f9391 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -10,7 +10,7 @@ import { DEBUG_BUILD } from './debug-build'; import { SentrySpanExporter } from './spanExporter'; import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; import { getHubFromContext } from './utils/contextData'; -import { getSpanHub, setSpanFinishScope, setSpanHub, setSpanParent, setSpanScope } from './utils/spanData'; +import { getSpanHub, setSpanHub, setSpanParent, setSpanScopes } from './utils/spanData'; function onSpanStart(span: Span, parentContext: Context, _ScopeClass: typeof OpenTelemetryScope): void { // This is a reliable way to get the parent span - because this is exactly how the parent is identified in the OTEL SDK @@ -34,13 +34,18 @@ function onSpanStart(span: Span, parentContext: Context, _ScopeClass: typeof Ope if (actualHub) { // eslint-disable-next-line deprecation/deprecation const scope = actualHub.getScope(); - setSpanScope(span, scope); + // eslint-disable-next-line deprecation/deprecation + const isolationScope = actualHub.getIsolationScope(); setSpanHub(span, actualHub); // Use this scope for finishing the span + // TODO: For now we need to clone this, as we need to store the `activeSpan` on it + // Once we can get rid of this (when we move breadcrumbs to the isolation scope), + // we can stop cloning this here const finishScope = (scope as OpenTelemetryScope).clone(); + // this is needed for breadcrumbs, for now, as they are stored on the span currently finishScope.activeSpan = span; - setSpanFinishScope(span, finishScope); + setSpanScopes(span, { scope: finishScope, isolationScope }); } } diff --git a/packages/opentelemetry/src/utils/spanData.ts b/packages/opentelemetry/src/utils/spanData.ts index 18d9661a6488..174573bd19f9 100644 --- a/packages/opentelemetry/src/utils/spanData.ts +++ b/packages/opentelemetry/src/utils/spanData.ts @@ -3,25 +3,20 @@ import type { Hub, Scope, TransactionMetadata } from '@sentry/types'; import type { AbstractSpan } from '../types'; -// We store the parent span, scope & metadata in separate weakmaps, so we can access them for a given span +// We store the parent span, scopes & metadata in separate weakmaps, so we can access them for a given span // This way we can enhance the data that an OTEL Span natively gives us // and since we are using weakmaps, we do not need to clean up after ourselves -const SpanScope = new WeakMap(); -const SpanFinishScope = new WeakMap(); +const SpanScopes = new WeakMap< + AbstractSpan, + { + scope: Scope; + isolationScope: Scope; + } +>(); const SpanHub = new WeakMap(); const SpanParent = new WeakMap(); const SpanMetadata = new WeakMap>(); -/** Set the Sentry scope on an OTEL span. */ -export function setSpanScope(span: AbstractSpan, scope: Scope): void { - SpanScope.set(span, scope); -} - -/** Get the Sentry scope of an OTEL span. */ -export function getSpanScope(span: AbstractSpan): Scope | undefined { - return SpanScope.get(span); -} - /** Set the Sentry hub on an OTEL span. */ export function setSpanHub(span: AbstractSpan, hub: Hub): void { SpanHub.set(span, hub); @@ -52,12 +47,27 @@ export function getSpanMetadata(span: AbstractSpan): Partial { - afterEach(() => { - jest.resetAllMocks(); - }); - - it('works with finishWithScope without arguments', () => { - const client = new TestClient(getDefaultTestClientOptions()); - - const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); - - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - setCurrentClient(client); - client.init(); - - // eslint-disable-next-line deprecation/deprecation - const transaction = new OpenTelemetryTransaction({ name: 'test', sampled: true }, hub); - - const res = transaction.finishWithScope(); - - expect(mockSend).toBeCalledTimes(1); - expect(mockSend).toBeCalledWith( - expect.objectContaining({ - contexts: { - trace: { - data: { - 'sentry.origin': 'manual', - }, - span_id: expect.any(String), - trace_id: expect.any(String), - origin: 'manual', - }, - }, - spans: [], - start_timestamp: expect.any(Number), - tags: {}, - timestamp: expect.any(Number), - transaction: 'test', - type: 'transaction', - sdkProcessingMetadata: { - source: 'custom', - spanMetadata: {}, - dynamicSamplingContext: { - environment: 'production', - trace_id: expect.any(String), - transaction: 'test', - sampled: 'true', - }, - }, - transaction_info: { source: 'custom' }, - }), - { event_id: expect.any(String) }, - undefined, - ); - expect(res).toBe('mocked'); - }); - - it('works with finishWithScope with endTime', () => { - const client = new TestClient(getDefaultTestClientOptions()); - - const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); - - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - setCurrentClient(client); - client.init(); - - // eslint-disable-next-line deprecation/deprecation - const transaction = new OpenTelemetryTransaction({ name: 'test', startTimestamp: 123456, sampled: true }, hub); - - const res = transaction.finishWithScope(1234567); - - expect(mockSend).toBeCalledTimes(1); - expect(mockSend).toBeCalledWith( - expect.objectContaining({ - start_timestamp: 123456, - timestamp: 1234567, - }), - { event_id: expect.any(String) }, - undefined, - ); - expect(res).toBe('mocked'); - }); - - it('works with finishWithScope with endTime & scope', () => { - const client = new TestClient(getDefaultTestClientOptions()); - - const mockSend = jest.spyOn(client, 'captureEvent').mockImplementation(() => 'mocked'); - - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - setCurrentClient(client); - client.init(); - - // eslint-disable-next-line deprecation/deprecation - const transaction = new OpenTelemetryTransaction({ name: 'test', startTimestamp: 123456, sampled: true }, hub); - - const scope = new OpenTelemetryScope(); - scope.setTags({ - tag1: 'yes', - tag2: 'no', - }); - scope.setContext('os', { name: 'Custom OS' }); - - const res = transaction.finishWithScope(1234567, scope); - - expect(mockSend).toBeCalledTimes(1); - expect(mockSend).toBeCalledWith( - expect.objectContaining({ - contexts: { - trace: { - data: { - 'sentry.origin': 'manual', - }, - span_id: expect.any(String), - trace_id: expect.any(String), - origin: 'manual', - }, - }, - spans: [], - start_timestamp: 123456, - tags: {}, - timestamp: 1234567, - transaction: 'test', - type: 'transaction', - sdkProcessingMetadata: { - source: 'custom', - spanMetadata: {}, - dynamicSamplingContext: { - environment: 'production', - trace_id: expect.any(String), - transaction: 'test', - sampled: 'true', - }, - }, - transaction_info: { source: 'custom' }, - }), - { event_id: expect.any(String) }, - scope, - ); - expect(res).toBe('mocked'); - }); -}); - describe('startTranscation', () => { afterEach(() => { jest.resetAllMocks(); }); - it('creates a NodeExperimentalTransaction', () => { + it('creates a Transaction', () => { const client = new TestClient(getDefaultTestClientOptions()); // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); @@ -161,7 +16,7 @@ describe('startTranscation', () => { const transaction = startTransaction(hub, { name: 'test' }); - expect(transaction).toBeInstanceOf(OpenTelemetryTransaction); + expect(transaction).toBeInstanceOf(Transaction); expect(transaction['_sampled']).toBe(undefined); // eslint-disable-next-line deprecation/deprecation expect(transaction.spanRecorder).toBeDefined(); @@ -197,7 +52,7 @@ describe('startTranscation', () => { traceId: 'trace1', }); - expect(transaction).toBeInstanceOf(OpenTelemetryTransaction); + expect(transaction).toBeInstanceOf(Transaction); // eslint-disable-next-line deprecation/deprecation expect(transaction.metadata).toEqual({ source: 'custom', diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index 791f93d75c54..0b9bac627491 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -1,9 +1,18 @@ -import { captureException, getClient, getCurrentHub, getCurrentScope, setTag, withScope } from '@sentry/core'; +import { + captureException, + getClient, + getCurrentHub, + getCurrentScope, + getIsolationScope, + setTag, + withIsolationScope, + withScope, +} from '@sentry/core'; import { OpenTelemetryHub } from '../../src/custom/hub'; import { OpenTelemetryScope } from '../../src/custom/scope'; import { startSpan } from '../../src/trace'; -import { getSpanScope } from '../../src/utils/spanData'; +import { getSpanScopes } from '../../src/utils/spanData'; import type { TestClientInterface } from '../helpers/TestClient'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; @@ -48,7 +57,11 @@ describe('Integration | Scope', () => { scope2.setTag('tag3', 'val3'); startSpan({ name: 'outer' }, span => { - expect(getSpanScope(span)).toBe(enableTracing ? scope2 : undefined); + // TODO: This is "incorrect" until we stop cloning the current scope for setSpanScopes + // Once we change this, the scopes _should_ be the same again + if (enableTracing) { + expect(getSpanScopes(span)?.scope).not.toBe(scope2); + } spanId = span.spanContext().spanId; traceId = span.spanContext().traceId; @@ -111,6 +124,7 @@ describe('Integration | Scope', () => { tag1: 'val1', tag2: 'val2', tag3: 'val3', + tag4: 'val4', }, timestamp: expect.any(Number), transaction: 'outer', @@ -124,7 +138,7 @@ describe('Integration | Scope', () => { } }); - it('isolates parallel root scopes', async () => { + it('isolates parallel scopes', async () => { const beforeSend = jest.fn(() => null); const beforeSendTransaction = jest.fn(() => null); @@ -147,13 +161,19 @@ describe('Integration | Scope', () => { rootScope.setTag('tag1', 'val1'); + const initialIsolationScope = getIsolationScope(); + withScope(scope1 => { scope1.setTag('tag2', 'val2a'); + expect(getIsolationScope()).toBe(initialIsolationScope); + withScope(scope2 => { scope2.setTag('tag3', 'val3a'); startSpan({ name: 'outer' }, span => { + expect(getIsolationScope()).toBe(initialIsolationScope); + spanId1 = span.spanContext().spanId; traceId1 = span.spanContext().traceId; @@ -167,10 +187,141 @@ describe('Integration | Scope', () => { withScope(scope1 => { scope1.setTag('tag2', 'val2b'); + expect(getIsolationScope()).toBe(initialIsolationScope); + withScope(scope2 => { scope2.setTag('tag3', 'val3b'); startSpan({ name: 'outer' }, span => { + expect(getIsolationScope()).toBe(initialIsolationScope); + + spanId2 = span.spanContext().spanId; + traceId2 = span.spanContext().traceId; + + setTag('tag4', 'val4b'); + + captureException(error2); + }); + }); + }); + + await client.flush(); + + expect(beforeSend).toHaveBeenCalledTimes(2); + expect(beforeSend).toHaveBeenCalledWith( + expect.objectContaining({ + contexts: expect.objectContaining({ + trace: spanId1 + ? { + span_id: spanId1, + trace_id: traceId1, + } + : expect.any(Object), + }), + tags: { + tag1: 'val1', + tag2: 'val2a', + tag3: 'val3a', + tag4: 'val4a', + }, + }), + { + event_id: expect.any(String), + originalException: error1, + syntheticException: expect.any(Error), + }, + ); + + expect(beforeSend).toHaveBeenCalledWith( + expect.objectContaining({ + contexts: expect.objectContaining({ + trace: spanId2 + ? { + span_id: spanId2, + trace_id: traceId2, + parent_span_id: undefined, + } + : expect.any(Object), + }), + tags: { + tag1: 'val1', + tag2: 'val2b', + tag3: 'val3b', + tag4: 'val4b', + }, + }), + { + event_id: expect.any(String), + originalException: error2, + syntheticException: expect.any(Error), + }, + ); + + if (enableTracing) { + expect(beforeSendTransaction).toHaveBeenCalledTimes(2); + } + }); + + it('isolates parallel isolation scopes', async () => { + const beforeSend = jest.fn(() => null); + const beforeSendTransaction = jest.fn(() => null); + + mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); + + // eslint-disable-next-line deprecation/deprecation + const hub = getCurrentHub(); + const client = getClient() as TestClientInterface; + const rootScope = getCurrentScope(); + + expect(hub).toBeInstanceOf(OpenTelemetryHub); + expect(rootScope).toBeInstanceOf(OpenTelemetryScope); + + const error1 = new Error('test error 1'); + const error2 = new Error('test error 2'); + let spanId1: string | undefined; + let spanId2: string | undefined; + let traceId1: string | undefined; + let traceId2: string | undefined; + + rootScope.setTag('tag1', 'val1'); + + const initialIsolationScope = getIsolationScope(); + initialIsolationScope.setTag('isolationTag1', 'val1'); + + withIsolationScope(scope1 => { + scope1.setTag('tag2', 'val2a'); + + expect(getIsolationScope()).not.toBe(initialIsolationScope); + getIsolationScope().setTag('isolationTag2', 'val2'); + + withScope(scope2 => { + scope2.setTag('tag3', 'val3a'); + + startSpan({ name: 'outer' }, span => { + expect(getIsolationScope()).not.toBe(initialIsolationScope); + + spanId1 = span.spanContext().spanId; + traceId1 = span.spanContext().traceId; + + setTag('tag4', 'val4a'); + + captureException(error1); + }); + }); + }); + + withIsolationScope(scope1 => { + scope1.setTag('tag2', 'val2b'); + + expect(getIsolationScope()).not.toBe(initialIsolationScope); + getIsolationScope().setTag('isolationTag2', 'val2b'); + + withScope(scope2 => { + scope2.setTag('tag3', 'val3b'); + + startSpan({ name: 'outer' }, span => { + expect(getIsolationScope()).not.toBe(initialIsolationScope); + spanId2 = span.spanContext().spanId; traceId2 = span.spanContext().traceId; @@ -199,6 +350,8 @@ describe('Integration | Scope', () => { tag2: 'val2a', tag3: 'val3a', tag4: 'val4a', + isolationTag1: 'val1', + isolationTag2: 'val2', }, }), { @@ -224,6 +377,8 @@ describe('Integration | Scope', () => { tag2: 'val2b', tag3: 'val3b', tag4: 'val4b', + isolationTag1: 'val1', + isolationTag2: 'val2b', }, }), { diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index d131f91ba0dc..f228d545861b 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -1,6 +1,6 @@ import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { addBreadcrumb, getClient, setTag } from '@sentry/core'; +import { addBreadcrumb, getClient, setTag, withIsolationScope } from '@sentry/core'; import type { PropagationContext, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -123,6 +123,7 @@ describe('Integration | Transactions', () => { start_timestamp: expect.any(Number), tags: { 'outer.tag': 'test value', + 'test.tag': 'test value', }, timestamp: expect.any(Number), transaction: 'test name', @@ -173,7 +174,7 @@ describe('Integration | Transactions', () => { ]); }); - it('correctly creates concurrent transaction & spans', async () => { + it('correctly creates concurrent transaction & spans xxx', async () => { const beforeSendTransaction = jest.fn(() => null); mockSdkInit({ enableTracing: true, beforeSendTransaction }); @@ -182,44 +183,48 @@ describe('Integration | Transactions', () => { addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); - startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => { - addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); + withIsolationScope(() => { + startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => { + addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); - span.setAttributes({ - 'test.outer': 'test value', - }); + span.setAttributes({ + 'test.outer': 'test value', + }); - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); + const subSpan = startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); - setTag('test.tag', 'test value'); + setTag('test.tag', 'test value'); - startSpan({ name: 'inner span 2' }, innerSpan => { - addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); + startSpan({ name: 'inner span 2' }, innerSpan => { + addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); - innerSpan.setAttributes({ - 'test.inner': 'test value', + innerSpan.setAttributes({ + 'test.inner': 'test value', + }); }); }); }); - startSpan({ op: 'test op b', name: 'test name b' }, span => { - addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 }); + withIsolationScope(() => { + startSpan({ op: 'test op b', name: 'test name b' }, span => { + addBreadcrumb({ message: 'test breadcrumb 2b', timestamp: 123456 }); - span.setAttributes({ - 'test.outer': 'test value b', - }); + span.setAttributes({ + 'test.outer': 'test value b', + }); - const subSpan = startInactiveSpan({ name: 'inner span 1b' }); - subSpan.end(); + const subSpan = startInactiveSpan({ name: 'inner span 1b' }); + subSpan.end(); - setTag('test.tag', 'test value b'); + setTag('test.tag', 'test value b'); - startSpan({ name: 'inner span 2b' }, innerSpan => { - addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 }); + startSpan({ name: 'inner span 2b' }, innerSpan => { + addBreadcrumb({ message: 'test breadcrumb 3b', timestamp: 123456 }); - innerSpan.setAttributes({ - 'test.inner': 'test value b', + innerSpan.setAttributes({ + 'test.inner': 'test value b', + }); }); }); }); @@ -262,7 +267,7 @@ describe('Integration | Transactions', () => { }), ], start_timestamp: expect.any(Number), - tags: {}, + tags: { 'test.tag': 'test value' }, timestamp: expect.any(Number), transaction: 'test name', transaction_info: { source: 'task' }, @@ -308,7 +313,7 @@ describe('Integration | Transactions', () => { }), ], start_timestamp: expect.any(Number), - tags: {}, + tags: { 'test.tag': 'test value b' }, timestamp: expect.any(Number), transaction: 'test name b', transaction_info: { source: 'custom' }, From 90b45f06c9765b899dc68a8ac828b9816f86cb82 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 9 Feb 2024 12:03:24 +0100 Subject: [PATCH 023/173] feat(core): Deprecate the `Hub` constructor (#10574) Co-authored-by: Francesco Novy --- MIGRATION.md | 39 ++++++++++++++++++ .../bun/test/integrations/bunserver.test.ts | 1 + packages/core/src/hub.ts | 41 +++++++++++++++++++ packages/core/test/lib/base.test.ts | 9 ++++ packages/core/test/lib/integration.test.ts | 4 ++ packages/core/test/lib/scope.test.ts | 1 + packages/core/test/lib/sdk.test.ts | 1 + .../tracing/dynamicSamplingContext.test.ts | 1 + packages/core/test/lib/tracing/errors.test.ts | 1 + packages/core/test/lib/tracing/trace.test.ts | 4 ++ .../deno/test/__snapshots__/mod.test.ts.snap | 4 +- packages/deno/test/mod.test.ts | 1 + packages/node-experimental/src/sdk/hub.ts | 2 +- packages/node/test/handlers.test.ts | 14 +++++++ packages/node/test/integrations/http.test.ts | 7 ++++ .../node/test/integrations/undici.test.ts | 2 + .../test/propagator.test.ts | 3 +- .../test/spanprocessor.test.ts | 4 ++ .../opentelemetry/test/propagator.test.ts | 4 +- packages/sveltekit/test/server/handle.test.ts | 1 + .../test/browser/backgroundtab.test.ts | 1 + 21 files changed, 140 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 1e9c993e7cd1..66114f62b03d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -289,6 +289,45 @@ If you are using the `Hub` right now, see the following table on how to migrate | endSession() | `Sentry.endSession()` | | shouldSendDefaultPii() | REMOVED - The closest equivalent is `Sentry.getClient().getOptions().sendDefaultPii` | +The `Hub` constructor is also deprecated and will be removed in the next major version. If you are creating Hubs for +multi-client use like so: + +```ts +// OLD +const hub = new Hub(); +hub.bindClient(client); +makeMain(hub); +``` + +instead initialize the client as follows: + +```ts +// NEW +Sentry.withIsolationScope(() => { + Sentry.setCurrentClient(client); + client.init(); +}); +``` + +If you are using the Hub to capture events like so: + +```ts +// OLD +const client = new Client(); +const hub = new Hub(client); +hub.captureException(); +``` + +instead capture isolated events as follows: + +```ts +// NEW +const client = new Client(); +const scope = new Scope(); +scope.setClient(client); +scope.captureException(); +``` + ## Deprecate `client.setupIntegrations()` Instead, use the new `client.init()` method. You should probably not use this directly and instead use `Sentry.init()`, diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index bd62881b8ccf..fd55e56ff50f 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -19,6 +19,7 @@ describe('Bun Serve Integration', () => { beforeEach(() => { const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true }); client = new BunClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 79359657cc8b..f8f274c9e091 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -110,6 +110,7 @@ function createHub(...options: ConstructorParameters): Hub { return sentry.createHub(...options); } + // eslint-disable-next-line deprecation/deprecation return new Hub(...options); } @@ -132,6 +133,46 @@ export class Hub implements HubInterface { * @param client bound to the hub. * @param scope bound to the hub. * @param version number, higher number means higher priority. + * + * @deprecated Instantiation of Hub objects is deprecated and the constructor will be removed in version 8 of the SDK. + * + * If you are currently using the Hub for multi-client use like so: + * + * ``` + * // OLD + * const hub = new Hub(); + * hub.bindClient(client); + * makeMain(hub) + * ``` + * + * instead initialize the client as follows: + * + * ``` + * // NEW + * Sentry.withIsolationScope(() => { + * Sentry.setCurrentClient(client); + * client.init(); + * }); + * ``` + * + * If you are using the Hub to capture events like so: + * + * ``` + * // OLD + * const client = new Client(); + * const hub = new Hub(client); + * hub.captureException() + * ``` + * + * instead capture isolated events as follows: + * + * ``` + * // NEW + * const client = new Client(); + * const scope = new Scope(); + * scope.setClient(client); + * scope.captureException(); + * ``` */ public constructor( client?: Client, diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 197ffa189779..855ea73da056 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -119,6 +119,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({}); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); scope.addBreadcrumb({ message: 'hello' }, 100); @@ -134,6 +135,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({}); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); scope.addBreadcrumb({ message: 'hello' }, 100); @@ -149,6 +151,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); scope.addBreadcrumb({ message: 'hello' }, 100); @@ -165,6 +168,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({}); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); scope.addBreadcrumb({ message: 'hello' }); @@ -181,6 +185,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); // eslint-disable-next-line deprecation/deprecation @@ -196,6 +201,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); // eslint-disable-next-line deprecation/deprecation @@ -211,6 +217,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); // eslint-disable-next-line deprecation/deprecation @@ -226,6 +233,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); // eslint-disable-next-line deprecation/deprecation @@ -620,6 +628,7 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, maxBreadcrumbs: 1 }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); // eslint-disable-next-line deprecation/deprecation hub.addBreadcrumb({ message: '1' }); diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index e819f9413aec..a86c83903152 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -617,6 +617,7 @@ describe('addIntegration', () => { } const client = getTestClient(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -635,6 +636,7 @@ describe('addIntegration', () => { setupOnce = jest.fn(); } + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -660,6 +662,7 @@ describe('addIntegration', () => { } const client = getTestClient(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -683,6 +686,7 @@ describe('addIntegration', () => { } const client = getTestClient(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 4b4242ce7dc6..88e275cc84dd 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -522,6 +522,7 @@ describe('withActiveSpan()', () => { const options = getDefaultTestClientOptions({ enableTracing: true }); const client = new TestClient(options); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); makeMain(hub); // eslint-disable-line deprecation/deprecation }); diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index c9d18c02c78e..c56bb8b11620 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -87,6 +87,7 @@ describe('SDK', () => { describe('captureCheckIn', () => { afterEach(function () { + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts index 01c99e3b87fd..db79c8850200 100644 --- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts +++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts @@ -9,6 +9,7 @@ describe('getDynamicSamplingContextFromSpan', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, release: '1.0.1' }); const client = new TestClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index b83820865ede..35a80f60265a 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -34,6 +34,7 @@ describe('registerErrorHandlers()', () => { mockAddGlobalErrorInstrumentationHandler.mockClear(); mockAddGlobalUnhandledRejectionInstrumentationHandler.mockClear(); const options = getDefaultBrowserClientOptions({ enableTracing: true }); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new BrowserClient(options)); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 4c9190e56b6a..bb5302bb54db 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -36,6 +36,7 @@ describe('startSpan', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); client = new TestClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -427,6 +428,7 @@ describe('startSpanManual', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -537,6 +539,7 @@ describe('startInactiveSpan', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -662,6 +665,7 @@ describe('continueTrace', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); client = new TestClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/deno/test/__snapshots__/mod.test.ts.snap b/packages/deno/test/__snapshots__/mod.test.ts.snap index 607d87b968bc..d728072d38d6 100644 --- a/packages/deno/test/__snapshots__/mod.test.ts.snap +++ b/packages/deno/test/__snapshots__/mod.test.ts.snap @@ -82,7 +82,7 @@ snapshot[`captureException 1`] = ` filename: "app:///test/mod.test.ts", function: "", in_app: true, - lineno: 46, + lineno: 47, post_context: [ "", " await delay(200);", @@ -108,7 +108,7 @@ snapshot[`captureException 1`] = ` filename: "app:///test/mod.test.ts", function: "something", in_app: true, - lineno: 43, + lineno: 44, post_context: [ " }", "", diff --git a/packages/deno/test/mod.test.ts b/packages/deno/test/mod.test.ts index 657ce0a1f233..aae0963b8da5 100644 --- a/packages/deno/test/mod.test.ts +++ b/packages/deno/test/mod.test.ts @@ -22,6 +22,7 @@ function getTestClient( }); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); return [hub, client]; diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index cce73a81d71f..13eb7aeb7707 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -138,7 +138,7 @@ export function getCurrentHub(): Hub { */ export function makeMain(hub: Hub): Hub { // eslint-disable-next-line no-console - console.warn('makeMain is a noop in @sentry/node-experimental. Use `setCurrentScope` instead.'); + console.warn('makeMain is a noop in @sentry/node-experimental. Use `setCurrentClient` instead.'); return hub; } diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 8438a3fc2acd..34e00f06b9c6 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -66,6 +66,7 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', () => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -80,6 +81,7 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not set requestSession, when handling a request', () => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -94,6 +96,7 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, calls _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -116,6 +119,7 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not call _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -162,6 +166,7 @@ describe('requestHandler', () => { }); it('stores request and request data options in `sdkProcessingMetadata`', () => { + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(getDefaultNodeClientOptions())); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); mockAsyncContextStrategy(() => hub); @@ -198,6 +203,7 @@ describe('tracingHandler', () => { } beforeEach(() => { + // eslint-disable-next-line deprecation/deprecation hub = new Hub(new NodeClient(getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }))); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -322,6 +328,7 @@ describe('tracingHandler', () => { it('extracts request data for sampling context', () => { const tracesSampler = jest.fn(); const options = getDefaultNodeClientOptions({ tracesSampler }); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); mockAsyncContextStrategy(() => hub); @@ -344,6 +351,7 @@ describe('tracingHandler', () => { it('puts its transaction on the scope', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -474,6 +482,7 @@ describe('tracingHandler', () => { it('stores request in transaction metadata', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -532,6 +541,7 @@ describe('errorHandler()', () => { client.initSessionFlusher(); const scope = getCurrentScope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(client, '_captureRequestSession'); @@ -548,6 +558,7 @@ describe('errorHandler()', () => { client = new NodeClient(options); const scope = getCurrentScope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(client, '_captureRequestSession'); @@ -566,6 +577,7 @@ describe('errorHandler()', () => { // by the`requestHandler`) client.initSessionFlusher(); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); mockAsyncContextStrategy(() => hub); @@ -588,6 +600,7 @@ describe('errorHandler()', () => { // by the`requestHandler`) client.initSessionFlusher(); const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); jest.spyOn(client, '_captureRequestSession'); @@ -602,6 +615,7 @@ describe('errorHandler()', () => { const options = getDefaultNodeClientOptions({}); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); mockAsyncContextStrategy(() => hub); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index dc2fbc69a37f..b7e2149c74c9 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -66,6 +66,7 @@ describe('tracing', () => { ...customOptions, }); const client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -269,6 +270,7 @@ describe('tracing', () => { environment: 'production', instrumenter: 'otel', }); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); // eslint-disable-next-line deprecation/deprecation @@ -370,6 +372,7 @@ describe('tracing', () => { }); const client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -621,6 +624,7 @@ describe('default protocols', () => { }, }); const client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -708,6 +712,7 @@ describe('httpIntegration', () => { environment: 'production', }); const client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -768,6 +773,7 @@ describe('httpIntegration', () => { describe('_shouldCreateSpans', () => { beforeEach(function () { + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -789,6 +795,7 @@ describe('_shouldCreateSpans', () => { describe('_getShouldCreateSpanForRequest', () => { beforeEach(function () { + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 1decb76006fd..e5aef807190f 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -34,6 +34,7 @@ const DEFAULT_OPTIONS = getDefaultNodeClientOptions({ beforeEach(() => { const client = new NodeClient(DEFAULT_OPTIONS); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -386,6 +387,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { environment: 'production', }); const client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts index ddd4594c5157..494b3aff7f07 100644 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ b/packages/opentelemetry-node/test/propagator.test.ts @@ -47,7 +47,8 @@ describe('SentryPropagator', () => { }), }; // @ts-expect-error Use mock client for unit tests - const hub: Hub = new Hub(client); + // eslint-disable-next-line deprecation/deprecation + const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 940f5c38bdab..072ba35881f8 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -46,6 +46,7 @@ describe('SentrySpanProcessor', () => { SPAN_MAP.clear(); client = new NodeClient(DEFAULT_NODE_CLIENT_OPTIONS); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -964,6 +965,7 @@ describe('SentrySpanProcessor', () => { return null; }, }); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -1011,6 +1013,7 @@ describe('SentrySpanProcessor', () => { return null; }, }); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); @@ -1058,6 +1061,7 @@ describe('SentrySpanProcessor', () => { sentryTransaction = transaction; }); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index a4eb98ab2126..012542d47e35 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -40,8 +40,10 @@ describe('SentryPropagator', () => { publicKey: 'abc', }), }; + // @ts-expect-error Use mock client for unit tests - const hub: Hub = new Hub(client); + // eslint-disable-next-line deprecation/deprecation + const hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 53dad5a23c0c..a90c70964a03 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -91,6 +91,7 @@ beforeAll(() => { beforeEach(() => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); client = new NodeClient(options); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(client); // eslint-disable-next-line deprecation/deprecation makeMain(hub); diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index 38dbc04bae37..aa5889c89958 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -15,6 +15,7 @@ describe('registerBackgroundTabDetection', () => { global.document = dom.window.document; const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); + // eslint-disable-next-line deprecation/deprecation hub = new Hub(new TestClient(options)); // eslint-disable-next-line deprecation/deprecation makeMain(hub); From bc81b1a1b2b08eb3b27b3f7c9d1af84f2ee3693e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 9 Feb 2024 16:25:35 +0100 Subject: [PATCH 024/173] fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) Add the Http 400 avoidance logic from our server-side `load` function instrumentation to the client-side wrapper. Didn't know that these errors were a thing on the client side but now that we know, we definitely don't want to capture them. Co-authored-by: Francesco Novy --- packages/sveltekit/src/client/load.ts | 8 +++++-- packages/sveltekit/test/client/load.test.ts | 24 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/sveltekit/src/client/load.ts b/packages/sveltekit/src/client/load.ts index 14528959d34e..3e454bdb364a 100644 --- a/packages/sveltekit/src/client/load.ts +++ b/packages/sveltekit/src/client/load.ts @@ -4,7 +4,7 @@ import { addNonEnumerableProperty, objectify } from '@sentry/utils'; import type { LoadEvent } from '@sveltejs/kit'; import type { SentryWrappedFlag } from '../common/utils'; -import { isRedirect } from '../common/utils'; +import { isHttpError, isRedirect } from '../common/utils'; type PatchedLoadEvent = LoadEvent & Partial; @@ -14,7 +14,11 @@ function sendErrorToSentry(e: unknown): unknown { const objectifiedErr = objectify(e); // We don't want to capture thrown `Redirect`s as these are not errors but expected behaviour - if (isRedirect(objectifiedErr)) { + // Neither 4xx errors, given that they are not valuable. + if ( + isRedirect(objectifiedErr) || + (isHttpError(objectifiedErr) && objectifiedErr.status < 500 && objectifiedErr.status >= 400) + ) { return objectifiedErr; } diff --git a/packages/sveltekit/test/client/load.test.ts b/packages/sveltekit/test/client/load.test.ts index e839b5a9cba5..ca7c1d625224 100644 --- a/packages/sveltekit/test/client/load.test.ts +++ b/packages/sveltekit/test/client/load.test.ts @@ -69,6 +69,30 @@ describe('wrapLoadWithSentry', () => { expect(mockCaptureException).not.toHaveBeenCalled(); }); + it.each([400, 404, 499])("doesn't call captureException for thrown `HttpError`s with status %s", async status => { + async function load(_: Parameters[0]): Promise> { + throw { status, body: 'error' }; + } + + const wrappedLoad = wrapLoadWithSentry(load); + const res = wrappedLoad(MOCK_LOAD_ARGS); + await expect(res).rejects.toThrow(); + + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + + it.each([500, 501, 599])('calls captureException for thrown `HttpError`s with status %s', async status => { + async function load(_: Parameters[0]): Promise> { + throw { status, body: 'error' }; + } + + const wrappedLoad = wrapLoadWithSentry(load); + const res = wrappedLoad(MOCK_LOAD_ARGS); + await expect(res).rejects.toThrow(); + + expect(mockCaptureException).toHaveBeenCalledTimes(1); + }); + describe('calls trace function', async () => { it('creates a load span', async () => { async function load({ params }: Parameters[0]): Promise> { From b220be5461c3b1ef7de0e8c169976cd624821425 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 9 Feb 2024 16:26:09 +0100 Subject: [PATCH 025/173] fix(angular-ivy): Add `exports` field to `package.json` (#10569) Add an `exports` field to the `package.json` for `@sentry/angular-ivy`. While it seems like regular Angular apps didn't need it, tools like `vitest` expect the field as soon as `type: "module"` is specified. --- Co-authored-by: Andrei Alecu --- packages/angular-ivy/scripts/prepack.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/angular-ivy/scripts/prepack.ts b/packages/angular-ivy/scripts/prepack.ts index b9ec3f0d787f..bc9159954b39 100644 --- a/packages/angular-ivy/scripts/prepack.ts +++ b/packages/angular-ivy/scripts/prepack.ts @@ -6,6 +6,7 @@ type PackageJson = { type?: string; nx?: string; volta?: any; + exports?: Record>; }; const buildDir = path.join(process.cwd(), 'build'); @@ -18,6 +19,18 @@ const pkgJson: PackageJson = JSON.parse(fs.readFileSync(pkjJsonPath).toString()) delete pkgJson.main; pkgJson.type = 'module'; +pkgJson.exports = { + '.': { + es2015: './fesm2015/sentry-angular-ivy.js', + esm2015: './esm2015/sentry-angular-ivy.js', + fesm2015: './fesm2015/sentry-angular-ivy.js', + import: './fesm2015/sentry-angular-ivy.js', + require: './bundles/sentry-angular-ivy.umd.js', + types: './sentry-angular-ivy.d.ts', + }, + './*': './*', +}; + // no need to keep around other properties that are only relevant for our reop: delete pkgJson.nx; delete pkgJson.volta; From d024a7695a47cdc17ef63a4b9cf190a3df56899b Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 9 Feb 2024 16:45:30 +0100 Subject: [PATCH 026/173] ref(nextjs): Remove all deprecated API (#10549) --- MIGRATION.md | 16 +++ .../create-next-app/pages/api/success.ts | 11 +- packages/browser/src/index.ts | 1 + .../src/client/browserTracingIntegration.ts | 55 +------- .../client/clientNormalizationIntegration.ts | 41 ++++++ packages/nextjs/src/client/index.ts | 107 +++----------- .../src/client/rewriteFramesIntegration.ts | 54 -------- .../appRouterRoutingInstrumentation.ts | 78 +++++------ .../routing/nextRoutingInstrumentation.ts | 32 ++--- .../pagesRouterRoutingInstrumentation.ts | 131 +++++++----------- packages/nextjs/src/common/index.ts | 46 ++---- packages/nextjs/src/common/types.ts | 20 --- .../src/common/wrapApiHandlerWithSentry.ts | 29 +--- .../wrapAppGetInitialPropsWithSentry.ts | 5 - .../wrapDocumentGetInitialPropsWithSentry.ts | 5 - .../wrapErrorGetInitialPropsWithSentry.ts | 5 - .../common/wrapGetInitialPropsWithSentry.ts | 5 - .../wrapGetServerSidePropsWithSentry.ts | 5 - .../common/wrapGetStaticPropsWithSentry.ts | 5 - .../src/common/wrapRouteHandlerWithSentry.ts | 8 +- .../common/wrapServerComponentWithSentry.ts | 6 +- .../edge/distDirRewriteFramesIntegration.ts | 24 ++++ packages/nextjs/src/edge/index.ts | 23 +-- .../src/edge/rewriteFramesIntegration.ts | 52 ------- .../src/edge/wrapApiHandlerWithSentry.ts | 5 - packages/nextjs/src/index.types.ts | 64 +-------- .../server/distDirRewriteFramesIntegration.ts | 25 ++++ packages/nextjs/src/server/index.ts | 36 ++--- .../src/server/rewriteFramesIntegration.ts | 52 ------- packages/nextjs/test/clientSdk.test.ts | 71 ++-------- .../nextjs/test/config/withSentry.test.ts | 7 +- .../nextjs/test/config/wrappingLoader.test.ts | 2 - .../test/integration/sentry.client.config.js | 8 -- .../appRouterInstrumentation.test.ts | 53 ++----- .../pagesRouterInstrumentation.test.ts | 129 ++++------------- packages/nextjs/test/serverSdk.test.ts | 45 +----- packages/tracing-internal/src/index.ts | 2 + 37 files changed, 339 insertions(+), 924 deletions(-) create mode 100644 packages/nextjs/src/client/clientNormalizationIntegration.ts delete mode 100644 packages/nextjs/src/client/rewriteFramesIntegration.ts create mode 100644 packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts delete mode 100644 packages/nextjs/src/edge/rewriteFramesIntegration.ts create mode 100644 packages/nextjs/src/server/distDirRewriteFramesIntegration.ts delete mode 100644 packages/nextjs/src/server/rewriteFramesIntegration.ts diff --git a/MIGRATION.md b/MIGRATION.md index 66114f62b03d..5f1c90c09ca5 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,21 @@ # Upgrading from 7.x to 8.x +## Removal of deprecated API in `@sentry/nextjs` + +The following previously deprecated API has been removed from the `@sentry/nextjs` package: + +- `withSentryApi` (Replacement: `wrapApiHandlerWithSentry`) +- `withSentryAPI` (Replacement: `wrapApiHandlerWithSentry`) +- `withSentryGetServerSideProps` (Replacement: `wrapGetServerSidePropsWithSentry`) +- `withSentryGetStaticProps` (Replacement: `wrapGetStaticPropsWithSentry`) +- `withSentryServerSideGetInitialProps` (Replacement: `wrapGetInitialPropsWithSentry`) +- `withSentryServerSideAppGetInitialProps` (Replacement: `wrapAppGetInitialPropsWithSentry`) +- `withSentryServerSideDocumentGetInitialProps` (Replacement: `wrapDocumentGetInitialPropsWithSentry`) +- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry` +- `nextRouterInstrumentation` (Replaced by using `browserTracingIntegration`) +- `IS_BUILD` +- `isBuild` + ## Removal of Severity Enum In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type. In v8 we removed the `Severity` diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts index d1891f5add90..94f7b003ffcb 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts @@ -3,16 +3,7 @@ import * as Sentry from '@sentry/nextjs'; import type { NextApiRequest, NextApiResponse } from 'next'; export default function handler(req: NextApiRequest, res: NextApiResponse) { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); - // eslint-disable-next-line deprecation/deprecation - Sentry.getCurrentScope().setSpan(transaction); - - // eslint-disable-next-line deprecation/deprecation - const span = transaction.startChild(); - - span.end(); - transaction.end(); + Sentry.startSpan({ name: 'test-span' }, span => undefined); Sentry.flush().then(() => { res.status(200).json({ diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 39a2d32fd0d8..9fa4d83e3984 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -62,6 +62,7 @@ export { browserTracingIntegration, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, + DEFAULT_TRACE_PROPAGATION_TARGETS, } from '@sentry-internal/tracing'; export type { RequestInstrumentationOptions } from '@sentry-internal/tracing'; export { diff --git a/packages/nextjs/src/client/browserTracingIntegration.ts b/packages/nextjs/src/client/browserTracingIntegration.ts index af8f59f53b6f..42af9f9a2045 100644 --- a/packages/nextjs/src/client/browserTracingIntegration.ts +++ b/packages/nextjs/src/client/browserTracingIntegration.ts @@ -1,61 +1,18 @@ import { - BrowserTracing as OriginalBrowserTracing, browserTracingIntegration as originalBrowserTracingIntegration, - defaultRequestInstrumentationOptions, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, } from '@sentry/react'; import type { Integration, StartSpanOptions } from '@sentry/types'; -import { nextRouterInstrumentation } from '../index.client'; - -/** - * A custom BrowserTracing integration for Next.js. - * - * @deprecated Use `browserTracingIntegration` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export class BrowserTracing extends OriginalBrowserTracing { - // eslint-disable-next-line deprecation/deprecation - public constructor(options?: ConstructorParameters[0]) { - super({ - // eslint-disable-next-line deprecation/deprecation - tracingOrigins: - process.env.NODE_ENV === 'development' - ? [ - // Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server - // has cors and it doesn't like extra headers when it's accessed from a different URL. - // TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764) - /^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/, - /^\/(?!\/)/, - ] - : // eslint-disable-next-line deprecation/deprecation - [...defaultRequestInstrumentationOptions.tracingOrigins, /^(api\/)/], - // eslint-disable-next-line deprecation/deprecation - routingInstrumentation: nextRouterInstrumentation, - ...options, - }); - } -} +import { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation'; /** * A custom BrowserTracing integration for Next.js. */ export function browserTracingIntegration( - options?: Parameters[0], + options: Parameters[0] = {}, ): Integration { const browserTracingIntegrationInstance = originalBrowserTracingIntegration({ - // eslint-disable-next-line deprecation/deprecation - tracingOrigins: - process.env.NODE_ENV === 'development' - ? [ - // Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server - // has cors and it doesn't like extra headers when it's accessed from a different URL. - // TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764) - /^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/, - /^\/(?!\/)/, - ] - : // eslint-disable-next-line deprecation/deprecation - [...defaultRequestInstrumentationOptions.tracingOrigins, /^(api\/)/], ...options, instrumentNavigation: false, instrumentPageLoad: false, @@ -76,21 +33,17 @@ export function browserTracingIntegration( // tracing integration because we need to ensure the order of execution is as follows: // Instrumentation to start span on RSC fetch request runs -> Instrumentation to put tracing headers from active span on fetch runs // If it were the other way around, the RSC fetch request would not receive the tracing headers from the navigation transaction. - // eslint-disable-next-line deprecation/deprecation nextRouterInstrumentation( - () => undefined, false, - options?.instrumentNavigation, + options.instrumentNavigation === undefined ? true : options.instrumentNavigation, startPageloadCallback, startNavigationCallback, ); browserTracingIntegrationInstance.afterAllSetup(client); - // eslint-disable-next-line deprecation/deprecation nextRouterInstrumentation( - () => undefined, - options?.instrumentPageLoad, + options.instrumentPageLoad === undefined ? true : options.instrumentPageLoad, false, startPageloadCallback, startNavigationCallback, diff --git a/packages/nextjs/src/client/clientNormalizationIntegration.ts b/packages/nextjs/src/client/clientNormalizationIntegration.ts new file mode 100644 index 000000000000..5caf7d2a3b59 --- /dev/null +++ b/packages/nextjs/src/client/clientNormalizationIntegration.ts @@ -0,0 +1,41 @@ +import { defineIntegration } from '@sentry/core'; +import { rewriteFramesIntegration } from '@sentry/integrations'; + +export const nextjsClientStackFrameNormalizationIntegration = defineIntegration( + ({ assetPrefixPath }: { assetPrefixPath: string }) => { + const rewriteFramesInstance = rewriteFramesIntegration({ + // Turn `//_next/static/...` into `app:///_next/static/...` + iteratee: frame => { + try { + const { origin } = new URL(frame.filename as string); + frame.filename = frame.filename?.replace(origin, 'app://').replace(assetPrefixPath, ''); + } catch (err) { + // Filename wasn't a properly formed URL, so there's nothing we can do + } + + // We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces. + // The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works. + if (frame.filename && frame.filename.startsWith('app:///_next')) { + frame.filename = decodeURI(frame.filename); + } + + if ( + frame.filename && + frame.filename.match( + /^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/, + ) + ) { + // We don't care about these frames. It's Next.js internal code. + frame.in_app = false; + } + + return frame; + }, + }); + + return { + ...rewriteFramesInstance, + name: 'NextjsClientStackFrameNormalization', + }; + }, +); diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index e0d22445a3a1..5737816ba873 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,7 +1,7 @@ import { applySdkMetadata, hasTracingEnabled } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; import { - Integrations as OriginalIntegrations, + DEFAULT_TRACE_PROPAGATION_TARGETS, getCurrentScope, getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit, @@ -11,35 +11,17 @@ import type { EventProcessor, Integration } from '@sentry/types'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; import { browserTracingIntegration } from './browserTracingIntegration'; -import { BrowserTracing } from './browserTracingIntegration'; -import { rewriteFramesIntegration } from './rewriteFramesIntegration'; +import { nextjsClientStackFrameNormalizationIntegration } from './clientNormalizationIntegration'; import { applyTunnelRouteOption } from './tunnelRoute'; export * from '@sentry/react'; -// eslint-disable-next-line deprecation/deprecation -export { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation'; + export { captureUnderscoreErrorException } from '../common/_error'; -/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ -export const Integrations = { - // eslint-disable-next-line deprecation/deprecation - ...OriginalIntegrations, - BrowserTracing, +const globalWithInjectedValues = global as typeof global & { + __rewriteFramesAssetPrefixPath__: string; }; -// Previously we expected users to import `BrowserTracing` like this: -// -// import { Integrations } from '@sentry/nextjs'; -// const instance = new Integrations.BrowserTracing(); -// -// This makes the integrations unable to be treeshaken though. To address this, we now have -// this individual export. We now expect users to consume BrowserTracing like so: -// -// import { BrowserTracing } from '@sentry/nextjs'; -// const instance = new BrowserTracing(); -// eslint-disable-next-line deprecation/deprecation -export { BrowserTracing, rewriteFramesIntegration }; - // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -47,11 +29,20 @@ declare const __SENTRY_TRACING__: boolean; export function init(options: BrowserOptions): void { const opts = { environment: getVercelEnv(true) || process.env.NODE_ENV, + + tracePropagationTargets: + process.env.NODE_ENV === 'development' + ? [ + // Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server + // has cors and it doesn't like extra headers when it's accessed from a different URL. + // TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764) + /^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/, + /^\/(?!\/)/, + ] + : [...DEFAULT_TRACE_PROPAGATION_TARGETS, /^(api\/)/], defaultIntegrations: getDefaultIntegrations(options), ...options, - }; - - fixBrowserTracingIntegration(opts); + } satisfies BrowserOptions; applyTunnelRouteOption(opts); applySdkMetadata(opts, 'nextjs', ['nextjs', 'react']); @@ -70,65 +61,8 @@ export function init(options: BrowserOptions): void { } } -// TODO v8: Remove this again -// We need to handle BrowserTracing passed to `integrations` that comes from `@sentry/tracing`, not `@sentry/nextjs` :( -function fixBrowserTracingIntegration(options: BrowserOptions): void { - const { integrations } = options; - if (!integrations) { - return; - } - - if (Array.isArray(integrations)) { - options.integrations = maybeUpdateBrowserTracingIntegration(integrations); - } else { - options.integrations = defaultIntegrations => { - const userFinalIntegrations = integrations(defaultIntegrations); - - return maybeUpdateBrowserTracingIntegration(userFinalIntegrations); - }; - } -} - -function isNewBrowserTracingIntegration( - integration: Integration, -): integration is Integration & { options?: Parameters[0] } { - // eslint-disable-next-line deprecation/deprecation - return !!integration.afterAllSetup && !!(integration as BrowserTracing).options; -} - -function maybeUpdateBrowserTracingIntegration(integrations: Integration[]): Integration[] { - const browserTracing = integrations.find(integration => integration.name === 'BrowserTracing'); - - if (!browserTracing) { - return integrations; - } - - // If `browserTracingIntegration()` was added, we need to force-convert it to our custom one - if (isNewBrowserTracingIntegration(browserTracing)) { - const { options } = browserTracing; - // eslint-disable-next-line deprecation/deprecation - integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options); - } - - // If BrowserTracing was added, but it is not our forked version, - // replace it with our forked version with the same options - // eslint-disable-next-line deprecation/deprecation - if (!(browserTracing instanceof BrowserTracing)) { - // eslint-disable-next-line deprecation/deprecation - const options: ConstructorParameters[0] = (browserTracing as BrowserTracing).options; - // This option is overwritten by the custom integration - delete options.routingInstrumentation; - // eslint-disable-next-line deprecation/deprecation - delete options.tracingOrigins; - // eslint-disable-next-line deprecation/deprecation - integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options); - } - - return integrations; -} - function getDefaultIntegrations(options: BrowserOptions): Integration[] { - const customDefaultIntegrations = [...getReactDefaultIntegrations(options), rewriteFramesIntegration()]; + const customDefaultIntegrations = getReactDefaultIntegrations(options); // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside // will get treeshaken away @@ -138,6 +72,11 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { } } + // This value is injected at build time, based on the output directory specified in the build config. Though a default + // is set there, we set it here as well, just in case something has gone wrong with the injection. + const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || ''; + customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); + return customDefaultIntegrations; } diff --git a/packages/nextjs/src/client/rewriteFramesIntegration.ts b/packages/nextjs/src/client/rewriteFramesIntegration.ts deleted file mode 100644 index 5c45ff63d983..000000000000 --- a/packages/nextjs/src/client/rewriteFramesIntegration.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { defineIntegration } from '@sentry/core'; -import { rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/integrations'; -import type { IntegrationFn, StackFrame } from '@sentry/types'; - -const globalWithInjectedValues = global as typeof global & { - __rewriteFramesAssetPrefixPath__: string; -}; - -type StackFrameIteratee = (frame: StackFrame) => StackFrame; - -interface RewriteFramesOptions { - root?: string; - prefix?: string; - iteratee?: StackFrameIteratee; -} - -export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || ''; - - return originalRewriteFramesIntegration({ - // Turn `//_next/static/...` into `app:///_next/static/...` - iteratee: frame => { - try { - const { origin } = new URL(frame.filename as string); - frame.filename = frame.filename?.replace(origin, 'app://').replace(assetPrefixPath, ''); - } catch (err) { - // Filename wasn't a properly formed URL, so there's nothing we can do - } - - // We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces. - // The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works. - if (frame.filename && frame.filename.startsWith('app:///_next')) { - frame.filename = decodeURI(frame.filename); - } - - if ( - frame.filename && - frame.filename.match( - /^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/, - ) - ) { - // We don't care about these frames. It's Next.js internal code. - frame.in_app = false; - } - - return frame; - }, - ...options, - }); -}) satisfies IntegrationFn; - -export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration); diff --git a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts index 25ec697a2161..af123524b152 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -1,8 +1,12 @@ +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from '@sentry/core'; import { WINDOW } from '@sentry/react'; -import type { Primitive, Span, StartSpanOptions, Transaction, TransactionContext } from '@sentry/types'; +import type { StartSpanOptions } from '@sentry/types'; import { addFetchInstrumentationHandler, browserPerformanceTimeOrigin } from '@sentry/utils'; -type StartTransactionCb = (context: TransactionContext) => Transaction | undefined; type StartSpanCb = (context: StartSpanOptions) => void; const DEFAULT_TAGS = { @@ -12,36 +16,31 @@ const DEFAULT_TAGS = { /** * Instruments the Next.js Client App Router. */ -// TODO(v8): Clean this function up by splitting into pageload and navigation instrumentation respectively. Also remove startTransactionCb in the process. export function appRouterInstrumentation( - startTransactionCb: StartTransactionCb, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, + shouldInstrumentPageload: boolean, + shouldInstrumentNavigation: boolean, startPageloadSpanCallback: StartSpanCb, startNavigationSpanCallback: StartSpanCb, ): void { - // We keep track of the active transaction so we can finish it when we start a navigation transaction. - let activeTransaction: Span | undefined = undefined; - // We keep track of the previous location name so we can set the `from` field on navigation transactions. // This is either a route or a pathname. - let prevLocationName = WINDOW.location.pathname; + let currPathname = WINDOW.location.pathname; - if (startTransactionOnPageLoad) { - const transactionContext = { - name: prevLocationName, - op: 'pageload', - origin: 'auto.pageload.nextjs.app_router_instrumentation', + if (shouldInstrumentPageload) { + startPageloadSpanCallback({ + name: currPathname, tags: DEFAULT_TAGS, // pageload should always start at timeOrigin (and needs to be in s, not ms) - startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, - metadata: { source: 'url' }, - } as const; - activeTransaction = startTransactionCb(transactionContext); - startPageloadSpanCallback(transactionContext); + startTime: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, + }); } - if (startTransactionOnLocationChange) { + if (shouldInstrumentNavigation) { addFetchInstrumentationHandler(handlerData => { // The instrumentation handler is invoked twice - once for starting a request and once when the req finishes // We can use the existence of the end-timestamp to filter out "finishing"-events. @@ -60,28 +59,21 @@ export function appRouterInstrumentation( return; } - const transactionName = parsedNavigatingRscFetchArgs.targetPathname; - const tags: Record = { - ...DEFAULT_TAGS, - from: prevLocationName, - }; - - prevLocationName = transactionName; - - if (activeTransaction) { - activeTransaction.end(); - } - - const transactionContext = { - name: transactionName, - op: 'navigation', - origin: 'auto.navigation.nextjs.app_router_instrumentation', - tags, - metadata: { source: 'url' }, - } as const; - - startTransactionCb(transactionContext); - startNavigationSpanCallback(transactionContext); + const newPathname = parsedNavigatingRscFetchArgs.targetPathname; + currPathname = newPathname; + + startNavigationSpanCallback({ + name: newPathname, + tags: { + ...DEFAULT_TAGS, + from: currPathname, + }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.app_router_instrumentation', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, + }); }); } } diff --git a/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts index 4706fb8a32f2..f092b2c0b3db 100644 --- a/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts @@ -1,40 +1,34 @@ import { WINDOW } from '@sentry/react'; -import type { StartSpanOptions, Transaction, TransactionContext } from '@sentry/types'; +import type { StartSpanOptions } from '@sentry/types'; import { appRouterInstrumentation } from './appRouterRoutingInstrumentation'; import { pagesRouterInstrumentation } from './pagesRouterRoutingInstrumentation'; -type StartTransactionCb = (context: TransactionContext) => Transaction | undefined; type StartSpanCb = (context: StartSpanOptions) => void; /** * Instruments the Next.js Client Router. - * - * @deprecated Use `browserTracingIntegration()` as exported from `@sentry/nextjs` instead. */ export function nextRouterInstrumentation( - startTransactionCb: StartTransactionCb, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, - startPageloadSpanCallback?: StartSpanCb, - startNavigationSpanCallback?: StartSpanCb, + shouldInstrumentPageload: boolean, + shouldInstrumentNavigation: boolean, + startPageloadSpanCallback: StartSpanCb, + startNavigationSpanCallback: StartSpanCb, ): void { const isAppRouter = !WINDOW.document.getElementById('__NEXT_DATA__'); if (isAppRouter) { appRouterInstrumentation( - startTransactionCb, - startTransactionOnPageLoad, - startTransactionOnLocationChange, - startPageloadSpanCallback || (() => undefined), - startNavigationSpanCallback || (() => undefined), + shouldInstrumentPageload, + shouldInstrumentNavigation, + startPageloadSpanCallback, + startNavigationSpanCallback, ); } else { pagesRouterInstrumentation( - startTransactionCb, - startTransactionOnPageLoad, - startTransactionOnLocationChange, - startPageloadSpanCallback || (() => undefined), - startNavigationSpanCallback || (() => undefined), + shouldInstrumentPageload, + shouldInstrumentNavigation, + startPageloadSpanCallback, + startNavigationSpanCallback, ); } } diff --git a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts index c3f466a566ea..a8180b3c875f 100644 --- a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts @@ -1,12 +1,17 @@ import type { ParsedUrlQuery } from 'querystring'; -import { getClient, getCurrentScope } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getClient, +} from '@sentry/core'; import { WINDOW } from '@sentry/react'; -import type { Primitive, StartSpanOptions, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; +import type { StartSpanOptions, TransactionSource } from '@sentry/types'; import { browserPerformanceTimeOrigin, logger, + propagationContextFromHeaders, stripUrlQueryAndFragment, - tracingContextFromHeaders, } from '@sentry/utils'; import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils'; import { default as Router } from 'next/router'; @@ -19,7 +24,6 @@ const globalObject = WINDOW as typeof WINDOW & { }; }; -type StartTransactionCb = (context: TransactionContext) => Transaction | undefined; type StartSpanCb = (context: StartSpanOptions) => void; /** @@ -97,15 +101,6 @@ const DEFAULT_TAGS = { 'routing.instrumentation': 'next-pages-router', } as const; -// We keep track of the active transaction so we can finish it when we start a navigation transaction. -let activeTransaction: Transaction | undefined = undefined; - -// We keep track of the previous location name so we can set the `from` field on navigation transactions. -// This is either a route or a pathname. -let prevLocationName: string | undefined = undefined; - -const client = getClient(); - /** * Instruments the Next.js pages router. Only supported for * client side routing. Works for Next >= 10. @@ -115,99 +110,67 @@ const client = getClient(); * transaction names. */ export function pagesRouterInstrumentation( - startTransactionCb: StartTransactionCb, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, + shouldInstrumentPageload: boolean, + shouldInstrumentNavigation: boolean, startPageloadSpanCallback: StartSpanCb, startNavigationSpanCallback: StartSpanCb, ): void { const { route, params, sentryTrace, baggage } = extractNextDataTagInformation(); - // eslint-disable-next-line deprecation/deprecation - const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders( - sentryTrace, - baggage, - ); - - getCurrentScope().setPropagationContext(propagationContext); - prevLocationName = route || globalObject.location.pathname; + const { traceId, dsc, parentSpanId, sampled } = propagationContextFromHeaders(sentryTrace, baggage); + let prevLocationName = route || globalObject.location.pathname; - if (startTransactionOnPageLoad) { - const source = route ? 'route' : 'url'; - const transactionContext = { + if (shouldInstrumentPageload) { + const client = getClient(); + startPageloadSpanCallback({ name: prevLocationName, - op: 'pageload', - origin: 'auto.pageload.nextjs.pages_router_instrumentation', tags: DEFAULT_TAGS, // pageload should always start at timeOrigin (and needs to be in s, not ms) - startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + startTime: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, + traceId, + parentSpanId, + parentSampled: sampled, ...(params && client && client.getOptions().sendDefaultPii && { data: params }), - ...traceparentData, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.nextjs.pages_router_instrumentation', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: route ? 'route' : 'url', + }, metadata: { - source, - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + dynamicSamplingContext: dsc, }, - } as const; - activeTransaction = startTransactionCb(transactionContext); - startPageloadSpanCallback(transactionContext); + }); } - if (startTransactionOnLocationChange) { + if (shouldInstrumentNavigation) { Router.events.on('routeChangeStart', (navigationTarget: string) => { const strippedNavigationTarget = stripUrlQueryAndFragment(navigationTarget); const matchedRoute = getNextRouteFromPathname(strippedNavigationTarget); - let transactionName: string; - let transactionSource: TransactionSource; + let newLocation: string; + let spanSource: TransactionSource; if (matchedRoute) { - transactionName = matchedRoute; - transactionSource = 'route'; + newLocation = matchedRoute; + spanSource = 'route'; } else { - transactionName = strippedNavigationTarget; - transactionSource = 'url'; + newLocation = strippedNavigationTarget; + spanSource = 'url'; } - const tags: Record = { - ...DEFAULT_TAGS, - from: prevLocationName, - }; - - prevLocationName = transactionName; - - if (activeTransaction) { - activeTransaction.end(); - } - - const transactionContext = { - name: transactionName, - op: 'navigation', - origin: 'auto.navigation.nextjs.pages_router_instrumentation', - tags, - metadata: { source: transactionSource }, - } as const; - const navigationTransaction = startTransactionCb(transactionContext); - startNavigationSpanCallback(transactionContext); - - if (navigationTransaction) { - // In addition to the navigation transaction we're also starting a span to mark Next.js's `routeChangeStart` - // and `routeChangeComplete` events. - // We don't want to finish the navigation transaction on `routeChangeComplete`, since users might want to attach - // spans to that transaction even after `routeChangeComplete` is fired (eg. HTTP requests in some useEffect - // hooks). Instead, we'll simply let the navigation transaction finish itself (it's an `IdleTransaction`). - // eslint-disable-next-line deprecation/deprecation - const nextRouteChangeSpan = navigationTransaction.startChild({ - op: 'ui.nextjs.route-change', - origin: 'auto.ui.nextjs.pages_router_instrumentation', - description: 'Next.js Route Change', - }); - - const finishRouteChangeSpan = (): void => { - nextRouteChangeSpan.end(); - Router.events.off('routeChangeComplete', finishRouteChangeSpan); - }; - - Router.events.on('routeChangeComplete', finishRouteChangeSpan); - } + startNavigationSpanCallback({ + name: newLocation, + tags: { + ...DEFAULT_TAGS, + from: prevLocationName, + }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.nextjs.pages_router_instrumentation', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource, + }, + }); + + prevLocationName = newLocation; }); } } diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index 3b0ce67fb16c..e308537f1358 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -1,38 +1,14 @@ -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetStaticProps, - wrapGetStaticPropsWithSentry, -} from './wrapGetStaticPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideGetInitialProps, - wrapGetInitialPropsWithSentry, -} from './wrapGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideAppGetInitialProps, - wrapAppGetInitialPropsWithSentry, -} from './wrapAppGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideDocumentGetInitialProps, - wrapDocumentGetInitialPropsWithSentry, -} from './wrapDocumentGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideErrorGetInitialProps, - wrapErrorGetInitialPropsWithSentry, -} from './wrapErrorGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetServerSideProps, - wrapGetServerSidePropsWithSentry, -} from './wrapGetServerSidePropsWithSentry'; +export { wrapGetStaticPropsWithSentry } from './wrapGetStaticPropsWithSentry'; + +export { wrapGetInitialPropsWithSentry } from './wrapGetInitialPropsWithSentry'; + +export { wrapAppGetInitialPropsWithSentry } from './wrapAppGetInitialPropsWithSentry'; + +export { wrapDocumentGetInitialPropsWithSentry } from './wrapDocumentGetInitialPropsWithSentry'; + +export { wrapErrorGetInitialPropsWithSentry } from './wrapErrorGetInitialPropsWithSentry'; + +export { wrapGetServerSidePropsWithSentry } from './wrapGetServerSidePropsWithSentry'; export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index cf7d881e9ea0..1286e8f9ae15 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -5,16 +5,6 @@ import type { RequestAsyncStorage } from '../config/templates/requestAsyncStorag export type ServerComponentContext = { componentRoute: string; componentType: string; - // TODO(v8): Remove - /** - * @deprecated pass a complete `Headers` object with the `headers` field instead. - */ - sentryTraceHeader?: string; - // TODO(v8): Remove - /** - * @deprecated pass a complete `Headers` object with the `headers` field instead. - */ - baggageHeader?: string; headers?: WebFetchHeaders; }; @@ -28,16 +18,6 @@ export type GenerationFunctionContext = { export interface RouteHandlerContext { method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS'; parameterizedRoute: string; - // TODO(v8): Remove - /** - * @deprecated pass a complete `Headers` object with the `headers` field instead. - */ - sentryTraceHeader?: string; - // TODO(v8): Remove - /** - * @deprecated pass a complete `Headers` object with the `headers` field instead. - */ - baggageHeader?: string; headers?: WebFetchHeaders; } diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts index 62124e46912e..6aa8de0cbac0 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts @@ -25,29 +25,6 @@ import { flushQueue } from './utils/responseEnd'; * @returns The wrapped handler */ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameterizedRoute: string): NextApiHandler { - return new Proxy(apiHandler, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - // eslint-disable-next-line deprecation/deprecation - return withSentry(wrappingTarget, parameterizedRoute).apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapApiHandlerWithSentry()` instead - */ -export const withSentryAPI = wrapApiHandlerWithSentry; - -/** - * Legacy function for manually wrapping API route handlers, now used as the innards of `wrapApiHandlerWithSentry`. - * - * @param apiHandler The user's original API route handler - * @param parameterizedRoute The route whose handler is being wrapped. Meant for internal use only. - * @returns A wrapped version of the handler - * - * @deprecated Use `wrapApiWithSentry()` instead - */ -export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: string): NextApiHandler { return new Proxy(apiHandler, { apply: ( wrappingTarget, @@ -81,6 +58,7 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri return runWithAsyncContext(() => { return continueTrace( { + // TODO(v8): Make it so that continue trace will allow null as sentryTrace value and remove this fallback here sentryTrace: req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined, baggage: req.headers?.baggage, }, @@ -144,14 +122,11 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri process.env.NODE_ENV === 'development' && !process.env.SENTRY_IGNORE_API_RESOLUTION_ERROR && !res.finished - // TODO(v8): Remove this warning? - // This can only happen (not always) when the user is using `withSentry` manually, which we're deprecating. - // Warning suppression on Next.JS is only necessary in that case. ) { consoleSandbox(() => { // eslint-disable-next-line no-console console.warn( - '[sentry] If Next.js logs a warning "API resolved without sending a response", it\'s a false positive, which may happen when you use `withSentry` manually to wrap your routes. To suppress this warning, set `SENTRY_IGNORE_API_RESOLUTION_ERROR` to 1 in your env. To suppress the nextjs warning, use the `externalResolver` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).', + '[sentry] If Next.js logs a warning "API resolved without sending a response", it\'s a false positive, which may happen when you use `wrapApiHandlerWithSentry` manually to wrap your routes. To suppress this warning, set `SENTRY_IGNORE_API_RESOLUTION_ERROR` to 1 in your env. To suppress the nextjs warning, use the `externalResolver` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).', ); }); } diff --git a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts index 218ed18b5f26..11730529de23 100644 --- a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts @@ -80,8 +80,3 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI }, }); } - -/** - * @deprecated Use `wrapAppGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideAppGetInitialProps = wrapAppGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts index df801a8bc027..695111429938 100644 --- a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts @@ -49,8 +49,3 @@ export function wrapDocumentGetInitialPropsWithSentry( }, }); } - -/** - * @deprecated Use `wrapDocumentGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideDocumentGetInitialProps = wrapDocumentGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts index 2b2ad24fd18e..44121a8c2e4a 100644 --- a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts @@ -73,8 +73,3 @@ export function wrapErrorGetInitialPropsWithSentry( }, }); } - -/** - * @deprecated Use `wrapErrorGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideErrorGetInitialProps = wrapErrorGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts index 2dbe5c34d6c9..add8af0c0f8f 100644 --- a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts @@ -69,8 +69,3 @@ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialPro }, }); } - -/** - * @deprecated Use `wrapGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideGetInitialProps = wrapGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts index 1f21952ec373..f854cedd8b5d 100644 --- a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts @@ -66,8 +66,3 @@ export function wrapGetServerSidePropsWithSentry( }, }); } - -/** - * @deprecated Use `withSentryGetServerSideProps` instead. - */ -export const withSentryGetServerSideProps = wrapGetServerSidePropsWithSentry; diff --git a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts index 5d2446d50769..1d5c0b1890de 100644 --- a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts @@ -39,8 +39,3 @@ export function wrapGetStaticPropsWithSentry( }, }); } - -/** - * @deprecated Use `wrapGetStaticPropsWithSentry` instead. - */ -export const withSentryGetStaticProps = wrapGetStaticPropsWithSentry; diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index e4a475f6ced6..d0bfa7578bd0 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -25,8 +25,7 @@ export function wrapRouteHandlerWithSentry any>( context: RouteHandlerContext, ): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise> { addTracingExtensions(); - // eslint-disable-next-line deprecation/deprecation - const { method, parameterizedRoute, baggageHeader, sentryTraceHeader, headers } = context; + const { method, parameterizedRoute, headers } = context; return new Proxy(routeHandler, { apply: (originalFunction, thisArg, args) => { return withIsolationScope(async isolationScope => { @@ -37,8 +36,9 @@ export function wrapRouteHandlerWithSentry any>( }); return continueTrace( { - sentryTrace: sentryTraceHeader ?? headers?.get('sentry-trace') ?? undefined, - baggage: baggageHeader ?? headers?.get('baggage'), + // TODO(v8): Make it so that continue trace will allow null as sentryTrace value and remove this fallback here + sentryTrace: headers?.get('sentry-trace') ?? undefined, + baggage: headers?.get('baggage'), }, async () => { try { diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index de0c1da9c1f9..7408fa7f39f3 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -44,10 +44,8 @@ export function wrapServerComponentWithSentry any> }); const incomingPropagationContext = propagationContextFromHeaders( - // eslint-disable-next-line deprecation/deprecation - context.sentryTraceHeader ?? completeHeadersDict['sentry-trace'], - // eslint-disable-next-line deprecation/deprecation - context.baggageHeader ?? completeHeadersDict['baggage'], + completeHeadersDict['sentry-trace'], + completeHeadersDict['baggage'], ); const propagationContext = commonObjectToPropagationContext(context.headers, incomingPropagationContext); diff --git a/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts b/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts new file mode 100644 index 000000000000..5e0555f193e3 --- /dev/null +++ b/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts @@ -0,0 +1,24 @@ +import { defineIntegration } from '@sentry/core'; +import { rewriteFramesIntegration } from '@sentry/integrations'; +import { escapeStringForRegex } from '@sentry/utils'; + +export const distDirRewriteFramesIntegration = defineIntegration(({ distDirName }: { distDirName: string }) => { + const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one + + // Normally we would use `path.resolve` to obtain the absolute path we will strip from the stack frame to align with + // the uploaded artifacts, however we don't have access to that API in edge so we need to be a bit more lax. + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped + const SOURCEMAP_FILENAME_REGEX = new RegExp(`.*${escapeStringForRegex(distDirAbsPath)}`); + + const rewriteFramesIntegrationInstance = rewriteFramesIntegration({ + iteratee: frame => { + frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); + return frame; + }, + }); + + return { + ...rewriteFramesIntegrationInstance, + name: 'DistDirRewriteFrames', + }; +}); diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 42599277c451..735dc40d3188 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -1,13 +1,16 @@ import { addTracingExtensions, applySdkMetadata } from '@sentry/core'; +import { GLOBAL_OBJ } from '@sentry/utils'; import type { VercelEdgeOptions } from '@sentry/vercel-edge'; import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge'; import { isBuild } from '../common/utils/isBuild'; -import { rewriteFramesIntegration } from './rewriteFramesIntegration'; +import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration'; export type EdgeOptions = VercelEdgeOptions; -export { rewriteFramesIntegration }; +const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { + __rewriteFramesDistDir__?: string; +}; /** Inits the Sentry NextJS SDK on the Edge Runtime. */ export function init(options: VercelEdgeOptions = {}): void { @@ -17,7 +20,15 @@ export function init(options: VercelEdgeOptions = {}): void { return; } - const customDefaultIntegrations = [...getDefaultIntegrations(options), rewriteFramesIntegration()]; + const customDefaultIntegrations = getDefaultIntegrations(options); + + // This value is injected at build time, based on the output directory specified in the build config. Though a default + // is set there, we set it here as well, just in case something has gone wrong with the injection. + const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + + if (distDirName) { + customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); + } const opts = { defaultIntegrations: customDefaultIntegrations, @@ -41,8 +52,4 @@ export { Span, Transaction } from '@sentry/core'; export * from '../common'; -export { - // eslint-disable-next-line deprecation/deprecation - withSentryAPI, - wrapApiHandlerWithSentry, -} from './wrapApiHandlerWithSentry'; +export { wrapApiHandlerWithSentry } from './wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts deleted file mode 100644 index 96e626178c4b..000000000000 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { defineIntegration } from '@sentry/core'; -import { - RewriteFrames as OriginalRewriteFrames, - rewriteFramesIntegration as originalRewriteFramesIntegration, -} from '@sentry/integrations'; -import type { IntegrationFn, StackFrame } from '@sentry/types'; -import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; - -const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; -}; - -type StackFrameIteratee = (frame: StackFrame) => StackFrame; -interface RewriteFramesOptions { - root?: string; - prefix?: string; - iteratee?: StackFrameIteratee; -} - -export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; - - if (distDirName) { - const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one - - // Normally we would use `path.resolve` to obtain the absolute path we will strip from the stack frame to align with - // the uploaded artifacts, however we don't have access to that API in edge so we need to be a bit more lax. - // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped - const SOURCEMAP_FILENAME_REGEX = new RegExp(`.*${escapeStringForRegex(distDirAbsPath)}`); - - return originalRewriteFramesIntegration({ - iteratee: frame => { - frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); - return frame; - }, - ...options, - }); - } - - // Do nothing if we can't find a distDirName - return { - // eslint-disable-next-line deprecation/deprecation - name: OriginalRewriteFrames.id, - // eslint-disable-next-line @typescript-eslint/no-empty-function - setupOnce: () => {}, - processEvent: event => event, - }; -}) satisfies IntegrationFn; - -export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration); diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index 71e3072d68b5..f66a03ee9586 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -29,8 +29,3 @@ export function wrapApiHandlerWithSentry( }, }); } - -/** - * @deprecated Use `wrapApiHandlerWithSentry` instead. - */ -export const withSentryAPI = wrapApiHandlerWithSentry; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 2328208e28c5..7facc2d580b2 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -19,12 +19,7 @@ export declare function init( options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions | edgeSdk.EdgeOptions, ): void; -// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere. -// eslint-disable-next-line deprecation/deprecation -export declare const Integrations: typeof clientSdk.Integrations & - typeof serverSdk.Integrations & - // eslint-disable-next-line deprecation/deprecation - typeof edgeSdk.Integrations; +export declare const Integrations: undefined; // TODO(v8): Remove this line. Can only be done when dependencies don't export `Integrations` anymore. export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; @@ -32,9 +27,6 @@ export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; export declare const defaultStackParser: StackParser; -// eslint-disable-next-line deprecation/deprecation -export declare const rewriteFramesIntegration: typeof clientSdk.rewriteFramesIntegration; - export declare function getSentryRelease(fallback?: string): string | undefined; export declare const ErrorBoundary: typeof clientSdk.ErrorBoundary; @@ -46,16 +38,6 @@ export declare const Transaction: typeof edgeSdk.Transaction; export { withSentryConfig } from './config'; -/** - * @deprecated Use `wrapApiHandlerWithSentry` instead - */ -export declare function withSentryAPI any>( - handler: APIHandler, - parameterizedRoute: string, -): ( - ...args: Parameters -) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a Next.js API handler with Sentry error and performance instrumentation. * @@ -80,13 +62,6 @@ export declare function wrapGetInitialPropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapGetInitialPropsWithSentry` instead. - */ -export declare function withSentryServerSideGetInitialProps any>( - getInitialProps: F, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a `getInitialProps` function of a custom `_app` page with Sentry error and performance instrumentation. * @@ -97,13 +72,6 @@ export declare function wrapAppGetInitialPropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapAppGetInitialPropsWithSentry` instead. - */ -export declare function withSentryServerSideAppGetInitialProps any>( - getInitialProps: F, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a `getInitialProps` function of a custom `_document` page with Sentry error and performance instrumentation. * @@ -114,13 +82,6 @@ export declare function wrapDocumentGetInitialPropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapDocumentGetInitialPropsWithSentry` instead. - */ -export declare function withSentryServerSideDocumentGetInitialProps any>( - getInitialProps: F, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a `getInitialProps` function of a custom `_error` page with Sentry error and performance instrumentation. * @@ -131,13 +92,6 @@ export declare function wrapErrorGetInitialPropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapErrorGetInitialPropsWithSentry` instead. - */ -export declare function withSentryServerSideErrorGetInitialProps any>( - getInitialProps: F, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a `getServerSideProps` function with Sentry error and performance instrumentation. * @@ -150,14 +104,6 @@ export declare function wrapGetServerSidePropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapGetServerSidePropsWithSentry` instead. - */ -export declare function withSentryGetServerSideProps any>( - origGetServerSideProps: F, - parameterizedRoute: string, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps a `getStaticProps` function with Sentry error and performance instrumentation. * @@ -170,14 +116,6 @@ export declare function wrapGetStaticPropsWithSentry) => ReturnType extends Promise ? ReturnType : Promise>; -/** - * @deprecated Use `wrapGetStaticPropsWithSentry` instead. - */ -export declare function withSentryGetStaticProps any>( - origGetStaticPropsa: F, - parameterizedRoute: string, -): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise>; - /** * Wraps an `app` directory server component with Sentry error and performance instrumentation. */ diff --git a/packages/nextjs/src/server/distDirRewriteFramesIntegration.ts b/packages/nextjs/src/server/distDirRewriteFramesIntegration.ts new file mode 100644 index 000000000000..71cb5f2d766c --- /dev/null +++ b/packages/nextjs/src/server/distDirRewriteFramesIntegration.ts @@ -0,0 +1,25 @@ +import * as path from 'path'; +import { defineIntegration } from '@sentry/core'; +import { rewriteFramesIntegration } from '@sentry/integrations'; +import { escapeStringForRegex } from '@sentry/utils'; + +export const distDirRewriteFramesIntegration = defineIntegration(({ distDirName }: { distDirName: string }) => { + // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so + // we can read in the project directory from the currently running process + const distDirAbsPath = path.resolve(distDirName).replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one + + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped + const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath)); + + const rewriteFramesInstance = rewriteFramesIntegration({ + iteratee: frame => { + frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); + return frame; + }, + }); + + return { + ...rewriteFramesInstance, + name: 'DistDirRewriteFrames', + }; +}); diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 1373bb7a0905..b9211141269f 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -13,9 +13,9 @@ import { DEBUG_BUILD } from '../common/debug-build'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; import { isBuild } from '../common/utils/isBuild'; +import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration'; import { Http } from './httpIntegration'; import { OnUncaughtException } from './onUncaughtExceptionIntegration'; -import { rewriteFramesIntegration } from './rewriteFramesIntegration'; export { createReduxEnhancer } from '@sentry/react'; export * from '@sentry/node'; @@ -27,7 +27,9 @@ export const Integrations = { OnUncaughtException, }; -export { rewriteFramesIntegration }; +const globalWithInjectedValues = global as typeof global & { + __rewriteFramesDistDir__?: string; +}; /** * A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors @@ -64,12 +66,6 @@ export function showReportDialog(): void { return; } -// TODO (v8): Remove this -/** - * @deprecated This constant will be removed in the next major update. - */ -export const IS_BUILD = isBuild(); - const IS_VERCEL = !!process.env.VERCEL; /** Inits the Sentry NextJS SDK on node. */ @@ -84,11 +80,17 @@ export function init(options: NodeOptions): void { ...getDefaultIntegrations(options).filter( integration => !['Http', 'OnUncaughtException'].includes(integration.name), ), - rewriteFramesIntegration(), new Http(), new OnUncaughtException(), ]; + // This value is injected at build time, based on the output directory specified in the build config. Though a default + // is set there, we set it here as well, just in case something has gone wrong with the injection. + const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + if (distDirName) { + customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); + } + const opts = { environment: process.env.SENTRY_ENVIRONMENT || getVercelEnv(false) || process.env.NODE_ENV, defaultIntegrations: customDefaultIntegrations, @@ -137,20 +139,6 @@ function sdkAlreadyInitialized(): boolean { return !!getClient(); } -// TODO (v8): Remove this -/** - * @deprecated This constant will be removed in the next major update. - */ -const deprecatedIsBuild = (): boolean => isBuild(); -// eslint-disable-next-line deprecation/deprecation -export { deprecatedIsBuild as isBuild }; - export * from '../common'; -export { - // eslint-disable-next-line deprecation/deprecation - withSentry, - // eslint-disable-next-line deprecation/deprecation - withSentryAPI, - wrapApiHandlerWithSentry, -} from '../common/wrapApiHandlerWithSentry'; +export { wrapApiHandlerWithSentry } from '../common/wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts deleted file mode 100644 index f27ff9a9993d..000000000000 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as path from 'path'; -import { defineIntegration } from '@sentry/core'; -import { - RewriteFrames as OriginalRewriteFrames, - rewriteFramesIntegration as originalRewriteFramesIntegration, -} from '@sentry/integrations'; -import type { IntegrationFn, StackFrame } from '@sentry/types'; -import { escapeStringForRegex } from '@sentry/utils'; - -const globalWithInjectedValues = global as typeof global & { - __rewriteFramesDistDir__?: string; -}; - -type StackFrameIteratee = (frame: StackFrame) => StackFrame; -interface RewriteFramesOptions { - root?: string; - prefix?: string; - iteratee?: StackFrameIteratee; -} - -export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; - - if (distDirName) { - // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so - // we can read in the project directory from the currently running process - const distDirAbsPath = path.resolve(distDirName).replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one - // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- user input is escaped - const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath)); - - return originalRewriteFramesIntegration({ - iteratee: frame => { - frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); - return frame; - }, - ...options, - }); - } - - // Do nothing if we can't find a distDirName - return { - // eslint-disable-next-line deprecation/deprecation - name: OriginalRewriteFrames.id, - // eslint-disable-next-line @typescript-eslint/no-empty-function - setupOnce: () => {}, - processEvent: event => event, - }; -}) satisfies IntegrationFn; - -export const rewriteFramesIntegration = defineIntegration(customRewriteFramesIntegration); diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 0ce7733dc137..4bfcb7729f52 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -1,13 +1,12 @@ import { BaseClient } from '@sentry/core'; import * as SentryReact from '@sentry/react'; import type { BrowserClient } from '@sentry/react'; -import { browserTracingIntegration } from '@sentry/react'; import { WINDOW, getClient, getCurrentScope } from '@sentry/react'; import type { Integration } from '@sentry/types'; import { logger } from '@sentry/utils'; import { JSDOM } from 'jsdom'; -import { BrowserTracing, breadcrumbsIntegration, init, nextRouterInstrumentation } from '../src/client'; +import { breadcrumbsIntegration, browserTracingIntegration, init } from '../src/client'; const reactInit = jest.spyOn(SentryReact, 'init'); const captureEvent = jest.spyOn(BaseClient.prototype, 'captureEvent'); @@ -65,7 +64,7 @@ describe('Client init()', () => { environment: 'test', defaultIntegrations: expect.arrayContaining([ expect.objectContaining({ - name: 'RewriteFrames', + name: 'NextjsClientStackFrameNormalization', }), ]), }), @@ -95,9 +94,7 @@ describe('Client init()', () => { // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(undefined); - SentryReact.startSpan({ name: '/404' }, () => { - // noop - }); + SentryReact.startInactiveSpan({ name: '/404' })?.end(); expect(transportSend).not.toHaveBeenCalled(); expect(captureEvent.mock.results[0].value).toBeUndefined(); @@ -117,73 +114,35 @@ describe('Client init()', () => { expect(installedBreadcrumbsIntegration).toBeDefined(); }); - it('forces correct router instrumentation if user provides `BrowserTracing` in an array', () => { + it('forces correct router instrumentation if user provides `browserTracingIntegration` in an array', () => { + const providedBrowserTracingInstance = browserTracingIntegration(); + init({ dsn: TEST_DSN, tracesSampleRate: 1.0, - // eslint-disable-next-line deprecation/deprecation - integrations: [new BrowserTracing({ finalTimeout: 10 })], + integrations: [providedBrowserTracingInstance], }); const client = getClient()!; - // eslint-disable-next-line deprecation/deprecation - const browserTracingIntegration = client.getIntegrationByName('BrowserTracing'); - - expect(browserTracingIntegration).toBeDefined(); - expect(browserTracingIntegration?.options).toEqual( - expect.objectContaining({ - // eslint-disable-next-line deprecation/deprecation - routingInstrumentation: nextRouterInstrumentation, - // This proves it's still the user's copy - finalTimeout: 10, - }), - ); - }); - - it('forces correct router instrumentation if user provides `browserTracingIntegration`', () => { - init({ - dsn: TEST_DSN, - integrations: [browserTracingIntegration({ finalTimeout: 10 })], - enableTracing: true, - }); - const client = getClient()!; - // eslint-disable-next-line deprecation/deprecation - const integration = client.getIntegrationByName('BrowserTracing'); - - expect(integration).toBeDefined(); - expect(integration?.options).toEqual( - expect.objectContaining({ - // eslint-disable-next-line deprecation/deprecation - routingInstrumentation: nextRouterInstrumentation, - // This proves it's still the user's copy - finalTimeout: 10, - }), - ); + const integration = client.getIntegrationByName('BrowserTracing'); + expect(integration).toBe(providedBrowserTracingInstance); }); it('forces correct router instrumentation if user provides `BrowserTracing` in a function', () => { + const providedBrowserTracingInstance = browserTracingIntegration(); + init({ dsn: TEST_DSN, tracesSampleRate: 1.0, - // eslint-disable-next-line deprecation/deprecation - integrations: defaults => [...defaults, new BrowserTracing({ startTransactionOnLocationChange: false })], + integrations: defaults => [...defaults, providedBrowserTracingInstance], }); const client = getClient()!; - // eslint-disable-next-line deprecation/deprecation - const browserTracingIntegration = client.getIntegrationByName('BrowserTracing'); - - expect(browserTracingIntegration).toBeDefined(); - expect(browserTracingIntegration?.options).toEqual( - expect.objectContaining({ - // eslint-disable-next-line deprecation/deprecation - routingInstrumentation: nextRouterInstrumentation, - // This proves it's still the user's copy - startTransactionOnLocationChange: false, - }), - ); + const integration = client.getIntegrationByName('BrowserTracing'); + + expect(integration).toBe(providedBrowserTracingInstance); }); describe('browserTracingIntegration()', () => { diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index 1ae933549b17..b199782cd7a6 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -3,7 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, add import type { NextApiRequest, NextApiResponse } from 'next'; import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/common/types'; -import { withSentry } from '../../src/server'; +import { wrapApiHandlerWithSentry } from '../../src/server'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the NodeClient // constructor but the client isn't used in these tests. @@ -18,8 +18,7 @@ describe('withSentry', () => { res.send('Good dog, Maisey!'); }; - // eslint-disable-next-line deprecation/deprecation - const wrappedHandlerNoError = withSentry(origHandlerNoError); + const wrappedHandlerNoError = wrapApiHandlerWithSentry(origHandlerNoError, '/my-parameterized-route'); beforeEach(() => { req = { url: 'http://dogs.are.great' } as NextApiRequest; @@ -42,7 +41,7 @@ describe('withSentry', () => { await wrappedHandlerNoError(req, res); expect(startSpanManualSpy).toHaveBeenCalledWith( { - name: 'GET http://dogs.are.great', + name: 'GET /my-parameterized-route', op: 'http.server', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', diff --git a/packages/nextjs/test/config/wrappingLoader.test.ts b/packages/nextjs/test/config/wrappingLoader.test.ts index 2c05b4bcdbff..eec179725e74 100644 --- a/packages/nextjs/test/config/wrappingLoader.test.ts +++ b/packages/nextjs/test/config/wrappingLoader.test.ts @@ -100,6 +100,4 @@ describe('wrappingLoader', () => { expect(callback).toHaveBeenCalledWith(null, expect.stringContaining("'/my/route'"), expect.anything()); }); - - it.todo('should correctly wrap API routes on unix'); }); diff --git a/packages/nextjs/test/integration/sentry.client.config.js b/packages/nextjs/test/integration/sentry.client.config.js index dbb6e760ea8d..4345033e9cdc 100644 --- a/packages/nextjs/test/integration/sentry.client.config.js +++ b/packages/nextjs/test/integration/sentry.client.config.js @@ -1,15 +1,7 @@ import * as Sentry from '@sentry/nextjs'; -import { Integrations } from '@sentry/tracing'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampler: () => true, debug: process.env.SDK_DEBUG, - - integrations: [ - new Integrations.BrowserTracing({ - // Used for testing http tracing - tracingOrigins: ['http://example.com'], - }), - ], }); diff --git a/packages/nextjs/test/performance/appRouterInstrumentation.test.ts b/packages/nextjs/test/performance/appRouterInstrumentation.test.ts index 34a6b31fc60f..47a8546e79a3 100644 --- a/packages/nextjs/test/performance/appRouterInstrumentation.test.ts +++ b/packages/nextjs/test/performance/appRouterInstrumentation.test.ts @@ -29,31 +29,18 @@ describe('appRouterInstrumentation', () => { it('should create a pageload transactions with the current location name', () => { setUpPage('https://example.com/some/page?someParam=foobar'); - const startTransactionCallbackFn = jest.fn(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); - appRouterInstrumentation(startTransactionCallbackFn, true, false, mockStartPageloadSpan, mockStartNavigationSpan); - expect(startTransactionCallbackFn).toHaveBeenCalledWith( - expect.objectContaining({ - name: '/some/page', - op: 'pageload', - origin: 'auto.pageload.nextjs.app_router_instrumentation', - tags: { - 'routing.instrumentation': 'next-app-router', - }, - metadata: { source: 'url' }, - }), - ); + appRouterInstrumentation(true, false, mockStartPageloadSpan, mockStartNavigationSpan); expect(mockStartPageloadSpan).toHaveBeenCalledWith( expect.objectContaining({ name: '/some/page', - op: 'pageload', - origin: 'auto.pageload.nextjs.app_router_instrumentation', - tags: { - 'routing.instrumentation': 'next-app-router', + attributes: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation', + 'sentry.source': 'url', }, - metadata: { source: 'url' }, }), ); }); @@ -64,7 +51,7 @@ describe('appRouterInstrumentation', () => { const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); - appRouterInstrumentation(startTransactionCallbackFn, false, false, mockStartPageloadSpan, mockStartNavigationSpan); + appRouterInstrumentation(false, false, mockStartPageloadSpan, mockStartNavigationSpan); expect(startTransactionCallbackFn).not.toHaveBeenCalled(); }); @@ -76,11 +63,10 @@ describe('appRouterInstrumentation', () => { fetchInstrumentationHandlerCallback = callback; }); - const startTransactionCallbackFn = jest.fn(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); - appRouterInstrumentation(startTransactionCallbackFn, false, true, mockStartPageloadSpan, mockStartNavigationSpan); + appRouterInstrumentation(false, true, mockStartPageloadSpan, mockStartNavigationSpan); fetchInstrumentationHandlerCallback!({ args: [ @@ -95,23 +81,15 @@ describe('appRouterInstrumentation', () => { startTimestamp: 1337, }); - expect(startTransactionCallbackFn).toHaveBeenCalledWith({ - name: '/some/server/component/page', - op: 'navigation', - origin: 'auto.navigation.nextjs.app_router_instrumentation', - metadata: { source: 'url' }, - tags: { - from: '/some/page', - 'routing.instrumentation': 'next-app-router', - }, - }); expect(mockStartNavigationSpan).toHaveBeenCalledWith({ name: '/some/server/component/page', - op: 'navigation', - origin: 'auto.navigation.nextjs.app_router_instrumentation', - metadata: { source: 'url' }, + attributes: { + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.nextjs.app_router_instrumentation', + 'sentry.source': 'url', + }, tags: { - from: '/some/page', + from: '/some/server/component/page', 'routing.instrumentation': 'next-app-router', }, }); @@ -176,7 +154,7 @@ describe('appRouterInstrumentation', () => { const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); - appRouterInstrumentation(startTransactionCallbackFn, false, true, mockStartPageloadSpan, mockStartNavigationSpan); + appRouterInstrumentation(false, true, mockStartPageloadSpan, mockStartNavigationSpan); fetchInstrumentationHandlerCallback!(fetchCallbackData); expect(startTransactionCallbackFn).not.toHaveBeenCalled(); expect(mockStartNavigationSpan).not.toHaveBeenCalled(); @@ -186,12 +164,11 @@ describe('appRouterInstrumentation', () => { it('should not create navigation transactions when `startTransactionOnLocationChange` is false', () => { setUpPage('https://example.com/some/page?someParam=foobar'); const addFetchInstrumentationHandlerImpl = jest.fn(); - const startTransactionCallbackFn = jest.fn(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); addFetchInstrumentationHandlerSpy.mockImplementationOnce(addFetchInstrumentationHandlerImpl); - appRouterInstrumentation(startTransactionCallbackFn, false, false, mockStartPageloadSpan, mockStartNavigationSpan); + appRouterInstrumentation(false, false, mockStartPageloadSpan, mockStartNavigationSpan); expect(addFetchInstrumentationHandlerImpl).not.toHaveBeenCalled(); expect(mockStartNavigationSpan).not.toHaveBeenCalled(); }); diff --git a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts index 3e032c1f01d1..a3bb0adf6e3e 100644 --- a/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts +++ b/packages/nextjs/test/performance/pagesRouterInstrumentation.test.ts @@ -1,5 +1,4 @@ import { WINDOW } from '@sentry/react'; -import type { Transaction } from '@sentry/types'; import { JSDOM } from 'jsdom'; import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils'; import { default as Router } from 'next/router'; @@ -45,18 +44,6 @@ jest.mock('next/router', () => { }; }); -function createMockStartTransaction() { - return jest.fn( - () => - ({ - startChild: () => ({ - end: () => undefined, - }), - end: () => undefined, - }) as Transaction, - ); -} - describe('pagesRouterInstrumentation', () => { const originalGlobalDocument = WINDOW.document; const originalGlobalLocation = WINDOW.location; @@ -134,17 +121,11 @@ describe('pagesRouterInstrumentation', () => { true, { name: '/[user]/posts/[id]', - op: 'pageload', - tags: { - 'routing.instrumentation': 'next-pages-router', - }, - metadata: { - source: 'route', - dynamicSamplingContext: { environment: 'myEnv', release: '2.1.0' }, + attributes: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation', + 'sentry.source': 'route', }, - traceId: 'c82b8554881b4d28ad977de04a4fb40a', - parentSpanId: 'a755953cd3394d5f', - parentSampled: true, }, ], [ @@ -160,17 +141,11 @@ describe('pagesRouterInstrumentation', () => { true, { name: '/some-page', - op: 'pageload', - tags: { - 'routing.instrumentation': 'next-pages-router', + attributes: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation', + 'sentry.source': 'route', }, - metadata: { - source: 'route', - dynamicSamplingContext: { environment: 'myEnv', release: '2.1.0' }, - }, - traceId: 'c82b8554881b4d28ad977de04a4fb40a', - parentSpanId: 'a755953cd3394d5f', - parentSampled: true, }, ], [ @@ -181,12 +156,10 @@ describe('pagesRouterInstrumentation', () => { true, { name: '/', - op: 'pageload', - tags: { - 'routing.instrumentation': 'next-pages-router', - }, - metadata: { - source: 'route', + attributes: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation', + 'sentry.source': 'route', }, }, ], @@ -198,55 +171,25 @@ describe('pagesRouterInstrumentation', () => { false, // no __NEXT_DATA__ tag { name: '/lforst/posts/1337', - op: 'pageload', - tags: { - 'routing.instrumentation': 'next-pages-router', - }, - metadata: { - source: 'url', + attributes: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation', + 'sentry.source': 'url', }, }, ], ])( 'creates a pageload transaction (#%#)', (url, route, query, props, hasNextData, expectedStartTransactionArgument) => { - const mockStartTransaction = createMockStartTransaction(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); setUpNextPage({ url, route, query, props, hasNextData }); - pagesRouterInstrumentation( - mockStartTransaction, - undefined, - undefined, - mockStartPageloadSpan, - mockStartNavigationSpan, - ); + pagesRouterInstrumentation(true, true, mockStartPageloadSpan, mockStartNavigationSpan); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenLastCalledWith( - expect.objectContaining(expectedStartTransactionArgument), - ); expect(mockStartPageloadSpan).toHaveBeenCalledWith(expect.objectContaining(expectedStartTransactionArgument)); }, ); - - it('does not create a pageload transaction if option not given', () => { - const mockStartTransaction = createMockStartTransaction(); - const mockStartPageloadSpan = jest.fn(); - const mockStartNavigationSpan = jest.fn(); - - setUpNextPage({ url: 'https://example.com/', route: '/', hasNextData: false }); - pagesRouterInstrumentation( - mockStartTransaction, - false, - undefined, - mockStartPageloadSpan, - mockStartNavigationSpan, - ); - expect(mockStartTransaction).not.toHaveBeenCalled(); - expect(mockStartPageloadSpan).not.toHaveBeenCalled(); - }); }); describe('new navigation transactions', () => { @@ -272,7 +215,6 @@ describe('pagesRouterInstrumentation', () => { ])( 'should create a parameterized transaction on route change (%s)', (targetLocation, expectedTransactionName, expectedTransactionSource) => { - const mockStartTransaction = createMockStartTransaction(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); @@ -293,46 +235,24 @@ describe('pagesRouterInstrumentation', () => { ], }); - pagesRouterInstrumentation(mockStartTransaction, false, true, mockStartPageloadSpan, mockStartNavigationSpan); + pagesRouterInstrumentation(false, true, mockStartPageloadSpan, mockStartNavigationSpan); Router.events.emit('routeChangeStart', targetLocation); - expect(mockStartTransaction).toHaveBeenCalledTimes(1); - expect(mockStartTransaction).toHaveBeenCalledWith( - expect.objectContaining({ - name: expectedTransactionName, - op: 'navigation', - tags: expect.objectContaining({ - 'routing.instrumentation': 'next-pages-router', - }), - metadata: expect.objectContaining({ - source: expectedTransactionSource, - }), - }), - ); expect(mockStartNavigationSpan).toHaveBeenCalledWith( expect.objectContaining({ name: expectedTransactionName, - op: 'navigation', - tags: expect.objectContaining({ - 'routing.instrumentation': 'next-pages-router', - }), - metadata: expect.objectContaining({ - source: expectedTransactionSource, - }), + attributes: { + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.nextjs.pages_router_instrumentation', + 'sentry.source': expectedTransactionSource, + }, }), ); - - Router.events.emit('routeChangeComplete', targetLocation); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(Router.events.off).toHaveBeenCalledWith('routeChangeComplete', expect.anything()); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(Router.events.off).toHaveBeenCalledTimes(1); }, ); it('should not create transaction when navigation transactions are disabled', () => { - const mockStartTransaction = createMockStartTransaction(); const mockStartPageloadSpan = jest.fn(); const mockStartNavigationSpan = jest.fn(); @@ -343,11 +263,10 @@ describe('pagesRouterInstrumentation', () => { navigatableRoutes: ['/home', '/posts/[id]'], }); - pagesRouterInstrumentation(mockStartTransaction, false, false, mockStartPageloadSpan, mockStartNavigationSpan); + pagesRouterInstrumentation(false, false, mockStartPageloadSpan, mockStartNavigationSpan); Router.events.emit('routeChangeStart', '/posts/42'); - expect(mockStartTransaction).not.toHaveBeenCalled(); expect(mockStartNavigationSpan).not.toHaveBeenCalled(); }); }); diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 1c7c9e384657..da30862eb6ac 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -1,6 +1,5 @@ -import { runWithAsyncContext } from '@sentry/core'; import * as SentryNode from '@sentry/node'; -import { NodeClient, getClient, getCurrentHub, getCurrentScope } from '@sentry/node'; +import { getClient, getCurrentScope } from '@sentry/node'; import type { Integration } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; @@ -113,44 +112,6 @@ describe('Server init()', () => { expect(loggerLogSpy).toHaveBeenCalledWith('An event processor returned `null`, will not send event.'); }); - it("initializes both global hub and domain hub when there's an active domain", () => { - // eslint-disable-next-line deprecation/deprecation - const globalHub = getCurrentHub(); - - runWithAsyncContext(() => { - // eslint-disable-next-line deprecation/deprecation - const globalHub2 = getCurrentHub(); - // If we call runWithAsyncContext before init, it executes the callback in the same context as there is no - // strategy yet - expect(globalHub2).toBe(globalHub); - // eslint-disable-next-line deprecation/deprecation - expect(globalHub.getClient()).toBeUndefined(); - // eslint-disable-next-line deprecation/deprecation - expect(globalHub2.getClient()).toBeUndefined(); - - init({}); - - runWithAsyncContext(() => { - // eslint-disable-next-line deprecation/deprecation - const domainHub = getCurrentHub(); - // this tag should end up only in the domain hub - // eslint-disable-next-line deprecation/deprecation - domainHub.setTag('dogs', 'areGreat'); - - // eslint-disable-next-line deprecation/deprecation - expect(globalHub.getClient()).toEqual(expect.any(NodeClient)); - // eslint-disable-next-line deprecation/deprecation - expect(domainHub.getClient()).toBe(globalHub.getClient()); - // @ts-expect-error need access to protected _tags attribute - // eslint-disable-next-line deprecation/deprecation - expect(globalHub.getScope()._tags).toEqual({ runtime: 'node' }); - // @ts-expect-error need access to protected _tags attribute - // eslint-disable-next-line deprecation/deprecation - expect(domainHub.getScope()._tags).toEqual({ runtime: 'node', dogs: 'areGreat' }); - }); - }); - }); - describe('integrations', () => { // Options passed by `@sentry/nextjs`'s `init` to `@sentry/node`'s `init` after modifying them type ModifiedInitOptions = { integrations: Integration[]; defaultIntegrations: Integration[] }; @@ -159,14 +120,14 @@ describe('Server init()', () => { init({}); const nodeInitOptions = nodeInit.mock.calls[0][0] as ModifiedInitOptions; - const rewriteFramesIntegration = findIntegrationByName(nodeInitOptions.defaultIntegrations, 'RewriteFrames'); + const integrationNames = nodeInitOptions.defaultIntegrations.map(integration => integration.name); const httpIntegration = findIntegrationByName(nodeInitOptions.defaultIntegrations, 'Http'); const onUncaughtExceptionIntegration = findIntegrationByName( nodeInitOptions.defaultIntegrations, 'OnUncaughtException', ); - expect(rewriteFramesIntegration).toBeDefined(); + expect(integrationNames).toContain('DistDirRewriteFrames'); expect(httpIntegration).toBeDefined(); expect(onUncaughtExceptionIntegration).toBeDefined(); }); diff --git a/packages/tracing-internal/src/index.ts b/packages/tracing-internal/src/index.ts index 4f09ca6a2e96..47a26c8a4d92 100644 --- a/packages/tracing-internal/src/index.ts +++ b/packages/tracing-internal/src/index.ts @@ -32,3 +32,5 @@ export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './commo export type { RequestInstrumentationOptions } from './browser'; export { addExtensionMethods } from './extensions'; + +export { DEFAULT_TRACE_PROPAGATION_TARGETS } from './browser/request'; From ee9a9d6c84c638de70da23b4d93e1bcc4edb1ebb Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 9 Feb 2024 17:00:29 -0500 Subject: [PATCH 027/173] feat(v8/node): Remove deprecated anr methods (#10562) Removes all references to `enableAnrDetection` and the `Anr` integration. Moves us toward https://github.com/getsentry/sentry-javascript/issues/8844 in the node side. --- MIGRATION.md | 6 ++++ .../scripts/consistentExports.ts | 2 +- .../suites/anr/basic-session.js | 2 +- .../suites/anr/basic.js | 2 +- .../suites/anr/basic.mjs | 2 +- .../suites/anr/forked.js | 2 +- .../suites/anr/legacy.js | 31 ----------------- .../suites/anr/should-exit-forced.js | 2 +- .../suites/anr/should-exit.js | 2 +- .../node-integration-tests/suites/anr/test.ts | 5 --- packages/bun/src/index.ts | 2 -- packages/node/src/index.ts | 3 -- packages/node/src/integrations/anr/index.ts | 21 ++---------- packages/node/src/integrations/anr/legacy.ts | 33 ------------------- packages/node/src/integrations/index.ts | 1 - packages/remix/src/index.server.ts | 2 -- packages/sveltekit/src/server/index.ts | 2 -- 17 files changed, 15 insertions(+), 105 deletions(-) delete mode 100644 dev-packages/node-integration-tests/suites/anr/legacy.js delete mode 100644 packages/node/src/integrations/anr/legacy.ts diff --git a/MIGRATION.md b/MIGRATION.md index 5f1c90c09ca5..24ba1c079bdc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -27,6 +27,12 @@ enum. If you were using the `Severity` enum, you should replace it with the `Sev The `Offline` integration has been removed in favor of the offline transport wrapper: http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching +## Removal of `enableAnrDetection` and `Anr` class (##10562) + +The `enableAnrDetection` and `Anr` class have been removed. See the +[docs](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) for more details how to migrate +to `anrIntegration`, the new integration for ANR detection. + ## Other changes - Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index cf8233680c11..23bb11e9d237 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -60,7 +60,7 @@ const DEPENDENTS: Dependent[] = [ { package: '@sentry/serverless', exports: Object.keys(SentryServerless), - ignoreExports: ['cron', 'hapiErrorPlugin', 'enableAnrDetection'], + ignoreExports: ['cron', 'hapiErrorPlugin'], }, { package: '@sentry/sveltekit', 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 fe4190c8cc46..5a41d1db6f43 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -11,7 +11,7 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', debug: true, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); function longWork() { diff --git a/dev-packages/node-integration-tests/suites/anr/basic.js b/dev-packages/node-integration-tests/suites/anr/basic.js index 097dec6c925c..f9552a974aed 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -12,7 +12,7 @@ Sentry.init({ release: '1.0', debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); function longWork() { diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index 43a8d02a41ac..5526c8b71c38 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -12,7 +12,7 @@ Sentry.init({ release: '1.0', debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); function longWork() { diff --git a/dev-packages/node-integration-tests/suites/anr/forked.js b/dev-packages/node-integration-tests/suites/anr/forked.js index 097dec6c925c..f9552a974aed 100644 --- a/dev-packages/node-integration-tests/suites/anr/forked.js +++ b/dev-packages/node-integration-tests/suites/anr/forked.js @@ -12,7 +12,7 @@ Sentry.init({ release: '1.0', debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); function longWork() { diff --git a/dev-packages/node-integration-tests/suites/anr/legacy.js b/dev-packages/node-integration-tests/suites/anr/legacy.js deleted file mode 100644 index f91db4bec054..000000000000 --- a/dev-packages/node-integration-tests/suites/anr/legacy.js +++ /dev/null @@ -1,31 +0,0 @@ -const crypto = require('crypto'); -const assert = require('assert'); - -const Sentry = require('@sentry/node'); - -setTimeout(() => { - process.exit(); -}, 10000); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - debug: true, - autoSessionTracking: false, -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 100 }).then(() => { - function longWork() { - for (let i = 0; i < 20; i++) { - const salt = crypto.randomBytes(128).toString('base64'); - // eslint-disable-next-line no-unused-vars - const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); - assert.ok(hash); - } - } - - setTimeout(() => { - longWork(); - }, 1000); -}); diff --git a/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js b/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js index cee261e8ccb3..447284d17018 100644 --- a/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js +++ b/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js @@ -6,7 +6,7 @@ function configureSentry() { release: '1.0', debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true })], }); } diff --git a/dev-packages/node-integration-tests/suites/anr/should-exit.js b/dev-packages/node-integration-tests/suites/anr/should-exit.js index 7d0ba8db4484..5816532ba972 100644 --- a/dev-packages/node-integration-tests/suites/anr/should-exit.js +++ b/dev-packages/node-integration-tests/suites/anr/should-exit.js @@ -6,7 +6,7 @@ function configureSentry() { release: '1.0', debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true })], + integrations: [Sentry.anrIntegration({ captureStackTrace: true })], }); } diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index 966c47ad0370..210f32588588 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -56,11 +56,6 @@ conditionalTest({ min: 16 })('should report ANR when event loop blocked', () => cleanupChildProcesses(); }); - // TODO (v8): Remove this old API and this test - test('Legacy API', done => { - createRunner(__dirname, 'legacy.js').expect({ event: EXPECTED_ANR_EVENT }).start(done); - }); - test('CJS', done => { createRunner(__dirname, 'basic.js').expect({ event: EXPECTED_ANR_EVENT }).start(done); }); diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index e8a4738b9bba..d58a59eab09d 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -87,8 +87,6 @@ export { } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { - // eslint-disable-next-line deprecation/deprecation - enableAnrDetection, // eslint-disable-next-line deprecation/deprecation getModuleFromFilename, DEFAULT_USER_INCLUDES, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 3e91aae28d14..7f195957249b 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -108,9 +108,6 @@ import { createGetModuleFromFilename } from './module'; export const getModuleFromFilename = createGetModuleFromFilename(); export { createGetModuleFromFilename }; -// eslint-disable-next-line deprecation/deprecation -export { enableAnrDetection } from './integrations/anr/legacy'; - import { Integrations as CoreIntegrations } from '@sentry/core'; import * as Handlers from './handlers'; diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 91deb2259e72..7da63fdc50bd 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -1,7 +1,7 @@ // TODO (v8): This import can be removed once we only support Node with global URL import { URL } from 'url'; -import { convertIntegrationFnToClass, defineIntegration, getCurrentScope } from '@sentry/core'; -import type { Client, Contexts, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { defineIntegration, getCurrentScope } from '@sentry/core'; +import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; import { dynamicRequire, logger } from '@sentry/utils'; import type { Worker, WorkerOptions } from 'worker_threads'; import type { NodeClient } from '../../client'; @@ -70,23 +70,6 @@ const _anrIntegration = ((options: Partial = {}) => { export const anrIntegration = defineIntegration(_anrIntegration); -/** - * Starts a thread to detect App Not Responding (ANR) events - * - * ANR detection requires Node 16.17.0 or later - * - * @deprecated Use `anrIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration) as IntegrationClass< - Integration & { setup: (client: NodeClient) => void } -> & { - new (options?: Partial): Integration & { setup(client: Client): void }; -}; - -// eslint-disable-next-line deprecation/deprecation -export type Anr = typeof Anr; - /** * Starts the ANR worker thread */ diff --git a/packages/node/src/integrations/anr/legacy.ts b/packages/node/src/integrations/anr/legacy.ts deleted file mode 100644 index d8b4ff1bc6dc..000000000000 --- a/packages/node/src/integrations/anr/legacy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getClient } from '@sentry/core'; -import { Anr } from '.'; -import type { NodeClient } from '../../client'; - -// TODO (v8): Remove this entire file and the `enableAnrDetection` export - -interface LegacyOptions { - entryScript: string; - pollInterval: number; - anrThreshold: number; - captureStackTrace: boolean; - debug: boolean; -} - -/** - * @deprecated Use the `Anr` integration instead. - * - * ```ts - * import * as Sentry from '@sentry/node'; - * - * Sentry.init({ - * dsn: '__DSN__', - * integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true })], - * }); - * ``` - */ -export function enableAnrDetection(options: Partial): Promise { - const client = getClient() as NodeClient; - // eslint-disable-next-line deprecation/deprecation - const integration = new Anr(options); - integration.setup(client); - return Promise.resolve(); -} diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts index 72f49efbc95d..083df21bd68d 100644 --- a/packages/node/src/integrations/index.ts +++ b/packages/node/src/integrations/index.ts @@ -9,5 +9,4 @@ export { Context } from './context'; export { RequestData } from '@sentry/core'; export { Undici } from './undici'; export { Spotlight } from './spotlight'; -export { Anr } from './anr'; export { Hapi } from './hapi'; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index fbf41d811b6f..51ac03a44694 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -98,8 +98,6 @@ export { createGetModuleFromFilename, hapiErrorPlugin, runWithAsyncContext, - // eslint-disable-next-line deprecation/deprecation - enableAnrDetection, } from '@sentry/node'; // Keeping the `*` exports for backwards compatibility and types diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 614ec4998969..0615398ec325 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -93,8 +93,6 @@ export { hapiErrorPlugin, metrics, runWithAsyncContext, - // eslint-disable-next-line deprecation/deprecation - enableAnrDetection, } from '@sentry/node'; // We can still leave this for the carrier init and type exports From 9f0ac3417fbb34876733592e4a4726af28210290 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 9 Feb 2024 23:01:12 +0100 Subject: [PATCH 028/173] ref: Remove `lastEventId` (#10585) After the deprecation in v7 we now remove `lastEventId`. --- packages/astro/src/index.server.ts | 2 -- packages/astro/src/index.types.ts | 5 ----- packages/browser/src/exports.ts | 2 -- packages/browser/src/sdk.ts | 7 ------- packages/bun/src/index.ts | 2 -- packages/core/src/exports.ts | 11 ----------- packages/core/src/hub.ts | 19 ++----------------- packages/core/src/index.ts | 2 -- packages/deno/src/index.ts | 2 -- packages/node-experimental/src/index.ts | 2 -- packages/node-experimental/src/sdk/api.ts | 9 --------- packages/node-experimental/src/sdk/hub.ts | 2 -- packages/node-experimental/src/sdk/scope.ts | 14 -------------- packages/node-experimental/src/sdk/types.ts | 4 ---- packages/node/src/index.ts | 2 -- packages/react/src/errorboundary.tsx | 3 +-- packages/remix/src/index.server.ts | 2 -- packages/remix/src/index.types.ts | 6 ------ packages/serverless/src/index.ts | 2 -- packages/sveltekit/src/index.types.ts | 5 ----- packages/sveltekit/src/server/index.ts | 2 -- packages/types/src/hub.ts | 9 --------- packages/vercel-edge/src/index.ts | 2 -- 23 files changed, 3 insertions(+), 113 deletions(-) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 6ba4f76f78a2..ad22273818f7 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -58,8 +58,6 @@ export { defaultIntegrations, getDefaultIntegrations, defaultStackParser, - // eslint-disable-next-line deprecation/deprecation - lastEventId, flush, close, getSentryRelease, diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index 026321e8ab3d..8c1602d371b6 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -26,9 +26,4 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; -/** - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ -export declare function lastEventId(): string | undefined; - export default sentryAstro; diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index f0f717f084cc..2394dd8013ea 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -42,8 +42,6 @@ export { getCurrentScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 1c57f534867c..4095a8a8c2ba 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -198,13 +198,6 @@ export const showReportDialog: ShowReportDialogFunction = ( }; } - // TODO(v8): Remove this entire if statement. `eventId` will be a required option. - // eslint-disable-next-line deprecation/deprecation - if (!options.eventId) { - // eslint-disable-next-line deprecation/deprecation - options.eventId = hub.lastEventId(); - } - const script = WINDOW.document.createElement('script'); script.async = true; script.crossOrigin = 'anonymous'; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index d58a59eab09d..295bad3271ea 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -49,8 +49,6 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, runWithAsyncContext, diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 22dd5f26a3ba..8691aa9e70cd 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -370,17 +370,6 @@ export async function close(timeout?: number): Promise { return Promise.resolve(false); } -/** - * This is the getter for lastEventId. - * - * @returns The last event id of a captured event. - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ -export function lastEventId(): string | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().lastEventId(); -} - /** * Get the currently active client. */ diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index f8f274c9e091..e729a5c8f763 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -121,9 +121,6 @@ export class Hub implements HubInterface { /** Is a {@link Layer}[] containing the client and scope */ private readonly _stack: Layer[]; - /** Contains the last event id of a captured event. */ - private _lastEventId?: string; - private _isolationScope: Scope; /** @@ -354,7 +351,7 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.captureException()` instead. */ public captureException(exception: unknown, hint?: EventHint): string { - const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4()); + const eventId = hint && hint.event_id ? hint.event_id : uuid4(); const syntheticException = new Error('Sentry syntheticException'); // eslint-disable-next-line deprecation/deprecation this.getScope().captureException(exception, { @@ -373,7 +370,7 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.captureMessage()` instead. */ public captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string { - const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4()); + const eventId = hint && hint.event_id ? hint.event_id : uuid4(); const syntheticException = new Error(message); // eslint-disable-next-line deprecation/deprecation this.getScope().captureMessage(message, level, { @@ -393,23 +390,11 @@ export class Hub implements HubInterface { */ public captureEvent(event: Event, hint?: EventHint): string { const eventId = hint && hint.event_id ? hint.event_id : uuid4(); - if (!event.type) { - this._lastEventId = eventId; - } // eslint-disable-next-line deprecation/deprecation this.getScope().captureEvent(event, { ...hint, event_id: eventId }); return eventId; } - /** - * @inheritDoc - * - * @deprecated This will be removed in v8. - */ - public lastEventId(): string | undefined { - return this._lastEventId; - } - /** * @inheritDoc * diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 849e34f6c92b..992de9f60739 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -20,8 +20,6 @@ export { configureScope, flush, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation startTransaction, setContext, setExtra, diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 0d0e357737ac..579bad78c869 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -48,8 +48,6 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, runWithAsyncContext, diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 680e45d1282a..5b19697732a8 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -36,8 +36,6 @@ export { captureMessage, addGlobalEventProcessor, addEventProcessor, - // eslint-disable-next-line deprecation/deprecation - lastEventId, setContext, setExtra, setExtras, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index c4a0f3858111..f1958ba883b4 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -96,15 +96,6 @@ export function withIsolationScope(callback: (isolationScope: Scope) => T): T }); } -/** - * Get the ID of the last sent error event. - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ -export function lastEventId(): string | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentScope().lastEventId(); -} - /** * Configure the current scope. * @deprecated Use `getCurrentScope()` instead. diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 13eb7aeb7707..0a5da5c09e0c 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -16,7 +16,6 @@ import { configureScope, getClient, getCurrentScope, - lastEventId, setContext, setExtra, setExtras, @@ -71,7 +70,6 @@ export function getCurrentHub(): Hub { return getCurrentScope().captureMessage(message, level, hint); }, captureEvent, - lastEventId, addBreadcrumb, setUser, setTags, diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index 2f8091586fbf..dd399243726f 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -74,8 +74,6 @@ export function isInitialized(): boolean { export class Scope extends OpenTelemetryScope implements ScopeInterface { protected _client: Client | undefined; - protected _lastEventId: string | undefined; - /** * @inheritDoc */ @@ -131,8 +129,6 @@ export class Scope extends OpenTelemetryScope implements ScopeInterface { this, ); - this._lastEventId = eventId; - return eventId; } @@ -153,28 +149,18 @@ export class Scope extends OpenTelemetryScope implements ScopeInterface { this, ); - this._lastEventId = eventId; - return eventId; } /** Capture a message for this scope. */ public captureEvent(event: Event, hint?: EventHint): string { const eventId = hint && hint.event_id ? hint.event_id : uuid4(); - if (!event.type) { - this._lastEventId = eventId; - } getClient().captureEvent(event, { ...hint, event_id: eventId }, this); return eventId; } - /** Get the ID of the last sent error event. */ - public lastEventId(): string | undefined { - return this._lastEventId; - } - /** * @inheritDoc */ diff --git a/packages/node-experimental/src/sdk/types.ts b/packages/node-experimental/src/sdk/types.ts index 37fd80bfab1a..ee6642dadb9c 100644 --- a/packages/node-experimental/src/sdk/types.ts +++ b/packages/node-experimental/src/sdk/types.ts @@ -30,10 +30,6 @@ export interface ScopeData { export interface Scope extends BaseScope { // @ts-expect-error typeof this is what we want here clone(scope?: Scope): typeof this; - /** - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ - lastEventId(): string | undefined; getScopeData(): ScopeData; } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 7f195957249b..ac7c54a0cf60 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -48,8 +48,6 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, runWithAsyncContext, diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index a4920cb37b2b..d120c9b44dfb 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -111,8 +111,7 @@ class ErrorBoundary extends React.Component { - if (!event.type && event.event_id === this._lastEventId) { - // eslint-disable-next-line deprecation/deprecation + if (!event.type && this._lastEventId && event.event_id === this._lastEventId) { showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId }); } }); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 51ac03a44694..6d5a117f965f 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -62,8 +62,6 @@ export { defaultIntegrations, getDefaultIntegrations, defaultStackParser, - // eslint-disable-next-line deprecation/deprecation - lastEventId, flush, close, getSentryRelease, diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index 0abe77c7a20d..2a801f954917 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -29,9 +29,3 @@ declare const runtime: 'client' | 'server'; export const close = runtime === 'client' ? clientSdk.close : serverSdk.close; export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush; - -/** - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ -// eslint-disable-next-line deprecation/deprecation -export const lastEventId = runtime === 'client' ? clientSdk.lastEventId : serverSdk.lastEventId; diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 7af085aac698..0c6c5b7864ba 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -63,8 +63,6 @@ export { flush, getSentryRelease, init, - // eslint-disable-next-line deprecation/deprecation - lastEventId, DEFAULT_USER_INCLUDES, addRequestDataToEvent, extractRequestData, diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 6a5d3e3883e9..868aff8de7f6 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -48,8 +48,3 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; - -/** - * @deprecated This function will be removed in the next major version of the Sentry SDK. - */ -export declare function lastEventId(): string | undefined; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 0615398ec325..9115d3008ad9 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -56,8 +56,6 @@ export { defaultIntegrations, getDefaultIntegrations, defaultStackParser, - // eslint-disable-next-line deprecation/deprecation - lastEventId, flush, close, getSentryRelease, diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index 3f46b88da498..346cba7e77b0 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -128,15 +128,6 @@ export interface Hub { */ captureEvent(event: Event, hint?: EventHint): string; - /** - * This is the getter for lastEventId. - * - * @returns The last event id of a captured event. - * - * @deprecated This will be removed in v8. - */ - lastEventId(): string | undefined; - /** * Records a new breadcrumb which will be attached to future events. * diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 8a27be0f1156..dcd6686d0562 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -48,8 +48,6 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, runWithAsyncContext, From 2014d6a9f51c9e5ef57577bf5701ddae2cbc02af Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 9 Feb 2024 18:16:36 -0500 Subject: [PATCH 029/173] feat(v8): Remove deprecated configureScope call (#10565) Removes `configureScope`, deprecated in #9887 --- MIGRATION.md | 13 ++++++++++++- .../configureScope/clear_scope/subject.js | 8 -------- .../configureScope/clear_scope/test.ts | 16 ---------------- .../suites/public-api/configureScope/init.js | 7 ------- .../configureScope/set_properties/subject.js | 7 ------- .../configureScope/set_properties/test.ts | 16 ---------------- .../backgroundtab-custom/subject.js | 2 +- .../backgroundtab-custom/subject.js | 2 +- packages/astro/src/index.server.ts | 2 -- packages/browser/README.md | 9 +++------ packages/browser/src/exports.ts | 2 -- packages/browser/src/loader.js | 1 + packages/browser/src/sdk.ts | 11 ----------- packages/browser/test/package/test-code.js | 13 ++++++------- packages/bun/README.md | 9 +++------ packages/bun/src/index.ts | 2 -- packages/bun/src/sdk.ts | 11 ----------- packages/core/src/exports.ts | 11 ----------- packages/core/src/hub.ts | 13 ------------- packages/core/src/index.ts | 2 -- packages/deno/README.md | 9 +++------ packages/deno/src/index.ts | 2 -- packages/deno/src/sdk.ts | 11 ----------- packages/nextjs/README.md | 9 +++------ .../nextjs/vercel/install-sentry-from-branch.sh | 14 ++++++-------- packages/node-experimental/src/index.ts | 2 -- packages/node-experimental/src/sdk/api.ts | 8 -------- packages/node-experimental/src/sdk/hub.ts | 3 --- packages/node/README.md | 9 +++------ packages/node/src/index.ts | 2 -- packages/node/src/sdk.ts | 11 ----------- .../manual/express-scope-separation/start.js | 16 ++++------------ .../test/manual/memory-leak/express-patient.js | 4 +--- .../test/manual/webpack-async-context/index.js | 9 ++------- packages/remix/README.md | 9 +++------ packages/remix/src/index.server.ts | 2 -- packages/serverless/src/index.ts | 2 -- packages/sveltekit/src/server/index.ts | 2 -- packages/types/src/hub.ts | 8 -------- packages/vercel-edge/README.md | 9 +++------ packages/vercel-edge/src/index.ts | 2 -- 41 files changed, 55 insertions(+), 245 deletions(-) delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/configureScope/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/test.ts diff --git a/MIGRATION.md b/MIGRATION.md index 24ba1c079bdc..2c9a352010fd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -33,9 +33,20 @@ The `enableAnrDetection` and `Anr` class have been removed. See the [docs](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) for more details how to migrate to `anrIntegration`, the new integration for ANR detection. -## Other changes +## Removal of `Sentry.configureScope` (#10565) + +The top level `Sentry.configureScope` function has been removed. Instead, you should use the `Sentry.getCurrentScope()` +to access and mutate the current scope. + +## Deletion of `@sentry/hub` package (#10530) + +`@sentry/hub` has been removed. All exports from `@sentry.hub` should be available in `@sentry/core`. + +## General API Changes - Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) +- Remove deprecated `deepReadDirSync` export from `@sentry/node` (#10564) +- Remove `_eventFromIncompleteOnError` usage (#10553) # Deprecations in 7.x diff --git a/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/subject.js b/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/subject.js deleted file mode 100644 index ae65f9976267..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/subject.js +++ /dev/null @@ -1,8 +0,0 @@ -Sentry.configureScope(scope => { - scope.setTag('foo', 'bar'); - scope.setUser({ id: 'baz' }); - scope.setExtra('qux', 'quux'); - scope.clear(); -}); - -Sentry.captureMessage('cleared_scope'); diff --git a/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/test.ts b/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/test.ts deleted file mode 100644 index 02a82ffef26d..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/configureScope/clear_scope/test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; - -sentryTest('should clear previously set properties of a scope', async ({ getLocalTestPath, page }) => { - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.message).toBe('cleared_scope'); - expect(eventData.user).toBeUndefined(); - expect(eventData.tags).toBeUndefined(); - expect(eventData.extra).toBeUndefined(); -}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/configureScope/init.js b/dev-packages/browser-integration-tests/suites/public-api/configureScope/init.js deleted file mode 100644 index d8c94f36fdd0..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/configureScope/init.js +++ /dev/null @@ -1,7 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', -}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/subject.js b/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/subject.js deleted file mode 100644 index db5d34977b1e..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/subject.js +++ /dev/null @@ -1,7 +0,0 @@ -Sentry.configureScope(scope => { - scope.setTag('foo', 'bar'); - scope.setUser({ id: 'baz' }); - scope.setExtra('qux', 'quux'); -}); - -Sentry.captureMessage('configured_scope'); diff --git a/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/test.ts b/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/test.ts deleted file mode 100644 index ba31c0ca18e7..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/configureScope/set_properties/test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; - -sentryTest('should set different properties of a scope', async ({ getLocalTestPath, page }) => { - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.message).toBe('configured_scope'); - expect(eventData.user).toMatchObject({ id: 'baz' }); - expect(eventData.tags).toMatchObject({ foo: 'bar' }); - expect(eventData.extra).toMatchObject({ qux: 'quux' }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js index 5355521f1655..7221e2302b21 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js @@ -7,5 +7,5 @@ document.getElementById('go-background').addEventListener('click', () => { document.getElementById('start-transaction').addEventListener('click', () => { window.transaction = Sentry.startTransaction({ name: 'test-transaction' }); - Sentry.getCurrentHub().configureScope(scope => scope.setSpan(window.transaction)); + Sentry.getCurrentScope().setSpan(window.transaction); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js index 5355521f1655..7221e2302b21 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js @@ -7,5 +7,5 @@ document.getElementById('go-background').addEventListener('click', () => { document.getElementById('start-transaction').addEventListener('click', () => { window.transaction = Sentry.startTransaction({ name: 'test-transaction' }); - Sentry.getCurrentHub().configureScope(scope => scope.setSpan(window.transaction)); + Sentry.getCurrentScope().setSpan(window.transaction); }); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index ad22273818f7..5937aa6ad889 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -17,8 +17,6 @@ export { captureMessage, captureCheckIn, withMonitor, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/browser/README.md b/packages/browser/README.md index 63f90f784189..98bbf0cabca2 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -37,12 +37,9 @@ functions will not perform any action before you have called `Sentry.init()`: import * as Sentry from '@sentry/browser'; // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 2394dd8013ea..eda89ac56b1c 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -30,8 +30,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, flush, getHubFromCarrier, diff --git a/packages/browser/src/loader.js b/packages/browser/src/loader.js index 19d7bfb5a7e9..c80d5a1a4129 100644 --- a/packages/browser/src/loader.js +++ b/packages/browser/src/loader.js @@ -184,6 +184,7 @@ 'captureMessage', 'captureException', 'captureEvent', + // TODO: Remove configureScope from loader 'configureScope', 'withScope', 'showReportDialog' diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 4095a8a8c2ba..39fabc92c8ec 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -79,17 +79,6 @@ declare const __SENTRY_RELEASE__: string | undefined; * @example * ``` * - * import { configureScope } from '@sentry/browser'; - * configureScope((scope: Scope) => { - * scope.setExtra({ battery: 0.7 }); - * scope.setTag({ user_mode: 'admin' }); - * scope.setUser({ id: '4711' }); - * }); - * ``` - * - * @example - * ``` - * * import { addBreadcrumb } from '@sentry/browser'; * addBreadcrumb({ * message: 'My Breadcrumb', diff --git a/packages/browser/test/package/test-code.js b/packages/browser/test/package/test-code.js index 3a3811eebb89..513009e945b5 100644 --- a/packages/browser/test/package/test-code.js +++ b/packages/browser/test/package/test-code.js @@ -17,13 +17,12 @@ Sentry.init({ }); // Configure -Sentry.configureScope(scope => { - scope.setExtra('foo', 'bar'); - scope.setFingerprint('foo'); - scope.setLevel('warning'); - scope.setTag('foo', 'bar'); - scope.setUser('foo', 'bar'); -}); +const scope = Sentry.getCurrentScope(); +scope.setExtra('foo', 'bar'); +scope.setFingerprint('foo'); +scope.setLevel('warning'); +scope.setTag('foo', 'bar'); +scope.setUser('foo', 'bar'); // Breadcrumbs integration window.console.log('Console', 'Breadcrumb'); diff --git a/packages/bun/README.md b/packages/bun/README.md index 56c795793532..d9c99350a613 100644 --- a/packages/bun/README.md +++ b/packages/bun/README.md @@ -39,12 +39,9 @@ functions will not perform any action before you have called `init()`: ```javascript // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 295bad3271ea..e295d4632fe3 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -31,8 +31,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index 2054c833fd01..1db72246178f 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -73,17 +73,6 @@ export function getDefaultIntegrations(_options: Options): Integration[] { * @example * ``` * - * const { configureScope } = require('@sentry/node'); - * configureScope((scope: Scope) => { - * scope.setExtra({ battery: 0.7 }); - * scope.setTag({ user_mode: 'admin' }); - * scope.setUser({ id: '4711' }); - * }); - * ``` - * - * @example - * ``` - * * const { addBreadcrumb } = require('@sentry/node'); * addBreadcrumb({ * message: 'My Breadcrumb', diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 8691aa9e70cd..6c18aa0ec9ba 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -76,17 +76,6 @@ export function captureEvent(event: Event, hint?: EventHint): string { return getCurrentHub().captureEvent(event, hint); } -/** - * Callback to set context information onto the scope. - * @param callback Callback function that receives Scope. - * - * @deprecated Use getCurrentScope() directly. - */ -export function configureScope(callback: (scope: Scope) => void): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().configureScope(callback); -} - /** * Records a new breadcrumb which will be attached to future events. * diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index e729a5c8f763..be06ca0c8185 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -507,19 +507,6 @@ export class Hub implements HubInterface { this.getIsolationScope().setContext(name, context); } - /** - * @inheritDoc - * - * @deprecated Use `getScope()` directly. - */ - public configureScope(callback: (scope: Scope) => void): void { - // eslint-disable-next-line deprecation/deprecation - const { scope, client } = this.getStackTop(); - if (client) { - callback(scope); - } - } - /** * @inheritDoc */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 992de9f60739..f42a411802e7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,8 +16,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, flush, // eslint-disable-next-line deprecation/deprecation startTransaction, diff --git a/packages/deno/README.md b/packages/deno/README.md index 4246a367ad81..67ac88fedc6e 100644 --- a/packages/deno/README.md +++ b/packages/deno/README.md @@ -41,12 +41,9 @@ functions will not perform any action before you have called `init()`: ```javascript // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 579bad78c869..2139767a5397 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -29,8 +29,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index c5bd3a1d002f..b9e3cdab1ebb 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -65,17 +65,6 @@ const defaultStackParser: StackParser = createStackParser(nodeStackLineParser()) * @example * ``` * - * import { configureScope } from 'npm:@sentry/deno'; - * configureScope((scope: Scope) => { - * scope.setExtra({ battery: 0.7 }); - * scope.setTag({ user_mode: 'admin' }); - * scope.setUser({ id: '4711' }); - * }); - * ``` - * - * @example - * ``` - * * import { addBreadcrumb } from 'npm:@sentry/deno'; * addBreadcrumb({ * message: 'My Breadcrumb', diff --git a/packages/nextjs/README.md b/packages/nextjs/README.md index f4da13beb8b8..26322828afe4 100644 --- a/packages/nextjs/README.md +++ b/packages/nextjs/README.md @@ -53,12 +53,9 @@ To set context information or send manual events, use the exported functions of import * as Sentry from '@sentry/nextjs'; // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/nextjs/vercel/install-sentry-from-branch.sh b/packages/nextjs/vercel/install-sentry-from-branch.sh index 84a0f59eb7c3..b36983b898dc 100644 --- a/packages/nextjs/vercel/install-sentry-from-branch.sh +++ b/packages/nextjs/vercel/install-sentry-from-branch.sh @@ -71,14 +71,12 @@ Error.stackTraceLimit = Infinity; SDK_COMMIT_MESSAGE=$(cd sentry-javascript && git log --format="%C(auto)%s" | head -n 1) CONFIGURE_SCOPE_CODE=" -Sentry.configureScope(scope => { - if (process.env.VERCEL) { - scope.setTag('vercel', true); - } - scope.setTag('commitMessage', process.env.VERCEL_GIT_COMMIT_MESSAGE); - scope.setTag('sdkCommitMessage', \"$SDK_COMMIT_MESSAGE\"); -}); - " +if (process.env.VERCEL) { + Sentry.setTag('vercel', true); +} +Sentry.setTag('commitMessage', process.env.VERCEL_GIT_COMMIT_MESSAGE); +Sentry.setTag('sdkCommitMessage', \"$SDK_COMMIT_MESSAGE\"); +" echo "$INFINITE_STACKTRACE_CODE" "$CONFIGURE_SCOPE_CODE" >>sentry.server.config.js echo "$INFINITE_STACKTRACE_CODE" "$CONFIGURE_SCOPE_CODE" >>sentry.client.config.js diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 5b19697732a8..1f2870f287f9 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -45,8 +45,6 @@ export { withScope, withIsolationScope, withActiveSpan, - // eslint-disable-next-line deprecation/deprecation - configureScope, getCurrentScope, getGlobalScope, getIsolationScope, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index f1958ba883b4..83685fe6d987 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -96,14 +96,6 @@ export function withIsolationScope(callback: (isolationScope: Scope) => T): T }); } -/** - * Configure the current scope. - * @deprecated Use `getCurrentScope()` instead. - */ -export function configureScope(callback: (scope: Scope) => void): void { - callback(getCurrentScope()); -} - /** Record an exception and send it to Sentry. */ export function captureException(exception: unknown, hint?: ExclusiveEventHintOrCaptureContext): string { return getCurrentScope().captureException(exception, parseEventHintOrCaptureContext(hint)); diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 0a5da5c09e0c..f3cd1f2a7d13 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -13,7 +13,6 @@ import { endSession, startSession } from '@sentry/core'; import { addBreadcrumb, captureEvent, - configureScope, getClient, getCurrentScope, setContext, @@ -77,8 +76,6 @@ export function getCurrentHub(): Hub { setExtra, setExtras, setContext, - // eslint-disable-next-line deprecation/deprecation - configureScope: configureScope, run(callback: (hub: Hub) => void): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/node/README.md b/packages/node/README.md index 0fdf96a4aa34..bd588bbbc34e 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -37,12 +37,9 @@ functions will not perform any action before you have called `init()`: ```javascript // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index ac7c54a0cf60..d555afee3e1d 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -30,8 +30,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 01825a404e20..14aff2fef1fe 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -93,17 +93,6 @@ export function getDefaultIntegrations(_options: Options): Integration[] { * @example * ``` * - * const { configureScope } = require('@sentry/node'); - * configureScope((scope: Scope) => { - * scope.setExtra({ battery: 0.7 }); - * scope.setTag({ user_mode: 'admin' }); - * scope.setUser({ id: '4711' }); - * }); - * ``` - * - * @example - * ``` - * * const { addBreadcrumb } = require('@sentry/node'); * addBreadcrumb({ * message: 'My Breadcrumb', diff --git a/packages/node/test/manual/express-scope-separation/start.js b/packages/node/test/manual/express-scope-separation/start.js index 63a73eba2dd2..cfe8df9d2427 100644 --- a/packages/node/test/manual/express-scope-separation/start.js +++ b/packages/node/test/manual/express-scope-separation/start.js @@ -64,32 +64,24 @@ Sentry.init({ }, }); -Sentry.configureScope(scope => { - scope.setTag('global', 'wat'); -}); +Sentry.setTag('global', 'wat'); app.use(Sentry.Handlers.requestHandler()); app.get('/foo', req => { - Sentry.configureScope(scope => { - scope.setTag('foo', 'wat'); - }); + Sentry.setTag('foo', 'wat'); throw new Error('foo'); }); app.get('/bar', req => { - Sentry.configureScope(scope => { - scope.setTag('bar', 'wat'); - }); + Sentry.setTag('bar', 'wat'); throw new Error('bar'); }); app.get('/baz', req => { - Sentry.configureScope(scope => { - scope.setTag('baz', 'wat'); - }); + Sentry.setTag('baz', 'wat'); throw new Error('baz'); }); diff --git a/packages/node/test/manual/memory-leak/express-patient.js b/packages/node/test/manual/memory-leak/express-patient.js index 7a677a442972..ad9ed267bde4 100644 --- a/packages/node/test/manual/memory-leak/express-patient.js +++ b/packages/node/test/manual/memory-leak/express-patient.js @@ -41,9 +41,7 @@ app.use((req, res, next) => { }); app.get('/context/basic', (req, res, next) => { - Sentry.configureScope(scope => { - scope.setExtra('example', 'hey look we set some example context data yay'); - }); + Sentry.setExtra('example', 'hey look we set some example context data yay'); res.textToSend = 'hello there! we set some stuff to the context'; next(); diff --git a/packages/node/test/manual/webpack-async-context/index.js b/packages/node/test/manual/webpack-async-context/index.js index 27f3fe0376ef..c8a9de6e7d41 100644 --- a/packages/node/test/manual/webpack-async-context/index.js +++ b/packages/node/test/manual/webpack-async-context/index.js @@ -43,15 +43,10 @@ Sentry.init({ }, }); -Sentry.configureScope(scope => { - scope.setTag('a', 'b'); -}); +Sentry.setTag('a', 'b'); Sentry.runWithAsyncContext(() => { - Sentry.configureScope(scope => { - scope.setTag('a', 'x'); - scope.setTag('b', 'c'); - }); + Sentry.setTag('a', 'x'); Sentry.captureMessage('inside'); }); diff --git a/packages/remix/README.md b/packages/remix/README.md index ae2dfbeff53a..7914555728f1 100644 --- a/packages/remix/README.md +++ b/packages/remix/README.md @@ -113,12 +113,9 @@ To set context information or send manual events, use the exported functions of import * as Sentry from '@sentry/remix'; // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 6d5a117f965f..cbcb9533ec95 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -21,8 +21,6 @@ export { captureException, captureEvent, captureMessage, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 0c6c5b7864ba..74edc639d49b 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -25,8 +25,6 @@ export { captureMessage, captureCheckIn, withMonitor, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 9115d3008ad9..b85ee1923752 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -14,8 +14,6 @@ export { captureMessage, captureCheckIn, withMonitor, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index 346cba7e77b0..36c7403af2d5 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -197,14 +197,6 @@ export interface Hub { */ setContext(name: string, context: { [key: string]: any } | null): void; - /** - * Callback to set context information onto the scope. - * - * @param callback Callback function that receives Scope. - * @deprecated Use `getScope()` directly. - */ - configureScope(callback: (scope: Scope) => void): void; - /** * For the duration of the callback, this hub will be set as the global current Hub. * This function is useful if you want to run your own client and hook into an already initialized one diff --git a/packages/vercel-edge/README.md b/packages/vercel-edge/README.md index e6e893bc5044..5f4744ac3c9d 100644 --- a/packages/vercel-edge/README.md +++ b/packages/vercel-edge/README.md @@ -39,12 +39,9 @@ functions will not perform any action before you have called `init()`: ```javascript // Set user information, as well as tags and further extras -Sentry.configureScope(scope => { - scope.setExtra('battery', 0.7); - scope.setTag('user_mode', 'admin'); - scope.setUser({ id: '4711' }); - // scope.clear(); -}); +Sentry.setExtra('battery', 0.7); +Sentry.setTag('user_mode', 'admin'); +Sentry.setUser({ id: '4711' }); // Add a breadcrumb for future events Sentry.addBreadcrumb({ diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index dcd6686d0562..97d5ceb6b730 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -30,8 +30,6 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation extractTraceparentData, From a7097d9ba2a74b2cb323da0ef22988a383782ffb Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 12 Feb 2024 09:17:46 +0100 Subject: [PATCH 030/173] ref: Make scope setters on hub only write to isolation scope (#10572) --- packages/astro/test/client/sdk.test.ts | 11 ++++++----- packages/astro/test/server/sdk.test.ts | 8 ++++---- packages/core/src/hub.ts | 18 ------------------ packages/sveltekit/src/client/sdk.ts | 6 +++--- packages/sveltekit/src/server/sdk.ts | 4 ++-- packages/sveltekit/test/client/sdk.test.ts | 10 +++++----- packages/sveltekit/test/server/sdk.test.ts | 8 ++++---- 7 files changed, 24 insertions(+), 41 deletions(-) diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index d6f22dc9ed7a..9136f5650135 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -1,10 +1,11 @@ import type { BrowserClient } from '@sentry/browser'; import { getActiveSpan } from '@sentry/browser'; -import { browserTracingIntegration, getCurrentScope } from '@sentry/browser'; +import { browserTracingIntegration } from '@sentry/browser'; import * as SentryBrowser from '@sentry/browser'; import { BrowserTracing, SDK_VERSION, WINDOW, getClient } from '@sentry/browser'; import { vi } from 'vitest'; +import { getIsolationScope } from '@sentry/core'; import { init } from '../../../astro/src/client/sdk'; const browserInit = vi.spyOn(SentryBrowser, 'init'); @@ -38,16 +39,16 @@ describe('Sentry client SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = getCurrentScope(); + it('sets the runtime tag on the isolation scope', () => { + const isolationScope = getIsolationScope(); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(isolationScope._tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'browser' }); + expect(isolationScope._tags).toEqual({ runtime: 'browser' }); }); describe('automatically adds integrations', () => { diff --git a/packages/astro/test/server/sdk.test.ts b/packages/astro/test/server/sdk.test.ts index b132c32a03c9..b4f0536a2a76 100644 --- a/packages/astro/test/server/sdk.test.ts +++ b/packages/astro/test/server/sdk.test.ts @@ -36,16 +36,16 @@ describe('Sentry server SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = SentryNode.getCurrentScope(); + it('sets the runtime tag on the isolation scope', () => { + const isolationScope = SentryNode.getIsolationScope(); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(isolationScope._tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'node' }); + expect(isolationScope._tags).toEqual({ runtime: 'node' }); }); }); }); diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index be06ca0c8185..6c46e7c427fa 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -439,9 +439,6 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.setUser()` instead. */ public setUser(user: User | null): void { - // TODO(v8): The top level `Sentry.setUser()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setUser(user); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setUser(user); } @@ -451,9 +448,6 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.setTags()` instead. */ public setTags(tags: { [key: string]: Primitive }): void { - // TODO(v8): The top level `Sentry.setTags()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setTags(tags); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setTags(tags); } @@ -463,9 +457,6 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.setExtras()` instead. */ public setExtras(extras: Extras): void { - // TODO(v8): The top level `Sentry.setExtras()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setExtras(extras); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setExtras(extras); } @@ -475,9 +466,6 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.setTag()` instead. */ public setTag(key: string, value: Primitive): void { - // TODO(v8): The top level `Sentry.setTag()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setTag(key, value); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setTag(key, value); } @@ -487,9 +475,6 @@ export class Hub implements HubInterface { * @deprecated Use `Sentry.setExtra()` instead. */ public setExtra(key: string, extra: Extra): void { - // TODO(v8): The top level `Sentry.setExtra()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setExtra(key, extra); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setExtra(key, extra); } @@ -500,9 +485,6 @@ export class Hub implements HubInterface { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any public setContext(name: string, context: { [key: string]: any } | null): void { - // TODO(v8): The top level `Sentry.setContext()` function should write ONLY to the isolation scope. - // eslint-disable-next-line deprecation/deprecation - this.getScope().setContext(name, context); // eslint-disable-next-line deprecation/deprecation this.getIsolationScope().setContext(name, context); } diff --git a/packages/sveltekit/src/client/sdk.ts b/packages/sveltekit/src/client/sdk.ts index b0dc7ee6af2d..a3c996457c46 100644 --- a/packages/sveltekit/src/client/sdk.ts +++ b/packages/sveltekit/src/client/sdk.ts @@ -1,7 +1,7 @@ -import { applySdkMetadata, hasTracingEnabled } from '@sentry/core'; +import { applySdkMetadata, hasTracingEnabled, setTag } from '@sentry/core'; import type { BrowserOptions, browserTracingIntegration } from '@sentry/svelte'; import { getDefaultIntegrations as getDefaultSvelteIntegrations } from '@sentry/svelte'; -import { WINDOW, getCurrentScope, init as initSvelteSdk } from '@sentry/svelte'; +import { WINDOW, init as initSvelteSdk } from '@sentry/svelte'; import type { Integration } from '@sentry/types'; import { @@ -42,7 +42,7 @@ export function init(options: BrowserOptions): void { restoreFetch(actualFetch); } - getCurrentScope().setTag('runtime', 'browser'); + setTag('runtime', 'browser'); } // TODO v8: Remove this again diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index c6b3bd26dffe..f16220775d3a 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -1,4 +1,4 @@ -import { applySdkMetadata, getCurrentScope } from '@sentry/core'; +import { applySdkMetadata, setTag } from '@sentry/core'; import type { NodeOptions } from '@sentry/node'; import { getDefaultIntegrations as getDefaultNodeIntegrations } from '@sentry/node'; import { init as initNodeSdk } from '@sentry/node'; @@ -19,5 +19,5 @@ export function init(options: NodeOptions): void { initNodeSdk(opts); - getCurrentScope().setTag('runtime', 'node'); + setTag('runtime', 'node'); } diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts index bc863af99897..0cf28d5f0514 100644 --- a/packages/sveltekit/test/client/sdk.test.ts +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -1,4 +1,4 @@ -import { getClient, getCurrentScope } from '@sentry/core'; +import { getClient, getIsolationScope } from '@sentry/core'; import type { BrowserClient } from '@sentry/svelte'; import * as SentrySvelte from '@sentry/svelte'; import { SDK_VERSION, WINDOW, browserTracingIntegration } from '@sentry/svelte'; @@ -38,16 +38,16 @@ describe('Sentry client SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = getCurrentScope(); + it('sets the runtime tag on the isolation scope', () => { + const isolationScope = getIsolationScope(); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(isolationScope._tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'browser' }); + expect(isolationScope._tags).toEqual({ runtime: 'browser' }); }); describe('automatically added integrations', () => { diff --git a/packages/sveltekit/test/server/sdk.test.ts b/packages/sveltekit/test/server/sdk.test.ts index 99dcb37516e1..53f3ee8e33e8 100644 --- a/packages/sveltekit/test/server/sdk.test.ts +++ b/packages/sveltekit/test/server/sdk.test.ts @@ -36,16 +36,16 @@ describe('Sentry server SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = SentryNode.getCurrentScope(); + it('sets the runtime tag on the isolation scope', () => { + const isolationScope = SentryNode.getIsolationScope(); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(isolationScope._tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'node' }); + expect(isolationScope._tags).toEqual({ runtime: 'node' }); }); it('adds rewriteFramesIntegration by default', () => { From 6bfe9a5fe585b0c03da107f454f8544897ed45f5 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 11:23:16 +0100 Subject: [PATCH 031/173] ref(replay): Use `beforeAddBreadcrumb` hook instead of scope listener (#10600) Currently, replay captures breadcrumbs by setting up a scope listener on the current scope. However, this is flawed because this means it will not pick up breadcrumbs that are added to the isolation scope (which will be the default going forward, in v8). This PR changes this to use the `beforeAddBreadcrumb` hook instead to listen to this. CAVEATS: This means we only capture breadcrumbs added via `Sentry.addBreadcrumb()`. If somebody adds a breadcrumb directly to a scope, the hook is not triggered. I _think_ this is OK, but something to be aware of - if we want to change this, we'd need to trigger the hook on the scope, which may be a bit iffy/tricky. But for the most part, this shouldn't matter, hopefully - this is also non-critical, meaning if we miss a breadcrumb (for whatever reason) it's not _that_ bad, it will simply not be visible in the Replay. --- packages/react/src/redux.ts | 4 +- packages/react/test/redux.test.ts | 12 +- .../{handleScope.ts => handleBreadcrumbs.ts} | 79 +++++----- .../replay/src/util/addGlobalListeners.ts | 6 +- .../coreHandlers/handleScope.test.ts | 38 ----- .../coreHandlers/handleBreadcrumbs.test.ts | 135 +++++++++++++++++ .../unit/coreHandlers/handleScope.test.ts | 143 ------------------ .../replay/test/unit/util/getReplay.test.ts | 2 +- 8 files changed, 188 insertions(+), 231 deletions(-) rename packages/replay/src/coreHandlers/{handleScope.ts => handleBreadcrumbs.ts} (58%) delete mode 100644 packages/replay/test/integration/coreHandlers/handleScope.test.ts create mode 100644 packages/replay/test/unit/coreHandlers/handleBreadcrumbs.test.ts delete mode 100644 packages/replay/test/unit/coreHandlers/handleScope.test.ts diff --git a/packages/react/src/redux.ts b/packages/react/src/redux.ts index fb3ca2fa073f..b9d6b951f7d2 100644 --- a/packages/react/src/redux.ts +++ b/packages/react/src/redux.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { getClient, getCurrentScope, getGlobalScope } from '@sentry/core'; +import { addBreadcrumb, getClient, getCurrentScope, getGlobalScope } from '@sentry/core'; import type { Scope } from '@sentry/types'; import { addNonEnumerableProperty } from '@sentry/utils'; @@ -121,7 +121,7 @@ function createReduxEnhancer(enhancerOptions?: Partial): /* Action breadcrumbs */ const transformedAction = options.actionTransformer(action); if (typeof transformedAction !== 'undefined' && transformedAction !== null) { - scope.addBreadcrumb({ + addBreadcrumb({ category: ACTION_BREADCRUMB_CATEGORY, data: transformedAction, type: ACTION_BREADCRUMB_TYPE, diff --git a/packages/react/test/redux.test.ts b/packages/react/test/redux.test.ts index 537c133cd3fd..50116c8db87e 100644 --- a/packages/react/test/redux.test.ts +++ b/packages/react/test/redux.test.ts @@ -1,9 +1,9 @@ import * as Sentry from '@sentry/browser'; +import * as SentryCore from '@sentry/core'; import * as Redux from 'redux'; import { createReduxEnhancer } from '../src/redux'; -const mockAddBreadcrumb = jest.fn(); const mockSetContext = jest.fn(); const mockGlobalScopeAddEventProcessor = jest.fn(); @@ -11,7 +11,6 @@ jest.mock('@sentry/core', () => ({ ...jest.requireActual('@sentry/core'), getCurrentScope() { return { - addBreadcrumb: mockAddBreadcrumb, setContext: mockSetContext, }; }, @@ -21,15 +20,22 @@ jest.mock('@sentry/core', () => ({ }; }, addEventProcessor: jest.fn(), + addBreadcrumb: jest.fn(), })); afterEach(() => { - mockAddBreadcrumb.mockReset(); mockSetContext.mockReset(); mockGlobalScopeAddEventProcessor.mockReset(); }); describe('createReduxEnhancer', () => { + let mockAddBreadcrumb: jest.SpyInstance; + + beforeEach(() => { + mockAddBreadcrumb = SentryCore.addBreadcrumb as unknown as jest.SpyInstance; + mockAddBreadcrumb.mockReset(); + }); + it('logs redux action as breadcrumb', () => { const enhancer = createReduxEnhancer(); diff --git a/packages/replay/src/coreHandlers/handleScope.ts b/packages/replay/src/coreHandlers/handleBreadcrumbs.ts similarity index 58% rename from packages/replay/src/coreHandlers/handleScope.ts rename to packages/replay/src/coreHandlers/handleBreadcrumbs.ts index 5366fdf16aeb..416bca4efb15 100644 --- a/packages/replay/src/coreHandlers/handleScope.ts +++ b/packages/replay/src/coreHandlers/handleBreadcrumbs.ts @@ -1,4 +1,5 @@ -import type { Breadcrumb, Scope } from '@sentry/types'; +import { getClient } from '@sentry/core'; +import type { Breadcrumb } from '@sentry/types'; import { normalize } from '@sentry/utils'; import { CONSOLE_ARG_MAX_SIZE } from '../constants'; @@ -7,61 +8,55 @@ import type { ReplayFrame } from '../types/replayFrame'; import { createBreadcrumb } from '../util/createBreadcrumb'; import { addBreadcrumbEvent } from './util/addBreadcrumbEvent'; -let _LAST_BREADCRUMB: null | Breadcrumb = null; - type BreadcrumbWithCategory = Required>; -function isBreadcrumbWithCategory(breadcrumb: Breadcrumb): breadcrumb is BreadcrumbWithCategory { - return !!breadcrumb.category; -} +/** + * Handle breadcrumbs that Sentry captures, and make sure to capture relevant breadcrumbs to Replay as well. + */ +export function handleBreadcrumbs(replay: ReplayContainer): void { + const client = getClient(); -export const handleScopeListener: (replay: ReplayContainer) => (scope: Scope) => void = - (replay: ReplayContainer) => - (scope: Scope): void => { - if (!replay.isEnabled()) { - return; - } + if (!client || !client.on) { + return; + } - const result = handleScope(scope); + client.on('beforeAddBreadcrumb', breadcrumb => beforeAddBreadcrumb(replay, breadcrumb)); +} - if (!result) { - return; - } +function beforeAddBreadcrumb(replay: ReplayContainer, breadcrumb: Breadcrumb): void { + if (!replay.isEnabled() || !isBreadcrumbWithCategory(breadcrumb)) { + return; + } + const result = normalizeBreadcrumb(breadcrumb); + if (result) { addBreadcrumbEvent(replay, result); - }; - -/** - * An event handler to handle scope changes. - */ -export function handleScope(scope: Scope): Breadcrumb | null { - // TODO (v8): Remove this guard. This was put in place because we introduced - // Scope.getLastBreadcrumb mid-v7 which caused incompatibilities with older SDKs. - // For now, we'll just return null if the method doesn't exist but we should eventually - // get rid of this guard. - const newBreadcrumb = scope.getLastBreadcrumb && scope.getLastBreadcrumb(); - - // Listener can be called when breadcrumbs have not changed, so we store the - // reference to the last crumb and only return a crumb if it has changed - if (_LAST_BREADCRUMB === newBreadcrumb || !newBreadcrumb) { - return null; } +} - _LAST_BREADCRUMB = newBreadcrumb; - +/** Exported only for tests. */ +export function normalizeBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null { if ( - !isBreadcrumbWithCategory(newBreadcrumb) || - ['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) || - newBreadcrumb.category.startsWith('ui.') + !isBreadcrumbWithCategory(breadcrumb) || + [ + // fetch & xhr are handled separately,in handleNetworkBreadcrumbs + 'fetch', + 'xhr', + // These two are breadcrumbs for emitted sentry events, we don't care about them + 'sentry.event', + 'sentry.transaction', + ].includes(breadcrumb.category) || + // We capture UI breadcrumbs separately + breadcrumb.category.startsWith('ui.') ) { return null; } - if (newBreadcrumb.category === 'console') { - return normalizeConsoleBreadcrumb(newBreadcrumb); + if (breadcrumb.category === 'console') { + return normalizeConsoleBreadcrumb(breadcrumb); } - return createBreadcrumb(newBreadcrumb); + return createBreadcrumb(breadcrumb); } /** exported for tests only */ @@ -116,3 +111,7 @@ export function normalizeConsoleBreadcrumb( }, }); } + +function isBreadcrumbWithCategory(breadcrumb: Breadcrumb): breadcrumb is BreadcrumbWithCategory { + return !!breadcrumb.category; +} diff --git a/packages/replay/src/util/addGlobalListeners.ts b/packages/replay/src/util/addGlobalListeners.ts index 85f253363c7a..b3373924e1fa 100644 --- a/packages/replay/src/util/addGlobalListeners.ts +++ b/packages/replay/src/util/addGlobalListeners.ts @@ -1,16 +1,15 @@ import type { BaseClient } from '@sentry/core'; -import { getCurrentScope } from '@sentry/core'; import { addEventProcessor, getClient } from '@sentry/core'; import type { Client, DynamicSamplingContext } from '@sentry/types'; import { addClickKeypressInstrumentationHandler, addHistoryInstrumentationHandler } from '@sentry/utils'; import { handleAfterSendEvent } from '../coreHandlers/handleAfterSendEvent'; import { handleBeforeSendEvent } from '../coreHandlers/handleBeforeSendEvent'; +import { handleBreadcrumbs } from '../coreHandlers/handleBreadcrumbs'; import { handleDomListener } from '../coreHandlers/handleDom'; import { handleGlobalEventListener } from '../coreHandlers/handleGlobalEvent'; import { handleHistorySpanListener } from '../coreHandlers/handleHistory'; import { handleNetworkBreadcrumbs } from '../coreHandlers/handleNetworkBreadcrumbs'; -import { handleScopeListener } from '../coreHandlers/handleScope'; import type { ReplayContainer } from '../types'; /** @@ -18,12 +17,11 @@ import type { ReplayContainer } from '../types'; */ export function addGlobalListeners(replay: ReplayContainer): void { // Listeners from core SDK // - const scope = getCurrentScope(); const client = getClient(); - scope.addScopeListener(handleScopeListener(replay)); addClickKeypressInstrumentationHandler(handleDomListener(replay)); addHistoryInstrumentationHandler(handleHistorySpanListener(replay)); + handleBreadcrumbs(replay); handleNetworkBreadcrumbs(replay); // Tag all (non replay) events that get sent to Sentry with the current diff --git a/packages/replay/test/integration/coreHandlers/handleScope.test.ts b/packages/replay/test/integration/coreHandlers/handleScope.test.ts deleted file mode 100644 index 0a704e626d3e..000000000000 --- a/packages/replay/test/integration/coreHandlers/handleScope.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getCurrentScope } from '@sentry/core'; - -import * as HandleScope from '../../../src/coreHandlers/handleScope'; -import { mockSdk } from './../../index'; - -jest.useFakeTimers(); - -describe('Integration | coreHandlers | handleScope', () => { - it('returns a breadcrumb only if last breadcrumb has changed', async function () { - const { replay } = await mockSdk({ autoStart: false }); - - // Note: mocks don't work for calls inside of the same module, - // So we need to make sure to mock the `handleScopeListener` call itself - const mockHandleScope = jest.spyOn(HandleScope, 'handleScope'); - const mockHandleScopeListener = jest.spyOn(HandleScope, 'handleScopeListener').mockImplementation(() => { - return scope => { - return HandleScope.handleScope(scope); - }; - }); - - replay.start(); - - expect(mockHandleScopeListener).toHaveBeenCalledTimes(1); - - getCurrentScope().addBreadcrumb({ category: 'console', message: 'testing' }); - - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ category: 'console', message: 'testing' })); - - mockHandleScope.mockClear(); - - // This will trigger breadcrumb/scope listener, but handleScope should return - // null because breadcrumbs has not changed - getCurrentScope().setUser({ email: 'foo@foo.com' }); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(null); - }); -}); diff --git a/packages/replay/test/unit/coreHandlers/handleBreadcrumbs.test.ts b/packages/replay/test/unit/coreHandlers/handleBreadcrumbs.test.ts new file mode 100644 index 000000000000..8fd41b553ccb --- /dev/null +++ b/packages/replay/test/unit/coreHandlers/handleBreadcrumbs.test.ts @@ -0,0 +1,135 @@ +import { CONSOLE_ARG_MAX_SIZE } from '../../../src/constants'; +import { normalizeBreadcrumb, normalizeConsoleBreadcrumb } from '../../../src/coreHandlers/handleBreadcrumbs'; + +describe('Unit | coreHandlers | handleBreadcrumbs', () => { + describe('normalizeBreadcrumb', () => { + it.each([undefined, 'ui.click', 'ui.scroll', 'fetch', 'xhr', 'sentry.event', 'sentry.transaction'])( + 'returns null if breadcrumb has category=%p', + category => { + const actual = normalizeBreadcrumb({ category }); + expect(actual).toBeNull(); + }, + ); + + it('returns breadcrumb when category is valid', () => { + const breadcrumb = { category: 'other' }; + const actual = normalizeBreadcrumb(breadcrumb); + expect(actual).toEqual({ + timestamp: expect.any(Number), + category: 'other', + type: 'default', + }); + }); + + it('timestamp takes precedence', () => { + const breadcrumb = { category: 'other', timestamp: 123456 }; + const actual = normalizeBreadcrumb(breadcrumb); + expect(actual).toEqual({ + timestamp: 123456, + category: 'other', + type: 'default', + }); + }); + + it('handles console breadcrumb', () => { + const breadcrumb = { + category: 'console', + message: 'test', + data: { + arguments: ['a'.repeat(CONSOLE_ARG_MAX_SIZE + 10), 'b'.repeat(CONSOLE_ARG_MAX_SIZE + 10)], + }, + }; + const actual = normalizeBreadcrumb(breadcrumb); + expect(actual).toEqual({ + timestamp: expect.any(Number), + category: 'console', + message: 'test', + type: 'default', + data: { + arguments: [`${'a'.repeat(CONSOLE_ARG_MAX_SIZE)}…`, `${'b'.repeat(CONSOLE_ARG_MAX_SIZE)}…`], + _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] }, + }, + }); + }); + }); + + describe('normalizeConsoleBreadcrumb', () => { + it('handles console messages with no arguments', () => { + const breadcrumb = { category: 'console', message: 'test' }; + const actual = normalizeConsoleBreadcrumb(breadcrumb); + + expect(actual).toMatchObject({ category: 'console', message: 'test' }); + }); + + it('handles console messages with empty arguments', () => { + const breadcrumb = { category: 'console', message: 'test', data: { arguments: [] } }; + const actual = normalizeConsoleBreadcrumb(breadcrumb); + + expect(actual).toMatchObject({ category: 'console', message: 'test', data: { arguments: [] } }); + }); + + it('handles console messages with simple arguments', () => { + const breadcrumb = { + category: 'console', + message: 'test', + data: { arguments: [1, 'a', true, null, undefined] }, + }; + const actual = normalizeConsoleBreadcrumb(breadcrumb); + + expect(actual).toMatchObject({ + category: 'console', + message: 'test', + data: { + arguments: [1, 'a', true, null, undefined], + }, + }); + }); + + it('truncates large strings', () => { + const breadcrumb = { + category: 'console', + message: 'test', + data: { + arguments: ['a'.repeat(CONSOLE_ARG_MAX_SIZE + 10), 'b'.repeat(CONSOLE_ARG_MAX_SIZE + 10)], + }, + }; + const actual = normalizeConsoleBreadcrumb(breadcrumb); + + expect(actual).toMatchObject({ + category: 'console', + message: 'test', + data: { + arguments: [`${'a'.repeat(CONSOLE_ARG_MAX_SIZE)}…`, `${'b'.repeat(CONSOLE_ARG_MAX_SIZE)}…`], + _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] }, + }, + }); + }); + + it('truncates large JSON objects', () => { + const bb = { bb: 'b'.repeat(CONSOLE_ARG_MAX_SIZE + 10) }; + const c = { c: 'c'.repeat(CONSOLE_ARG_MAX_SIZE + 10) }; + + const breadcrumb = { + category: 'console', + message: 'test', + data: { + arguments: [{ aa: 'yes' }, bb, c], + }, + }; + const actual = normalizeConsoleBreadcrumb(breadcrumb); + + expect(actual).toMatchObject({ + category: 'console', + message: 'test', + data: { + arguments: [ + { aa: 'yes' }, + `${JSON.stringify(bb, null, 2).slice(0, CONSOLE_ARG_MAX_SIZE)}…`, + `${JSON.stringify(c, null, 2).slice(0, CONSOLE_ARG_MAX_SIZE)}…`, + ], + _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] }, + }, + }); + }); + }); +}); diff --git a/packages/replay/test/unit/coreHandlers/handleScope.test.ts b/packages/replay/test/unit/coreHandlers/handleScope.test.ts deleted file mode 100644 index ebf6373ed54b..000000000000 --- a/packages/replay/test/unit/coreHandlers/handleScope.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import type { Breadcrumb, Scope } from '@sentry/types'; - -import { CONSOLE_ARG_MAX_SIZE } from '../../../src/constants'; -import * as HandleScope from '../../../src/coreHandlers/handleScope'; - -describe('Unit | coreHandlers | handleScope', () => { - let mockHandleScope: jest.SpyInstance; - - beforeEach(() => { - mockHandleScope = jest.spyOn(HandleScope, 'handleScope'); - mockHandleScope.mockClear(); - }); - - it('returns a breadcrumb only if last breadcrumb has changed', function () { - const scope = { - _breadcrumbs: [], - getLastBreadcrumb() { - return this._breadcrumbs[this._breadcrumbs.length - 1]; - }, - } as unknown as Scope; - - function addBreadcrumb(breadcrumb: Breadcrumb) { - // @ts-expect-error using private member - scope._breadcrumbs.push(breadcrumb); - } - - const testMsg = { - timestamp: Date.now() / 1000, - message: 'testing', - category: 'console', - }; - - addBreadcrumb(testMsg); - // integration testing here is a bit tricky, because the core SDK can - // interfere with console output from test runner - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing', category: 'console' })); - - // This will trigger breadcrumb/scope listener, but handleScope should return - // null because breadcrumbs has not changed - mockHandleScope.mockClear(); - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(null); - - mockHandleScope.mockClear(); - addBreadcrumb({ - message: 'f00', - category: 'console', - }); - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'f00', category: 'console' })); - }); - - it('returns null if the method does not exist on the scope', () => { - const scope = {} as unknown as Scope; - HandleScope.handleScope(scope); - expect(mockHandleScope).toHaveBeenCalledTimes(1); - expect(mockHandleScope).toHaveReturnedWith(null); - }); - - describe('normalizeConsoleBreadcrumb', () => { - it('handles console messages with no arguments', () => { - const breadcrumb = { category: 'console', message: 'test' }; - const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb); - - expect(actual).toMatchObject({ category: 'console', message: 'test' }); - }); - - it('handles console messages with empty arguments', () => { - const breadcrumb = { category: 'console', message: 'test', data: { arguments: [] } }; - const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb); - - expect(actual).toMatchObject({ category: 'console', message: 'test', data: { arguments: [] } }); - }); - - it('handles console messages with simple arguments', () => { - const breadcrumb = { - category: 'console', - message: 'test', - data: { arguments: [1, 'a', true, null, undefined] }, - }; - const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb); - - expect(actual).toMatchObject({ - category: 'console', - message: 'test', - data: { - arguments: [1, 'a', true, null, undefined], - }, - }); - }); - - it('truncates large strings', () => { - const breadcrumb = { - category: 'console', - message: 'test', - data: { - arguments: ['a'.repeat(CONSOLE_ARG_MAX_SIZE + 10), 'b'.repeat(CONSOLE_ARG_MAX_SIZE + 10)], - }, - }; - const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb); - - expect(actual).toMatchObject({ - category: 'console', - message: 'test', - data: { - arguments: [`${'a'.repeat(CONSOLE_ARG_MAX_SIZE)}…`, `${'b'.repeat(CONSOLE_ARG_MAX_SIZE)}…`], - _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] }, - }, - }); - }); - - it('truncates large JSON objects', () => { - const bb = { bb: 'b'.repeat(CONSOLE_ARG_MAX_SIZE + 10) }; - const c = { c: 'c'.repeat(CONSOLE_ARG_MAX_SIZE + 10) }; - - const breadcrumb = { - category: 'console', - message: 'test', - data: { - arguments: [{ aa: 'yes' }, bb, c], - }, - }; - const actual = HandleScope.normalizeConsoleBreadcrumb(breadcrumb); - - expect(actual).toMatchObject({ - category: 'console', - message: 'test', - data: { - arguments: [ - { aa: 'yes' }, - `${JSON.stringify(bb, null, 2).slice(0, CONSOLE_ARG_MAX_SIZE)}…`, - `${JSON.stringify(c, null, 2).slice(0, CONSOLE_ARG_MAX_SIZE)}…`, - ], - _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] }, - }, - }); - }); - }); -}); diff --git a/packages/replay/test/unit/util/getReplay.test.ts b/packages/replay/test/unit/util/getReplay.test.ts index 7f614d4fdc33..87ba1e23aded 100644 --- a/packages/replay/test/unit/util/getReplay.test.ts +++ b/packages/replay/test/unit/util/getReplay.test.ts @@ -24,7 +24,7 @@ describe('getReplay', () => { expect(actual).toBeUndefined(); }); - it('works with a client with Replay xxx', () => { + it('works with a client with Replay', () => { const replay = replayIntegration(); init( getDefaultClientOptions({ From d2ef1cbea8d158dc5dba1188955f51d98a188536 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 12 Feb 2024 11:52:11 +0100 Subject: [PATCH 032/173] fix(sveltekit): Properly await sourcemaps flattening (#10602) We didn't properly await sourcemaps flattening via sorcery before proceeding to upload them. The reason is that the async callbacks in `forEach` weren't awaited. A `for` loop is the better approach here. Wondering if we should lint against async `forEach` callbacks. This behaviour could have caused various inconsistencies. My suspicion is that the timing worked _well enough_ in most cases but we definitely want to properly await this step. Thanks to @MSDev201 for bringing this up! Unfortunately this likely won't fix #10589 as a whole :( --- packages/sveltekit/src/vite/sourceMaps.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index eea862e9d901..2f5fd28ac1f8 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -166,7 +166,7 @@ export async function makeCustomSentryVitePlugin(options?: CustomSentryVitePlugi // eslint-disable-next-line no-console debug && console.log('[Source Maps Plugin] Flattening source maps'); - jsFiles.forEach(async file => { + for (const file of jsFiles) { try { await (sorcery as Sorcery).load(file).then(async chain => { if (!chain) { @@ -202,7 +202,7 @@ export async function makeCustomSentryVitePlugin(options?: CustomSentryVitePlugi ); await fs.promises.writeFile(mapFile, cleanedMapContent); } - }); + } try { // @ts-expect-error - this hook exists on the plugin! From cb6912a56106f8f5948d080efe9dfb57b2ba22b2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 11:58:19 +0100 Subject: [PATCH 033/173] ref(core): Make `on` and `emit` required on client (#10603) This allows us to remove a bunch of code around fallbacks etc. Esp. in replay this also allows us to remove a bunch of code around breadcrumbs etc. note @JonasBa I did not remove this in profiling-node yet because the test rely on this quite a bunch, can you take a look at this when you've got some time? --- packages/angular/src/errorhandler.ts | 4 +- .../browser/src/integrations/breadcrumbs.ts | 2 +- packages/browser/src/profiling/integration.ts | 5 -- packages/core/src/hub.ts | 4 +- packages/core/src/integration.ts | 2 +- packages/core/src/integrations/metadata.ts | 4 -- .../src/tracing/dynamicSamplingContext.ts | 4 +- packages/core/src/tracing/hubextensions.ts | 4 +- packages/core/src/tracing/transaction.ts | 2 +- packages/core/test/lib/base.test.ts | 8 +-- .../feedback/src/util/prepareFeedbackEvent.ts | 4 +- .../feedback/src/util/sendFeedbackRequest.ts | 4 +- packages/integrations/src/debug.ts | 4 -- packages/node-experimental/src/sdk/api.ts | 4 +- packages/node/src/integrations/spotlight.ts | 5 -- .../node/test/integrations/spotlight.test.ts | 10 --- .../opentelemetry-node/src/spanprocessor.ts | 2 +- .../test/propagator.test.ts | 1 + .../opentelemetry/src/custom/transaction.ts | 2 +- .../opentelemetry/test/propagator.test.ts | 1 + packages/react/src/errorboundary.tsx | 2 +- .../src/coreHandlers/handleBreadcrumbs.ts | 2 +- .../replay/src/coreHandlers/handleFetch.ts | 42 ------------ .../src/coreHandlers/handleGlobalEvent.ts | 15 +---- .../coreHandlers/handleNetworkBreadcrumbs.ts | 10 +-- packages/replay/src/coreHandlers/handleXhr.ts | 49 -------------- .../replay/src/util/addGlobalListeners.ts | 12 +--- .../replay/src/util/prepareReplayEvent.ts | 4 +- .../coreHandlers/handleGlobalEvent.test.ts | 35 +--------- .../unit/coreHandlers/handleFetch.test.ts | 64 ------------------- .../test/unit/coreHandlers/handleXhr.test.ts | 62 ------------------ .../client/browserTracingIntegration.test.ts | 2 +- .../src/browser/browserTracingIntegration.ts | 46 ++++++------- packages/types/src/client.ts | 51 +++++++-------- 34 files changed, 75 insertions(+), 397 deletions(-) delete mode 100644 packages/replay/src/coreHandlers/handleFetch.ts delete mode 100644 packages/replay/src/coreHandlers/handleXhr.ts delete mode 100644 packages/replay/test/unit/coreHandlers/handleFetch.test.ts delete mode 100644 packages/replay/test/unit/coreHandlers/handleXhr.test.ts diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index ec7a735ab559..b22536307e9a 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -118,7 +118,7 @@ class SentryErrorHandler implements AngularErrorHandler { if (this._options.showDialog) { const client = Sentry.getClient(); - if (client && client.on && !this._registeredAfterSendEventHandler) { + if (client && !this._registeredAfterSendEventHandler) { client.on('afterSendEvent', (event: Event) => { if (!event.type) { // eslint-disable-next-line deprecation/deprecation @@ -128,7 +128,7 @@ class SentryErrorHandler implements AngularErrorHandler { // We only want to register this hook once in the lifetime of the error handler this._registeredAfterSendEventHandler = true; - } else if (!client || !client.on) { + } else if (!client) { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId }); } } diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 1cbd8321346e..0fb305767414 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -88,7 +88,7 @@ const _breadcrumbsIntegration = ((options: Partial = {}) => if (_options.history) { addHistoryInstrumentationHandler(_getHistoryBreadcrumbHandler(client)); } - if (_options.sentry && client.on) { + if (_options.sentry) { client.on('beforeSendEvent', _getSentryBreadcrumbHandler(client)); } }, diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index bf8e56a626b5..1c076e09b17d 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -35,11 +35,6 @@ const _browserProfilingIntegration = (() => { } } - if (typeof client.on !== 'function') { - logger.warn('[Profiling] Client does not support hooks, profiling will be disabled'); - return; - } - client.on('startTransaction', (transaction: Transaction) => { if (shouldProfileTransaction(transaction)) { startProfileForTransaction(transaction); diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 6c46e7c427fa..329e0c957a77 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -419,9 +419,7 @@ export class Hub implements HubInterface { if (finalBreadcrumb === null) return; - if (client.emit) { - client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); - } + client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); // TODO(v8): I know this comment doesn't make much sense because the hub will be deprecated but I still wanted to // write it down. In theory, we would have to add the breadcrumbs to the isolation scope here, however, that would diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 8a91fa10e303..c95dce4b2f79 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -140,7 +140,7 @@ export function setupIntegration(client: Client, integration: Integration, integ integration.setup(client); } - if (client.on && typeof integration.preprocessEvent === 'function') { + if (typeof integration.preprocessEvent === 'function') { const callback = integration.preprocessEvent.bind(integration) as typeof integration.preprocessEvent; client.on('preprocessEvent', (event, hint) => callback(event, hint, client)); } diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/metadata.ts index d4e48620d726..3d5b89e07d2e 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -12,10 +12,6 @@ const _moduleMetadataIntegration = (() => { // TODO v8: Remove this setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { - if (typeof client.on !== 'function') { - return; - } - // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. client.on('beforeEnvelope', envelope => { forEachEnvelopeItem(envelope, (item, type) => { diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index 93acfe77d10a..d7fe3872b42f 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -23,7 +23,7 @@ export function getDynamicSamplingContextFromClient(trace_id: string, client: Cl trace_id, }) as DynamicSamplingContext; - client.emit && client.emit('createDsc', dsc); + client.emit('createDsc', dsc); return dsc; } @@ -81,7 +81,7 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly { traceId: '86f39e84263a4de99c326acab3bfe3bd', } as Transaction; - client.on?.('startTransaction', transaction => { + client.on('startTransaction', transaction => { expect(transaction).toEqual(mockTransaction); }); - client.emit?.('startTransaction', mockTransaction); + client.emit('startTransaction', mockTransaction); }); it('should call a beforeEnvelope hook', () => { @@ -1974,11 +1974,11 @@ describe('BaseClient', () => { {}, ] as Envelope; - client.on?.('beforeEnvelope', envelope => { + client.on('beforeEnvelope', envelope => { expect(envelope).toEqual(mockEnvelope); }); - client.emit?.('beforeEnvelope', mockEnvelope); + client.emit('beforeEnvelope', mockEnvelope); }); }); }); diff --git a/packages/feedback/src/util/prepareFeedbackEvent.ts b/packages/feedback/src/util/prepareFeedbackEvent.ts index cb48efeaf89d..312b6252a9e0 100644 --- a/packages/feedback/src/util/prepareFeedbackEvent.ts +++ b/packages/feedback/src/util/prepareFeedbackEvent.ts @@ -17,9 +17,7 @@ export async function prepareFeedbackEvent({ event, }: PrepareFeedbackEventParams): Promise { const eventHint = {}; - if (client.emit) { - client.emit('preprocessEvent', event, eventHint); - } + client.emit('preprocessEvent', event, eventHint); const preparedEvent = (await prepareEvent( client.getOptions(), diff --git a/packages/feedback/src/util/sendFeedbackRequest.ts b/packages/feedback/src/util/sendFeedbackRequest.ts index f1629a00670a..d8f3a880ba2c 100644 --- a/packages/feedback/src/util/sendFeedbackRequest.ts +++ b/packages/feedback/src/util/sendFeedbackRequest.ts @@ -51,9 +51,7 @@ export async function sendFeedbackRequest( return; } - if (client.emit) { - client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) }); - } + client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) }); const envelope = createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel); diff --git a/packages/integrations/src/debug.ts b/packages/integrations/src/debug.ts index 3e76353688d3..7c23cba9ae57 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/integrations/src/debug.ts @@ -23,10 +23,6 @@ const _debugIntegration = ((options: DebugOptions = {}) => { // TODO v8: Remove this setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { - if (!client.on) { - return; - } - client.on('beforeSendEvent', (event: Event, hint?: EventHint) => { if (_options.debugger) { // eslint-disable-next-line no-debugger diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index 83685fe6d987..c4b0c397ea43 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -134,9 +134,7 @@ export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): vo if (finalBreadcrumb === null) return; - if (client.emit) { - client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); - } + client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); getIsolationScope().addBreadcrumb(finalBreadcrumb, maxBreadcrumbs); } diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts index 52b8941daa71..bf72f653f58c 100644 --- a/packages/node/src/integrations/spotlight.ts +++ b/packages/node/src/integrations/spotlight.ts @@ -65,11 +65,6 @@ function connectToSpotlight(client: Client, options: Required { if (failedRequests > 3) { logger.warn('[Spotlight] Disabled Sentry -> Spotlight integration due to too many failed requests'); diff --git a/packages/node/test/integrations/spotlight.test.ts b/packages/node/test/integrations/spotlight.test.ts index 756170d89518..a892677d0dd0 100644 --- a/packages/node/test/integrations/spotlight.test.ts +++ b/packages/node/test/integrations/spotlight.test.ts @@ -120,16 +120,6 @@ describe('Spotlight', () => { integration.setup!(client); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sidecar URL: invalid-url')); }); - - it("the client doesn't support life cycle hooks", () => { - const integration = spotlightIntegration({ sidecarUrl: 'http://mylocalhost:8969' }); - const clientWithoutHooks = { ...client }; - // @ts-expect-error - this is fine in tests - delete client.on; - // @ts-expect-error - this is fine in tests - integration.setup(clientWithoutHooks); - expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining(' missing method on SDK client (`client.on`)')); - }); }); it('warns if the NODE_ENV variable doesn\'t equal "development"', () => { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index d14d77010422..9996fc73111f 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -114,7 +114,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { const client = getClient(); const mutableOptions = { drop: false }; - client && client.emit && client?.emit('otelSpanEnd', otelSpan, mutableOptions); + client && client.emit('otelSpanEnd', otelSpan, mutableOptions); if (mutableOptions.drop) { clearSpan(otelSpanId); diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts index 494b3aff7f07..c723b5bc6f0b 100644 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ b/packages/opentelemetry-node/test/propagator.test.ts @@ -45,6 +45,7 @@ describe('SentryPropagator', () => { getDsn: () => ({ publicKey: 'abc', }), + emit: () => {}, }; // @ts-expect-error Use mock client for unit tests // eslint-disable-next-line deprecation/deprecation diff --git a/packages/opentelemetry/src/custom/transaction.ts b/packages/opentelemetry/src/custom/transaction.ts index ceb950bc610d..31b2efe31d03 100644 --- a/packages/opentelemetry/src/custom/transaction.ts +++ b/packages/opentelemetry/src/custom/transaction.ts @@ -17,7 +17,7 @@ export function startTransaction(hub: HubInterface, transactionContext: Transact // Any sampling decision happens in OpenTelemetry's sampler transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); - if (client && client.emit) { + if (client) { client.emit('startTransaction', transaction); } return transaction; diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index 012542d47e35..fc67f4bb3030 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -39,6 +39,7 @@ describe('SentryPropagator', () => { getDsn: () => ({ publicKey: 'abc', }), + emit: () => {}, }; // @ts-expect-error Use mock client for unit tests diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index d120c9b44dfb..c521c0204393 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -108,7 +108,7 @@ class ErrorBoundary extends React.Component { if (!event.type && this._lastEventId && event.event_id === this._lastEventId) { diff --git a/packages/replay/src/coreHandlers/handleBreadcrumbs.ts b/packages/replay/src/coreHandlers/handleBreadcrumbs.ts index 416bca4efb15..034e5cdb05af 100644 --- a/packages/replay/src/coreHandlers/handleBreadcrumbs.ts +++ b/packages/replay/src/coreHandlers/handleBreadcrumbs.ts @@ -16,7 +16,7 @@ type BreadcrumbWithCategory = Required>; export function handleBreadcrumbs(replay: ReplayContainer): void { const client = getClient(); - if (!client || !client.on) { + if (!client) { return; } diff --git a/packages/replay/src/coreHandlers/handleFetch.ts b/packages/replay/src/coreHandlers/handleFetch.ts deleted file mode 100644 index 54681c2871c0..000000000000 --- a/packages/replay/src/coreHandlers/handleFetch.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { HandlerDataFetch } from '@sentry/types'; - -import type { NetworkRequestData, ReplayContainer, ReplayPerformanceEntry } from '../types'; -import { addNetworkBreadcrumb } from './util/addNetworkBreadcrumb'; - -/** only exported for tests */ -export function handleFetch(handlerData: HandlerDataFetch): null | ReplayPerformanceEntry { - const { startTimestamp, endTimestamp, fetchData, response } = handlerData; - - if (!endTimestamp) { - return null; - } - - // This is only used as a fallback, so we know the body sizes are never set here - const { method, url } = fetchData; - - return { - type: 'resource.fetch', - start: startTimestamp / 1000, - end: endTimestamp / 1000, - name: url, - data: { - method, - statusCode: response ? (response as Response).status : undefined, - }, - }; -} - -/** - * Returns a listener to be added to `addFetchInstrumentationHandler(listener)`. - */ -export function handleFetchSpanListener(replay: ReplayContainer): (handlerData: HandlerDataFetch) => void { - return (handlerData: HandlerDataFetch) => { - if (!replay.isEnabled()) { - return; - } - - const result = handleFetch(handlerData); - - addNetworkBreadcrumb(replay, result); - }; -} diff --git a/packages/replay/src/coreHandlers/handleGlobalEvent.ts b/packages/replay/src/coreHandlers/handleGlobalEvent.ts index 39983a85d72e..88651d449fe6 100644 --- a/packages/replay/src/coreHandlers/handleGlobalEvent.ts +++ b/packages/replay/src/coreHandlers/handleGlobalEvent.ts @@ -5,19 +5,13 @@ import { DEBUG_BUILD } from '../debug-build'; import type { ReplayContainer } from '../types'; import { isErrorEvent, isFeedbackEvent, isReplayEvent, isTransactionEvent } from '../util/eventUtils'; import { isRrwebError } from '../util/isRrwebError'; -import { handleAfterSendEvent } from './handleAfterSendEvent'; import { addFeedbackBreadcrumb } from './util/addFeedbackBreadcrumb'; import { shouldSampleForBufferEvent } from './util/shouldSampleForBufferEvent'; /** * Returns a listener to be added to `addEventProcessor(listener)`. */ -export function handleGlobalEventListener( - replay: ReplayContainer, - includeAfterSendEventHandling = false, -): (event: Event, hint: EventHint) => Event | null { - const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined; - +export function handleGlobalEventListener(replay: ReplayContainer): (event: Event, hint: EventHint) => Event | null { return Object.assign( (event: Event, hint: EventHint) => { // Do nothing if replay has been disabled @@ -73,13 +67,6 @@ export function handleGlobalEventListener( event.tags = { ...event.tags, replayId: replay.getSessionId() }; } - // In cases where a custom client is used that does not support the new hooks (yet), - // we manually call this hook method here - if (afterSendHandler) { - // Pretend the error had a 200 response so we always capture it - afterSendHandler(event, { statusCode: 200 }); - } - return event; }, { id: 'Replay' }, diff --git a/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts b/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts index 4a201ae9b65e..4acafd5ca207 100644 --- a/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts +++ b/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts @@ -6,12 +6,10 @@ import type { TextEncoderInternal, XhrBreadcrumbData, } from '@sentry/types'; -import { addFetchInstrumentationHandler, addXhrInstrumentationHandler, logger } from '@sentry/utils'; +import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { FetchHint, ReplayContainer, ReplayNetworkOptions, XhrHint } from '../types'; -import { handleFetchSpanListener } from './handleFetch'; -import { handleXhrSpanListener } from './handleXhr'; import { captureFetchBreadcrumbToReplay, enrichFetchBreadcrumb } from './util/fetchUtils'; import { captureXhrBreadcrumbToReplay, enrichXhrBreadcrumb } from './util/xhrUtils'; @@ -50,12 +48,8 @@ export function handleNetworkBreadcrumbs(replay: ReplayContainer): void { networkResponseHeaders, }; - if (client && client.on) { + if (client) { client.on('beforeAddBreadcrumb', (breadcrumb, hint) => beforeAddNetworkBreadcrumb(options, breadcrumb, hint)); - } else { - // Fallback behavior - addFetchInstrumentationHandler(handleFetchSpanListener(replay)); - addXhrInstrumentationHandler(handleXhrSpanListener(replay)); } } catch { // Do nothing diff --git a/packages/replay/src/coreHandlers/handleXhr.ts b/packages/replay/src/coreHandlers/handleXhr.ts deleted file mode 100644 index afe76411bdcf..000000000000 --- a/packages/replay/src/coreHandlers/handleXhr.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { HandlerDataXhr } from '@sentry/types'; -import { SENTRY_XHR_DATA_KEY } from '@sentry/utils'; - -import type { NetworkRequestData, ReplayContainer, ReplayPerformanceEntry } from '../types'; -import { addNetworkBreadcrumb } from './util/addNetworkBreadcrumb'; - -/** only exported for tests */ -export function handleXhr(handlerData: HandlerDataXhr): ReplayPerformanceEntry | null { - const { startTimestamp, endTimestamp, xhr } = handlerData; - - const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY]; - - if (!startTimestamp || !endTimestamp || !sentryXhrData) { - return null; - } - - // This is only used as a fallback, so we know the body sizes are never set here - const { method, url, status_code: statusCode } = sentryXhrData; - - if (url === undefined) { - return null; - } - - return { - type: 'resource.xhr', - name: url, - start: startTimestamp / 1000, - end: endTimestamp / 1000, - data: { - method, - statusCode, - }, - }; -} - -/** - * Returns a listener to be added to `addXhrInstrumentationHandler(listener)`. - */ -export function handleXhrSpanListener(replay: ReplayContainer): (handlerData: HandlerDataXhr) => void { - return (handlerData: HandlerDataXhr) => { - if (!replay.isEnabled()) { - return; - } - - const result = handleXhr(handlerData); - - addNetworkBreadcrumb(replay, result); - }; -} diff --git a/packages/replay/src/util/addGlobalListeners.ts b/packages/replay/src/util/addGlobalListeners.ts index b3373924e1fa..797a6c3cfa96 100644 --- a/packages/replay/src/util/addGlobalListeners.ts +++ b/packages/replay/src/util/addGlobalListeners.ts @@ -1,6 +1,5 @@ -import type { BaseClient } from '@sentry/core'; import { addEventProcessor, getClient } from '@sentry/core'; -import type { Client, DynamicSamplingContext } from '@sentry/types'; +import type { DynamicSamplingContext } from '@sentry/types'; import { addClickKeypressInstrumentationHandler, addHistoryInstrumentationHandler } from '@sentry/utils'; import { handleAfterSendEvent } from '../coreHandlers/handleAfterSendEvent'; @@ -26,7 +25,7 @@ export function addGlobalListeners(replay: ReplayContainer): void { // Tag all (non replay) events that get sent to Sentry with the current // replay ID so that we can reference them later in the UI - const eventProcessor = handleGlobalEventListener(replay, !hasHooks(client)); + const eventProcessor = handleGlobalEventListener(replay); if (client && client.addEventProcessor) { client.addEventProcessor(eventProcessor); } else { @@ -34,7 +33,7 @@ export function addGlobalListeners(replay: ReplayContainer): void { } // If a custom client has no hooks yet, we continue to use the "old" implementation - if (hasHooks(client)) { + if (client) { client.on('beforeSendEvent', handleBeforeSendEvent(replay)); client.on('afterSendEvent', handleAfterSendEvent(replay)); client.on('createDsc', (dsc: DynamicSamplingContext) => { @@ -73,8 +72,3 @@ export function addGlobalListeners(replay: ReplayContainer): void { }); } } - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function hasHooks(client: Client | undefined): client is BaseClient { - return !!(client && client.on); -} diff --git a/packages/replay/src/util/prepareReplayEvent.ts b/packages/replay/src/util/prepareReplayEvent.ts index 65e190d29551..e564f0509326 100644 --- a/packages/replay/src/util/prepareReplayEvent.ts +++ b/packages/replay/src/util/prepareReplayEvent.ts @@ -24,9 +24,7 @@ export async function prepareReplayEvent({ const eventHint: EventHint = { event_id, integrations }; - if (client.emit) { - client.emit('preprocessEvent', event, eventHint); - } + client.emit('preprocessEvent', event, eventHint); const preparedEvent = (await prepareEvent( client.getOptions(), diff --git a/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts index a1ca45a8d76a..c383bc57dc83 100644 --- a/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -146,7 +146,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { ); }); - it('does not collect errorIds when hooks are available', async () => { + it('does not collect errorIds', async () => { const error1 = Error({ event_id: 'err1' }); const error2 = Error({ event_id: 'err2' }); const error3 = Error({ event_id: 'err3' }); @@ -160,21 +160,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); }); - it('collects errorIds when hooks are not available', async () => { - const error1 = Error({ event_id: 'err1' }); - const error2 = Error({ event_id: 'err2' }); - const error3 = Error({ event_id: 'err3' }); - - const handler = handleGlobalEventListener(replay, true); - - handler(error1, {}); - handler(error2, {}); - handler(error3, {}); - - expect(Array.from(replay.getContext().errorIds)).toEqual(['err1', 'err2', 'err3']); - }); - - it('does not collect traceIds when hooks are available', async () => { + it('does not collect traceIds', async () => { const transaction1 = Transaction('tr1'); const transaction2 = Transaction('tr2'); const transaction3 = Transaction('tr3'); @@ -188,33 +174,16 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual([]); }); - it('collects traceIds when hooks are not available', async () => { - const transaction1 = Transaction('tr1'); - const transaction2 = Transaction('tr2'); - const transaction3 = Transaction('tr3'); - - const handler = handleGlobalEventListener(replay, true); - - handler(transaction1, {}); - handler(transaction2, {}); - handler(transaction3, {}); - - expect(Array.from(replay.getContext().traceIds)).toEqual(['tr1', 'tr2', 'tr3']); - }); - it('ignores profile & replay events', async () => { const profileEvent: Event = { type: 'profile' }; const replayEvent: Event = { type: 'replay_event' }; const handler = handleGlobalEventListener(replay); - const handler2 = handleGlobalEventListener(replay, true); expect(replay.recordingMode).toBe('buffer'); handler(profileEvent, {}); handler(replayEvent, {}); - handler2(profileEvent, {}); - handler2(replayEvent, {}); expect(Array.from(replay.getContext().traceIds)).toEqual([]); expect(Array.from(replay.getContext().errorIds)).toEqual([]); diff --git a/packages/replay/test/unit/coreHandlers/handleFetch.test.ts b/packages/replay/test/unit/coreHandlers/handleFetch.test.ts deleted file mode 100644 index 3938b351d796..000000000000 --- a/packages/replay/test/unit/coreHandlers/handleFetch.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { HandlerDataFetch } from '@sentry/types'; - -import { handleFetch } from '../../../src/coreHandlers/handleFetch'; - -const DEFAULT_DATA: HandlerDataFetch = { - args: ['/api/0/organizations/sentry/', { method: 'GET', headers: {}, credentials: 'include' }] as Parameters< - typeof fetch - >, - endTimestamp: 15000, - fetchData: { - method: 'GET', - url: '/api/0/organizations/sentry/', - }, - response: { - type: 'basic', - url: '', - redirected: false, - status: 200, - ok: true, - } as Response, - startTimestamp: 10000, -}; - -describe('Unit | coreHandlers | handleFetch', () => { - it('formats fetch calls from core SDK to replay breadcrumbs', function () { - expect(handleFetch(DEFAULT_DATA)).toEqual({ - type: 'resource.fetch', - name: '/api/0/organizations/sentry/', - start: 10, - end: 15, - data: { - method: 'GET', - statusCode: 200, - }, - }); - }); - - it('ignores fetches that have not completed yet', function () { - const data = { - ...DEFAULT_DATA, - endTimestamp: undefined, - response: undefined, - }; - - expect(handleFetch(data)).toEqual(null); - }); - - // This cannot happen as of now, this test just shows the expected behavior - it('ignores request/response sizes', function () { - const data = { - ...DEFAULT_DATA, - fetchData: { - ...DEFAULT_DATA.fetchData, - request_body_size: 123, - response_body_size: 456, - }, - }; - - expect(handleFetch(data)?.data).toEqual({ - method: 'GET', - statusCode: 200, - }); - }); -}); diff --git a/packages/replay/test/unit/coreHandlers/handleXhr.test.ts b/packages/replay/test/unit/coreHandlers/handleXhr.test.ts deleted file mode 100644 index 84fdf2facfdf..000000000000 --- a/packages/replay/test/unit/coreHandlers/handleXhr.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { HandlerDataXhr, SentryWrappedXMLHttpRequest, SentryXhrData } from '@sentry/types'; -import { SENTRY_XHR_DATA_KEY } from '@sentry/utils'; - -import { handleXhr } from '../../../src/coreHandlers/handleXhr'; - -const DEFAULT_DATA: HandlerDataXhr = { - args: ['GET', '/api/0/organizations/sentry/'], - xhr: { - [SENTRY_XHR_DATA_KEY]: { - method: 'GET', - url: '/api/0/organizations/sentry/', - status_code: 200, - request_headers: {}, - }, - } as SentryWrappedXMLHttpRequest, - startTimestamp: 10000, - endTimestamp: 15000, -}; - -describe('Unit | coreHandlers | handleXhr', () => { - it('formats fetch calls from core SDK to replay breadcrumbs', function () { - expect(handleXhr(DEFAULT_DATA)).toEqual({ - type: 'resource.xhr', - name: '/api/0/organizations/sentry/', - start: 10, - end: 15, - data: { - method: 'GET', - statusCode: 200, - }, - }); - }); - - it('ignores xhr that have not completed yet', function () { - const data = { - ...DEFAULT_DATA, - endTimestamp: undefined, - }; - - expect(handleXhr(data)).toEqual(null); - }); - - // This cannot happen as of now, this test just shows the expected behavior - it('ignores request/response sizes', function () { - const data: HandlerDataXhr = { - ...DEFAULT_DATA, - xhr: { - ...DEFAULT_DATA.xhr, - [SENTRY_XHR_DATA_KEY]: { - ...(DEFAULT_DATA.xhr[SENTRY_XHR_DATA_KEY] as SentryXhrData), - request_body_size: 123, - response_body_size: 456, - }, - }, - }; - - expect(handleXhr(data)?.data).toEqual({ - method: 'GET', - statusCode: 200, - }); - }); -}); diff --git a/packages/sveltekit/test/client/browserTracingIntegration.test.ts b/packages/sveltekit/test/client/browserTracingIntegration.test.ts index 83984c0b19f5..0fcd8f3f1e46 100644 --- a/packages/sveltekit/test/client/browserTracingIntegration.test.ts +++ b/packages/sveltekit/test/client/browserTracingIntegration.test.ts @@ -58,7 +58,7 @@ describe('browserTracingIntegration', () => { }; }); - const fakeClient = { getOptions: () => undefined }; + const fakeClient = { getOptions: () => undefined, on: () => {} }; const mockedRoutingSpan = { end: () => {}, diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index 31660eff00a7..a521933c420e 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -309,27 +309,25 @@ export const browserTracingIntegration = ((_options: Partial { - if (activeSpan) { - DEBUG_BUILD && logger.log(`[Tracing] Finishing current transaction with op: ${spanToJSON(activeSpan).op}`); - // If there's an open transaction on the scope, we need to finish it before creating an new one. - activeSpan.end(); - } - activeSpan = _createRouteTransaction(context); - }); + client.on('startNavigationSpan', (context: StartSpanOptions) => { + if (activeSpan) { + DEBUG_BUILD && logger.log(`[Tracing] Finishing current transaction with op: ${spanToJSON(activeSpan).op}`); + // If there's an open transaction on the scope, we need to finish it before creating an new one. + activeSpan.end(); + } + activeSpan = _createRouteTransaction(context); + }); - client.on('startPageLoadSpan', (context: StartSpanOptions) => { - if (activeSpan) { - DEBUG_BUILD && logger.log(`[Tracing] Finishing current transaction with op: ${spanToJSON(activeSpan).op}`); - // If there's an open transaction on the scope, we need to finish it before creating an new one. - activeSpan.end(); - } - activeSpan = _createRouteTransaction(context); - }); - } + client.on('startPageLoadSpan', (context: StartSpanOptions) => { + if (activeSpan) { + DEBUG_BUILD && logger.log(`[Tracing] Finishing current transaction with op: ${spanToJSON(activeSpan).op}`); + // If there's an open transaction on the scope, we need to finish it before creating an new one. + activeSpan.end(); + } + activeSpan = _createRouteTransaction(context); + }); - if (options.instrumentPageLoad && client.emit) { + if (options.instrumentPageLoad) { const context: StartSpanOptions = { name: WINDOW.location.pathname, // pageload should always start at timeOrigin (and needs to be in s, not ms) @@ -343,7 +341,7 @@ export const browserTracingIntegration = ((_options: Partial { /** * This early return is there to account for some cases where a navigation transaction starts right after @@ -402,10 +400,6 @@ export const browserTracingIntegration = ((_options: Partial { * Register a callback for transaction start. * Receives the transaction as argument. */ - on?(hook: 'startTransaction', callback: (transaction: Transaction) => void): void; + on(hook: 'startTransaction', callback: (transaction: Transaction) => void): void; /** * Register a callback for transaction finish. * Receives the transaction as argument. */ - on?(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void; + on(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void; /** * Register a callback for transaction start and finish. */ - on?(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; + on(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; /** * Register a callback for before sending an event. * This is called right before an event is sent and should not be used to mutate the event. * Receives an Event & EventHint as arguments. */ - on?(hook: 'beforeSendEvent', callback: (event: Event, hint?: EventHint | undefined) => void): void; + on(hook: 'beforeSendEvent', callback: (event: Event, hint?: EventHint | undefined) => void): void; /** * Register a callback for preprocessing an event, * before it is passed to (global) event processors. * Receives an Event & EventHint as arguments. */ - on?(hook: 'preprocessEvent', callback: (event: Event, hint?: EventHint | undefined) => void): void; + on(hook: 'preprocessEvent', callback: (event: Event, hint?: EventHint | undefined) => void): void; /** * Register a callback for when an event has been sent. */ - on?( - hook: 'afterSendEvent', - callback: (event: Event, sendResponse: TransportMakeRequestResponse | void) => void, - ): void; + on(hook: 'afterSendEvent', callback: (event: Event, sendResponse: TransportMakeRequestResponse | void) => void): void; /** * Register a callback before a breadcrumb is added. */ - on?(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void; + on(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void; /** * Register a callback when a DSC (Dynamic Sampling Context) is created. */ - on?(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; + on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; /** * Register a callback when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). * The option argument may be mutated to drop the span. */ - on?(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; + on(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; /** * Register a callback when a Feedback event has been prepared. * This should be used to mutate the event. The options argument can hint * about what kind of mutation it expects. */ - on?( + on( hook: 'beforeSendFeedback', callback: (feedback: FeedbackEvent, options?: { includeReplay?: boolean }) => void, ): void; @@ -265,83 +262,83 @@ export interface Client { /** * A hook for BrowserTracing to trigger a span start for a page load. */ - on?(hook: 'startPageLoadSpan', callback: (options: StartSpanOptions) => void): void; + on(hook: 'startPageLoadSpan', callback: (options: StartSpanOptions) => void): void; /** * A hook for BrowserTracing to trigger a span for a navigation. */ - on?(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; + on(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; /** * Fire a hook event for transaction start. * Expects to be given a transaction as the second argument. */ - emit?(hook: 'startTransaction', transaction: Transaction): void; + emit(hook: 'startTransaction', transaction: Transaction): void; /** * Fire a hook event for transaction finish. * Expects to be given a transaction as the second argument. */ - emit?(hook: 'finishTransaction', transaction: Transaction): void; + emit(hook: 'finishTransaction', transaction: Transaction): void; /* * Fire a hook event for envelope creation and sending. Expects to be given an envelope as the * second argument. */ - emit?(hook: 'beforeEnvelope', envelope: Envelope): void; + emit(hook: 'beforeEnvelope', envelope: Envelope): void; /** * Fire a hook event before sending an event. * This is called right before an event is sent and should not be used to mutate the event. * Expects to be given an Event & EventHint as the second/third argument. */ - emit?(hook: 'beforeSendEvent', event: Event, hint?: EventHint): void; + emit(hook: 'beforeSendEvent', event: Event, hint?: EventHint): void; /** * Fire a hook event to process events before they are passed to (global) event processors. * Expects to be given an Event & EventHint as the second/third argument. */ - emit?(hook: 'preprocessEvent', event: Event, hint?: EventHint): void; + emit(hook: 'preprocessEvent', event: Event, hint?: EventHint): void; /* * Fire a hook event after sending an event. Expects to be given an Event as the * second argument. */ - emit?(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void; + emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void; /** * Fire a hook for when a breadcrumb is added. Expects the breadcrumb as second argument. */ - emit?(hook: 'beforeAddBreadcrumb', breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void; + emit(hook: 'beforeAddBreadcrumb', breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void; /** * Fire a hook for when a DSC (Dynamic Sampling Context) is created. Expects the DSC as second argument. */ - emit?(hook: 'createDsc', dsc: DynamicSamplingContext): void; + emit(hook: 'createDsc', dsc: DynamicSamplingContext): void; /** * Fire a hook for when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). * Expects the OTEL span & as second argument, and an option object as third argument. * The option argument may be mutated to drop the span. */ - emit?(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; + emit(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; /** * Fire a hook event for after preparing a feedback event. Events to be given * a feedback event as the second argument, and an optional options object as * third argument. */ - emit?(hook: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay?: boolean }): void; + emit(hook: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay?: boolean }): void; /** * Emit a hook event for BrowserTracing to trigger a span start for a page load. */ - emit?(hook: 'startPageLoadSpan', options: StartSpanOptions): void; + emit(hook: 'startPageLoadSpan', options: StartSpanOptions): void; /** * Emit a hook event for BrowserTracing to trigger a span for a navigation. */ - emit?(hook: 'startNavigationSpan', options: StartSpanOptions): void; + emit(hook: 'startNavigationSpan', options: StartSpanOptions): void; /* eslint-enable @typescript-eslint/unified-signatures */ } From 31faca3dedd44e837c6deb341e76c21994442412 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 13:16:51 +0100 Subject: [PATCH 034/173] docs: Fix deprecation message for span data/attributes (#10604) This was apparently left over from some in-between state, users should actually use `spanToJSON(span)` there to get attributes. --- packages/core/src/tracing/span.ts | 6 +++--- packages/types/src/span.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 165677455d7f..28a2de56475d 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -67,13 +67,13 @@ export class SpanRecorder { export class Span implements SpanInterface { /** * Tags for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ public tags: { [key: string]: Primitive }; /** * Data for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any public data: { [key: string]: any }; @@ -263,7 +263,7 @@ export class Span implements SpanInterface { /** * Attributes for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ public get attributes(): SpanAttributes { return this._attributes; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 73c2fbdaaaa8..fc7f1077368e 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -226,19 +226,19 @@ export interface Span extends Omit { /** * Tags for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ tags: { [key: string]: Primitive }; /** * Data for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ data: { [key: string]: any }; /** * Attributes for the span. - * @deprecated Use `getSpanAttributes(span)` instead. + * @deprecated Use `spanToJSON(span).atttributes` instead. */ attributes: SpanAttributes; From 93db2dc10c7a07037619727f12b9a968f1cee949 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 13:20:09 +0100 Subject: [PATCH 035/173] feat(core): Update `addEventProcessor` to add to isolation scope (#10606) Instead of to the client. This was done to ensure this would be "global" without using the fully global event processors (which need to be removed in a separate step). But in the new model, it makes more sense to add them to the isolation scope. --- packages/core/src/baseclient.ts | 15 --------------- packages/core/src/exports.ts | 10 ++++++++++ packages/core/src/index.ts | 3 ++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index b54e1887fd2b..35a90bd29cda 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -49,7 +49,6 @@ import { import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; import { DEBUG_BUILD } from './debug-build'; import { createEventEnvelope, createSessionEnvelope } from './envelope'; -import { getClient } from './exports'; import { getIsolationScope } from './hub'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations } from './integration'; @@ -933,17 +932,3 @@ function isErrorEvent(event: Event): event is ErrorEvent { function isTransactionEvent(event: Event): event is TransactionEvent { return event.type === 'transaction'; } - -/** - * Add an event processor to the current client. - * This event processor will run for all events processed by this client. - */ -export function addEventProcessor(callback: EventProcessor): void { - const client = getClient(); - - if (!client || !client.addEventProcessor) { - return; - } - - client.addEventProcessor(callback); -} diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 6c18aa0ec9ba..1e5c6e5503c5 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -7,6 +7,7 @@ import type { CustomSamplingContext, Event, EventHint, + EventProcessor, Extra, Extras, FinishedCheckIn, @@ -382,6 +383,15 @@ export function getCurrentScope(): Scope { return getCurrentHub().getScope(); } +/** + * Add an event processor. + * This will be added to the current isolation scope, ensuring any event that is processed in the current execution + * context will have the processor applied. + */ +export function addEventProcessor(callback: EventProcessor): void { + getIsolationScope().addEventProcessor(callback); +} + /** * Start a session on the current isolation scope. * diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f42a411802e7..b97d07c2cd21 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -34,6 +34,7 @@ export { endSession, captureSession, withActiveSpan, + addEventProcessor, } from './exports'; export { // eslint-disable-next-line deprecation/deprecation @@ -58,7 +59,7 @@ export { addGlobalEventProcessor, } from './eventProcessors'; export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api'; -export { BaseClient, addEventProcessor } from './baseclient'; +export { BaseClient } from './baseclient'; export { ServerRuntimeClient } from './server-runtime-client'; export { initAndBind, setCurrentClient } from './sdk'; export { createTransport } from './transports/base'; From d0accc01d893ee1a44ab673ce17b7e1227adb922 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 13:38:22 +0100 Subject: [PATCH 036/173] feat(core): Update `Sentry.addBreadcrumb` to skip hub (#10601) Build on top of https://github.com/getsentry/sentry-javascript/pull/10600, as that relies on this to ensure replay still works. This basically goes a step further than https://github.com/getsentry/sentry-javascript/pull/10586/files to actually change the implementation to skip the hub completely. --- packages/core/src/breadcrumbs.ts | 41 ++++ packages/core/src/exports.ts | 15 -- packages/core/src/index.ts | 2 +- packages/core/test/lib/base.test.ts | 146 ++++++------ packages/node-experimental/src/index.ts | 2 +- packages/node-experimental/src/sdk/api.ts | 26 --- packages/node-experimental/src/sdk/hub.ts | 3 +- packages/node-experimental/src/sdk/scope.ts | 9 +- .../test/integration/breadcrumbs.test.ts | 4 +- .../test/integration/scope.test.ts | 6 +- packages/opentelemetry/src/custom/scope.ts | 71 +----- packages/opentelemetry/src/spanProcessor.ts | 10 +- .../opentelemetry/test/custom/scope.test.ts | 220 +----------------- .../test/integration/breadcrumbs.test.ts | 76 +++--- .../test/integration/scope.test.ts | 6 +- 15 files changed, 163 insertions(+), 474 deletions(-) create mode 100644 packages/core/src/breadcrumbs.ts diff --git a/packages/core/src/breadcrumbs.ts b/packages/core/src/breadcrumbs.ts new file mode 100644 index 000000000000..28d84e2a0971 --- /dev/null +++ b/packages/core/src/breadcrumbs.ts @@ -0,0 +1,41 @@ +import type { Breadcrumb, BreadcrumbHint } from '@sentry/types'; +import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils'; +import { getClient } from './exports'; +import { getIsolationScope } from './hub'; + +/** + * Default maximum number of breadcrumbs added to an event. Can be overwritten + * with {@link Options.maxBreadcrumbs}. + */ +const DEFAULT_BREADCRUMBS = 100; + +/** + * Records a new breadcrumb which will be attached to future events. + * + * Breadcrumbs will be added to subsequent events to provide more context on + * user's actions prior to an error or crash. + */ +export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void { + const client = getClient(); + const isolationScope = getIsolationScope(); + + if (!client) return; + + const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } = client.getOptions(); + + if (maxBreadcrumbs <= 0) return; + + const timestamp = dateTimestampInSeconds(); + const mergedBreadcrumb = { timestamp, ...breadcrumb }; + const finalBreadcrumb = beforeBreadcrumb + ? (consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) as Breadcrumb | null) + : mergedBreadcrumb; + + if (finalBreadcrumb === null) return; + + if (client.emit) { + client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); + } + + isolationScope.addBreadcrumb(finalBreadcrumb, maxBreadcrumbs); +} diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 1e5c6e5503c5..c5ab7b1436c8 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,6 +1,4 @@ import type { - Breadcrumb, - BreadcrumbHint, CaptureContext, CheckIn, Client, @@ -77,19 +75,6 @@ export function captureEvent(event: Event, hint?: EventHint): string { return getCurrentHub().captureEvent(event, hint); } -/** - * Records a new breadcrumb which will be attached to future events. - * - * Breadcrumbs will be added to subsequent events to provide more context on - * user's actions prior to an error or crash. - * - * @param breadcrumb The breadcrumb to record. - */ -export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().addBreadcrumb(breadcrumb, hint); -} - /** * Sets context data with the given name. * @param name of the context diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b97d07c2cd21..89d3e2d615ce 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -9,7 +9,6 @@ export * from './tracing'; export * from './semanticAttributes'; export { createEventEnvelope, createSessionEnvelope } from './envelope'; export { - addBreadcrumb, captureCheckIn, withMonitor, captureException, @@ -95,6 +94,7 @@ export { RequestData } from './integrations/requestdata'; export { InboundFilters } from './integrations/inboundfilters'; export { FunctionToString } from './integrations/functiontostring'; export { LinkedErrors } from './integrations/linkederrors'; +export { addBreadcrumb } from './breadcrumbs'; /* eslint-enable deprecation/deprecation */ import * as INTEGRATIONS from './integrations'; export { functionToStringIntegration } from './integrations/functiontostring'; diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 0238b79146ef..f07b0738e508 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,7 +1,15 @@ import type { Client, Envelope, Event, Span, Transaction } from '@sentry/types'; import { SentryError, SyncPromise, dsnToString, logger } from '@sentry/utils'; -import { Hub, Scope, makeSession, setGlobalScope } from '../../src'; +import { + Scope, + addBreadcrumb, + getCurrentScope, + getIsolationScope, + makeSession, + setCurrentClient, + setGlobalScope, +} from '../../src'; import * as integrationModule from '../../src/integration'; import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; import { AdHocIntegration, TestIntegration } from '../mocks/integration'; @@ -55,6 +63,8 @@ describe('BaseClient', () => { TestClient.sendEventCalled = undefined; TestClient.instance = undefined; setGlobalScope(undefined); + getCurrentScope().clear(); + getIsolationScope().clear(); }); afterEach(() => { @@ -114,133 +124,108 @@ describe('BaseClient', () => { describe('getBreadcrumbs() / addBreadcrumb()', () => { test('adds a breadcrumb', () => { - expect.assertions(1); - const options = getDefaultTestClientOptions({}); const client = new TestClient(options); + setCurrentClient(client); + client.init(); + const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); scope.addBreadcrumb({ message: 'hello' }, 100); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'world' }); + addBreadcrumb({ message: 'world' }); - expect((scope as any)._breadcrumbs[1].message).toEqual('world'); - }); + const breadcrumbs = scope.getScopeData().breadcrumbs; + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; - test('adds a timestamp to new breadcrumbs', () => { - expect.assertions(1); + expect(breadcrumbs).toEqual([{ message: 'hello', timestamp: expect.any(Number) }]); + expect(isolationScopeBreadcrumbs).toEqual([{ message: 'world', timestamp: expect.any(Number) }]); + }); + test('accepts a timestamp for new breadcrumbs', () => { const options = getDefaultTestClientOptions({}); const client = new TestClient(options); + setCurrentClient(client); + client.init(); + const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - scope.addBreadcrumb({ message: 'hello' }, 100); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'world' }); + scope.addBreadcrumb({ message: 'hello', timestamp: 1234 }, 100); + addBreadcrumb({ message: 'world', timestamp: 12345 }); - expect((scope as any)._breadcrumbs[1].timestamp).toBeGreaterThan(1); + const breadcrumbs = scope.getScopeData().breadcrumbs; + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; + + expect(breadcrumbs).toEqual([{ message: 'hello', timestamp: 1234 }]); + expect(isolationScopeBreadcrumbs).toEqual([{ message: 'world', timestamp: 12345 }]); }); test('discards breadcrumbs beyond `maxBreadcrumbs`', () => { - expect.assertions(2); - const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - - scope.addBreadcrumb({ message: 'hello' }, 100); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'world' }); - - expect((scope as any)._breadcrumbs.length).toEqual(1); - expect((scope as any)._breadcrumbs[0].message).toEqual('world'); - }); - - test('allows concurrent updates', () => { - expect.assertions(1); + setCurrentClient(client); + client.init(); - const options = getDefaultTestClientOptions({}); - const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); + addBreadcrumb({ message: 'hello1' }); + addBreadcrumb({ message: 'hello2' }); + addBreadcrumb({ message: 'hello3' }); - scope.addBreadcrumb({ message: 'hello' }); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'world' }); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; - expect((scope as any)._breadcrumbs).toHaveLength(2); + expect(isolationScopeBreadcrumbs).toEqual([{ message: 'hello3', timestamp: expect.any(Number) }]); }); test('calls `beforeBreadcrumb` and adds the breadcrumb without any changes', () => { - expect.assertions(1); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); + setCurrentClient(client); + client.init(); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'hello' }); + addBreadcrumb({ message: 'hello' }); - expect((scope as any)._breadcrumbs[0].message).toEqual('hello'); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; + expect(isolationScopeBreadcrumbs).toEqual([{ message: 'hello', timestamp: expect.any(Number) }]); }); test('calls `beforeBreadcrumb` and uses the new one', () => { - expect.assertions(1); - const beforeBreadcrumb = jest.fn(() => ({ message: 'changed' })); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); + setCurrentClient(client); + client.init(); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'hello' }); + addBreadcrumb({ message: 'hello' }); - expect((scope as any)._breadcrumbs[0].message).toEqual('changed'); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; + expect(isolationScopeBreadcrumbs).toEqual([{ message: 'changed', timestamp: expect.any(Number) }]); }); test('calls `beforeBreadcrumb` and discards the breadcrumb when returned `null`', () => { - expect.assertions(1); - const beforeBreadcrumb = jest.fn(() => null); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); + setCurrentClient(client); + client.init(); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'hello' }); + addBreadcrumb({ message: 'hello' }); - expect((scope as any)._breadcrumbs.length).toEqual(0); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; + expect(isolationScopeBreadcrumbs).toEqual([]); }); test('`beforeBreadcrumb` gets an access to a hint as a second argument', () => { - expect.assertions(2); - const beforeBreadcrumb = jest.fn((breadcrumb, hint) => ({ ...breadcrumb, data: hint.data })); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); + setCurrentClient(client); + client.init(); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'hello' }, { data: 'someRandomThing' }); + addBreadcrumb({ message: 'hello' }, { data: 'someRandomThing' }); - expect((scope as any)._breadcrumbs[0].message).toEqual('hello'); - expect((scope as any)._breadcrumbs[0].data).toEqual('someRandomThing'); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; + expect(isolationScopeBreadcrumbs).toEqual([ + { message: 'hello', data: 'someRandomThing', timestamp: expect.any(Number) }, + ]); }); }); @@ -627,13 +612,12 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, maxBreadcrumbs: 1 }); const client = new TestClient(options); + setCurrentClient(client); + client.init(); const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: '1' }); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: '2' }); + + addBreadcrumb({ message: '1' }); + addBreadcrumb({ message: '2' }); client.captureEvent({ message: 'message' }, undefined, scope); diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 1f2870f287f9..016fda4df497 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -30,7 +30,6 @@ export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan } from '@s export { getClient, isInitialized, - addBreadcrumb, captureException, captureEvent, captureMessage, @@ -55,6 +54,7 @@ export { getCurrentHub, makeMain } from './sdk/hub'; export { Scope } from './sdk/scope'; export { + addBreadcrumb, makeNodeTransport, defaultStackParser, getSentryRelease, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index c4b0c397ea43..658e78028137 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -3,8 +3,6 @@ import type { Span } from '@opentelemetry/api'; import { context, trace } from '@opentelemetry/api'; import type { - Breadcrumb, - BreadcrumbHint, CaptureContext, Event, EventHint, @@ -15,7 +13,6 @@ import type { SeverityLevel, User, } from '@sentry/types'; -import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils'; import { getContextFromScope, getScopesFromContext, setScopesOnContext } from '../utils/contextData'; import type { ExclusiveEventHintOrCaptureContext } from '../utils/prepareEvent'; @@ -116,29 +113,6 @@ export function captureEvent(event: Event, hint?: EventHint): string { return getCurrentScope().captureEvent(event, hint); } -/** - * Add a breadcrumb to the current isolation scope. - */ -export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void { - const client = getClient(); - - const { beforeBreadcrumb, maxBreadcrumbs } = client.getOptions(); - - if (maxBreadcrumbs && maxBreadcrumbs <= 0) return; - - const timestamp = dateTimestampInSeconds(); - const mergedBreadcrumb = { timestamp, ...breadcrumb }; - const finalBreadcrumb = beforeBreadcrumb - ? (consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) as Breadcrumb | null) - : mergedBreadcrumb; - - if (finalBreadcrumb === null) return; - - client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); - - getIsolationScope().addBreadcrumb(finalBreadcrumb, maxBreadcrumbs); -} - /** * Add a global event processor. */ diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index f3cd1f2a7d13..dc9a6fa0037b 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -9,9 +9,8 @@ import type { TransactionContext, } from '@sentry/types'; -import { endSession, startSession } from '@sentry/core'; +import { addBreadcrumb, endSession, startSession } from '@sentry/core'; import { - addBreadcrumb, captureEvent, getClient, getCurrentScope, diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index dd399243726f..a92403c3e664 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -1,6 +1,6 @@ import { getGlobalScope as _getGlobalScope, setGlobalScope } from '@sentry/core'; import { OpenTelemetryScope } from '@sentry/opentelemetry'; -import type { Breadcrumb, Client, Event, EventHint, SeverityLevel } from '@sentry/types'; +import type { Client, Event, EventHint, SeverityLevel } from '@sentry/types'; import { uuid4 } from '@sentry/utils'; import { getGlobalCarrier } from './globals'; @@ -161,13 +161,6 @@ export class Scope extends OpenTelemetryScope implements ScopeInterface { return eventId; } - /** - * @inheritDoc - */ - public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this { - return this._addBreadcrumb(breadcrumb, maxBreadcrumbs); - } - /** Get scope data for this scope only. */ public getOwnScopeData(): ScopeData { return super.getScopeData(); diff --git a/packages/node-experimental/test/integration/breadcrumbs.test.ts b/packages/node-experimental/test/integration/breadcrumbs.test.ts index e80c2e1a90e2..9c09b12efcc1 100644 --- a/packages/node-experimental/test/integration/breadcrumbs.test.ts +++ b/packages/node-experimental/test/integration/breadcrumbs.test.ts @@ -1,6 +1,6 @@ -import { captureException, withScope } from '@sentry/core'; +import { addBreadcrumb, captureException, withScope } from '@sentry/core'; import { startSpan } from '@sentry/opentelemetry'; -import { addBreadcrumb, getClient, withIsolationScope } from '../../src/sdk/api'; +import { getClient, withIsolationScope } from '../../src/sdk/api'; import type { NodeExperimentalClient } from '../../src/types'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index 163bed572f24..9eb9773e6f53 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -41,11 +41,7 @@ describe('Integration | Scope', () => { scope2.setTag('tag3', 'val3'); Sentry.startSpan({ name: 'outer' }, span => { - // TODO: This is "incorrect" until we stop cloning the current scope for setSpanScopes - // Once we change this, the scopes _should_ be the same again - if (enableTracing) { - expect(getSpanScopes(span)?.scope).not.toBe(scope2); - } + expect(getSpanScopes(span)?.scope).toBe(enableTracing ? scope2 : undefined); spanId = span.spanContext().spanId; traceId = span.spanContext().traceId; diff --git a/packages/opentelemetry/src/custom/scope.ts b/packages/opentelemetry/src/custom/scope.ts index 2455d616ff39..a4ceda669499 100644 --- a/packages/opentelemetry/src/custom/scope.ts +++ b/packages/opentelemetry/src/custom/scope.ts @@ -2,23 +2,17 @@ import type { Span } from '@opentelemetry/api'; import type { TimedEvent } from '@opentelemetry/sdk-trace-base'; import { Scope } from '@sentry/core'; import type { Breadcrumb, ScopeData, SeverityLevel, Span as SentrySpan } from '@sentry/types'; -import { dateTimestampInSeconds, dropUndefinedKeys, logger, normalize } from '@sentry/utils'; +import { dropUndefinedKeys, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { InternalSentrySemanticAttributes } from '../semanticAttributes'; import { convertOtelTimeToSeconds } from '../utils/convertOtelTimeToSeconds'; -import { getActiveSpan, getRootSpan } from '../utils/getActiveSpan'; +import { getActiveSpan } from '../utils/getActiveSpan'; import { getSpanParent } from '../utils/spanData'; import { spanHasEvents } from '../utils/spanTypes'; /** A fork of the classic scope with some otel specific stuff. */ export class OpenTelemetryScope extends Scope { - /** - * This can be set to ensure the scope uses _this_ span as the active one, - * instead of using getActiveSpan(). - */ - public activeSpan: Span | undefined; - /** * @inheritDoc */ @@ -71,26 +65,6 @@ export class OpenTelemetryScope extends Scope { return this; } - /** - * @inheritDoc - */ - public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this { - const activeSpan = this.activeSpan || getActiveSpan(); - const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; - - if (rootSpan) { - const mergedBreadcrumb = { - timestamp: dateTimestampInSeconds(), - ...breadcrumb, - }; - - rootSpan.addEvent(...breadcrumbToOtelEvent(mergedBreadcrumb)); - return this; - } - - return this._addBreadcrumb(breadcrumb, maxBreadcrumbs); - } - /** @inheritDoc */ public getScopeData(): ScopeData { const data = super.getScopeData(); @@ -100,17 +74,11 @@ export class OpenTelemetryScope extends Scope { return data; } - /** Add a breadcrumb to this scope. */ - protected _addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this { - return super.addBreadcrumb(breadcrumb, maxBreadcrumbs); - } - /** * @inheritDoc */ protected _getBreadcrumbs(): Breadcrumb[] { - const span = this.activeSpan || getActiveSpan(); - + const span = getActiveSpan(); const spanBreadcrumbs = span ? getBreadcrumbsForSpan(span) : []; return spanBreadcrumbs.length > 0 ? this._breadcrumbs.concat(spanBreadcrumbs) : this._breadcrumbs; @@ -126,39 +94,6 @@ function getBreadcrumbsForSpan(span: Span): Breadcrumb[] { return events.map(otelEventToBreadcrumb); } -function breadcrumbToOtelEvent(breadcrumb: Breadcrumb): Parameters { - const name = breadcrumb.message || ''; - - const dataAttrs = serializeBreadcrumbData(breadcrumb.data); - - return [ - name, - dropUndefinedKeys({ - [InternalSentrySemanticAttributes.BREADCRUMB_TYPE]: breadcrumb.type, - [InternalSentrySemanticAttributes.BREADCRUMB_LEVEL]: breadcrumb.level, - [InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID]: breadcrumb.event_id, - [InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY]: breadcrumb.category, - ...dataAttrs, - }), - breadcrumb.timestamp ? new Date(breadcrumb.timestamp * 1000) : undefined, - ]; -} - -function serializeBreadcrumbData(data: Breadcrumb['data']): undefined | Record { - if (!data || Object.keys(data).length === 0) { - return undefined; - } - - try { - const normalizedData = normalize(data); - return { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify(normalizedData), - }; - } catch (e) { - return undefined; - } -} - function otelEventToBreadcrumb(event: TimedEvent): Breadcrumb { const attributes = event.attributes || {}; diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index 83aac51f9391..6a68d1fbce1c 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -37,15 +37,7 @@ function onSpanStart(span: Span, parentContext: Context, _ScopeClass: typeof Ope // eslint-disable-next-line deprecation/deprecation const isolationScope = actualHub.getIsolationScope(); setSpanHub(span, actualHub); - - // Use this scope for finishing the span - // TODO: For now we need to clone this, as we need to store the `activeSpan` on it - // Once we can get rid of this (when we move breadcrumbs to the isolation scope), - // we can stop cloning this here - const finishScope = (scope as OpenTelemetryScope).clone(); - // this is needed for breadcrumbs, for now, as they are stored on the span currently - finishScope.activeSpan = span; - setSpanScopes(span, { scope: finishScope, isolationScope }); + setSpanScopes(span, { scope, isolationScope }); } } diff --git a/packages/opentelemetry/test/custom/scope.test.ts b/packages/opentelemetry/test/custom/scope.test.ts index 1662f571331c..4f362435eda9 100644 --- a/packages/opentelemetry/test/custom/scope.test.ts +++ b/packages/opentelemetry/test/custom/scope.test.ts @@ -94,182 +94,8 @@ describe('NodeExperimentalScope', () => { expect(scope['_span']).toBeUndefined(); }); - describe('addBreadcrumb', () => { - it('adds to scope if no root span is found', () => { - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { message: 'test' }; - - const now = Date.now(); - jest.useFakeTimers(); - jest.setSystemTime(now); - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([{ message: 'test', timestamp: now / 1000 }]); - }); - - it('adds to scope if no root span is found & uses given timestamp', () => { - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { message: 'test', timestamp: 1234 }; - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([breadcrumb]); - }); - - it('adds to root span if found', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { message: 'test' }; - - const now = Date.now(); - jest.useFakeTimers(); - jest.setSystemTime(now); - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: 'test', - time: [Math.floor(now / 1000), (now % 1000) * 1_000_000], - attributes: {}, - }), - ]); - }); - - it('adds to root span if found & uses given timestamp', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { timestamp: 12345, message: 'test' }; - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: 'test', - time: [12345, 0], - attributes: {}, - }), - ]); - }); - - it('adds many breadcrumbs to root span if found', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb1: Breadcrumb = { timestamp: 12345, message: 'test1' }; - const breadcrumb2: Breadcrumb = { timestamp: 5678, message: 'test2' }; - const breadcrumb3: Breadcrumb = { timestamp: 9101112, message: 'test3' }; - - scope.addBreadcrumb(breadcrumb1); - scope.addBreadcrumb(breadcrumb2); - scope.addBreadcrumb(breadcrumb3); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: 'test1', - time: [12345, 0], - attributes: {}, - }), - expect.objectContaining({ - name: 'test2', - time: [5678, 0], - attributes: {}, - }), - expect.objectContaining({ - name: 'test3', - time: [9101112, 0], - attributes: {}, - }), - ]); - }); - - it('adds to root span if found & no message is given', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { timestamp: 12345 }; - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: '', - time: [12345, 0], - attributes: {}, - }), - ]); - }); - - it('adds to root span with full attributes', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { - timestamp: 12345, - message: 'test', - data: { nested: { indeed: true } }, - level: 'info', - category: 'test-category', - type: 'test-type', - event_id: 'test-event-id', - }; - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: 'test', - time: [12345, 0], - attributes: { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }), - [InternalSentrySemanticAttributes.BREADCRUMB_TYPE]: 'test-type', - [InternalSentrySemanticAttributes.BREADCRUMB_LEVEL]: 'info', - [InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID]: 'test-event-id', - [InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY]: 'test-category', - }, - }), - ]); - }); - - it('adds to root span with empty data', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - const breadcrumb: Breadcrumb = { timestamp: 12345, message: 'test', data: {} }; - - scope.addBreadcrumb(breadcrumb); - - expect(scope['_breadcrumbs']).toEqual([]); - expect(span.events).toEqual([ - expect.objectContaining({ - name: 'test', - time: [12345, 0], - attributes: {}, - }), - ]); - }); - }); - describe('_getBreadcrumbs', () => { - it('gets from scope if no root span is found', () => { + it('gets from scope if no span is found', () => { jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined); const scope = new OpenTelemetryScope(); @@ -283,7 +109,7 @@ describe('NodeExperimentalScope', () => { expect(scope['_getBreadcrumbs']()).toEqual(breadcrumbs); }); - it('gets from root span if found', () => { + it('gets events from active span if found', () => { const span = createSpan(); jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); @@ -328,7 +154,7 @@ describe('NodeExperimentalScope', () => { ]); }); - it('gets from spans up the parent chain if found', () => { + it('gets events from spans up the parent chain if found', () => { const span = createSpan(); const parentSpan = createSpan(); const rootSpan = createSpan(); @@ -392,45 +218,5 @@ describe('NodeExperimentalScope', () => { { message: 'breadcrumb event', timestamp: now / 1000 + 1 }, ]); }); - - it('gets from activeSpan if defined', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - - const now = Date.now(); - - span.addEvent('basic event', now); - span.addEvent('breadcrumb event', {}, now + 1000); - span.addEvent( - 'breadcrumb event 2', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }), - [InternalSentrySemanticAttributes.BREADCRUMB_TYPE]: 'test-type', - [InternalSentrySemanticAttributes.BREADCRUMB_LEVEL]: 'info', - [InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID]: 'test-event-id', - [InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY]: 'test-category', - }, - now + 3000, - ); - span.addEvent( - 'breadcrumb event invalid JSON data', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: 'this is not JSON...', - }, - now + 2000, - ); - - const activeSpan = createSpan(); - activeSpan.addEvent('event 1', now); - activeSpan.addEvent('event 2', {}, now + 1000); - scope.activeSpan = activeSpan; - - expect(scope['_getBreadcrumbs']()).toEqual([ - { message: 'event 1', timestamp: now / 1000 }, - { message: 'event 2', timestamp: now / 1000 + 1 }, - ]); - }); }); }); diff --git a/packages/opentelemetry/test/integration/breadcrumbs.test.ts b/packages/opentelemetry/test/integration/breadcrumbs.test.ts index 65baab5b95a9..6a745855251b 100644 --- a/packages/opentelemetry/test/integration/breadcrumbs.test.ts +++ b/packages/opentelemetry/test/integration/breadcrumbs.test.ts @@ -1,4 +1,4 @@ -import { addBreadcrumb, captureException, getClient, getCurrentHub, withScope } from '@sentry/core'; +import { addBreadcrumb, captureException, getClient, getCurrentHub, withIsolationScope, withScope } from '@sentry/core'; import { OpenTelemetryHub } from '../../src/custom/hub'; import { startSpan } from '../../src/trace'; @@ -54,7 +54,7 @@ describe('Integration | breadcrumbs', () => { ); }); - it('handles parallel scopes', async () => { + it('handles parallel isolation scopes', async () => { const beforeSend = jest.fn(() => null); const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); @@ -70,17 +70,17 @@ describe('Integration | breadcrumbs', () => { addBreadcrumb({ timestamp: 123456, message: 'test0' }); - withScope(() => { + withIsolationScope(() => { addBreadcrumb({ timestamp: 123456, message: 'test1' }); }); - withScope(() => { + withIsolationScope(() => { addBreadcrumb({ timestamp: 123456, message: 'test2' }); // eslint-disable-next-line deprecation/deprecation captureException(error); }); - withScope(() => { + withIsolationScope(() => { addBreadcrumb({ timestamp: 123456, message: 'test3' }); }); @@ -150,7 +150,7 @@ describe('Integration | breadcrumbs', () => { ); }); - it('correctly adds & retrieves breadcrumbs for the current root span only', async () => { + it('correctly adds & retrieves breadcrumbs for the current isolation scope only', async () => { const beforeSend = jest.fn(() => null); const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); @@ -160,22 +160,26 @@ describe('Integration | breadcrumbs', () => { const error = new Error('test'); - startSpan({ name: 'test1' }, () => { - addBreadcrumb({ timestamp: 123456, message: 'test1-a' }); + withIsolationScope(() => { + startSpan({ name: 'test1' }, () => { + addBreadcrumb({ timestamp: 123456, message: 'test1-a' }); - startSpan({ name: 'inner1' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test1-b' }); + startSpan({ name: 'inner1' }, () => { + addBreadcrumb({ timestamp: 123457, message: 'test1-b' }); + }); }); }); - startSpan({ name: 'test2' }, () => { - addBreadcrumb({ timestamp: 123456, message: 'test2-a' }); + withIsolationScope(() => { + startSpan({ name: 'test2' }, () => { + addBreadcrumb({ timestamp: 123456, message: 'test2-a' }); - startSpan({ name: 'inner2' }, () => { - addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); - }); + startSpan({ name: 'inner2' }, () => { + addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); + }); - captureException(error); + captureException(error); + }); }); await client.flush(); @@ -297,7 +301,7 @@ describe('Integration | breadcrumbs', () => { ); }); - it('correctly adds & retrieves breadcrumbs in async spans', async () => { + it('correctly adds & retrieves breadcrumbs in async isolation scopes', async () => { const beforeSend = jest.fn(() => null); const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); @@ -307,31 +311,35 @@ describe('Integration | breadcrumbs', () => { const error = new Error('test'); - const promise1 = startSpan({ name: 'test' }, async () => { - addBreadcrumb({ timestamp: 123456, message: 'test1' }); + const promise1 = withIsolationScope(() => { + return startSpan({ name: 'test' }, async () => { + addBreadcrumb({ timestamp: 123456, message: 'test1' }); - await startSpan({ name: 'inner1' }, async () => { - addBreadcrumb({ timestamp: 123457, message: 'test2' }); - }); + await startSpan({ name: 'inner1' }, async () => { + addBreadcrumb({ timestamp: 123457, message: 'test2' }); + }); - await startSpan({ name: 'inner2' }, async () => { - addBreadcrumb({ timestamp: 123455, message: 'test3' }); - }); + await startSpan({ name: 'inner2' }, async () => { + addBreadcrumb({ timestamp: 123455, message: 'test3' }); + }); - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, 10)); - captureException(error); + captureException(error); + }); }); - const promise2 = startSpan({ name: 'test-b' }, async () => { - addBreadcrumb({ timestamp: 123456, message: 'test1-b' }); + const promise2 = withIsolationScope(() => { + return startSpan({ name: 'test-b' }, async () => { + addBreadcrumb({ timestamp: 123456, message: 'test1-b' }); - await startSpan({ name: 'inner1' }, async () => { - addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); - }); + await startSpan({ name: 'inner1' }, async () => { + addBreadcrumb({ timestamp: 123457, message: 'test2-b' }); + }); - await startSpan({ name: 'inner2' }, async () => { - addBreadcrumb({ timestamp: 123455, message: 'test3-b' }); + await startSpan({ name: 'inner2' }, async () => { + addBreadcrumb({ timestamp: 123455, message: 'test3-b' }); + }); }); }); diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index 0b9bac627491..6b61fd42c34c 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -57,11 +57,7 @@ describe('Integration | Scope', () => { scope2.setTag('tag3', 'val3'); startSpan({ name: 'outer' }, span => { - // TODO: This is "incorrect" until we stop cloning the current scope for setSpanScopes - // Once we change this, the scopes _should_ be the same again - if (enableTracing) { - expect(getSpanScopes(span)?.scope).not.toBe(scope2); - } + expect(getSpanScopes(span)?.scope).toBe(enableTracing ? scope2 : undefined); spanId = span.spanContext().spanId; traceId = span.spanContext().traceId; From cb88a7c8a7a5261f74cc108b288dbf71e86e1003 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 13:52:34 +0100 Subject: [PATCH 037/173] ref(core): Make remaining client methods required (#10605) This makes the following required on the client: * `captureSession` * `getSdkMetadata` * `addEventProcessor` * `getEventProcessor` * `getIntegrationByName` * `addIntegration` * `init` * `captureAggregrateMetrics` --------- Co-authored-by: Lukas Stracke --- packages/core/src/exports.ts | 2 +- packages/core/src/integration.ts | 4 ++-- packages/core/src/metrics/aggregator.ts | 2 +- .../core/src/metrics/browser-aggregator.ts | 10 ++++---- packages/core/src/sdk.ts | 17 +------------- packages/core/src/utils/prepareEvent.ts | 2 +- packages/node-experimental/src/sdk/hub.ts | 2 +- packages/node-experimental/src/sdk/init.ts | 23 +++++++++---------- packages/node/src/sdk.ts | 2 +- .../src/setupEventContextTrace.ts | 4 ---- packages/replay/src/integration.ts | 2 +- .../replay/src/util/addGlobalListeners.ts | 6 +---- packages/replay/src/util/getReplay.ts | 4 +--- .../replay/src/util/prepareReplayEvent.ts | 2 +- packages/types/src/client.ts | 22 +++++++----------- 15 files changed, 36 insertions(+), 68 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index c5ab7b1436c8..56d64e2a64e9 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -451,7 +451,7 @@ function _sendSessionUpdate(): void { // TODO (v8): Remove currentScope and only use the isolation scope(?). // For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession() const session = currentScope.getSession() || isolationScope.getSession(); - if (session && client && client.captureSession) { + if (session && client) { client.captureSession(session); } } diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index c95dce4b2f79..80400455363e 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -145,7 +145,7 @@ export function setupIntegration(client: Client, integration: Integration, integ client.on('preprocessEvent', (event, hint) => callback(event, hint, client)); } - if (client.addEventProcessor && typeof integration.processEvent === 'function') { + if (typeof integration.processEvent === 'function') { const callback = integration.processEvent.bind(integration) as typeof integration.processEvent; const processor = Object.assign((event: Event, hint: EventHint) => callback(event, hint, client), { @@ -162,7 +162,7 @@ export function setupIntegration(client: Client, integration: Integration, integ export function addIntegration(integration: Integration): void { const client = getClient(); - if (!client || !client.addIntegration) { + if (!client) { DEBUG_BUILD && logger.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`); return; } diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 6a49fda5918b..945a2f59d6d0 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -153,7 +153,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { * @param flushedBuckets */ private _captureMetrics(flushedBuckets: MetricBucket): void { - if (flushedBuckets.size > 0 && this._client.captureAggregateMetrics) { + if (flushedBuckets.size > 0) { // TODO(@anonrig): Optimization opportunity. // This copy operation can be avoided if we store the key in the bucketItem. const buckets = Array.from(flushedBuckets).map(([, bucketItem]) => bucketItem); diff --git a/packages/core/src/metrics/browser-aggregator.ts b/packages/core/src/metrics/browser-aggregator.ts index 5b5c81353024..2493cdb03ede 100644 --- a/packages/core/src/metrics/browser-aggregator.ts +++ b/packages/core/src/metrics/browser-aggregator.ts @@ -74,11 +74,11 @@ export class BrowserMetricsAggregator implements MetricsAggregator { if (this._buckets.size === 0) { return; } - if (this._client.captureAggregateMetrics) { - // TODO(@anonrig): Use Object.values() when we support ES6+ - const metricBuckets = Array.from(this._buckets).map(([, bucketItem]) => bucketItem); - this._client.captureAggregateMetrics(metricBuckets); - } + + // TODO(@anonrig): Use Object.values() when we support ES6+ + const metricBuckets = Array.from(this._buckets).map(([, bucketItem]) => bucketItem); + this._client.captureAggregateMetrics(metricBuckets); + this._buckets.clear(); } diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index f15f6c976e56..f5cb480a1eaf 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -35,7 +35,7 @@ export function initAndBind( const client = new clientClass(options); setCurrentClient(client); - initializeClient(client); + client.init(); } /** @@ -49,18 +49,3 @@ export function setCurrentClient(client: Client): void { top.client = client; top.scope.setClient(client); } - -/** - * Initialize the client for the current scope. - * Make sure to call this after `setCurrentClient()`. - */ -function initializeClient(client: Client): void { - if (client.init) { - client.init(); - // TODO v8: Remove this fallback - // eslint-disable-next-line deprecation/deprecation - } else if (client.setupIntegrations) { - // eslint-disable-next-line deprecation/deprecation - client.setupIntegrations(); - } -} diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 17be8c5354de..5f5aef7003d2 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -75,7 +75,7 @@ export function prepareEvent( addExceptionMechanism(prepared, hint.mechanism); } - const clientEventProcessors = client && client.getEventProcessors ? client.getEventProcessors() : []; + const clientEventProcessors = client ? client.getEventProcessors() : []; // This should be the last thing called, since we want that // {@link Hub.addEventProcessor} gets the finished prepared event. diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index dc9a6fa0037b..fe9124dbe40f 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -144,7 +144,7 @@ function _sendSessionUpdate(): void { const client = getClient(); const session = scope.getSession(); - if (session && client.captureSession) { + if (session) { client.captureSession(session); } } diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index d617845b9e2e..d15c28dc3f98 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -86,19 +86,18 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { if (options.spotlight) { const client = getClient(); - if (client.addIntegration) { - // force integrations to be setup even if no DSN was set - // If they have already been added before, they will be ignored anyhow - const integrations = client.getOptions().integrations; - for (const integration of integrations) { - client.addIntegration(integration); - } - client.addIntegration( - spotlightIntegration({ - sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined, - }), - ); + + // force integrations to be setup even if no DSN was set + // If they have already been added before, they will be ignored anyhow + const integrations = client.getOptions().integrations; + for (const integration of integrations) { + client.addIntegration(integration); } + client.addIntegration( + spotlightIntegration({ + sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined, + }), + ); } // Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 14aff2fef1fe..6aa6b0499ca2 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -176,7 +176,7 @@ export function init(options: NodeOptions = {}): void { if (options.spotlight) { const client = getClient(); - if (client && client.addIntegration) { + if (client) { // force integrations to be setup even if no DSN was set // If they have already been added before, they will be ignored anyhow const integrations = client.getOptions().integrations; diff --git a/packages/opentelemetry/src/setupEventContextTrace.ts b/packages/opentelemetry/src/setupEventContextTrace.ts index c55fc27ed52f..fb41ce463af5 100644 --- a/packages/opentelemetry/src/setupEventContextTrace.ts +++ b/packages/opentelemetry/src/setupEventContextTrace.ts @@ -5,10 +5,6 @@ import { spanHasParentId } from './utils/spanTypes'; /** Ensure the `trace` context is set on all events. */ export function setupEventContextTrace(client: Client): void { - if (!client.addEventProcessor) { - return; - } - client.addEventProcessor(event => { const span = getActiveSpan(); if (!span) { diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts index 3434888f6574..c52cccd51dd2 100644 --- a/packages/replay/src/integration.ts +++ b/packages/replay/src/integration.ts @@ -367,7 +367,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`, /* eslint-disable @typescript-eslint/no-non-null-assertion */ try { const client = getClient()!; - const canvasIntegration = client.getIntegrationByName!('ReplayCanvas') as Integration & { + const canvasIntegration = client.getIntegrationByName('ReplayCanvas') as Integration & { getOptions(): ReplayCanvasIntegrationOptions; }; if (!canvasIntegration) { diff --git a/packages/replay/src/util/addGlobalListeners.ts b/packages/replay/src/util/addGlobalListeners.ts index 797a6c3cfa96..9b57e7dafec8 100644 --- a/packages/replay/src/util/addGlobalListeners.ts +++ b/packages/replay/src/util/addGlobalListeners.ts @@ -26,11 +26,7 @@ export function addGlobalListeners(replay: ReplayContainer): void { // Tag all (non replay) events that get sent to Sentry with the current // replay ID so that we can reference them later in the UI const eventProcessor = handleGlobalEventListener(replay); - if (client && client.addEventProcessor) { - client.addEventProcessor(eventProcessor); - } else { - addEventProcessor(eventProcessor); - } + addEventProcessor(eventProcessor); // If a custom client has no hooks yet, we continue to use the "old" implementation if (client) { diff --git a/packages/replay/src/util/getReplay.ts b/packages/replay/src/util/getReplay.ts index 278505f15338..75d794da8e69 100644 --- a/packages/replay/src/util/getReplay.ts +++ b/packages/replay/src/util/getReplay.ts @@ -7,7 +7,5 @@ import type { replayIntegration } from '../integration'; // eslint-disable-next-line deprecation/deprecation export function getReplay(): ReturnType | undefined { const client = getClient(); - return ( - client && client.getIntegrationByName && client.getIntegrationByName>('Replay') - ); + return client && client.getIntegrationByName>('Replay'); } diff --git a/packages/replay/src/util/prepareReplayEvent.ts b/packages/replay/src/util/prepareReplayEvent.ts index e564f0509326..dfa5e8bbd620 100644 --- a/packages/replay/src/util/prepareReplayEvent.ts +++ b/packages/replay/src/util/prepareReplayEvent.ts @@ -46,7 +46,7 @@ export async function prepareReplayEvent({ preparedEvent.platform = preparedEvent.platform || 'javascript'; // extract the SDK name because `client._prepareEvent` doesn't add it to the event - const metadata = client.getSdkMetadata && client.getSdkMetadata(); + const metadata = client.getSdkMetadata(); const { name, version } = (metadata && metadata.sdk) || {}; preparedEvent.sdk = { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index ea50bd5a67f3..5a0ddcb38cb8 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -65,7 +65,7 @@ export interface Client { * * @param session Session to be delivered */ - captureSession?(session: Session): void; + captureSession(session: Session): void; /** * Create a cron monitor check in and send it to Sentry. This method is not available on all clients. @@ -87,9 +87,8 @@ export interface Client { /** * @inheritdoc * - * TODO (v8): Make this a required method. */ - getSdkMetadata?(): SdkMetadata | undefined; + getSdkMetadata(): SdkMetadata | undefined; /** * Returns the transport that is used by the client. @@ -121,17 +120,13 @@ export interface Client { /** * Adds an event processor that applies to any event processed by this client. - * - * TODO (v8): Make this a required method. */ - addEventProcessor?(eventProcessor: EventProcessor): void; + addEventProcessor(eventProcessor: EventProcessor): void; /** * Get all added event processors for this client. - * - * TODO (v8): Make this a required method. */ - getEventProcessors?(): EventProcessor[]; + getEventProcessors(): EventProcessor[]; /** * Returns the client's instance of the given integration class, it any. @@ -140,7 +135,7 @@ export interface Client { getIntegration(integration: IntegrationClass): T | null; /** Get the instance of the integration with the given name on the client, if it was added. */ - getIntegrationByName?(name: string): T | undefined; + getIntegrationByName(name: string): T | undefined; /** * Add an integration to the client. @@ -148,9 +143,8 @@ export interface Client { * In most cases, this should not be necessary, and you're better off just passing the integrations via `integrations: []` at initialization time. * However, if you find the need to conditionally load & add an integration, you can use `addIntegration` to do so. * - * TODO (v8): Make this a required method. * */ - addIntegration?(integration: Integration): void; + addIntegration(integration: Integration): void; /** * This is an internal function to setup all integrations that should run on the client. @@ -162,7 +156,7 @@ export interface Client { * Initialize this client. * Call this after the client was set on a scope. */ - init?(): void; + init(): void; /** Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -191,7 +185,7 @@ export interface Client { * * @experimental This API is experimental and might experience breaking changes */ - captureAggregateMetrics?(metricBucketItems: Array): void; + captureAggregateMetrics(metricBucketItems: Array): void; // HOOKS // TODO(v8): Make the hooks non-optional. From ab410c6ab535db6c2319c1b880ac87d26bc4cc74 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 12 Feb 2024 14:08:00 +0100 Subject: [PATCH 038/173] ci: Warn on unused disabled directives (#10610) --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index e20424aef7f3..150c7e8efd2f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,7 @@ module.exports = { 'test/manual/**', 'types/**', ], + reportUnusedDisableDirectives: true, overrides: [ { files: ['*.ts', '*.tsx', '*.d.ts'], From 11d854eeb12b5ac4209de4c0500c77e11b1b677e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 12 Feb 2024 14:36:18 +0100 Subject: [PATCH 039/173] feat(opentelemetry): Do not capture span events as breadcrumbs (#10612) This allows us to simplify things, for now - we may add this back later in a more generic way, when/if we add the events to our shared span interface. --- packages/opentelemetry/src/custom/scope.ts | 81 +---------- .../opentelemetry/test/custom/scope.test.ts | 131 ------------------ .../test/integration/transactions.test.ts | 2 +- 3 files changed, 3 insertions(+), 211 deletions(-) diff --git a/packages/opentelemetry/src/custom/scope.ts b/packages/opentelemetry/src/custom/scope.ts index a4ceda669499..f686ecbc5c34 100644 --- a/packages/opentelemetry/src/custom/scope.ts +++ b/packages/opentelemetry/src/custom/scope.ts @@ -1,15 +1,8 @@ -import type { Span } from '@opentelemetry/api'; -import type { TimedEvent } from '@opentelemetry/sdk-trace-base'; import { Scope } from '@sentry/core'; -import type { Breadcrumb, ScopeData, SeverityLevel, Span as SentrySpan } from '@sentry/types'; -import { dropUndefinedKeys, logger } from '@sentry/utils'; +import type { Span as SentrySpan } from '@sentry/types'; +import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import { InternalSentrySemanticAttributes } from '../semanticAttributes'; -import { convertOtelTimeToSeconds } from '../utils/convertOtelTimeToSeconds'; -import { getActiveSpan } from '../utils/getActiveSpan'; -import { getSpanParent } from '../utils/spanData'; -import { spanHasEvents } from '../utils/spanTypes'; /** A fork of the classic scope with some otel specific stuff. */ export class OpenTelemetryScope extends Scope { @@ -64,74 +57,4 @@ export class OpenTelemetryScope extends Scope { return this; } - - /** @inheritDoc */ - public getScopeData(): ScopeData { - const data = super.getScopeData(); - - data.breadcrumbs = this._getBreadcrumbs(); - - return data; - } - - /** - * @inheritDoc - */ - protected _getBreadcrumbs(): Breadcrumb[] { - const span = getActiveSpan(); - const spanBreadcrumbs = span ? getBreadcrumbsForSpan(span) : []; - - return spanBreadcrumbs.length > 0 ? this._breadcrumbs.concat(spanBreadcrumbs) : this._breadcrumbs; - } -} - -/** - * Get all breadcrumbs for the given span as well as it's parents. - */ -function getBreadcrumbsForSpan(span: Span): Breadcrumb[] { - const events = span ? getOtelEvents(span) : []; - - return events.map(otelEventToBreadcrumb); -} - -function otelEventToBreadcrumb(event: TimedEvent): Breadcrumb { - const attributes = event.attributes || {}; - - const type = attributes[InternalSentrySemanticAttributes.BREADCRUMB_TYPE] as string | undefined; - const level = attributes[InternalSentrySemanticAttributes.BREADCRUMB_LEVEL] as SeverityLevel | undefined; - const eventId = attributes[InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID] as string | undefined; - const category = attributes[InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY] as string | undefined; - const dataStr = attributes[InternalSentrySemanticAttributes.BREADCRUMB_DATA] as string | undefined; - - const breadcrumb: Breadcrumb = dropUndefinedKeys({ - timestamp: convertOtelTimeToSeconds(event.time), - message: event.name, - type, - level, - event_id: eventId, - category, - }); - - if (typeof dataStr === 'string') { - try { - const data = JSON.parse(dataStr); - breadcrumb.data = data; - } catch (e) {} // eslint-disable-line no-empty - } - - return breadcrumb; -} - -function getOtelEvents(span: Span, events: TimedEvent[] = []): TimedEvent[] { - if (spanHasEvents(span)) { - events.push(...span.events); - } - - // Go up parent chain and collect events - const parent = getSpanParent(span); - if (parent) { - return getOtelEvents(parent, events); - } - - return events; } diff --git a/packages/opentelemetry/test/custom/scope.test.ts b/packages/opentelemetry/test/custom/scope.test.ts index 4f362435eda9..978e68e2df85 100644 --- a/packages/opentelemetry/test/custom/scope.test.ts +++ b/packages/opentelemetry/test/custom/scope.test.ts @@ -1,11 +1,6 @@ import { makeSession } from '@sentry/core'; -import type { Breadcrumb } from '@sentry/types'; import { OpenTelemetryScope } from '../../src/custom/scope'; -import { InternalSentrySemanticAttributes } from '../../src/semanticAttributes'; -import { setSpanParent } from '../../src/utils/spanData'; -import { createSpan } from '../helpers/createSpan'; -import * as GetActiveSpan from './../../src/utils/getActiveSpan'; describe('NodeExperimentalScope', () => { afterEach(() => { @@ -93,130 +88,4 @@ describe('NodeExperimentalScope', () => { expect(scope['_span']).toBeUndefined(); }); - - describe('_getBreadcrumbs', () => { - it('gets from scope if no span is found', () => { - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined); - - const scope = new OpenTelemetryScope(); - const breadcrumbs: Breadcrumb[] = [ - { message: 'test1', timestamp: 1234 }, - { message: 'test2', timestamp: 12345 }, - { message: 'test3', timestamp: 12346 }, - ]; - scope['_breadcrumbs'] = breadcrumbs; - - expect(scope['_getBreadcrumbs']()).toEqual(breadcrumbs); - }); - - it('gets events from active span if found', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - - const now = Date.now(); - - span.addEvent('basic event', now); - span.addEvent('breadcrumb event', {}, now + 1000); - span.addEvent( - 'breadcrumb event 2', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }), - [InternalSentrySemanticAttributes.BREADCRUMB_TYPE]: 'test-type', - [InternalSentrySemanticAttributes.BREADCRUMB_LEVEL]: 'info', - [InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID]: 'test-event-id', - [InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY]: 'test-category', - }, - now + 3000, - ); - span.addEvent( - 'breadcrumb event invalid JSON data', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: 'this is not JSON...', - }, - now + 2000, - ); - - expect(scope['_getBreadcrumbs']()).toEqual([ - { message: 'basic event', timestamp: now / 1000 }, - { message: 'breadcrumb event', timestamp: now / 1000 + 1 }, - { - message: 'breadcrumb event 2', - timestamp: now / 1000 + 3, - data: { nested: { indeed: true } }, - level: 'info', - event_id: 'test-event-id', - category: 'test-category', - type: 'test-type', - }, - { message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 }, - ]); - }); - - it('gets events from spans up the parent chain if found', () => { - const span = createSpan(); - const parentSpan = createSpan(); - const rootSpan = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - setSpanParent(span, parentSpan); - setSpanParent(parentSpan, rootSpan); - - const scope = new OpenTelemetryScope(); - - const now = Date.now(); - - span.addEvent('basic event', now); - parentSpan.addEvent('parent breadcrumb event', {}, now + 1000); - span.addEvent( - 'breadcrumb event 2', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: true }), - }, - now + 3000, - ); - rootSpan.addEvent( - 'breadcrumb event invalid JSON data', - { - [InternalSentrySemanticAttributes.BREADCRUMB_DATA]: 'this is not JSON...', - }, - now + 2000, - ); - - expect(scope['_getBreadcrumbs']()).toEqual([ - { message: 'basic event', timestamp: now / 1000 }, - { message: 'breadcrumb event 2', timestamp: now / 1000 + 3, data: { nested: true } }, - { message: 'parent breadcrumb event', timestamp: now / 1000 + 1 }, - { message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 }, - ]); - }); - - it('combines scope & span breadcrumbs if both exist', () => { - const span = createSpan(); - jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span); - - const scope = new OpenTelemetryScope(); - - const breadcrumbs: Breadcrumb[] = [ - { message: 'test1', timestamp: 1234 }, - { message: 'test2', timestamp: 12345 }, - { message: 'test3', timestamp: 12346 }, - ]; - scope['_breadcrumbs'] = breadcrumbs; - - const now = Date.now(); - - span.addEvent('basic event', now); - span.addEvent('breadcrumb event', {}, now + 1000); - - expect(scope['_getBreadcrumbs']()).toEqual([ - { message: 'test1', timestamp: 1234 }, - { message: 'test2', timestamp: 12345 }, - { message: 'test3', timestamp: 12346 }, - { message: 'basic event', timestamp: now / 1000 }, - { message: 'breadcrumb event', timestamp: now / 1000 + 1 }, - ]); - }); - }); }); diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index f228d545861b..2da60af0195d 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -174,7 +174,7 @@ describe('Integration | Transactions', () => { ]); }); - it('correctly creates concurrent transaction & spans xxx', async () => { + it('correctly creates concurrent transaction & spans', async () => { const beforeSendTransaction = jest.fn(() => null); mockSdkInit({ enableTracing: true, beforeSendTransaction }); From b62eecbbcfc56bb78900e5b43be81317bed5efed Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 12 Feb 2024 14:53:36 +0100 Subject: [PATCH 040/173] chore(ci): Always run Node unit tests (#10613) Always run Node unit tests because unlike the name suggests, they not only execute @sentry/node specific unit tests but all server-side package unit tests, including packages like Astro, NextJS or Serverless. --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ccda1b605e7c..3856ea843d7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -516,7 +516,6 @@ jobs: job_node_unit_tests: name: Node (${{ matrix.node }}) Unit Tests - if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request' needs: [job_get_metadata, job_build] timeout-minutes: 10 runs-on: ubuntu-20.04 From 3ef44a4e015476ef6329d3df77dd0f7120f0f70c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 12 Feb 2024 15:34:51 +0100 Subject: [PATCH 041/173] ref: Remove deprecated `showReportDialog` APIs (#10609) --- packages/angular/src/errorhandler.ts | 7 +- packages/angular/test/errorhandler.test.ts | 2 +- packages/browser/src/exports.ts | 3 +- packages/browser/src/helpers.ts | 34 +-------- packages/browser/src/sdk.ts | 74 ++++++++++--------- packages/browser/test/unit/index.test.ts | 10 +-- .../test/integration/pages/reportDialog.tsx | 5 +- packages/react/src/errorboundary.tsx | 1 - 8 files changed, 55 insertions(+), 81 deletions(-) diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index b22536307e9a..e8c4350d971e 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -2,6 +2,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import type { ErrorHandler as AngularErrorHandler } from '@angular/core'; import { Inject, Injectable } from '@angular/core'; import * as Sentry from '@sentry/browser'; +import type { ReportDialogOptions } from '@sentry/browser'; import type { Event } from '@sentry/types'; import { isString } from '@sentry/utils'; @@ -13,8 +14,7 @@ import { runOutsideAngular } from './zone'; export interface ErrorHandlerOptions { logErrors?: boolean; showDialog?: boolean; - // eslint-disable-next-line deprecation/deprecation - dialogOptions?: Omit; + dialogOptions?: Omit; /** * Custom implementation of error extraction from the raw value captured by the Angular. * @param error Value captured by Angular's ErrorHandler provider @@ -120,8 +120,7 @@ class SentryErrorHandler implements AngularErrorHandler { if (client && !this._registeredAfterSendEventHandler) { client.on('afterSendEvent', (event: Event) => { - if (!event.type) { - // eslint-disable-next-line deprecation/deprecation + if (!event.type && event.event_id) { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id }); } }); diff --git a/packages/angular/test/errorhandler.test.ts b/packages/angular/test/errorhandler.test.ts index bba65056cba8..18521e536581 100644 --- a/packages/angular/test/errorhandler.test.ts +++ b/packages/angular/test/errorhandler.test.ts @@ -514,7 +514,7 @@ describe('SentryErrorHandler', () => { expect(client.on).toHaveBeenCalledWith('afterSendEvent', expect.any(Function)); // this simulates the afterSend hook being called - client.cb({}); + client.cb({ event_id: 'foobar' }); expect(showReportDialogSpy).toBeCalledTimes(1); }); diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index eda89ac56b1c..9356fa7c77a4 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -17,8 +17,7 @@ export type { export type { BrowserOptions } from './client'; -// eslint-disable-next-line deprecation/deprecation -export type { ReportDialogOptions } from './helpers'; +export type { ReportDialogOptions } from './sdk'; export { // eslint-disable-next-line deprecation/deprecation diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 35930167672d..6b9df98f1751 100644 --- a/packages/browser/src/helpers.ts +++ b/packages/browser/src/helpers.ts @@ -1,7 +1,7 @@ import type { browserTracingIntegration } from '@sentry-internal/tracing'; import { BrowserTracing } from '@sentry-internal/tracing'; import { captureException, withScope } from '@sentry/core'; -import type { DsnLike, Integration, Mechanism, WrappedFunction } from '@sentry/types'; +import type { Integration, Mechanism, WrappedFunction } from '@sentry/types'; import { GLOBAL_OBJ, addExceptionMechanism, @@ -156,38 +156,6 @@ export function wrap( return sentryWrapped; } -/** - * All properties the report dialog supports - * - * @deprecated This type will be removed in the next major version of the Sentry SDK. `showReportDialog` will still be around, however the `eventId` option will now be required. - */ -export interface ReportDialogOptions { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - eventId?: string; - dsn?: DsnLike; - user?: { - email?: string; - name?: string; - }; - lang?: string; - title?: string; - subtitle?: string; - subtitle2?: string; - labelName?: string; - labelEmail?: string; - labelComments?: string; - labelClose?: string; - labelSubmit?: string; - errorGeneric?: string; - errorFormEntry?: string; - successMessage?: string; - /** Callback after reportDialog showed up */ - onLoad?(this: void): void; - /** Callback after reportDialog closed */ - onClose?(this: void): void; -} - /** * This is a slim shim of `browserTracingIntegration` for the CDN bundles. * Since the actual functional integration uses a different code from `BrowserTracing`, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 39fabc92c8ec..871258fd49b7 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,15 +1,14 @@ -import type { Hub } from '@sentry/core'; +import { getCurrentScope } from '@sentry/core'; import { functionToStringIntegration, inboundFiltersIntegration } from '@sentry/core'; import { captureSession, getClient, - getCurrentHub, getIntegrationsToSetup, getReportDialogEndpoint, initAndBind, startSession, } from '@sentry/core'; -import type { Integration, Options, UserFeedback } from '@sentry/types'; +import type { DsnLike, Integration, Options, UserFeedback } from '@sentry/types'; import { addHistoryInstrumentationHandler, logger, @@ -20,7 +19,6 @@ import { import type { BrowserClientOptions, BrowserOptions } from './client'; import { BrowserClient } from './client'; import { DEBUG_BUILD } from './debug-build'; -import type { ReportDialogOptions } from './helpers'; import { WINDOW, wrap as internalWrap } from './helpers'; import { breadcrumbsIntegration } from './integrations/breadcrumbs'; import { dedupeIntegration } from './integrations/dedupe'; @@ -139,42 +137,52 @@ export function init(options: BrowserOptions = {}): void { } } -type NewReportDialogOptions = ReportDialogOptions & { eventId: string }; // eslint-disable-line - -interface ShowReportDialogFunction { - /** - * Present the user with a report dialog. - * - * @param options Everything is optional, we try to fetch all info need from the global scope. - */ - (options: NewReportDialogOptions): void; - - /** - * Present the user with a report dialog. - * - * @param options Everything is optional, we try to fetch all info need from the global scope. - * - * @deprecated Please always pass an `options` argument with `eventId`. The `hub` argument will not be used in the next version of the SDK. - */ - // eslint-disable-next-line deprecation/deprecation - (options?: ReportDialogOptions, hub?: Hub): void; +/** + * All properties the report dialog supports + */ +export interface ReportDialogOptions { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; + eventId: string; + dsn?: DsnLike; + user?: { + email?: string; + name?: string; + }; + lang?: string; + title?: string; + subtitle?: string; + subtitle2?: string; + labelName?: string; + labelEmail?: string; + labelComments?: string; + labelClose?: string; + labelSubmit?: string; + errorGeneric?: string; + errorFormEntry?: string; + successMessage?: string; + /** Callback after reportDialog showed up */ + onLoad?(this: void): void; + /** Callback after reportDialog closed */ + onClose?(this: void): void; } -export const showReportDialog: ShowReportDialogFunction = ( - // eslint-disable-next-line deprecation/deprecation - options: ReportDialogOptions = {}, - // eslint-disable-next-line deprecation/deprecation - hub: Hub = getCurrentHub(), -) => { +/** + * Present the user with a report dialog. + * + * @param options Everything is optional, we try to fetch all info need from the global scope. + */ +export function showReportDialog(options: ReportDialogOptions): void { // doesn't work without a document (React Native) if (!WINDOW.document) { DEBUG_BUILD && logger.error('Global document not defined in showReportDialog call'); return; } - // eslint-disable-next-line deprecation/deprecation - const { client, scope } = hub.getStackTop(); - const dsn = options.dsn || (client && client.getDsn()); + const scope = getCurrentScope(); + const client = scope.getClient(); + const dsn = client && client.getDsn(); + if (!dsn) { DEBUG_BUILD && logger.error('DSN not configured for showReportDialog call'); return; @@ -216,7 +224,7 @@ export const showReportDialog: ShowReportDialogFunction = ( } else { DEBUG_BUILD && logger.error('Not injecting report dialog. No injection point found in HTML'); } -}; +} /** * This function is here to be API compatible with the loader. diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index fe884ccc5438..b2c844976c1d 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -88,7 +88,7 @@ describe('SentryBrowser', () => { setCurrentClient(client); // eslint-disable-next-line deprecation/deprecation - showReportDialog(); + showReportDialog({ eventId: 'foobar' }); expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1); expect(getReportDialogEndpoint).toHaveBeenCalledWith( @@ -103,7 +103,7 @@ describe('SentryBrowser', () => { const DIALOG_OPTION_USER = { email: 'option@example.com' }; // eslint-disable-next-line deprecation/deprecation - showReportDialog({ user: DIALOG_OPTION_USER }); + showReportDialog({ eventId: 'foobar', user: DIALOG_OPTION_USER }); expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1); expect(getReportDialogEndpoint).toHaveBeenCalledWith( @@ -137,7 +137,7 @@ describe('SentryBrowser', () => { it('should call `onClose` when receiving `__sentry_reportdialog_closed__` MessageEvent', async () => { const onClose = jest.fn(); // eslint-disable-next-line deprecation/deprecation - showReportDialog({ onClose }); + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('__sentry_reportdialog_closed__'); expect(onClose).toHaveBeenCalledTimes(1); @@ -152,7 +152,7 @@ describe('SentryBrowser', () => { throw new Error(); }); // eslint-disable-next-line deprecation/deprecation - showReportDialog({ onClose }); + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('__sentry_reportdialog_closed__'); expect(onClose).toHaveBeenCalledTimes(1); @@ -165,7 +165,7 @@ describe('SentryBrowser', () => { it('should not call `onClose` for other MessageEvents', async () => { const onClose = jest.fn(); // eslint-disable-next-line deprecation/deprecation - showReportDialog({ onClose }); + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('some_message'); expect(onClose).not.toHaveBeenCalled(); diff --git a/packages/nextjs/test/integration/pages/reportDialog.tsx b/packages/nextjs/test/integration/pages/reportDialog.tsx index b3337c0ee389..bfc9704c3aa9 100644 --- a/packages/nextjs/test/integration/pages/reportDialog.tsx +++ b/packages/nextjs/test/integration/pages/reportDialog.tsx @@ -1,9 +1,10 @@ -import { showReportDialog } from '@sentry/nextjs'; +import { captureException, showReportDialog } from '@sentry/nextjs'; const ReportDialogPage = (): JSX.Element => ( diff --git a/dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts index 8cda20ec3853..9b5f1b08e9d2 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/tests/performance.test.ts @@ -52,3 +52,77 @@ test('sends a navigation transaction with a parameterized URL', async ({ page }) }, }); }); + +test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('angular-17', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + + // immediately navigate to a different route + const [_, pageloadTxn, navigationTxn] = await Promise.all([ + page.locator('#navLink').click(), + pageloadTxnPromise, + navigationTxnPromise, + ]); + + expect(pageloadTxn).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.angular', + }, + }, + transaction: '/home/', + transaction_info: { + source: 'route', + }, + }); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.angular', + }, + }, + transaction: '/users/:id/', + transaction_info: { + source: 'route', + }, + }); +}); + +test('groups redirects within one navigation root span', async ({ page }) => { + const navigationTxnPromise = waitForTransaction('angular-17', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + + // immediately navigate to a different route + const [_, navigationTxn] = await Promise.all([page.locator('#redirectLink').click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.angular', + }, + }, + transaction: '/users/:id/', + transaction_info: { + source: 'route', + }, + }); + + const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing'); + + expect(routingSpan).toBeDefined(); + expect(routingSpan?.description).toBe('/redirect1'); +}); diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index 43c6227b0f8e..bbfe3afd3b67 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -8,19 +8,14 @@ import type { ActivatedRouteSnapshot, Event, RouterState } from '@angular/router import { NavigationCancel, NavigationError, Router } from '@angular/router'; import { NavigationEnd, NavigationStart, ResolveEnd } from '@angular/router'; import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, WINDOW, browserTracingIntegration as originalBrowserTracingIntegration, getCurrentScope, startBrowserTracingNavigationSpan, } from '@sentry/browser'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - getActiveSpan, - getClient, - spanToJSON, - startInactiveSpan, -} from '@sentry/core'; +import { getActiveSpan, getClient, getRootSpan, spanToJSON, startInactiveSpan } from '@sentry/core'; import type { Integration, Span, Transaction, TransactionContext } from '@sentry/types'; import { logger, stripUrlQueryAndFragment, timestampInSeconds } from '@sentry/utils'; import type { Observable } from 'rxjs'; @@ -126,24 +121,31 @@ export class TraceService implements OnDestroy { const strippedUrl = stripUrlQueryAndFragment(navigationEvent.url); if (client && hooksBasedInstrumentation) { - if (!getActiveSpan()) { + // see comment in `_isPageloadOngoing` for rationale + if (!this._isPageloadOngoing()) { startBrowserTracingNavigationSpan(client, { name: strippedUrl, - origin: 'auto.navigation.angular', attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.angular', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }); + } else { + // The first time we end up here, we set the pageload flag to false + // Subsequent navigations are going to get their own navigation root span + // even if the pageload root span is still ongoing. + this._pageloadOngoing = false; } - // eslint-disable-next-line deprecation/deprecation this._routingSpan = startInactiveSpan({ name: `${navigationEvent.url}`, op: ANGULAR_ROUTING_OP, - origin: 'auto.ui.angular', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, tags: { - 'routing.instrumentation': '@sentry/angular', url: strippedUrl, ...(navigationEvent.navigationTrigger && { navigationTrigger: navigationEvent.navigationTrigger, @@ -232,8 +234,15 @@ export class TraceService implements OnDestroy { private _subscription: Subscription; + /** + * @see _isPageloadOngoing() + */ + private _pageloadOngoing: boolean; + public constructor(private readonly _router: Router) { this._routingSpan = null; + this._pageloadOngoing = true; + this._subscription = new Subscription(); this._subscription.add(this.navStart$.subscribe()); @@ -248,6 +257,49 @@ export class TraceService implements OnDestroy { public ngOnDestroy(): void { this._subscription.unsubscribe(); } + + /** + * We only _avoid_ creating a navigation root span in one case: + * + * There is an ongoing pageload span AND the router didn't yet emit the first navigation start event + * + * The first navigation start event will create the child routing span + * and update the pageload root span name on ResolveEnd. + * + * There's an edge case we need to avoid here: If the router fires the first navigation start event + * _after_ the pageload root span finished. This is why we check for the pageload root span. + * Possible real-world scenario: Angular application and/or router is bootstrapped after the pageload + * idle root span finished + * + * The overall rationale is: + * - if we already avoided creating a navigation root span once, we don't avoid it again + * (i.e. set `_pageloadOngoing` to `false`) + * - if `_pageloadOngoing` is already `false`, create a navigation root span + * - if there's no active/pageload root span, create a navigation root span + * - only if there's an ongoing pageload root span AND `_pageloadOngoing` is still `true, + * con't create a navigation root span + */ + private _isPageloadOngoing(): boolean { + if (!this._pageloadOngoing) { + // pageload is already finished, no need to update + return false; + } + + const activeSpan = getActiveSpan(); + if (!activeSpan) { + this._pageloadOngoing = false; + return false; + } + + const rootSpan = getRootSpan(activeSpan); + if (!rootSpan) { + this._pageloadOngoing = false; + return false; + } + + this._pageloadOngoing = spanToJSON(rootSpan).op === 'pageload'; + return this._pageloadOngoing; + } } const UNKNOWN_COMPONENT = 'unknown'; From c00bbace839f3ab30a929018c5bea4f3820b43aa Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 14 Feb 2024 17:02:28 +0100 Subject: [PATCH 068/173] feat(browser): Export `getIsolationScope` and `getGlobalScope` (#10658) We forgot to re-export these, apparently. --- packages/browser/src/exports.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index c0ac39c6a7a4..e9ac6816c3c1 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -37,6 +37,8 @@ export { getClient, isInitialized, getCurrentScope, + getIsolationScope, + getGlobalScope, Hub, // eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation From 6361f9caac72dcffa69ddfaec0394dbe81f0c106 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 14 Feb 2024 17:32:39 +0100 Subject: [PATCH 069/173] ref: Store runtime on isolation scope (#10657) This was a bit inconsistent now, we sometimes stored it on the current and sometimes on the isolation scope. Fixing this to always be on the isolation scope, for consistency. --- packages/astro/test/client/sdk.test.ts | 8 ++------ packages/astro/test/server/sdk.test.ts | 14 ++++++------- packages/nextjs/src/client/index.ts | 10 ++++------ packages/nextjs/src/server/index.ts | 18 ++++++----------- packages/nextjs/test/clientSdk.test.ts | 17 ++++++++-------- packages/nextjs/test/serverSdk.test.ts | 18 ++++++++--------- packages/remix/src/index.client.tsx | 6 +++--- packages/remix/src/index.server.ts | 5 ++--- packages/remix/test/index.client.test.ts | 17 +++++++--------- packages/remix/test/index.server.test.ts | 15 +++++++------- packages/sveltekit/test/client/sdk.test.ts | 23 +++++++++++++--------- packages/sveltekit/test/server/sdk.test.ts | 14 ++++++------- 12 files changed, 75 insertions(+), 90 deletions(-) diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index 8313fa3bb149..6dcf2e984873 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -40,15 +40,11 @@ describe('Sentry client SDK', () => { }); it('sets the runtime tag on the isolation scope', () => { - const isolationScope = getIsolationScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({}); + expect(getIsolationScope().getScopeData().tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({ runtime: 'browser' }); + expect(getIsolationScope().getScopeData().tags).toEqual({ runtime: 'browser' }); }); describe('automatically adds integrations', () => { diff --git a/packages/astro/test/server/sdk.test.ts b/packages/astro/test/server/sdk.test.ts index b4f0536a2a76..d07f95263c97 100644 --- a/packages/astro/test/server/sdk.test.ts +++ b/packages/astro/test/server/sdk.test.ts @@ -1,6 +1,5 @@ import * as SentryNode from '@sentry/node'; import { SDK_VERSION } from '@sentry/node'; -import { GLOBAL_OBJ } from '@sentry/utils'; import { vi } from 'vitest'; import { init } from '../../src/server/sdk'; @@ -11,7 +10,10 @@ describe('Sentry server SDK', () => { describe('init', () => { afterEach(() => { vi.clearAllMocks(); - GLOBAL_OBJ.__SENTRY__.hub = undefined; + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); }); it('adds Astro metadata to the SDK options', () => { @@ -37,15 +39,11 @@ describe('Sentry server SDK', () => { }); it('sets the runtime tag on the isolation scope', () => { - const isolationScope = SentryNode.getIsolationScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({}); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({ runtime: 'node' }); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'node' }); }); }); }); diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 5737816ba873..a37bb91e5acb 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,8 +1,7 @@ -import { applySdkMetadata, hasTracingEnabled } from '@sentry/core'; +import { addEventProcessor, applySdkMetadata, hasTracingEnabled, setTag } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; import { DEFAULT_TRACE_PROPAGATION_TARGETS, - getCurrentScope, getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit, } from '@sentry/react'; @@ -49,15 +48,14 @@ export function init(options: BrowserOptions): void { reactInit(opts); - const scope = getCurrentScope(); - scope.setTag('runtime', 'browser'); + setTag('runtime', 'browser'); const filterTransactions: EventProcessor = event => event.type === 'transaction' && event.transaction === '/404' ? null : event; filterTransactions.id = 'NextClient404Filter'; - scope.addEventProcessor(filterTransactions); + addEventProcessor(filterTransactions); if (process.env.NODE_ENV === 'development') { - scope.addEventProcessor(devErrorSymbolicationEventProcessor); + addEventProcessor(devErrorSymbolicationEventProcessor); } } diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index b9211141269f..b4f42b2fbad7 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,11 +1,6 @@ -import { addTracingExtensions, applySdkMetadata, getClient } from '@sentry/core'; +import { addEventProcessor, addTracingExtensions, applySdkMetadata, getClient, setTag } from '@sentry/core'; import type { NodeOptions } from '@sentry/node'; -import { - Integrations as OriginalIntegrations, - getCurrentScope, - getDefaultIntegrations, - init as nodeInit, -} from '@sentry/node'; +import { Integrations as OriginalIntegrations, getDefaultIntegrations, init as nodeInit } from '@sentry/node'; import type { EventProcessor } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -120,16 +115,15 @@ export function init(options: NodeOptions): void { filterTransactions.id = 'NextServer404TransactionFilter'; - const scope = getCurrentScope(); - scope.setTag('runtime', 'node'); + setTag('runtime', 'node'); if (IS_VERCEL) { - scope.setTag('vercel', true); + setTag('vercel', true); } - scope.addEventProcessor(filterTransactions); + addEventProcessor(filterTransactions); if (process.env.NODE_ENV === 'development') { - scope.addEventProcessor(devErrorSymbolicationEventProcessor); + addEventProcessor(devErrorSymbolicationEventProcessor); } DEBUG_BUILD && logger.log('SDK successfully initialized'); diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 4bfcb7729f52..113117d519b7 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -1,4 +1,4 @@ -import { BaseClient } from '@sentry/core'; +import { BaseClient, getGlobalScope, getIsolationScope } from '@sentry/core'; import * as SentryReact from '@sentry/react'; import type { BrowserClient } from '@sentry/react'; import { WINDOW, getClient, getCurrentScope } from '@sentry/react'; @@ -36,7 +36,10 @@ const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337'; describe('Client init()', () => { afterEach(() => { jest.clearAllMocks(); - WINDOW.__SENTRY__.hub = undefined; + + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); }); it('inits the React SDK', () => { @@ -72,15 +75,11 @@ describe('Client init()', () => { }); it('sets runtime on scope', () => { - const currentScope = getCurrentScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(SentryReact.getIsolationScope().getScopeData().tags).toEqual({}); - init({}); + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'browser' }); + expect(SentryReact.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'browser' }); }); it('adds 404 transaction filter', () => { diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index da30862eb6ac..ea998dedfe44 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -18,8 +18,12 @@ function findIntegrationByName(integrations: Integration[] = [], name: string): describe('Server init()', () => { afterEach(() => { jest.clearAllMocks(); - // @ts-expect-error for testing - delete GLOBAL_OBJ.__SENTRY__; + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); + + GLOBAL_OBJ.__SENTRY__.hub = undefined; delete process.env.VERCEL; }); @@ -68,15 +72,11 @@ describe('Server init()', () => { }); it('sets runtime on scope', () => { - const currentScope = getCurrentScope(); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({}); - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - init({}); - - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'node' }); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'node' }); }); // TODO: test `vercel` tag when running on Vercel diff --git a/packages/remix/src/index.client.tsx b/packages/remix/src/index.client.tsx index 3842e9a3701d..a1d21486f6cc 100644 --- a/packages/remix/src/index.client.tsx +++ b/packages/remix/src/index.client.tsx @@ -1,5 +1,5 @@ -import { applySdkMetadata } from '@sentry/core'; -import { getCurrentScope, init as reactInit } from '@sentry/react'; +import { applySdkMetadata, setTag } from '@sentry/core'; +import { init as reactInit } from '@sentry/react'; import type { RemixOptions } from './utils/remixOptions'; export { captureRemixErrorBoundaryError } from './client/errors'; export { @@ -22,5 +22,5 @@ export function init(options: RemixOptions): void { reactInit(opts); - getCurrentScope().setTag('runtime', 'browser'); + setTag('runtime', 'browser'); } diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index bb16845307b4..1c5cfe5be87e 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -1,7 +1,6 @@ import { applySdkMetadata } from '@sentry/core'; import type { NodeOptions } from '@sentry/node'; -import { getClient } from '@sentry/node'; -import { getCurrentScope, init as nodeInit } from '@sentry/node'; +import { getClient, init as nodeInit, setTag } from '@sentry/node'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from './utils/debug-build'; @@ -127,5 +126,5 @@ export function init(options: RemixOptions): void { nodeInit(options as NodeOptions); - getCurrentScope().setTag('runtime', 'node'); + setTag('runtime', 'node'); } diff --git a/packages/remix/test/index.client.test.ts b/packages/remix/test/index.client.test.ts index af4861968bf7..ab93d9e34a3b 100644 --- a/packages/remix/test/index.client.test.ts +++ b/packages/remix/test/index.client.test.ts @@ -1,6 +1,4 @@ -import { getCurrentScope } from '@sentry/core'; import * as SentryReact from '@sentry/react'; -import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../src/index.client'; @@ -9,7 +7,10 @@ const reactInit = jest.spyOn(SentryReact, 'init'); describe('Client init()', () => { afterEach(() => { jest.clearAllMocks(); - GLOBAL_OBJ.__SENTRY__.hub = undefined; + + SentryReact.getGlobalScope().clear(); + SentryReact.getIsolationScope().clear(); + SentryReact.getCurrentScope().clear(); }); it('inits the React SDK', () => { @@ -39,14 +40,10 @@ describe('Client init()', () => { }); it('sets runtime on scope', () => { - const currentScope = getCurrentScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + expect(SentryReact.getIsolationScope().getScopeData().tags).toEqual({}); - init({}); + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'browser' }); + expect(SentryReact.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'browser' }); }); }); diff --git a/packages/remix/test/index.server.test.ts b/packages/remix/test/index.server.test.ts index 46e1bd409afb..a244d8c2a3f2 100644 --- a/packages/remix/test/index.server.test.ts +++ b/packages/remix/test/index.server.test.ts @@ -8,6 +8,11 @@ const nodeInit = jest.spyOn(SentryNode, 'init'); describe('Server init()', () => { afterEach(() => { jest.clearAllMocks(); + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); + GLOBAL_OBJ.__SENTRY__.hub = undefined; }); @@ -46,15 +51,11 @@ describe('Server init()', () => { }); it('sets runtime on scope', () => { - const currentScope = SentryNode.getCurrentScope(); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({}); - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); - - init({}); + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({ runtime: 'node' }); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'node' }); }); it('has both node and tracing integrations', () => { diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts index 0cf28d5f0514..d71c938539df 100644 --- a/packages/sveltekit/test/client/sdk.test.ts +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -1,7 +1,13 @@ -import { getClient, getIsolationScope } from '@sentry/core'; import type { BrowserClient } from '@sentry/svelte'; import * as SentrySvelte from '@sentry/svelte'; -import { SDK_VERSION, WINDOW, browserTracingIntegration } from '@sentry/svelte'; +import { + SDK_VERSION, + browserTracingIntegration, + getClient, + getCurrentScope, + getGlobalScope, + getIsolationScope, +} from '@sentry/svelte'; import { vi } from 'vitest'; import { BrowserTracing, init } from '../../src/client'; @@ -13,7 +19,10 @@ describe('Sentry client SDK', () => { describe('init', () => { afterEach(() => { vi.clearAllMocks(); - WINDOW.__SENTRY__.hub = undefined; + + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); }); it('adds SvelteKit metadata to the SDK options', () => { @@ -39,15 +48,11 @@ describe('Sentry client SDK', () => { }); it('sets the runtime tag on the isolation scope', () => { - const isolationScope = getIsolationScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({}); + expect(getIsolationScope().getScopeData().tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({ runtime: 'browser' }); + expect(getIsolationScope().getScopeData().tags).toEqual({ runtime: 'browser' }); }); describe('automatically added integrations', () => { diff --git a/packages/sveltekit/test/server/sdk.test.ts b/packages/sveltekit/test/server/sdk.test.ts index 53f3ee8e33e8..8f6f5c1b606f 100644 --- a/packages/sveltekit/test/server/sdk.test.ts +++ b/packages/sveltekit/test/server/sdk.test.ts @@ -1,7 +1,6 @@ import * as SentryNode from '@sentry/node'; import type { NodeClient } from '@sentry/node'; import { SDK_VERSION, getClient } from '@sentry/node'; -import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../../src/server/sdk'; @@ -11,7 +10,10 @@ describe('Sentry server SDK', () => { describe('init', () => { afterEach(() => { vi.clearAllMocks(); - GLOBAL_OBJ.__SENTRY__.hub = undefined; + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); }); it('adds SvelteKit metadata to the SDK options', () => { @@ -37,15 +39,11 @@ describe('Sentry server SDK', () => { }); it('sets the runtime tag on the isolation scope', () => { - const isolationScope = SentryNode.getIsolationScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({}); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({}); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - // @ts-expect-error need access to protected _tags attribute - expect(isolationScope._tags).toEqual({ runtime: 'node' }); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'node' }); }); it('adds rewriteFramesIntegration by default', () => { From bc79d32932d8d8a96f5238989a5d9ab3db915164 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 14 Feb 2024 19:10:08 +0100 Subject: [PATCH 070/173] fix(publish): Ensure `@sentry/hub` is published in v7 (#10661) Since we deleted `@sentry/hub` in #10530, we also removed the craft NPM target for the hub package. Given that the craft config is always taken from `develop` (generally, the default branch of the repository), Craft no longer published `@sentry/hub` when we cut a release from our `v7` branch. Unfortunately, the NPM target does not support an optional `onlyIfPresent` configuration, meaning for now, we have to continue publishing a `@sentry/hub` placeholder package when we cut a release from `develop` (i.e. our v8 branch at the moment). --- .craft.yml | 3 +++ .../e2e-tests/verdaccio-config/config.yaml | 6 +++++ package.json | 1 + packages/hub/LICENSE | 14 ++++++++++ packages/hub/README.md | 21 +++++++++++++++ packages/hub/package.json | 26 +++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 packages/hub/LICENSE create mode 100644 packages/hub/README.md create mode 100644 packages/hub/package.json diff --git a/.craft.yml b/.craft.yml index f795d317b05e..99efaf3d095e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -132,6 +132,9 @@ targets: includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ ## 8. Deprecated packages we still release (but no packages depend on them anymore) + - name: npm + id: '@sentry/hub' + includeNames: /^sentry-hub-\d.*\.tgz$/ - name: npm id: '@sentry/tracing' includeNames: /^sentry-tracing-\d.*\.tgz$/ diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 143596b74849..4952397bc706 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -200,6 +200,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/hub': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry-internal/*': access: $all publish: $all diff --git a/package.json b/package.json index 74fbfe2a650f..9ecacd34c252 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "packages/eslint-plugin-sdk", "packages/feedback", "packages/gatsby", + "packages/hub", "packages/integrations", "packages/integration-shims", "packages/nextjs", diff --git a/packages/hub/LICENSE b/packages/hub/LICENSE new file mode 100644 index 000000000000..535ef0561e1b --- /dev/null +++ b/packages/hub/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. All rights reserved. + +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 the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/hub/README.md b/packages/hub/README.md new file mode 100644 index 000000000000..91f796ac0b68 --- /dev/null +++ b/packages/hub/README.md @@ -0,0 +1,21 @@ +

+ + Sentry + +

+ +# Sentry JavaScript SDK Hub + +[![npm version](https://img.shields.io/npm/v/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) +[![npm dm](https://img.shields.io/npm/dm/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) +[![npm dt](https://img.shields.io/npm/dt/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) + +## Links + +- [Official SDK Docs](https://docs.sentry.io/quickstart/) +- [TypeDoc](http://getsentry.github.io/sentry-javascript/) + +## IMPORTANT + +This package only exists anymore to enable a seamless publishing process between v7 and v8 of the JS SDK packages. It +will be removed in a future version. diff --git a/packages/hub/package.json b/packages/hub/package.json new file mode 100644 index 000000000000..982e739a680e --- /dev/null +++ b/packages/hub/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sentry/hub", + "version": "7.100.0", + "description": "Placeholder package for the former @sentry/hub package for publishing", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/hub", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "files": [], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "mkdir -p build && touch build/dummy.js", + "build:transpile": "yarn build", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "clean": "rimraf build coverage sentry-hub-*.tgz" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} From af2a78dc9c7bfddf8b254a570742209bbcf3d2e8 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 14 Feb 2024 19:07:22 -0500 Subject: [PATCH 071/173] chore(codeowners): rename replay team sdk (#10666) --- CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 6a09d1924613..5a46de52cb1a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ -packages/replay @getsentry/replay-sdk -packages/replay-worker @getsentry/replay-sdk -dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk +packages/replay @getsentry/replay-sdk-web +packages/replay-worker @getsentry/replay-sdk-web +dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web From 575e5c83ede45faf26c67dab40f13e0645e81279 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Wed, 14 Feb 2024 22:11:08 -0500 Subject: [PATCH 072/173] chore(codeowners): canvas and feedback to replay team (#10667) --- CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 5a46de52cb1a..6ae2679deafa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,5 @@ packages/replay @getsentry/replay-sdk-web packages/replay-worker @getsentry/replay-sdk-web +packages/replay-canvas @getsentry/replay-sdk-web +packages/feedback @getsentry/replay-sdk-web dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web From 4917612de03563e608696ab02889928ec89c00f6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 15 Feb 2024 09:03:54 +0100 Subject: [PATCH 073/173] fix(tracing): Guard against missing `window.location` (#10659) We should also backport this to v7, probably. Closes https://github.com/getsentry/sentry-javascript/issues/10578 --- .../src/browser/browserTracingIntegration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index 77bf14f621a9..95dee0c42f5e 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -266,7 +266,7 @@ export const browserTracingIntegration = ((_options: Partial { if (['interactive', 'complete'].includes(WINDOW.document.readyState)) { idleTransaction.sendAutoFinishSignal(); @@ -295,7 +295,7 @@ export const browserTracingIntegration = ((_options: Partial { if (activeSpan) { @@ -321,7 +321,7 @@ export const browserTracingIntegration = ((_options: Partial { /** * This early return is there to account for some cases where a navigation transaction starts right after From 05bb6092f66be0033bb67ba1a767ef74c615660a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 15 Feb 2024 11:14:21 +0100 Subject: [PATCH 074/173] fix(sveltekit): Ensure navigations and redirects always create a new transaction (#10656) Adjust the root span/transaction creation behaviour of the SvelteKit router instrumentation: - We no longer check for an active span to create a new navigation span - This is possible without any `pageloadOngoing` flag because, luckily, pageload and navigation instrumentation is completely decoupled - Consequently, navigations happening directly after the first pageload will now create their own transaction instead of being attached to the pageload transaction - Consequently, redirects within a (user perceived) navigation are now treated as separate transactions, where the "current" navigation finishes the transaction of the previous redirect. Additional changes: * Removed tag for routing instrumentation (we now have `sentry.origin` for this anyway) * Added more navigation info to the spans: * `sentry.sveltekit.navigation.type`: indicates the source of the navigation (link click, back/forward button press, goto/redirect call, etc) * `sentry.sveltekit.navigation.from`: parameterized navigation start * `sentry.sveltekit.navigation.to`: parameterized navigation end --------- Co-authored-by: Francesco Novy --- .../sveltekit-2/package.json | 1 + .../sveltekit-2/src/routes/+layout.svelte | 14 +- .../sveltekit-2/src/routes/+page.svelte | 5 +- .../sveltekit-2/src/routes/redirect1/+page.ts | 5 + .../sveltekit-2/src/routes/redirect2/+page.ts | 5 + .../sveltekit-2/test/performance.test.ts | 218 ++++++++++++++++++ .../src/client/browserTracingIntegration.ts | 76 +++--- .../client/browserTracingIntegration.test.ts | 27 ++- 8 files changed, 298 insertions(+), 53 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect1/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect2/+page.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 a323c3c415be..dbdb431960de 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -6,6 +6,7 @@ "dev": "vite dev", "build": "vite build", "preview": "vite preview", + "proxy": "ts-node-script start-event-proxy.ts", "clean": "npx rimraf node_modules,pnpm-lock.yaml", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+layout.svelte index 08c4b6468a93..8eb402c9eda6 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+layout.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+layout.svelte @@ -1,10 +1,14 @@

Sveltekit E2E Test app

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+page.svelte index aeb12d58603f..29ce9ec693a8 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+page.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/+page.svelte @@ -15,7 +15,7 @@ Server Route error
  • - Route with Params + Route with Params
  • Route with Server Load @@ -23,4 +23,7 @@
  • Route with fetch in universal load
  • +
  • + Redirect +
  • diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect1/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect1/+page.ts new file mode 100644 index 000000000000..3f462bf810fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect1/+page.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export const load = async () => { + redirect(301, '/redirect2'); +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect2/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect2/+page.ts new file mode 100644 index 000000000000..99a810761d18 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/redirect2/+page.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export const load = async () => { + redirect(301, '/users/789'); +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/test/performance.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/test/performance.test.ts index aed2040392e7..53b1ea128f00 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/test/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/test/performance.test.ts @@ -184,4 +184,222 @@ test.describe('performance events', () => { }, }); }); + + test('captures a navigation transaction directly after pageload', async ({ page }) => { + await page.goto('/'); + + const clientPageloadTxnPromise = waitForTransaction('sveltekit-2', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'pageload' && txnEvent?.tags?.runtime === 'browser'; + }); + + const clientNavigationTxnPromise = waitForTransaction('sveltekit-2', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.tags?.runtime === 'browser'; + }); + + const navigationClickPromise = page.locator('#routeWithParamsLink').click(); + + const [pageloadTxnEvent, navigationTxnEvent, _] = await Promise.all([ + clientPageloadTxnPromise, + clientNavigationTxnPromise, + navigationClickPromise, + ]); + + expect(pageloadTxnEvent).toMatchObject({ + transaction: '/', + tags: { runtime: 'browser' }, + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.sveltekit', + }, + }, + }); + + expect(navigationTxnEvent).toMatchObject({ + transaction: '/users/[id]', + tags: { runtime: 'browser' }, + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', + }, + }, + }, + }); + + const routingSpans = navigationTxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(routingSpans).toHaveLength(1); + + const routingSpan = routingSpans && routingSpans[0]; + expect(routingSpan).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', + }, + }); + }); + + test('captures one navigation transaction per redirect', async ({ page }) => { + await page.goto('/'); + + const clientNavigationRedirect1TxnPromise = waitForTransaction('sveltekit-2', txnEvent => { + return ( + txnEvent?.contexts?.trace?.op === 'navigation' && + txnEvent?.tags?.runtime === 'browser' && + txnEvent?.transaction === '/redirect1' + ); + }); + + const clientNavigationRedirect2TxnPromise = waitForTransaction('sveltekit-2', txnEvent => { + return ( + txnEvent?.contexts?.trace?.op === 'navigation' && + txnEvent?.tags?.runtime === 'browser' && + txnEvent?.transaction === '/redirect2' + ); + }); + + const clientNavigationRedirect3TxnPromise = waitForTransaction('sveltekit-2', txnEvent => { + return ( + txnEvent?.contexts?.trace?.op === 'navigation' && + txnEvent?.tags?.runtime === 'browser' && + txnEvent?.transaction === '/users/[id]' + ); + }); + + const navigationClickPromise = page.locator('#redirectLink').click(); + + const [redirect1TxnEvent, redirect2TxnEvent, redirect3TxnEvent, _] = await Promise.all([ + clientNavigationRedirect1TxnPromise, + clientNavigationRedirect2TxnPromise, + clientNavigationRedirect3TxnPromise, + navigationClickPromise, + ]); + + expect(redirect1TxnEvent).toMatchObject({ + transaction: '/redirect1', + tags: { runtime: 'browser' }, + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'link', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect1', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect1Spans = redirect1TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect1Spans).toHaveLength(1); + + const redirect1Span = redirect1Spans && redirect1Spans[0]; + expect(redirect1Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect1', + 'sentry.sveltekit.navigation.type': 'link', + }, + }); + + expect(redirect2TxnEvent).toMatchObject({ + transaction: '/redirect2', + tags: { runtime: 'browser' }, + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'goto', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect2', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect2Spans = redirect2TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect2Spans).toHaveLength(1); + + const redirect2Span = redirect2Spans && redirect2Spans[0]; + expect(redirect2Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect2', + 'sentry.sveltekit.navigation.type': 'goto', + }, + }); + + expect(redirect3TxnEvent).toMatchObject({ + transaction: '/users/[id]', + tags: { runtime: 'browser' }, + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'goto', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect3Spans = redirect3TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect3Spans).toHaveLength(1); + + const redirect3Span = redirect3Spans && redirect3Spans[0]; + expect(redirect3Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'goto', + }, + }); + }); }); diff --git a/packages/sveltekit/src/client/browserTracingIntegration.ts b/packages/sveltekit/src/client/browserTracingIntegration.ts index 5e80cdc92f28..07de01e86c11 100644 --- a/packages/sveltekit/src/client/browserTracingIntegration.ts +++ b/packages/sveltekit/src/client/browserTracingIntegration.ts @@ -4,12 +4,12 @@ import { BrowserTracing as OriginalBrowserTracing, WINDOW, browserTracingIntegration as originalBrowserTracingIntegration, - getActiveSpan, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, startInactiveSpan, } from '@sentry/svelte'; import type { Client, Integration, Span } from '@sentry/types'; +import { dropUndefinedKeys } from '@sentry/utils'; import { svelteKitRoutingInstrumentation } from './router'; /** @@ -64,7 +64,7 @@ export function browserTracingIntegration( function _instrumentPageload(client: Client): void { const initialPath = WINDOW && WINDOW.location && WINDOW.location.pathname; - startBrowserTracingPageLoadSpan(client, { + const pageloadSpan = startBrowserTracingPageLoadSpan(client, { name: initialPath, op: 'pageload', description: initialPath, @@ -76,8 +76,9 @@ function _instrumentPageload(client: Client): void { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }); - - const pageloadSpan = getActiveSpan(); + if (!pageloadSpan) { + return; + } page.subscribe(page => { if (!page) { @@ -86,7 +87,7 @@ function _instrumentPageload(client: Client): void { const routeId = page.route && page.route.id; - if (pageloadSpan && routeId) { + if (routeId) { pageloadSpan.updateName(routeId); pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); } @@ -98,7 +99,6 @@ function _instrumentPageload(client: Client): void { */ function _instrumentNavigations(client: Client): void { let routingSpan: Span | undefined; - let activeSpan: Span | undefined; navigating.subscribe(navigation => { if (!navigation) { @@ -129,36 +129,42 @@ function _instrumentNavigations(client: Client): void { const parameterizedRouteOrigin = from && from.route.id; const parameterizedRouteDestination = to && to.route.id; - activeSpan = getActiveSpan(); - - if (!activeSpan) { - startBrowserTracingNavigationSpan(client, { - name: parameterizedRouteDestination || rawRouteDestination || 'unknown', - op: 'navigation', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedRouteDestination ? 'route' : 'url', - }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', - }, - }); - activeSpan = getActiveSpan(); + if (routingSpan) { + // If a routing span is still open from a previous navigation, we finish it. + // This is important for e.g. redirects when a new navigation root span finishes + // the first root span. If we don't `.end()` the previous span, it will get + // status 'cancelled' which isn't entirely correct. + routingSpan.end(); } - if (activeSpan) { - if (routingSpan) { - // If a routing span is still open from a previous navigation, we finish it. - routingSpan.end(); - } - routingSpan = startInactiveSpan({ - op: 'ui.sveltekit.routing', - name: 'SvelteKit Route Change', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit', - }, - }); - activeSpan.setAttribute('sentry.sveltekit.navigation.from', parameterizedRouteOrigin || undefined); - } + const navigationInfo = dropUndefinedKeys({ + // `navigation.type` denotes the origin of the navigation. e.g.: + // - link (clicking on a link) + // - goto (programmatic via goto() or redirect()) + // - popstate (back/forward navigation) + 'sentry.sveltekit.navigation.type': navigation.type, + 'sentry.sveltekit.navigation.from': parameterizedRouteOrigin || undefined, + 'sentry.sveltekit.navigation.to': parameterizedRouteDestination || undefined, + }); + + startBrowserTracingNavigationSpan(client, { + name: parameterizedRouteDestination || rawRouteDestination || 'unknown', + op: 'navigation', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedRouteDestination ? 'route' : 'url', + ...navigationInfo, + }, + }); + + routingSpan = startInactiveSpan({ + op: 'ui.sveltekit.routing', + name: 'SvelteKit Route Change', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit', + ...navigationInfo, + }, + onlyIfParent: true, + }); }); } diff --git a/packages/sveltekit/test/client/browserTracingIntegration.test.ts b/packages/sveltekit/test/client/browserTracingIntegration.test.ts index 415637c05560..fba7136bd5eb 100644 --- a/packages/sveltekit/test/client/browserTracingIntegration.test.ts +++ b/packages/sveltekit/test/client/browserTracingIntegration.test.ts @@ -45,6 +45,7 @@ describe('browserTracingIntegration', () => { }), setTag: vi.fn(), }; + return createdRootSpan; }); const startBrowserTracingNavigationSpanSpy = vi @@ -56,6 +57,7 @@ describe('browserTracingIntegration', () => { setAttribute: vi.fn(), setTag: vi.fn(), }; + return createdRootSpan; }); const fakeClient = { getOptions: () => ({}), on: () => {} }; @@ -173,6 +175,7 @@ describe('browserTracingIntegration', () => { navigating.set({ from: { route: { id: '/users' }, url: { pathname: '/users' } }, to: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, + type: 'link', }); // This should update the transaction name with the parameterized route: @@ -183,9 +186,9 @@ describe('browserTracingIntegration', () => { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', + 'sentry.sveltekit.navigation.from': '/users', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', }, }); @@ -195,12 +198,13 @@ describe('browserTracingIntegration', () => { name: 'SvelteKit Route Change', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/users', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', }, + onlyIfParent: true, }); - // eslint-disable-next-line deprecation/deprecation - expect(createdRootSpan?.setAttribute).toHaveBeenCalledWith('sentry.sveltekit.navigation.from', '/users'); - // We emit `null` here to simulate the end of the navigation lifecycle // @ts-expect-error - page is a writable but the types say it's just readable navigating.set(null); @@ -246,9 +250,8 @@ describe('browserTracingIntegration', () => { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', - }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', + 'sentry.sveltekit.navigation.from': '/users/[id]', + 'sentry.sveltekit.navigation.to': '/users/[id]', }, }); @@ -258,11 +261,11 @@ describe('browserTracingIntegration', () => { name: 'SvelteKit Route Change', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/users/[id]', + 'sentry.sveltekit.navigation.to': '/users/[id]', }, + onlyIfParent: true, }); - - // eslint-disable-next-line deprecation/deprecation - expect(createdRootSpan?.setAttribute).toHaveBeenCalledWith('sentry.sveltekit.navigation.from', '/users/[id]'); }); it('falls back to `window.location.pathname` to determine the raw origin', () => { From afa5304188e1a9cb25a04dfc7ccbfd999a6706a7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 15 Feb 2024 11:37:27 +0100 Subject: [PATCH 075/173] ref: Make span types more robust (#10660) With more strict TS settings, these could lead to problems: 1. `startChild()` should return a span interface, to match the interface implementation. 2. All properties that are optional in span/transactions need to also accept `| undefined` to be really correct - otherwise when we extend this and set `undefined` explicitly it complains there. Fixes https://github.com/getsentry/sentry-javascript/issues/10654 --- packages/core/src/tracing/span.ts | 10 +++--- packages/core/src/tracing/transaction.ts | 2 +- .../test/integrations/apollo-nestjs.test.ts | 7 ++-- .../tracing/test/integrations/apollo.test.ts | 7 ++-- .../tracing/test/integrations/graphql.test.ts | 7 ++-- .../test/integrations/node/mongo.test.ts | 7 ++-- .../test/integrations/node/postgres.test.ts | 9 ++--- packages/tracing/test/span.test.ts | 4 +-- packages/types/src/span.ts | 36 +++++++++---------- packages/types/src/transaction.ts | 4 +-- 10 files changed, 49 insertions(+), 44 deletions(-) diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 1c178fc05fbe..5ded23cc386d 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -105,16 +105,16 @@ export class Span implements SpanInterface { protected _traceId: string; protected _spanId: string; - protected _parentSpanId?: string; + protected _parentSpanId?: string | undefined; protected _sampled: boolean | undefined; - protected _name?: string; + protected _name?: string | undefined; protected _attributes: SpanAttributes; /** Epoch timestamp in seconds when the span started. */ protected _startTime: number; /** Epoch timestamp in seconds when the span ended. */ - protected _endTime?: number; + protected _endTime?: number | undefined; /** Internal keeper of the status */ - protected _status?: SpanStatusType | string; + protected _status?: SpanStatusType | string | undefined; private _logMessage?: string; @@ -385,7 +385,7 @@ export class Span implements SpanInterface { */ public startChild( spanContext?: Pick>, - ): Span { + ): SpanInterface { const childSpan = new Span({ ...spanContext, parentSpanId: this._spanId, diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 70101a402a83..53105363e3ea 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -35,7 +35,7 @@ export class Transaction extends SpanClass implements TransactionInterface { private _contexts: Contexts; - private _trimEnd?: boolean; + private _trimEnd?: boolean | undefined; // DO NOT yet remove this property, it is used in a hack for v7 backwards compatibility. private _frozenDynamicSamplingContext: Readonly> | undefined; diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts index 7e9866146385..51f82f210e59 100644 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ b/packages/tracing/test/integrations/apollo-nestjs.test.ts @@ -1,9 +1,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope } from '@sentry/core'; +import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { Integrations, Span } from '../../src'; +import { Integrations } from '../../src'; import { getTestClient } from '../testutils'; type ApolloResolverGroup = { @@ -79,7 +80,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new Span(); + parentSpan = new SpanClass(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index ea861dcdec1f..5de017275080 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -1,9 +1,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope } from '@sentry/core'; +import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { Integrations, Span } from '../../src'; +import { Integrations } from '../../src'; import { getTestClient } from '../testutils'; type ApolloResolverGroup = { @@ -79,7 +80,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new Span(); + parentSpan = new SpanClass(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index 06b9495d8061..7189a148130d 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -1,9 +1,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope } from '@sentry/core'; +import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { Integrations, Span } from '../../src'; +import { Integrations } from '../../src'; import { getTestClient } from '../testutils'; const GQLExecute = { @@ -41,7 +42,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new Span(); + parentSpan = new SpanClass(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index 8caa5f35750f..4a42a096de69 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -1,9 +1,10 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope } from '@sentry/core'; +import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { Integrations, Span } from '../../../src'; +import { Integrations } from '../../../src'; import { getTestClient } from '../../testutils'; class Collection { @@ -63,7 +64,7 @@ describe('patchOperation()', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new Span(); + parentSpan = new SpanClass(); childSpan = parentSpan.startChild(); testClient = getTestClient({}); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index c94b9870907b..800657d20fa1 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -1,10 +1,11 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope } from '@sentry/core'; +import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import type { Span } from '@sentry/types'; import { loadModule, logger } from '@sentry/utils'; import pg from 'pg'; -import { Integrations, Span } from '../../../src'; +import { Integrations } from '../../../src'; import { getTestClient } from '../../testutils'; class PgClient { @@ -63,7 +64,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new Span(); + parentSpan = new SpanClass(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); @@ -134,7 +135,7 @@ describe('setupOnce', () => { it('does not attempt resolution when module is passed directly', async () => { const scope = new Scope(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new Span()); + jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new SpanClass()); new Integrations.Postgres({ module: mockModule }).setupOnce( () => undefined, diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index ae13f42ea698..798cc2a8263c 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -57,8 +57,8 @@ describe('Span', () => { const span2 = transaction.startChild(); const span3 = span2.startChild(); span3.end(); - expect(transaction.spanRecorder).toBe(span2.spanRecorder); - expect(transaction.spanRecorder).toBe(span3.spanRecorder); + expect(transaction.spanRecorder).toBe((span2 as Span).spanRecorder); + expect(transaction.spanRecorder).toBe((span3 as Span).spanRecorder); }); }); diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 6aa6ea1113f6..69704d497b8f 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -104,43 +104,43 @@ export interface SpanContext { * * @deprecated Use `name` instead. */ - description?: string; + description?: string | undefined; /** * Human-readable identifier for the span. Alias for span.description. */ - name?: string; + name?: string | undefined; /** * Operation of the Span. */ - op?: string; + op?: string | undefined; /** * Completion status of the Span. * See: {@sentry/tracing SpanStatus} for possible values */ - status?: string; + status?: string | undefined; /** * Parent Span ID */ - parentSpanId?: string; + parentSpanId?: string | undefined; /** * Was this span chosen to be sent as part of the sample? */ - sampled?: boolean; + sampled?: boolean | undefined; /** * Span ID */ - spanId?: string; + spanId?: string | undefined; /** * Trace ID */ - traceId?: string; + traceId?: string | undefined; /** * Tags of the Span. @@ -162,22 +162,22 @@ export interface SpanContext { /** * Timestamp in seconds (epoch time) indicating when the span started. */ - startTimestamp?: number; + startTimestamp?: number | undefined; /** * Timestamp in seconds (epoch time) indicating when the span ended. */ - endTimestamp?: number; + endTimestamp?: number | undefined; /** * The instrumenter that created this span. */ - instrumenter?: Instrumenter; + instrumenter?: Instrumenter | undefined; /** * The origin of the span, giving context about what created the span. */ - origin?: SpanOrigin; + origin?: SpanOrigin | undefined; } /** Span holding trace_id, span_id */ @@ -194,7 +194,7 @@ export interface Span extends Omit { * @deprecated Use `startSpan()` functions to set, `span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'op') * to update and `spanToJSON().op` to read the op instead */ - op?: string; + op?: string | undefined; /** * The ID of the span. @@ -207,7 +207,7 @@ export interface Span extends Omit { * * @deprecated Use `spanToJSON(span).parent_span_id` instead. */ - parentSpanId?: string; + parentSpanId?: string | undefined; /** * The ID of the trace. @@ -219,7 +219,7 @@ export interface Span extends Omit { * Was this span chosen to be sent as part of the sample? * @deprecated Use `isRecording()` instead. */ - sampled?: boolean; + sampled?: boolean | undefined; /** * Timestamp in seconds (epoch time) indicating when the span started. @@ -231,7 +231,7 @@ export interface Span extends Omit { * Timestamp in seconds (epoch time) indicating when the span ended. * @deprecated Use `spanToJSON()` instead. */ - endTimestamp?: number; + endTimestamp?: number | undefined; /** * Tags for the span. @@ -271,14 +271,14 @@ export interface Span extends Omit { * * @deprecated Use `.setStatus` to set or update and `spanToJSON()` to read the status. */ - status?: string; + status?: string | undefined; /** * The origin of the span, giving context about what created the span. * * @deprecated Use `startSpan` function to set and `spanToJSON(span).origin` to read the origin instead. */ - origin?: SpanOrigin; + origin?: SpanOrigin | undefined; /** * Get context data for this span. diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index b297dd0ea9c2..fbcf8b38f02d 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -21,12 +21,12 @@ export interface TransactionContext extends SpanContext { * accounted for in child spans, like what happens in the idle transaction Tracing integration, where we finish the * transaction after a given "idle time" and we don't want this "idle time" to be part of the transaction. */ - trimEnd?: boolean; + trimEnd?: boolean | undefined; /** * If this transaction has a parent, the parent's sampling decision */ - parentSampled?: boolean; + parentSampled?: boolean | undefined; /** * Metadata associated with the transaction, for internal SDK use. From 8af205eba4171462c4b7f1d2d45044c97e748e37 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 15 Feb 2024 12:52:02 +0100 Subject: [PATCH 076/173] test: Improve node integration test running (#10673) This improves the output and formatting of the node integration tests, making it a bit easier to work with them: 1. Removed the coverage output, making the general output much clearer 2. Ensure we have color in the jest output, making it easier to scan it 3. Remove logging around started tests & workers, streamlining the output and aligning it more with the usual jest output. 4. Move skipped output to the end of the test, clearing up the output for actually run tests 5. Update summary at the bottom to show success/failed/skipped test numbers 6. Add color to summary output (error/success/skipped) to make it clearer. 7. Show relative path names instead of full path names Skipped tests: Screenshot 2024-02-15 at 12 04 29 Failed test: Screenshot 2024-02-15 at 11 46 42 Successful test: Screenshot 2024-02-15 at 11 48 28 Failed test summary: Screenshot 2024-02-15 at 11 52 19 Successful test summary: Screenshot 2024-02-15 at 11 55 28 --- .../node-integration-tests/jest.config.js | 1 + .../suites/anr/basic-session.js | 1 - .../suites/anr/basic.js | 1 - .../suites/anr/basic.mjs | 1 - .../suites/anr/forked.js | 3 +- .../suites/anr/should-exit-forced.js | 2 +- .../suites/anr/should-exit.js | 2 +- .../suites/proxy/basic.js | 1 - .../tracing-experimental/hapi/scenario.js | 1 - .../tracing-experimental/mongoose/scenario.js | 1 - .../httpIntegration/spans/scenario.ts | 1 - .../node-integration-tests/utils/run-tests.ts | 78 +++++++++++++------ 12 files changed, 60 insertions(+), 33 deletions(-) diff --git a/dev-packages/node-integration-tests/jest.config.js b/dev-packages/node-integration-tests/jest.config.js index fe377b41b880..9be635aba9ad 100644 --- a/dev-packages/node-integration-tests/jest.config.js +++ b/dev-packages/node-integration-tests/jest.config.js @@ -5,4 +5,5 @@ module.exports = { ...baseConfig, testMatch: ['**/test.ts'], setupFilesAfterEnv: ['./jest.setup.js'], + coverageReporters: ['json', 'lcov', 'clover'], }; 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 5a41d1db6f43..7b22e9a6a83e 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -17,7 +17,6 @@ Sentry.init({ function longWork() { for (let i = 0; i < 20; i++) { const salt = crypto.randomBytes(128).toString('base64'); - // eslint-disable-next-line no-unused-vars const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); assert.ok(hash); } diff --git a/dev-packages/node-integration-tests/suites/anr/basic.js b/dev-packages/node-integration-tests/suites/anr/basic.js index f9552a974aed..712e0e26a3f8 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -18,7 +18,6 @@ Sentry.init({ function longWork() { for (let i = 0; i < 20; i++) { const salt = crypto.randomBytes(128).toString('base64'); - // eslint-disable-next-line no-unused-vars const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); assert.ok(hash); } diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index 5526c8b71c38..0184ca9583f7 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -18,7 +18,6 @@ Sentry.init({ function longWork() { for (let i = 0; i < 20; i++) { const salt = crypto.randomBytes(128).toString('base64'); - // eslint-disable-next-line no-unused-vars const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); assert.ok(hash); } diff --git a/dev-packages/node-integration-tests/suites/anr/forked.js b/dev-packages/node-integration-tests/suites/anr/forked.js index f9552a974aed..0db282eacb07 100644 --- a/dev-packages/node-integration-tests/suites/anr/forked.js +++ b/dev-packages/node-integration-tests/suites/anr/forked.js @@ -10,15 +10,14 @@ setTimeout(() => { Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, autoSessionTracking: false, + debug: true, integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); function longWork() { for (let i = 0; i < 20; i++) { const salt = crypto.randomBytes(128).toString('base64'); - // eslint-disable-next-line no-unused-vars const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); assert.ok(hash); } diff --git a/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js b/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js index 447284d17018..01ee6f283819 100644 --- a/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js +++ b/dev-packages/node-integration-tests/suites/anr/should-exit-forced.js @@ -4,8 +4,8 @@ function configureSentry() { Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, autoSessionTracking: false, + debug: true, integrations: [Sentry.anrIntegration({ captureStackTrace: true })], }); } diff --git a/dev-packages/node-integration-tests/suites/anr/should-exit.js b/dev-packages/node-integration-tests/suites/anr/should-exit.js index 5816532ba972..5b3d23bf8cff 100644 --- a/dev-packages/node-integration-tests/suites/anr/should-exit.js +++ b/dev-packages/node-integration-tests/suites/anr/should-exit.js @@ -4,8 +4,8 @@ function configureSentry() { Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, autoSessionTracking: false, + debug: true, integrations: [Sentry.anrIntegration({ captureStackTrace: true })], }); } diff --git a/dev-packages/node-integration-tests/suites/proxy/basic.js b/dev-packages/node-integration-tests/suites/proxy/basic.js index 37c568ee613d..aaa0a958554e 100644 --- a/dev-packages/node-integration-tests/suites/proxy/basic.js +++ b/dev-packages/node-integration-tests/suites/proxy/basic.js @@ -8,7 +8,6 @@ proxy.listen(0, () => { Sentry.init({ dsn: process.env.SENTRY_DSN, - debug: true, transportOptions: { proxy: `http://localhost:${proxyPort}`, }, diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js index 5f2c898fad60..8ec0015d744e 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js @@ -4,7 +4,6 @@ const Sentry = require('@sentry/node-experimental'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js index 47a67e4aaf78..3cfa9409917c 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js @@ -4,7 +4,6 @@ const Sentry = require('@sentry/node-experimental'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts index 9b1abf466db1..eba5aa576e86 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts @@ -8,7 +8,6 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, integrations: [Sentry.httpIntegration({})], - debug: true, }); // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/dev-packages/node-integration-tests/utils/run-tests.ts b/dev-packages/node-integration-tests/utils/run-tests.ts index 90b78bf2521c..68243884d0a1 100644 --- a/dev-packages/node-integration-tests/utils/run-tests.ts +++ b/dev-packages/node-integration-tests/utils/run-tests.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import childProcess from 'child_process'; import os from 'os'; +import path from 'path'; import yargs from 'yargs'; const args = yargs @@ -19,14 +20,19 @@ const testPaths = childProcess.execSync('jest --listTests', { encoding: 'utf8' } const numTests = testPaths.length; const fails: string[] = []; +const skips: string[] = []; + +function getTestPath(testPath: string): string { + const cwd = process.cwd(); + return path.relative(cwd, testPath); +} // We're creating a worker for each CPU core. -const workers = os.cpus().map(async (_, i) => { +const workers = os.cpus().map(async () => { while (testPaths.length > 0) { - const testPath = testPaths.pop(); - console.log(`(Worker ${i}) Running test "${testPath}"`); + const testPath = testPaths.pop() as string; await new Promise(resolve => { - const jestArgs = ['--runTestsByPath', testPath as string, '--forceExit']; + const jestArgs = ['--runTestsByPath', testPath as string, '--forceExit', '--colors']; if (args.t) { jestArgs.push('-t', args.t); @@ -51,18 +57,24 @@ const workers = os.cpus().map(async (_, i) => { }); jestProcess.on('error', error => { + console.log(`"${getTestPath(testPath)}" finished with error`, error); console.log(output); - console.log(`(Worker ${i}) Error in test "${testPath}"`, error); - fails.push(`FAILED: "${testPath}"`); + fails.push(`FAILED: ${getTestPath(testPath)}`); resolve(); }); jestProcess.on('exit', exitcode => { - output = checkSkippedAllTests(output, i, testPath); - console.log(`(Worker ${i}) Finished test "${testPath}"`); - console.log(output); - if (exitcode !== 0) { - fails.push(`FAILED: "${testPath}"`); + const hasError = exitcode !== 0; + const skippedOutput = checkSkippedAllTests(output); + + if (skippedOutput && !hasError) { + skips.push(`SKIPPED: ${getTestPath(testPath)}`); + } else { + console.log(output); + } + + if (hasError) { + fails.push(`FAILED: ${getTestPath(testPath)}`); } resolve(); }); @@ -73,15 +85,35 @@ const workers = os.cpus().map(async (_, i) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.all(workers).then(() => { console.log('-------------------'); - console.log(`Successfully ran ${numTests} tests.`); - if (fails.length > 0) { - console.log('Not all tests succeeded:\n'); + + const failCount = fails.length; + const skipCount = skips.length; + const totalCount = numTests; + const successCount = numTests - failCount - skipCount; + const nonSkippedCount = totalCount - skipCount; + + if (skips.length) { + console.log('\x1b[2m%s\x1b[0m', '\nSkipped tests:'); + skips.forEach(skip => { + console.log('\x1b[2m%s\x1b[0m', `● ${skip}`); + }); + } + + if (failCount > 0) { + console.log( + '\x1b[31m%s\x1b[0m', + `\n${failCount} of ${nonSkippedCount} tests failed${skipCount ? ` (${skipCount} skipped)` : ''}:\n`, + ); fails.forEach(fail => { - console.log(`● ${fail}`); + console.log('\x1b[31m%s\x1b[0m', `● ${fail}`); }); process.exit(1); } else { - console.log('All tests succeeded.'); + console.log( + '\x1b[32m%s\x1b[0m', + `\nSuccessfully ran ${successCount} tests${skipCount ? ` (${skipCount} skipped)` : ''}.`, + ); + console.log('\x1b[32m%s\x1b[0m', 'All tests succeeded.'); process.exit(0); } }); @@ -90,15 +122,17 @@ Promise.all(workers).then(() => { * Suppress jest output for test suites where all tests were skipped. * This only clutters the logs and we can safely print a one-liner instead. */ -function checkSkippedAllTests(output: string, workerNumber: number, testPath: string | undefined): string { - const regex = /Tests:\s+(\d+) skipped, (\d+) total/gm; +function checkSkippedAllTests(output: string): boolean { + const regex = /(.+)Tests:(.+)\s+(.+?)(\d+) skipped(.+), (\d+) total/gm; const matches = regex.exec(output); + if (matches) { - const skipped = Number(matches[1]); - const total = Number(matches[2]); + const skipped = Number(matches[4]); + const total = Number(matches[6]); if (!isNaN(skipped) && !isNaN(total) && total === skipped) { - return `(Worker ${workerNumber}) > Skipped all (${total} tests) in ${testPath}`; + return true; } } - return output; + + return false; } From be0d7a93882e60d02e3d0bd91ea9d0b706bc160b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 15 Feb 2024 16:24:23 +0100 Subject: [PATCH 077/173] feat(core): Make `setXXX` methods set on isolation scope (#10678) The hub already writes there, but this circumvents the hub now! --- packages/core/src/exports.ts | 18 +++----- packages/node-experimental/src/index.ts | 14 +++--- packages/node-experimental/src/sdk/api.ts | 54 +---------------------- packages/node-experimental/src/sdk/hub.ts | 11 +++-- 4 files changed, 19 insertions(+), 78 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index c67d634afaf3..d43e3efe26e2 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -82,8 +82,7 @@ export function captureEvent(event: Event, hint?: EventHint): string { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function setContext(name: string, context: { [key: string]: any } | null): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setContext(name, context); + getIsolationScope().setContext(name, context); } /** @@ -91,8 +90,7 @@ export function setContext(name: string, context: { [key: string]: any } | null) * @param extras Extras object to merge into current context. */ export function setExtras(extras: Extras): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setExtras(extras); + getIsolationScope().setExtras(extras); } /** @@ -101,8 +99,7 @@ export function setExtras(extras: Extras): ReturnType { * @param extra Any kind of data. This data will be normalized. */ export function setExtra(key: string, extra: Extra): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setExtra(key, extra); + getIsolationScope().setExtra(key, extra); } /** @@ -110,8 +107,7 @@ export function setExtra(key: string, extra: Extra): ReturnType * @param tags Tags context object to merge into current context. */ export function setTags(tags: { [key: string]: Primitive }): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setTags(tags); + getIsolationScope().setTags(tags); } /** @@ -123,8 +119,7 @@ export function setTags(tags: { [key: string]: Primitive }): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setTag(key, value); + getIsolationScope().setTag(key, value); } /** @@ -133,8 +128,7 @@ export function setTag(key: string, value: Primitive): ReturnType * @param user User context object to be set in the current context. Pass `null` to unset the user. */ export function setUser(user: User | null): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setUser(user); + getIsolationScope().setUser(user); } /** diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index a3fc490fcb6d..e4dc265b1adc 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -32,13 +32,6 @@ export { captureException, captureEvent, captureMessage, - addEventProcessor, - setContext, - setExtra, - setExtras, - setTag, - setTags, - setUser, withScope, withIsolationScope, withActiveSpan, @@ -84,6 +77,13 @@ export { functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, + addEventProcessor, + setContext, + setExtra, + setExtras, + setTag, + setTags, + setUser, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index 017bcdf31df7..f4731f9f126c 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -2,18 +2,7 @@ import type { Span } from '@opentelemetry/api'; import { context, trace } from '@opentelemetry/api'; -import type { - CaptureContext, - Event, - EventHint, - EventProcessor, - Extra, - Extras, - Primitive, - Scope, - SeverityLevel, - User, -} from '@sentry/types'; +import type { CaptureContext, Event, EventHint, Scope, SeverityLevel } from '@sentry/types'; import { getContextFromScope, getScopesFromContext, setScopesOnContext } from '../utils/contextData'; import type { ExclusiveEventHintOrCaptureContext } from '../utils/prepareEvent'; @@ -112,44 +101,3 @@ export function captureMessage(message: string, captureContext?: CaptureContext export function captureEvent(event: Event, hint?: EventHint): string { return getCurrentScope().captureEvent(event, hint); } - -/** - * Add an event processor to the current isolation scope. - */ -export function addEventProcessor(eventProcessor: EventProcessor): void { - getIsolationScope().addEventProcessor(eventProcessor); -} - -/** Set the user for the current isolation scope. */ -export function setUser(user: User | null): void { - getIsolationScope().setUser(user); -} - -/** Set tags for the current isolation scope. */ -export function setTags(tags: { [key: string]: Primitive }): void { - getIsolationScope().setTags(tags); -} - -/** Set a single tag user for the current isolation scope. */ -export function setTag(key: string, value: Primitive): void { - getIsolationScope().setTag(key, value); -} - -/** Set extra data for the current isolation scope. */ -export function setExtra(key: string, extra: Extra): void { - getIsolationScope().setExtra(key, extra); -} - -/** Set multiple extra data for the current isolation scope. */ -export function setExtras(extras: Extras): void { - getIsolationScope().setExtras(extras); -} - -/** Set context data for the current isolation scope. */ -export function setContext( - name: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - context: { [key: string]: any } | null, -): void { - getIsolationScope().setContext(name, context); -} diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 9a15f2befe66..d9928c73905e 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -10,19 +10,18 @@ import type { TransactionContext, } from '@sentry/types'; -import { addBreadcrumb, endSession, startSession } from '@sentry/core'; import { - captureEvent, - getClient, - getCurrentScope, + addBreadcrumb, + endSession, setContext, setExtra, setExtras, setTag, setTags, setUser, - withScope, -} from './api'; + startSession, +} from '@sentry/core'; +import { captureEvent, getClient, getCurrentScope, withScope } from './api'; import { callExtensionMethod, getGlobalCarrier } from './globals'; import { getIsolationScope } from './scope'; import type { SentryCarrier } from './types'; From b420f199b118fc1dfe303f57eccd91499a0f2402 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 15 Feb 2024 16:34:14 +0100 Subject: [PATCH 078/173] feat: Update default trace propagation targets logic in the browser (#10621) --- MIGRATION.md | 28 ++ .../defaultTargetsMatch/subject.js | 2 +- .../defaultTargetsMatch/test.ts | 11 +- .../defaultTargetsNoMatch/test.ts | 2 +- .../defaultTargetsMatch/subject.js | 2 +- .../defaultTargetsMatch/test.ts | 11 +- .../defaultTargetsNoMatch/init.js | 3 +- .../defaultTargetsNoMatch/test.ts | 11 +- packages/browser/src/index.ts | 1 - packages/nextjs/src/client/index.ts | 17 +- .../tracing-internal/src/browser/request.ts | 76 ++++- packages/tracing-internal/src/index.ts | 2 - .../test/browser/request.test.ts | 262 +++++++++++++++++- packages/types/src/options.ts | 35 ++- 14 files changed, 383 insertions(+), 80 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index c38572523fab..261ee10d0bf6 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,33 @@ # Upgrading from 7.x to 8.x +## Updated behaviour of `tracePropagationTargets` in the browser (HTTP tracing headers & CORS) + +We updated the behaviour of the SDKs when no `tracePropagationTargets` option was defined. As a reminder, you can +provide a list of strings or RegExes that will be matched against URLs to tell the SDK, to which outgoing requests +tracing HTTP headers should be attached to. These tracing headers are used for distributed tracing. + +Previously, on the browser, when `tracePropagationTargets` were not defined, they defaulted to the following: +`['localhost', /^\/(?!\/)/]`. This meant that all request targets to that had "localhost" in the URL, or started with a +`/` were equipped with tracing headers. This default was chosen to prevent CORS errors in your browser applications. +However, this default had a few flaws. + +Going forward, when the `tracePropagationTargets` option is not set, tracing headers will be attached to all outgoing +requests on the same origin. For example, if you're on `https://example.com/` and you send a request to +`https://example.com/api`, the request will be traced (ie. will have trace headers attached). Requests to +`https://api.example.com/` will not, because it is on a different origin. The same goes for all applications running on +`localhost`. + +When you provide a `tracePropagationTargets` option, all of the entries you defined will now be matched be matched +against the full URL of the outgoing request. Previously, it was only matched against what you called request APIs with. +For example, if you made a request like `fetch("/api/posts")`, the provided `tracePropagationTargets` were only compared +against `"/api/posts"`. Going forward they will be matched against the entire URL, for example, if you were on the page +`https://example.com/` and you made the same request, it would be matched against `"https://example.com/api/posts"`. + +But that is not all. Because it would be annoying having to create matchers for the entire URL, if the request is a +same-origin request, we also match the `tracePropagationTargets` against the resolved `pathname` of the request. +Meaning, a matcher like `/^\/api/` would match a request call like `fetch('/api/posts')`, or +`fetch('https://same-origin.com/api/posts')` but not `fetch('https://different-origin.com/api/posts')`. + ## Removal of the `tracingOrigins` option After its deprecation in v7 the `tracingOrigins` option is now removed in favor of the `tracePropagationTargets` option. diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/subject.js index 4e9cf0d01004..c6cebfd1d083 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/subject.js @@ -1 +1 @@ -fetch('http://localhost:4200/0').then(fetch('http://localhost:4200/1').then(fetch('http://localhost:4200/2'))); +fetch(`/0`).then(fetch('/1').then(fetch('/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/test.ts index 120b36ec88db..f9f9af3ddb47 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsMatch/test.ts @@ -4,19 +4,16 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../../utils/helpers'; sentryTest( - 'should attach `sentry-trace` and `baggage` header to request matching default tracePropagationTargets', - async ({ getLocalTestPath, page }) => { + 'should attach `sentry-trace` and `baggage` header to same-origin requests when no tracePropagationTargets are defined', + async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); const requests = ( - await Promise.all([ - page.goto(url), - Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://localhost:4200/${idx}`))), - ]) + await Promise.all([page.goto(url), Promise.all([0, 1, 2].map(idx => page.waitForRequest(`**/${idx}`)))]) )[1]; expect(requests).toHaveLength(3); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/test.ts index 116319259101..0f7323d484e7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/defaultTargetsNoMatch/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../../utils/helpers'; sentryTest( - 'should not attach `sentry-trace` and `baggage` header to request not matching default tracePropagationTargets', + 'should not attach `sentry-trace` and `baggage` header to cross-origin requests when no tracePropagationTargets are defined', async ({ getLocalTestPath, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js index 4e9cf0d01004..7e662b55c333 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js @@ -1 +1 @@ -fetch('http://localhost:4200/0').then(fetch('http://localhost:4200/1').then(fetch('http://localhost:4200/2'))); +fetch('/0').then(fetch('/1').then(fetch('/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts index 120b36ec88db..f9f9af3ddb47 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts @@ -4,19 +4,16 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../../utils/helpers'; sentryTest( - 'should attach `sentry-trace` and `baggage` header to request matching default tracePropagationTargets', - async ({ getLocalTestPath, page }) => { + 'should attach `sentry-trace` and `baggage` header to same-origin requests when no tracePropagationTargets are defined', + async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); const requests = ( - await Promise.all([ - page.goto(url), - Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://localhost:4200/${idx}`))), - ]) + await Promise.all([page.goto(url), Promise.all([0, 1, 2].map(idx => page.waitForRequest(`**/${idx}`)))]) )[1]; expect(requests).toHaveLength(3); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js index ce4e0c4ad7f7..83076460599f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js @@ -1,10 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts index 116319259101..6739b7ce3621 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts @@ -4,19 +4,16 @@ import { sentryTest } from '../../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../../utils/helpers'; sentryTest( - 'should not attach `sentry-trace` and `baggage` header to request not matching default tracePropagationTargets', - async ({ getLocalTestPath, page }) => { + 'should not attach `sentry-trace` and `baggage` header to cross-origin requests when no tracePropagationTargets are defined', + async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname }); const requests = ( - await Promise.all([ - page.goto(url), - Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))), - ]) + await Promise.all([page.goto(url), Promise.all([0, 1, 2].map(idx => page.waitForRequest(`**/${idx}`)))]) )[1]; expect(requests).toHaveLength(3); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 4e9369707eca..15c5adee2cc4 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -62,7 +62,6 @@ export { browserTracingIntegration, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, - DEFAULT_TRACE_PROPAGATION_TARGETS, } from '@sentry-internal/tracing'; export type { RequestInstrumentationOptions } from '@sentry-internal/tracing'; export { diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index a37bb91e5acb..fd155705a885 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,10 +1,6 @@ import { addEventProcessor, applySdkMetadata, hasTracingEnabled, setTag } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; -import { - DEFAULT_TRACE_PROPAGATION_TARGETS, - getDefaultIntegrations as getReactDefaultIntegrations, - init as reactInit, -} from '@sentry/react'; +import { getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit } from '@sentry/react'; import type { EventProcessor, Integration } from '@sentry/types'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; @@ -28,17 +24,6 @@ declare const __SENTRY_TRACING__: boolean; export function init(options: BrowserOptions): void { const opts = { environment: getVercelEnv(true) || process.env.NODE_ENV, - - tracePropagationTargets: - process.env.NODE_ENV === 'development' - ? [ - // Will match any URL that contains "localhost" but not "webpack.hot-update.json" - The webpack dev-server - // has cors and it doesn't like extra headers when it's accessed from a different URL. - // TODO(v8): Ideally we rework our tracePropagationTargets logic so this hack won't be necessary anymore (see issue #9764) - /^(?=.*localhost)(?!.*webpack\.hot-update\.json).*/, - /^\/(?!\/)/, - ] - : [...DEFAULT_TRACE_PROPAGATION_TARGETS, /^(api\/)/], defaultIntegrations: getDefaultIntegrations(options), ...options, } satisfies BrowserOptions; diff --git a/packages/tracing-internal/src/browser/request.ts b/packages/tracing-internal/src/browser/request.ts index e722f4be6a85..5bcea7eac108 100644 --- a/packages/tracing-internal/src/browser/request.ts +++ b/packages/tracing-internal/src/browser/request.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getClient, @@ -26,8 +25,7 @@ import { import { instrumentFetchRequest } from '../common/fetch'; import { addPerformanceInstrumentationHandler } from './instrument'; - -export const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\/(?!\/)/]; +import { WINDOW } from './types'; /** Options for Request Instrumentation */ export interface RequestInstrumentationOptions { @@ -35,9 +33,30 @@ export interface RequestInstrumentationOptions { * List of strings and/or Regular Expressions used to determine which outgoing requests will have `sentry-trace` and `baggage` * headers attached. * - * Default: ['localhost', /^\//] + * **Default:** If this option is not provided, tracing headers will be attached to all outgoing requests. + * If you are using a browser SDK, by default, tracing headers will only be attached to outgoing requests to the same origin. + * + * **Disclaimer:** Carelessly setting this option in browser environments may result into CORS errors! + * Only attach tracing headers to requests to the same origin, or to requests to services you can control CORS headers of. + * Cross-origin requests, meaning requests to a different domain, for example a request to `https://api.example.com/` while you're on `https://example.com/`, take special care. + * If you are attaching headers to cross-origin requests, make sure the backend handling the request returns a `"Access-Control-Allow-Headers: sentry-trace, baggage"` header to ensure your requests aren't blocked. + * + * If you provide a `tracePropagationTargets` array, the entries you provide will be matched against the entire URL of the outgoing request. + * If you are using a browser SDK, the entries will also be matched against the pathname of the outgoing requests. + * This is so you can have matchers for relative requests, for example, `/^\/api/` if you want to trace requests to your `/api` routes on the same domain. + * + * If any of the two match any of the provided values, tracing headers will be attached to the outgoing request. + * Both, the string values, and the RegExes you provide in the array will match if they partially match the URL or pathname. + * + * Examples: + * - `tracePropagationTargets: [/^\/api/]` and request to `https://same-origin.com/api/posts`: + * - Tracing headers will be attached because the request is sent to the same origin and the regex matches the pathname "/api/posts". + * - `tracePropagationTargets: [/^\/api/]` and request to `https://different-origin.com/api/posts`: + * - Tracing headers will not be attached because the pathname will only be compared when the request target lives on the same origin. + * - `tracePropagationTargets: [/^\/api/, 'https://external-api.com']` and request to `https://external-api.com/v1/data`: + * - Tracing headers will be attached because the request URL matches the string `'https://external-api.com'`. */ - tracePropagationTargets: Array; + tracePropagationTargets?: Array; /** * Flag to disable patching all together for fetch requests. @@ -73,7 +92,6 @@ export const defaultRequestInstrumentationOptions: RequestInstrumentationOptions traceFetch: true, traceXHR: true, enableHTTPTimings: true, - tracePropagationTargets: DEFAULT_TRACE_PROPAGATION_TARGETS, }; /** Registers span creators for xhr and fetch requests */ @@ -207,11 +225,48 @@ function resourceTimingEntryToSpanData(resourceTiming: PerformanceResourceTiming /** * A function that determines whether to attach tracing headers to a request. - * This was extracted from `instrumentOutgoingRequests` to make it easier to test shouldAttachHeaders. - * We only export this fuction for testing purposes. + * We only export this function for testing purposes. */ -export function shouldAttachHeaders(url: string, tracePropagationTargets: (string | RegExp)[] | undefined): boolean { - return stringMatchesSomePattern(url, tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS); +export function shouldAttachHeaders( + targetUrl: string, + tracePropagationTargets: (string | RegExp)[] | undefined, +): boolean { + // window.location.href not being defined is an edge case in the browser but we need to handle it. + // Potentially dangerous situations where it may not be defined: Browser Extensions, Web Workers, patching of the location obj + const href: string | undefined = WINDOW.location && WINDOW.location.href; + + if (!href) { + // If there is no window.location.origin, we default to only attaching tracing headers to relative requests, i.e. ones that start with `/` + // BIG DISCLAIMER: Users can call URLs with a double slash (fetch("//example.com/api")), this is a shorthand for "send to the same protocol", + // so we need a to exclude those requests, because they might be cross origin. + const isRelativeSameOriginRequest = !!targetUrl.match(/^\/(?!\/)/); + if (!tracePropagationTargets) { + return isRelativeSameOriginRequest; + } else { + return stringMatchesSomePattern(targetUrl, tracePropagationTargets); + } + } else { + let resolvedUrl; + let currentOrigin; + + // URL parsing may fail, we default to not attaching trace headers in that case. + try { + resolvedUrl = new URL(targetUrl, href); + currentOrigin = new URL(href).origin; + } catch (e) { + return false; + } + + const isSameOriginRequest = resolvedUrl.origin === currentOrigin; + if (!tracePropagationTargets) { + return isSameOriginRequest; + } else { + return ( + stringMatchesSomePattern(resolvedUrl.toString(), tracePropagationTargets) || + (isSameOriginRequest && stringMatchesSomePattern(resolvedUrl.pathname, tracePropagationTargets)) + ); + } + } } /** @@ -219,7 +274,6 @@ export function shouldAttachHeaders(url: string, tracePropagationTargets: (strin * * @returns Span if a span was created, otherwise void. */ -// eslint-disable-next-line complexity export function xhrCallback( handlerData: HandlerDataXhr, shouldCreateSpan: (url: string) => boolean, diff --git a/packages/tracing-internal/src/index.ts b/packages/tracing-internal/src/index.ts index 47a26c8a4d92..4f09ca6a2e96 100644 --- a/packages/tracing-internal/src/index.ts +++ b/packages/tracing-internal/src/index.ts @@ -32,5 +32,3 @@ export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './commo export type { RequestInstrumentationOptions } from './browser'; export { addExtensionMethods } from './extensions'; - -export { DEFAULT_TRACE_PROPAGATION_TARGETS } from './browser/request'; diff --git a/packages/tracing-internal/test/browser/request.test.ts b/packages/tracing-internal/test/browser/request.test.ts index 426325072984..8855203ce136 100644 --- a/packages/tracing-internal/test/browser/request.test.ts +++ b/packages/tracing-internal/test/browser/request.test.ts @@ -2,6 +2,7 @@ import * as utils from '@sentry/utils'; import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/browser/request'; +import { WINDOW } from '../../src/browser/types'; beforeAll(() => { // @ts-expect-error need to override global Request because it's not in the jest environment (even with an @@ -107,23 +108,262 @@ describe('shouldAttachHeaders', () => { }); }); - describe('should fall back to defaults if no options are specified', () => { + describe('with no defined `tracePropagationTargets`', () => { + let originalWindowLocation: Location; + + beforeAll(() => { + originalWindowLocation = WINDOW.location; + // @ts-expect-error We are missing some fields of the Origin interface but it doesn't matter for these tests. + WINDOW.location = new URL('https://my-origin.com'); + }); + + afterAll(() => { + WINDOW.location = originalWindowLocation; + }); + it.each([ - '/api/test', - 'http://localhost:3000/test', - 'http://somewhere.com/test/localhost/123', - 'http://somewhere.com/test?url=localhost:3000&test=123', - '//localhost:3000/test', + 'https://my-origin.com', + 'https://my-origin.com/test', '/', - ])('return `true` for urls matching defaults (%s)', url => { + '/api/test', + '//my-origin.com/', + '//my-origin.com/test', + 'foobar', // this is a relative request + 'not-my-origin.com', // this is a relative request + 'not-my-origin.com/api/test', // this is a relative request + ])('should return `true` for same-origin URLs (%s)', url => { expect(shouldAttachHeaders(url, undefined)).toBe(true); }); - it.each(['notmydoman/api/test', 'example.com', '//example.com'])( - 'return `false` for urls not matching defaults (%s)', - url => { - expect(shouldAttachHeaders(url, undefined)).toBe(false); + it.each([ + 'http://my-origin.com', // wrong protocol + 'http://my-origin.com/api', // wrong protocol + 'http://localhost:3000', + '//not-my-origin.com/test', + 'https://somewhere.com/test/localhost/123', + 'https://somewhere.com/test?url=https://my-origin.com', + '//example.com', + ])('should return `false` for cross-origin URLs (%s)', url => { + expect(shouldAttachHeaders(url, undefined)).toBe(false); + }); + }); + + describe('with `tracePropagationTargets`', () => { + let originalWindowLocation: Location; + + beforeAll(() => { + originalWindowLocation = WINDOW.location; + // @ts-expect-error We are missing some fields of the Origin interface but it doesn't matter for these tests. + WINDOW.location = new URL('https://my-origin.com/api/my-route'); + }); + + afterAll(() => { + WINDOW.location = originalWindowLocation; + }); + + it.each([ + ['https://my-origin.com', /^\//, true], // pathname defaults to "/" + ['https://my-origin.com/', /^\//, true], + ['https://not-my-origin.com', /^\//, false], // pathname does not match in isolation for cross origin + ['https://not-my-origin.com/', /^\//, false], // pathname does not match in isolation for cross origin + + ['http://my-origin.com/', /^\//, false], // different protocol than origin + + ['//my-origin.com', /^\//, true], // pathname defaults to "/" + ['//my-origin.com/', /^\//, true], // matches pathname + ['//not-my-origin.com', /^\//, false], + ['//not-my-origin.com/', /^\//, false], // different origin should not match pathname + + ['//my-origin.com', /^https:/, true], + ['//not-my-origin.com', /^https:/, true], + ['//my-origin.com', /^http:/, false], + ['//not-my-origin.com', /^http:/, false], + + ['https://my-origin.com/api', /^\/api/, true], + ['https://not-my-origin.com/api', /^\/api/, false], // different origin should not match pathname in isolation + + ['https://my-origin.com/api', /api/, true], + ['https://not-my-origin.com/api', /api/, true], + + ['/api', /^\/api/, true], // matches pathname + ['/api', /\/\/my-origin\.com\/api/, true], // matches full url + ['foobar', /\/foobar/, true], // matches full url + ['foobar', /^\/api\/foobar/, true], // full url match + ['some-url.com', /\/some-url\.com/, true], + ['some-url.com', /^\/some-url\.com/, false], // does not match pathname or full url + ['some-url.com', /^\/api\/some-url\.com/, true], // matches pathname + + ['/api', /^http:/, false], + ['foobar', /^http:/, false], + ['some-url.com', /^http:/, false], + ['/api', /^https:/, true], + ['foobar', /^https:/, true], + ['some-url.com', /^https:/, true], + + ['https://my-origin.com', 'my-origin', true], + ['https://not-my-origin.com', 'my-origin', true], + ['https://my-origin.com', 'not-my-origin', false], + ['https://not-my-origin.com', 'not-my-origin', true], + + ['https://my-origin.com', 'https', true], + ['https://my-origin.com', 'http', true], // partially matches https + ['//my-origin.com', 'https', true], + ['//my-origin.com', 'http', true], // partially matches https + + ['/api', '/api', true], + ['api', '/api', true], // full url match + ['https://not-my-origin.com/api', 'api', true], + ['https://my-origin.com?my-query', 'my-query', true], + ['https://not-my-origin.com?my-query', 'my-query', true], + ])( + 'for url %p and tracePropagationTarget %p on page "https://my-origin.com/api/my-route" should return %p', + (url, matcher, result) => { + expect(shouldAttachHeaders(url, [matcher])).toBe(result); }, ); }); + + it.each([ + 'https://my-origin.com', + 'https://my-origin.com/', + 'https://not-my-origin.com', + 'https://not-my-origin.com/', + 'http://my-origin.com/', + '//my-origin.com', + '//my-origin.com/', + '//not-my-origin.com', + '//not-my-origin.com/', + '//my-origin.com', + '//not-my-origin.com', + '//my-origin.com', + '//not-my-origin.com', + 'https://my-origin.com/api', + 'https://not-my-origin.com/api', + 'https://my-origin.com/api', + 'https://not-my-origin.com/api', + '/api', + '/api', + 'foobar', + 'foobar', + 'some-url.com', + 'some-url.com', + 'some-url.com', + '/api', + 'foobar', + 'some-url.com', + '/api', + 'foobar', + 'some-url.com', + 'https://my-origin.com', + 'https://not-my-origin.com', + 'https://my-origin.com', + 'https://not-my-origin.com', + 'https://my-origin.com', + 'https://my-origin.com', + '//my-origin.com', + '//my-origin.com', + '/api', + 'api', + 'https://not-my-origin.com/api', + 'https://my-origin.com?my-query', + 'https://not-my-origin.com?my-query', + ])('should return false for everything if tracePropagationTargets are empty (%p)', url => { + expect(shouldAttachHeaders(url, [])).toBe(false); + }); + + describe('when window.location.href is not available', () => { + let originalWindowLocation: Location; + + beforeAll(() => { + originalWindowLocation = WINDOW.location; + // @ts-expect-error We need to simulate an edge-case + WINDOW.location = undefined; + }); + + afterAll(() => { + WINDOW.location = originalWindowLocation; + }); + + describe('with no defined `tracePropagationTargets`', () => { + it.each([ + ['https://my-origin.com', false], + ['https://my-origin.com/test', false], + ['/', true], + ['/api/test', true], + ['//my-origin.com/', false], + ['//my-origin.com/test', false], + ['//not-my-origin.com/test', false], + ['foobar', false], + ['not-my-origin.com', false], + ['not-my-origin.com/api/test', false], + ['http://my-origin.com', false], + ['http://my-origin.com/api', false], + ['http://localhost:3000', false], + ['https://somewhere.com/test/localhost/123', false], + ['https://somewhere.com/test?url=https://my-origin.com', false], + ])('for URL %p should return %p', (url, expectedResult) => { + expect(shouldAttachHeaders(url, undefined)).toBe(expectedResult); + }); + }); + + // Here we should only quite literally match the provided urls + it.each([ + ['https://my-origin.com', /^\//, false], + ['https://my-origin.com/', /^\//, false], + ['https://not-my-origin.com', /^\//, false], + ['https://not-my-origin.com/', /^\//, false], + + ['http://my-origin.com/', /^\//, false], + + // It is arguably bad that these match, at the same time, these targets are very unusual in environments without location. + ['//my-origin.com', /^\//, true], + ['//my-origin.com/', /^\//, true], + ['//not-my-origin.com', /^\//, true], + ['//not-my-origin.com/', /^\//, true], + + ['//my-origin.com', /^https:/, false], + ['//not-my-origin.com', /^https:/, false], + ['//my-origin.com', /^http:/, false], + ['//not-my-origin.com', /^http:/, false], + + ['https://my-origin.com/api', /^\/api/, false], + ['https://not-my-origin.com/api', /^\/api/, false], + + ['https://my-origin.com/api', /api/, true], + ['https://not-my-origin.com/api', /api/, true], + + ['/api', /^\/api/, true], + ['/api', /\/\/my-origin\.com\/api/, false], + ['foobar', /\/foobar/, false], + ['foobar', /^\/api\/foobar/, false], + ['some-url.com', /\/some-url\.com/, false], + ['some-url.com', /^\/some-url\.com/, false], + ['some-url.com', /^\/api\/some-url\.com/, false], + + ['/api', /^http:/, false], + ['foobar', /^http:/, false], + ['some-url.com', /^http:/, false], + ['/api', /^https:/, false], + ['foobar', /^https:/, false], + ['some-url.com', /^https:/, false], + + ['https://my-origin.com', 'my-origin', true], + ['https://not-my-origin.com', 'my-origin', true], + ['https://my-origin.com', 'not-my-origin', false], + ['https://not-my-origin.com', 'not-my-origin', true], + + ['https://my-origin.com', 'https', true], + ['https://my-origin.com', 'http', true], + ['//my-origin.com', 'https', false], + ['//my-origin.com', 'http', false], + + ['/api', '/api', true], + ['api', '/api', false], + ['https://not-my-origin.com/api', 'api', true], + ['https://my-origin.com?my-query', 'my-query', true], + ['https://not-my-origin.com?my-query', 'my-query', true], + ])('for url %p and tracePropagationTarget %p should return %p', (url, matcher, result) => { + expect(shouldAttachHeaders(url, [matcher])).toBe(result); + }); + }); }); diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index 74bcfad771f4..bcc3498a1e59 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -223,22 +223,31 @@ export interface ClientOptions; /** - * List of strings/regex controlling to which outgoing requests - * the SDK will attach tracing headers. + * List of strings and/or Regular Expressions used to determine which outgoing requests will have `sentry-trace` and `baggage` + * headers attached. * - * By default the SDK will attach those headers to all requests to localhost - * and same origin. If this option is provided, the SDK will match the - * request URL of outgoing requests against the items in this - * array, and only attach tracing headers if a match was found. + * **Default:** If this option is not provided, tracing headers will be attached to all outgoing requests. + * If you are using a browser SDK, by default, tracing headers will only be attached to outgoing requests to the same origin. * - * @example - * ```js - * Sentry.init({ - * tracePropagationTargets: ['api.site.com'], - * }); - * ``` + * **Disclaimer:** Carelessly setting this option in browser environments may result into CORS errors! + * Only attach tracing headers to requests to the same origin, or to requests to services you can control CORS headers of. + * Cross-origin requests, meaning requests to a different domain, for example a request to `https://api.example.com/` while you're on `https://example.com/`, take special care. + * If you are attaching headers to cross-origin requests, make sure the backend handling the request returns a `"Access-Control-Allow-Headers: sentry-trace, baggage"` header to ensure your requests aren't blocked. * - * Default: ['localhost', /^\//] {@see DEFAULT_TRACE_PROPAGATION_TARGETS} + * If you provide a `tracePropagationTargets` array, the entries you provide will be matched against the entire URL of the outgoing request. + * If you are using a browser SDK, the entries will also be matched against the pathname of the outgoing requests. + * This is so you can have matchers for relative requests, for example, `/^\/api/` if you want to trace requests to your `/api` routes on the same domain. + * + * If any of the two match any of the provided values, tracing headers will be attached to the outgoing request. + * Both, the string values, and the RegExes you provide in the array will match if they partially match the URL or pathname. + * + * Examples: + * - `tracePropagationTargets: [/^\/api/]` and request to `https://same-origin.com/api/posts`: + * - Tracing headers will be attached because the request is sent to the same origin and the regex matches the pathname "/api/posts". + * - `tracePropagationTargets: [/^\/api/]` and request to `https://different-origin.com/api/posts`: + * - Tracing headers will not be attached because the pathname will only be compared when the request target lives on the same origin. + * - `tracePropagationTargets: [/^\/api/, 'https://external-api.com']` and request to `https://external-api.com/v1/data`: + * - Tracing headers will be attached because the request URL matches the string `'https://external-api.com'`. */ tracePropagationTargets?: TracePropagationTargets; From 551aa86ff687b6412b9342a4f84f459f9e90e39c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 15 Feb 2024 17:11:58 +0100 Subject: [PATCH 079/173] ref: Remove usage of `makeMain` in tests (#10681) This removes most (but not all) usage of `makeMain` in our own tests. Some node tests remain for now, most of these will eventually be removed, so I figured no need to change them right now. --- .../bun/test/integrations/bunserver.test.ts | 9 +- packages/core/test/lib/integration.test.ts | 25 ++-- packages/core/test/lib/scope.test.ts | 9 +- packages/core/test/lib/sdk.test.ts | 10 +- .../tracing/dynamicSamplingContext.test.ts | 13 +- packages/core/test/lib/tracing/errors.test.ts | 9 +- packages/core/test/lib/tracing/trace.test.ts | 129 ++++++++++-------- .../test/propagator.test.ts | 15 +- .../test/spanprocessor.test.ts | 80 ++--------- .../opentelemetry/test/propagator.test.ts | 12 +- packages/sveltekit/test/server/handle.test.ts | 11 +- .../test/browser/backgroundtab.test.ts | 14 +- .../test/browser/browsertracing.test.ts | 56 ++++---- packages/tracing/test/idletransaction.test.ts | 74 ++++++---- packages/tracing/test/span.test.ts | 52 +++++-- 15 files changed, 242 insertions(+), 276 deletions(-) diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index fd55e56ff50f..b51fb649fd25 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -1,5 +1,5 @@ import { beforeAll, beforeEach, describe, expect, test } from 'bun:test'; -import { Hub, getDynamicSamplingContextFromSpan, makeMain, spanIsSampled, spanToJSON } from '@sentry/core'; +import { getDynamicSamplingContextFromSpan, setCurrentClient, spanIsSampled, spanToJSON } from '@sentry/core'; import { BunClient } from '../../src/client'; import { instrumentBunServe } from '../../src/integrations/bunserver'; @@ -9,7 +9,6 @@ import { getDefaultBunClientOptions } from '../helpers'; const DEFAULT_PORT = 22114; describe('Bun Serve Integration', () => { - let hub: Hub; let client: BunClient; beforeAll(() => { @@ -19,10 +18,8 @@ describe('Bun Serve Integration', () => { beforeEach(() => { const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true }); client = new BunClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); test('generates a transaction around a request', async () => { diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index a86c83903152..46c7eba84e34 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -1,7 +1,7 @@ import type { Integration, Options } from '@sentry/types'; import { logger } from '@sentry/utils'; +import { getCurrentScope } from '../../src/currentScopes'; -import { Hub, makeMain } from '../../src/hub'; import { addIntegration, convertIntegrationFnToClass, @@ -9,6 +9,7 @@ import { installedIntegrations, setupIntegration, } from '../../src/integration'; +import { setCurrentClient } from '../../src/sdk'; import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; function getTestClient(): TestClient { @@ -617,10 +618,7 @@ describe('addIntegration', () => { } const client = getTestClient(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); const integration = new CustomIntegration(); addIntegration(integration); @@ -636,10 +634,7 @@ describe('addIntegration', () => { setupOnce = jest.fn(); } - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + getCurrentScope().setClient(undefined); const integration = new CustomIntegration(); addIntegration(integration); @@ -662,10 +657,8 @@ describe('addIntegration', () => { } const client = getTestClient(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); const integration = new CustomIntegration(); addIntegration(integration); @@ -686,10 +679,8 @@ describe('addIntegration', () => { } const client = getTestClient(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); const integration1 = new CustomIntegration(); const integration2 = new CustomIntegration(); diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 7ba2bef91a08..27609f8214bf 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -1,13 +1,12 @@ import type { Attachment, Breadcrumb, Client, Event } from '@sentry/types'; import { - Hub, addTracingExtensions, applyScopeDataToEvent, getActiveSpan, getCurrentScope, getGlobalScope, getIsolationScope, - makeMain, + setCurrentClient, setGlobalScope, spanToJSON, startInactiveSpan, @@ -555,10 +554,8 @@ describe('withActiveSpan()', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ enableTracing: true }); const client = new TestClient(options); - const scope = new Scope(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - makeMain(hub); // eslint-disable-line deprecation/deprecation + setCurrentClient(client); + client.init(); }); it('should set the active span within the callback', () => { diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index c56bb8b11620..ad3551b783da 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -1,4 +1,4 @@ -import { Hub, captureCheckIn, makeMain, setCurrentClient } from '@sentry/core'; +import { captureCheckIn, getCurrentScope, setCurrentClient } from '@sentry/core'; import type { Client, Integration, IntegrationFnResult } from '@sentry/types'; import { installedIntegrations } from '../../src/integration'; @@ -86,13 +86,6 @@ describe('SDK', () => { }); describe('captureCheckIn', () => { - afterEach(function () { - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - }); - it('returns an id when client is defined', () => { const client = { captureCheckIn: () => 'some-id-wasd-1234', @@ -103,6 +96,7 @@ describe('captureCheckIn', () => { }); it('returns an id when client is undefined', () => { + getCurrentScope().setClient(undefined); expect(captureCheckIn({ monitorSlug: 'gogogo', status: 'in_progress' })).toStrictEqual(expect.any(String)); }); }); diff --git a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts index db79c8850200..802769b3ec1f 100644 --- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts +++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts @@ -1,18 +1,19 @@ import type { TransactionSource } from '@sentry/types'; -import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, makeMain } from '../../../src'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + setCurrentClient, +} from '../../../src'; import { Transaction, getDynamicSamplingContextFromSpan, startInactiveSpan } from '../../../src/tracing'; import { addTracingExtensions } from '../../../src/tracing'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; describe('getDynamicSamplingContextFromSpan', () => { - let hub: Hub; beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, release: '1.0.1' }); const client = new TestClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); addTracingExtensions(); }); diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index 35a80f60265a..e448f3ccb240 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -1,5 +1,5 @@ import { BrowserClient } from '@sentry/browser'; -import { Hub, addTracingExtensions, makeMain, spanToJSON, startInactiveSpan, startSpan } from '@sentry/core'; +import { addTracingExtensions, setCurrentClient, spanToJSON, startInactiveSpan, startSpan } from '@sentry/core'; import type { HandlerDataError, HandlerDataUnhandledRejection } from '@sentry/types'; import { getDefaultBrowserClientOptions } from '../../../../tracing/test/testutils'; @@ -34,10 +34,9 @@ describe('registerErrorHandlers()', () => { mockAddGlobalErrorInstrumentationHandler.mockClear(); mockAddGlobalUnhandledRejectionInstrumentationHandler.mockClear(); const options = getDefaultBrowserClientOptions({ enableTracing: true }); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(new BrowserClient(options)); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new BrowserClient(options); + setCurrentClient(client); + client.init(); }); it('registers error instrumentation', () => { diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index a3276952fd48..ea4e9b2d43ca 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,10 +1,11 @@ import type { Span as SpanType } from '@sentry/types'; import { - Hub, SEMANTIC_ATTRIBUTE_SENTRY_OP, addTracingExtensions, + getCurrentHub, getCurrentScope, - makeMain, + getGlobalScope, + getIsolationScope, setCurrentClient, spanToJSON, withScope, @@ -28,17 +29,24 @@ const enum Type { Async = 'async', } -let hub: Hub; let client: TestClient; describe('startSpan', () => { beforeEach(() => { + addTracingExtensions(); + + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); client = new TestClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); + }); + + afterEach(() => { + jest.clearAllMocks(); }); describe.each([ @@ -70,7 +78,9 @@ describe('startSpan', () => { // @ts-expect-error we are force overriding the transaction return to be undefined // The `startTransaction` types are actually wrong - it can return undefined // if tracingExtensions are not enabled - jest.spyOn(hub, 'startTransaction').mockReturnValue(undefined); + // eslint-disable-next-line deprecation/deprecation + jest.spyOn(getCurrentHub(), 'startTransaction').mockImplementationOnce(() => undefined); + try { const result = await startSpan({ name: 'GET users/[id]' }, () => { return callback(); @@ -280,7 +290,7 @@ describe('startSpan', () => { expect(spanToJSON(_span!).timestamp).toBeDefined(); }); - it('allows to pass a `startTime`', () => { + it('allows to pass a `startTime` yyy', () => { const start = startSpan({ name: 'outer', startTime: [1234, 0] }, span => { return spanToJSON(span!).start_timestamp; }); @@ -368,6 +378,7 @@ describe('startSpan', () => { const options = getDefaultTestClientOptions({ tracesSampler }); client = new TestClient(options); setCurrentClient(client); + client.init(); startSpan( { name: 'outer', attributes: { test1: 'aa', test2: 'aa' }, data: { test1: 'bb', test3: 'bb' } }, @@ -390,20 +401,17 @@ describe('startSpan', () => { }); it('includes the scope at the time the span was started when finished', async () => { - const transactionEventPromise = new Promise(resolve => { - setCurrentClient( - new TestClient( - getDefaultTestClientOptions({ - dsn: 'https://username@domain/123', - tracesSampleRate: 1, - beforeSendTransaction(event) { - resolve(event); - return event; - }, - }), - ), - ); - }); + const beforeSendTransaction = jest.fn(event => event); + + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://username@domain/123', + tracesSampleRate: 1, + beforeSendTransaction, + }), + ); + setCurrentClient(client); + client.init(); withScope(scope1 => { scope1.setTag('scope', 1); @@ -415,11 +423,17 @@ describe('startSpan', () => { }); }); - expect(await transactionEventPromise).toMatchObject({ - tags: { - scope: 1, - }, - }); + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(1); + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + tags: expect.objectContaining({ + scope: 1, + }), + }), + expect.anything(), + ); }); }); @@ -427,10 +441,8 @@ describe('startSpanManual', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('creates & finishes span', async () => { @@ -538,10 +550,8 @@ describe('startInactiveSpan', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('creates & finishes span', async () => { @@ -627,20 +637,17 @@ describe('startInactiveSpan', () => { }); it('includes the scope at the time the span was started when finished', async () => { - const transactionEventPromise = new Promise(resolve => { - setCurrentClient( - new TestClient( - getDefaultTestClientOptions({ - dsn: 'https://username@domain/123', - tracesSampleRate: 1, - beforeSendTransaction(event) { - resolve(event); - return event; - }, - }), - ), - ); - }); + const beforeSendTransaction = jest.fn(event => event); + + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://username@domain/123', + tracesSampleRate: 1, + beforeSendTransaction, + }), + ); + setCurrentClient(client); + client.init(); let span: SpanType | undefined; @@ -654,11 +661,17 @@ describe('startInactiveSpan', () => { span?.end(); }); - expect(await transactionEventPromise).toMatchObject({ - tags: { - scope: 1, - }, - }); + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(1); + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + tags: expect.objectContaining({ + scope: 1, + }), + }), + expect.anything(), + ); }); }); @@ -666,10 +679,8 @@ describe('continueTrace', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); client = new TestClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('works without trace & baggage data', () => { diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts index c723b5bc6f0b..6067b5d7e90d 100644 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ b/packages/opentelemetry-node/test/propagator.test.ts @@ -7,8 +7,8 @@ import { trace, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { Hub, Transaction, addTracingExtensions, makeMain } from '@sentry/core'; -import type { TransactionContext } from '@sentry/types'; +import { Transaction, addTracingExtensions, getCurrentHub, setCurrentClient } from '@sentry/core'; +import type { Client, TransactionContext } from '@sentry/types'; import { SENTRY_BAGGAGE_HEADER, @@ -46,12 +46,9 @@ describe('SentryPropagator', () => { publicKey: 'abc', }), emit: () => {}, - }; - // @ts-expect-error Use mock client for unit tests - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + } as unknown as Client; + + setCurrentClient(client); afterEach(() => { SPAN_MAP.clear(); @@ -64,7 +61,7 @@ describe('SentryPropagator', () => { function createTransactionAndMaybeSpan(type: PerfType, transactionContext: TransactionContext) { // eslint-disable-next-line deprecation/deprecation - const transaction = new Transaction(transactionContext, hub); + const transaction = new Transaction(transactionContext, getCurrentHub()); setSentrySpan(transaction.spanContext().spanId, transaction); if (type === PerfType.Span) { // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 072ba35881f8..768c12369130 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -5,15 +5,8 @@ import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import type { SpanStatusType } from '@sentry/core'; -import { - Hub, - Span as SentrySpan, - Transaction, - addTracingExtensions, - createTransport, - makeMain, - spanToJSON, -} from '@sentry/core'; +import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; +import { Span as SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; import { NodeClient } from '@sentry/node'; import { resolvedSyncPromise } from '@sentry/utils'; @@ -36,7 +29,6 @@ beforeAll(() => { }); describe('SentrySpanProcessor', () => { - let hub: Hub; let client: NodeClient; let provider: NodeTracerProvider; let spanProcessor: SentrySpanProcessor; @@ -46,10 +38,8 @@ describe('SentrySpanProcessor', () => { SPAN_MAP.clear(); client = new NodeClient(DEFAULT_NODE_CLIENT_OPTIONS); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); spanProcessor = new SentrySpanProcessor(); provider = new NodeTracerProvider({ @@ -130,7 +120,7 @@ describe('SentrySpanProcessor', () => { expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); // eslint-disable-next-line deprecation/deprecation - expect(hub.getScope().getSpan()).toBeUndefined(); + expect(getCurrentScope().getSpan()).toBeUndefined(); child.end(endTime); @@ -174,7 +164,7 @@ describe('SentrySpanProcessor', () => { expect(sentrySpan?.parentSpanId).toEqual(parentOtelSpan.spanContext().spanId); // eslint-disable-next-line deprecation/deprecation - expect(hub.getScope().getSpan()).toBeUndefined(); + expect(getCurrentScope().getSpan()).toBeUndefined(); child.end(endTime); @@ -965,10 +955,8 @@ describe('SentrySpanProcessor', () => { return null; }, }); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); // Need to register the spanprocessor again spanProcessor = new SentrySpanProcessor(); @@ -984,8 +972,7 @@ describe('SentrySpanProcessor', () => { tracer.startActiveSpan('GET /users', parentOtelSpan => { tracer.startActiveSpan('SELECT * FROM users;', child => { - // eslint-disable-next-line deprecation/deprecation - hub.captureException(new Error('oh nooooo!')); + captureException(new Error('oh nooooo!')); otelSpan = child as OtelSpan; child.end(); }); @@ -1013,11 +1000,8 @@ describe('SentrySpanProcessor', () => { return null; }, }); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - + setCurrentClient(client); + client.init(); const tracer = provider.getTracer('default'); tracer.startActiveSpan('GET /users', parentOtelSpan => { @@ -1043,48 +1027,6 @@ describe('SentrySpanProcessor', () => { trace_id: otelSpan.spanContext().traceId, }); }); - - // Regression test for https://github.com/getsentry/sentry-javascript/issues/7538 - // Since otel context does not map to when Sentry hubs are cloned - // we can't rely on the original hub at transaction creation to contain all - // the scope information we want. Let's test to make sure that the information is - // grabbed from the new hub. - it('handles when a different hub creates the transaction', () => { - let sentryTransaction: any; - - client = new NodeClient({ - ...DEFAULT_NODE_CLIENT_OPTIONS, - tracesSampleRate: 1.0, - }); - - client.on('finishTransaction', transaction => { - sentryTransaction = transaction; - }); - - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - - // eslint-disable-next-line deprecation/deprecation - const newHub = new Hub(client, hub.getScope().clone()); - // eslint-disable-next-line deprecation/deprecation - newHub.getScope().setTag('foo', 'bar'); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - // eslint-disable-next-line deprecation/deprecation - makeMain(newHub); - child.end(); - }); - - parentOtelSpan.end(); - }); - - expect(sentryTransaction._hub.getScope()._tags.foo).toEqual('bar'); - }); }); // OTEL expects a custom date format diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index fc67f4bb3030..4129b6cfd0e0 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -7,8 +7,8 @@ import { trace, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { Hub, addTracingExtensions, makeMain } from '@sentry/core'; -import type { PropagationContext } from '@sentry/types'; +import { addTracingExtensions, setCurrentClient } from '@sentry/core'; +import type { Client, PropagationContext } from '@sentry/types'; import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER } from '../src/constants'; import { SentryPropagator } from '../src/propagator'; @@ -40,13 +40,9 @@ describe('SentryPropagator', () => { publicKey: 'abc', }), emit: () => {}, - }; + } as unknown as Client; - // @ts-expect-error Use mock client for unit tests - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); describe('with active span', () => { it.each([ diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index a90c70964a03..9bba3c99e68e 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,5 +1,5 @@ -import { Hub, addTracingExtensions, makeMain } from '@sentry/core'; -import { NodeClient } from '@sentry/node'; +import { addTracingExtensions } from '@sentry/core'; +import { NodeClient, setCurrentClient } from '@sentry/node'; import * as SentryNode from '@sentry/node'; import type { Transaction } from '@sentry/types'; import type { Handle } from '@sveltejs/kit'; @@ -81,7 +81,6 @@ function resolve( }; } -let hub: Hub; let client: NodeClient; beforeAll(() => { @@ -91,10 +90,8 @@ beforeAll(() => { beforeEach(() => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); mockCaptureException.mockClear(); }); diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index aa5889c89958..dd2eaec00a69 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -1,4 +1,5 @@ -import { Hub, makeMain, spanToJSON, startSpan } from '@sentry/core'; +import { getCurrentScope } from '@sentry/core'; +import { setCurrentClient, spanToJSON, startSpan } from '@sentry/core'; import { JSDOM } from 'jsdom'; import { addExtensionMethods } from '../../../tracing/src'; @@ -8,17 +9,15 @@ import { TestClient } from '../utils/TestClient'; describe('registerBackgroundTabDetection', () => { let events: Record = {}; - let hub: Hub; beforeEach(() => { const dom = new JSDOM(); // @ts-expect-error need to override global document global.document = dom.window.document; const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(new TestClient(options)); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); // If we do not add extension methods, invoking hub.startTransaction returns undefined // eslint-disable-next-line deprecation/deprecation @@ -32,8 +31,7 @@ describe('registerBackgroundTabDetection', () => { afterEach(() => { events = {}; - // eslint-disable-next-line deprecation/deprecation - hub.getScope().setSpan(undefined); + getCurrentScope().clear(); }); it('does not create an event listener if global document is undefined', () => { diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index 9d4105dc415d..be16c8cf092e 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { Hub, TRACING_DEFAULTS, makeMain, setCurrentClient, spanToJSON } from '@sentry/core'; +import { TRACING_DEFAULTS, getClient, getCurrentHub, setCurrentClient, spanToJSON } from '@sentry/core'; import * as hubExtensions from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents, HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; @@ -56,12 +56,12 @@ beforeAll(() => { }); describe('BrowserTracing', () => { - let hub: Hub; beforeEach(() => { jest.useFakeTimers(); const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - hub = new Hub(new TestClient(options)); - makeMain(hub); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); document.head.innerHTML = ''; mockStartTrackingWebVitals.mockClear(); @@ -79,7 +79,7 @@ describe('BrowserTracing', () => { const instance = new BrowserTracing(_options); if (setup) { const processor = () => undefined; - instance.setupOnce(processor, () => hub); + instance.setupOnce(processor, () => getCurrentHub() as hubExtensions.Hub); } return instance; @@ -166,7 +166,7 @@ describe('BrowserTracing', () => { routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.name).toBe('a/path'); expect(transaction.op).toBe('pageload'); @@ -177,7 +177,7 @@ describe('BrowserTracing', () => { routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; const span = transaction.startChild(); const timestamp = timestampInSeconds(); @@ -194,7 +194,7 @@ describe('BrowserTracing', () => { beforeNavigate: mockBeforeNavigation, routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); @@ -206,7 +206,7 @@ describe('BrowserTracing', () => { beforeNavigate: mockBeforeNavigation, routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction.sampled).toBe(false); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); @@ -221,7 +221,7 @@ describe('BrowserTracing', () => { beforeNavigate: mockBeforeNavigation, routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.op).toBe('not-pageload'); @@ -237,7 +237,7 @@ describe('BrowserTracing', () => { beforeNavigate: mockBeforeNavigation, routingInstrumentation: customInstrumentRouting, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.name).toBe('newName'); expect(transaction.metadata.source).toBe('custom'); @@ -255,7 +255,7 @@ describe('BrowserTracing', () => { customStartTransaction({ name: 'a/path', op: 'pageload', metadata: { source: 'url' } }); }, }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.name).toBe('a/path'); expect(transaction.metadata.source).toBe('url'); @@ -296,7 +296,7 @@ describe('BrowserTracing', () => { it('is created by default', () => { createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); const mockFinish = jest.fn(); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; transaction.sendAutoFinishSignal(); transaction.end = mockFinish; @@ -311,7 +311,7 @@ describe('BrowserTracing', () => { it('can be a custom value', () => { createBrowserTracing(true, { idleTimeout: 2000, routingInstrumentation: customInstrumentRouting }); const mockFinish = jest.fn(); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; transaction.sendAutoFinishSignal(); transaction.end = mockFinish; @@ -325,7 +325,7 @@ describe('BrowserTracing', () => { it('calls `_collectWebVitals` if enabled', () => { createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; const span = transaction.startChild(); // activities = 1 span.end(); // activities = 0 @@ -340,7 +340,7 @@ describe('BrowserTracing', () => { const interval = 200; createBrowserTracing(true, { heartbeatInterval: interval, routingInstrumentation: customInstrumentRouting }); const mockFinish = jest.fn(); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; transaction.sendAutoFinishSignal(); transaction.end = mockFinish; @@ -359,7 +359,7 @@ describe('BrowserTracing', () => { describe('pageload transaction', () => { it('is created on setup on scope', () => { createBrowserTracing(true); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.op).toBe('pageload'); @@ -367,7 +367,7 @@ describe('BrowserTracing', () => { it('is not created if the option is false', () => { createBrowserTracing(true, { startTransactionOnPageLoad: false }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).not.toBeDefined(); }); }); @@ -381,18 +381,18 @@ describe('BrowserTracing', () => { createBrowserTracing(true); jest.runAllTimers(); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).not.toBeDefined(); }); it('is created on location change', () => { createBrowserTracing(true); - const transaction1 = getActiveTransaction(hub) as IdleTransaction; + const transaction1 = getActiveTransaction() as IdleTransaction; expect(transaction1.op).toBe('pageload'); expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); - const transaction2 = getActiveTransaction(hub) as IdleTransaction; + const transaction2 = getActiveTransaction() as IdleTransaction; expect(transaction2.op).toBe('navigation'); expect(spanToJSON(transaction1).timestamp).toBeDefined(); @@ -400,12 +400,12 @@ describe('BrowserTracing', () => { it('is not created if startTransactionOnLocationChange is false', () => { createBrowserTracing(true, { startTransactionOnLocationChange: false }); - const transaction1 = getActiveTransaction(hub) as IdleTransaction; + const transaction1 = getActiveTransaction() as IdleTransaction; expect(transaction1.op).toBe('pageload'); expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); - const transaction2 = getActiveTransaction(hub) as IdleTransaction; + const transaction2 = getActiveTransaction() as IdleTransaction; expect(transaction2.op).toBe('pageload'); }); }); @@ -443,14 +443,14 @@ describe('BrowserTracing', () => { describe('using the tag data', () => { beforeEach(() => { - hub.getClient()!.getOptions = () => { + getClient()!.getOptions = () => { return { release: '1.0.0', environment: 'production', } as ClientOptions; }; - hub.getClient()!.getDsn = () => { + getClient()!.getDsn = () => { return { publicKey: 'pubKey', } as DsnComponents; @@ -465,7 +465,7 @@ describe('BrowserTracing', () => { // pageload transactions are created as part of the BrowserTracing integration's initialization createBrowserTracing(true); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); @@ -485,7 +485,7 @@ describe('BrowserTracing', () => { // pageload transactions are created as part of the BrowserTracing integration's initialization createBrowserTracing(true); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); @@ -505,7 +505,7 @@ describe('BrowserTracing', () => { createBrowserTracing(true); mockChangeHistory({ to: 'here', from: 'there' }); - const transaction = getActiveTransaction(hub) as IdleTransaction; + const transaction = getActiveTransaction() as IdleTransaction; const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index bc0f7efac86a..f210330c7049 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -3,23 +3,35 @@ import { BrowserClient } from '@sentry/browser'; import { TRACING_DEFAULTS, Transaction, + getCurrentHub, getCurrentScope, + getGlobalScope, + getIsolationScope, + setCurrentClient, spanToJSON, startInactiveSpan, startSpan, startSpanManual, } from '@sentry/core'; -import { Hub, IdleTransaction, Span, getClient, makeMain } from '../../core/src'; +import { IdleTransaction, Span, getClient } from '../../core/src'; import { IdleTransactionSpanRecorder } from '../../core/src/tracing/idletransaction'; import { getDefaultBrowserClientOptions } from './testutils'; const dsn = 'https://123@sentry.io/42'; -let hub: Hub; beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + const options = getDefaultBrowserClientOptions({ dsn, tracesSampleRate: 1 }); - hub = new Hub(new BrowserClient(options)); - makeMain(hub); + const client = new BrowserClient(options); + setCurrentClient(client); + client.init(); +}); + +afterEach(() => { + jest.clearAllMocks(); }); describe('IdleTransaction', () => { @@ -27,7 +39,7 @@ describe('IdleTransaction', () => { it('sets the transaction on the scope on creation if onScope is true', () => { const transaction = new IdleTransaction( { name: 'foo' }, - hub, + getCurrentHub(), TRACING_DEFAULTS.idleTimeout, TRACING_DEFAULTS.finalTimeout, TRACING_DEFAULTS.heartbeatInterval, @@ -41,7 +53,7 @@ describe('IdleTransaction', () => { }); it('does not set the transaction on the scope on creation if onScope is falsey', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); transaction.initSpanRecorder(10); const scope = getCurrentScope(); @@ -52,7 +64,7 @@ describe('IdleTransaction', () => { it('removes sampled transaction from scope on finish if onScope is true', () => { const transaction = new IdleTransaction( { name: 'foo' }, - hub, + getCurrentHub(), TRACING_DEFAULTS.idleTimeout, TRACING_DEFAULTS.finalTimeout, TRACING_DEFAULTS.heartbeatInterval, @@ -71,7 +83,7 @@ describe('IdleTransaction', () => { it('removes unsampled transaction from scope on finish if onScope is true', () => { const transaction = new IdleTransaction( { name: 'foo', sampled: false }, - hub, + getCurrentHub(), TRACING_DEFAULTS.idleTimeout, TRACING_DEFAULTS.finalTimeout, TRACING_DEFAULTS.heartbeatInterval, @@ -89,7 +101,7 @@ describe('IdleTransaction', () => { it('does not remove transaction from scope on finish if another transaction was set there', () => { const transaction = new IdleTransaction( { name: 'foo' }, - hub, + getCurrentHub(), TRACING_DEFAULTS.idleTimeout, TRACING_DEFAULTS.finalTimeout, TRACING_DEFAULTS.heartbeatInterval, @@ -97,7 +109,7 @@ describe('IdleTransaction', () => { ); transaction.initSpanRecorder(10); - const otherTransaction = new Transaction({ name: 'bar' }, hub); + const otherTransaction = new Transaction({ name: 'bar' }, getCurrentHub()); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(otherTransaction); @@ -115,7 +127,7 @@ describe('IdleTransaction', () => { }); it('push and pops activities', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); const mockFinish = jest.spyOn(transaction, 'end'); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); @@ -135,7 +147,7 @@ describe('IdleTransaction', () => { }); it('does not push activities if a span already has an end timestamp', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); // eslint-disable-next-line deprecation/deprecation @@ -146,7 +158,7 @@ describe('IdleTransaction', () => { }); it('does not finish if there are still active activities', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); const mockFinish = jest.spyOn(transaction, 'end'); transaction.initSpanRecorder(10); expect(transaction.activities).toMatchObject({}); @@ -170,7 +182,7 @@ describe('IdleTransaction', () => { it('calls beforeFinish callback before finishing', () => { const mockCallback1 = jest.fn(); const mockCallback2 = jest.fn(); - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); transaction.initSpanRecorder(10); transaction.registerBeforeFinishCallback(mockCallback1); transaction.registerBeforeFinishCallback(mockCallback2); @@ -190,7 +202,7 @@ describe('IdleTransaction', () => { }); it('filters spans on finish', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub()); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -231,7 +243,7 @@ describe('IdleTransaction', () => { }); it('filters out spans that exceed final timeout', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, 1000, 3000); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), 1000, 3000); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -246,7 +258,11 @@ describe('IdleTransaction', () => { }); it('should record dropped transactions', async () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234, sampled: false }, hub, 1000); + const transaction = new IdleTransaction( + { name: 'foo', startTimestamp: 1234, sampled: false }, + getCurrentHub(), + 1000, + ); const client = getClient()!; @@ -260,7 +276,7 @@ describe('IdleTransaction', () => { describe('_idleTimeout', () => { it('finishes if no activities are added to the transaction', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub()); transaction.initSpanRecorder(10); jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); @@ -268,7 +284,7 @@ describe('IdleTransaction', () => { }); it('does not finish if a activity is started', () => { - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub()); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -281,7 +297,7 @@ describe('IdleTransaction', () => { it('does not finish when idleTimeout is not exceed after last activity finished', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -299,7 +315,7 @@ describe('IdleTransaction', () => { it('finish when idleTimeout is exceeded after last activity finished', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -319,7 +335,7 @@ describe('IdleTransaction', () => { describe('cancelIdleTimeout', () => { it('permanent idle timeout cancel is not restarted by child span start', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -335,7 +351,7 @@ describe('IdleTransaction', () => { it('permanent idle timeout cancel finished the transaction with the last child', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -357,7 +373,7 @@ describe('IdleTransaction', () => { it('permanent idle timeout cancel finishes transaction if there are no activities', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -373,7 +389,7 @@ describe('IdleTransaction', () => { it('default idle cancel timeout is restarted by child span change', () => { const idleTimeout = 10; - const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, hub, idleTimeout); + const transaction = new IdleTransaction({ name: 'foo', startTimestamp: 1234 }, getCurrentHub(), idleTimeout); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(transaction); @@ -397,7 +413,7 @@ describe('IdleTransaction', () => { describe('heartbeat', () => { it('does not mark transaction as `DeadlineExceeded` if idle timeout has not been reached', () => { // 20s to exceed 3 heartbeats - const transaction = new IdleTransaction({ name: 'foo' }, hub, 20000); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub(), 20000); const mockFinish = jest.spyOn(transaction, 'end'); expect(transaction.status).not.toEqual('deadline_exceeded'); @@ -420,7 +436,7 @@ describe('IdleTransaction', () => { }); it('finishes a transaction after 3 beats', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, TRACING_DEFAULTS.idleTimeout); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub(), TRACING_DEFAULTS.idleTimeout); const mockFinish = jest.spyOn(transaction, 'end'); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation @@ -443,7 +459,7 @@ describe('IdleTransaction', () => { }); it('resets after new activities are added', () => { - const transaction = new IdleTransaction({ name: 'foo' }, hub, TRACING_DEFAULTS.idleTimeout, 50000); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub(), TRACING_DEFAULTS.idleTimeout, 50000); const mockFinish = jest.spyOn(transaction, 'end'); transaction.initSpanRecorder(10); // eslint-disable-next-line deprecation/deprecation @@ -537,7 +553,7 @@ describe('IdleTransactionSpanRecorder', () => { const mockPushActivity = jest.fn(); const mockPopActivity = jest.fn(); - const transaction = new IdleTransaction({ name: 'foo' }, hub); + const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub()); const spanRecorder = new IdleTransactionSpanRecorder( mockPushActivity, mockPopActivity, diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 798cc2a8263c..0574bdae7da3 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -1,19 +1,35 @@ /* eslint-disable deprecation/deprecation */ import { BrowserClient } from '@sentry/browser'; -import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, makeMain, spanToJSON } from '@sentry/core'; +import { + Hub, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getClient, + getCurrentHub, + getCurrentScope, + getGlobalScope, + getIsolationScope, + setCurrentClient, + spanToJSON, +} from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; import { Span, TRACEPARENT_REGEXP, Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; describe('Span', () => { - let hub: Hub; - beforeEach(() => { - const myScope = new Scope(); + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); + const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - hub = new Hub(new BrowserClient(options), myScope); - makeMain(hub); + const client = new BrowserClient(options); + setCurrentClient(client); + client.init(); + }); + + afterEach(() => { + jest.clearAllMocks(); }); describe('new Span', () => { @@ -190,6 +206,12 @@ describe('Span', () => { }); describe('hub.startTransaction', () => { + let hub: Hub; + + beforeEach(() => { + hub = getCurrentHub() as Hub; + }); + test('finish a transaction', () => { const spy = jest.spyOn(hub as any, 'captureEvent') as any; const transaction = hub.startTransaction({ name: 'test' }); @@ -333,6 +355,12 @@ describe('Span', () => { }); describe('hub.startTransaction', () => { + let hub: Hub; + + beforeEach(() => { + hub = getCurrentHub() as Hub; + }); + test('finish a transaction', () => { const spy = jest.spyOn(hub as any, 'captureEvent') as any; const transaction = hub.startTransaction({ name: 'test' }); @@ -587,7 +615,7 @@ describe('Span', () => { describe('getDynamicSamplingContext', () => { beforeEach(() => { - hub.getClient()!.getOptions = () => { + getClient()!.getOptions = () => { return { release: '1.0.1', environment: 'production', @@ -601,7 +629,7 @@ describe('Span', () => { name: 'tx', metadata: { dynamicSamplingContext: { environment: 'myEnv' } }, }, - hub, + getCurrentHub(), ); const dynamicSamplingContext = transaction.getDynamicSamplingContext(); @@ -618,7 +646,7 @@ describe('Span', () => { sampled: true, }); - const getOptionsSpy = jest.spyOn(hub.getClient()!, 'getOptions'); + const getOptionsSpy = jest.spyOn(getClient()!, 'getOptions'); const dynamicSamplingContext = transaction.getDynamicSamplingContext(); @@ -642,7 +670,7 @@ describe('Span', () => { source: 'url', }, }, - hub, + getCurrentHub(), ); const dsc = transaction.getDynamicSamplingContext()!; @@ -660,7 +688,7 @@ describe('Span', () => { ...(source && { source: source as TransactionSource }), }, }, - hub, + getCurrentHub(), ); const dsc = transaction.getDynamicSamplingContext()!; @@ -672,6 +700,8 @@ describe('Span', () => { describe('Transaction source', () => { test('is included when transaction metadata is set', () => { + const hub = getCurrentHub(); + const spy = jest.spyOn(hub as any, 'captureEvent') as any; const transaction = hub.startTransaction({ name: 'test', sampled: true }); transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url'); From 4ef3bd518618f5f2a4fb7bb17fa0353c6d54ab8a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 15 Feb 2024 16:22:07 -0500 Subject: [PATCH 080/173] feat(v8): Remove requestData deprecations (#10626) --- packages/core/src/integrations/requestdata.ts | 18 +- packages/node/src/handlers.ts | 49 +- packages/node/src/requestDataDeprecated.ts | 54 -- packages/node/src/sdk.ts | 1 - packages/node/test/handlers.test.ts | 1 - .../test/integrations/requestdata.test.ts | 112 ---- packages/node/test/requestdata.test.ts | 582 ------------------ packages/serverless/src/gcpfunction/http.ts | 40 +- packages/serverless/test/gcpfunction.test.ts | 3 +- packages/types/src/transaction.ts | 6 - packages/utils/src/requestdata.ts | 7 +- packages/utils/test/requestdata.test.ts | 493 +++++++++++++++ 12 files changed, 507 insertions(+), 859 deletions(-) delete mode 100644 packages/node/src/requestDataDeprecated.ts delete mode 100644 packages/node/test/integrations/requestdata.test.ts delete mode 100644 packages/node/test/requestdata.test.ts create mode 100644 packages/utils/test/requestdata.test.ts diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index d3aa6bb7b850..1835fda4ec4a 100644 --- a/packages/core/src/integrations/requestdata.ts +++ b/packages/core/src/integrations/requestdata.ts @@ -50,20 +50,16 @@ const DEFAULT_OPTIONS = { email: true, }, }, - transactionNamingScheme: 'methodPath', + transactionNamingScheme: 'methodPath' as const, }; const INTEGRATION_NAME = 'RequestData'; const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) => { - const _addRequestData = addRequestDataToEvent; const _options: Required = { ...DEFAULT_OPTIONS, ...options, include: { - // @ts-expect-error It's mad because `method` isn't a known `include` key. (It's only here and not set by default in - // `addRequestDataToEvent` for legacy reasons. TODO (v8): Change that.) - method: true, ...DEFAULT_OPTIONS.include, ...options.include, user: @@ -95,15 +91,9 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) = return event; } - // The Express request handler takes a similar `include` option to that which can be passed to this integration. - // If passed there, we store it in `sdkProcessingMetadata`. TODO(v8): Force express and GCP people to use this - // integration, so that all of this passing and conversion isn't necessary - const addRequestDataOptions = - sdkProcessingMetadata.requestDataOptionsFromExpressHandler || - sdkProcessingMetadata.requestDataOptionsFromGCPWrapper || - convertReqDataIntegrationOptsToAddReqDataOpts(_options); + const addRequestDataOptions = convertReqDataIntegrationOptsToAddReqDataOpts(_options); - const processedEvent = _addRequestData(event, req, addRequestDataOptions); + const processedEvent = addRequestDataToEvent(event, req, addRequestDataOptions); // Transaction events already have the right `transaction` value if (event.type === 'transaction' || transactionNamingScheme === 'handler') { @@ -185,7 +175,7 @@ function convertReqDataIntegrationOptsToAddReqDataOpts( include: { ip, user, ...requestOptions }, } = integrationOptions; - const requestIncludeKeys: string[] = []; + const requestIncludeKeys: string[] = ['method']; for (const [key, value] of Object.entries(requestOptions)) { if (value) { requestIncludeKeys.push(key); diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index b1140d0d9c28..89bbb85cc91a 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -18,7 +18,6 @@ import type { Span } from '@sentry/types'; import type { AddRequestDataToEventOptions } from '@sentry/utils'; import { addRequestDataToTransaction, - dropUndefinedKeys, extractPathForTransaction, extractRequestData, isString, @@ -29,8 +28,6 @@ import { import type { NodeClient } from './client'; import { DEBUG_BUILD } from './debug-build'; -// TODO (v8 / XXX) Remove this import -import type { ParseRequestOptions } from './requestDataDeprecated'; import { isAutoSessionTrackingEnabled } from './sdk'; /** @@ -115,37 +112,9 @@ export function tracingHandler(): ( }; } -export type RequestHandlerOptions = - // TODO (v8 / XXX) Remove ParseRequestOptions type and eslint override - // eslint-disable-next-line deprecation/deprecation - (ParseRequestOptions | AddRequestDataToEventOptions) & { - flushTimeout?: number; - }; - -/** - * Backwards compatibility shim which can be removed in v8. Forces the given options to follow the - * `AddRequestDataToEventOptions` interface. - * - * TODO (v8): Get rid of this, and stop passing `requestDataOptionsFromExpressHandler` to `setSDKProcessingMetadata`. - */ -function convertReqHandlerOptsToAddReqDataOpts( - reqHandlerOptions: RequestHandlerOptions = {}, -): AddRequestDataToEventOptions | undefined { - let addRequestDataOptions: AddRequestDataToEventOptions | undefined; - - if ('include' in reqHandlerOptions) { - addRequestDataOptions = { include: reqHandlerOptions.include }; - } else { - // eslint-disable-next-line deprecation/deprecation - const { ip, request, transaction, user } = reqHandlerOptions as ParseRequestOptions; - - if (ip || request || transaction || user) { - addRequestDataOptions = { include: dropUndefinedKeys({ ip, request, transaction, user }) }; - } - } - - return addRequestDataOptions; -} +export type RequestHandlerOptions = AddRequestDataToEventOptions & { + flushTimeout?: number; +}; /** * Express compatible request handler. @@ -154,9 +123,6 @@ function convertReqHandlerOptsToAddReqDataOpts( export function requestHandler( options?: RequestHandlerOptions, ): (req: http.IncomingMessage, res: http.ServerResponse, next: (error?: any) => void) => void { - // TODO (v8): Get rid of this - const requestDataOptions = convertReqHandlerOptsToAddReqDataOpts(options); - const client = getClient(); // Initialise an instance of SessionFlusher on the client when `autoSessionTracking` is enabled and the // `requestHandler` middleware is used indicating that we are running in SessionAggregates mode @@ -193,8 +159,6 @@ export function requestHandler( const scope = getCurrentScope(); scope.setSDKProcessingMetadata({ request: req, - // TODO (v8): Stop passing this - requestDataOptionsFromExpressHandler: requestDataOptions, }); const client = getClient(); @@ -372,7 +336,6 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { } if (isThenable(maybePromiseResult)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access Promise.resolve(maybePromiseResult).then( nextResult => { captureIfError(nextResult as any); @@ -389,9 +352,3 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { return maybePromiseResult; }; } - -// TODO (v8 / #5257): Remove this -// eslint-disable-next-line deprecation/deprecation -export type { ParseRequestOptions, ExpressRequest } from './requestDataDeprecated'; -// eslint-disable-next-line deprecation/deprecation -export { parseRequest, extractRequestData } from './requestDataDeprecated'; diff --git a/packages/node/src/requestDataDeprecated.ts b/packages/node/src/requestDataDeprecated.ts deleted file mode 100644 index 74e0a9c98666..000000000000 --- a/packages/node/src/requestDataDeprecated.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Deprecated functions which are slated for removal in v8. When the time comes, this entire file can be deleted. - * - * See https://github.com/getsentry/sentry-javascript/pull/5257. - */ - -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Event, ExtractedNodeRequestData, PolymorphicRequest } from '@sentry/types'; -import type { AddRequestDataToEventOptions } from '@sentry/utils'; -import { addRequestDataToEvent, extractRequestData as _extractRequestData } from '@sentry/utils'; - -/** - * @deprecated `Handlers.ExpressRequest` is deprecated and will be removed in v8. Use `PolymorphicRequest` instead. - */ -export type ExpressRequest = PolymorphicRequest; - -/** - * Normalizes data from the request object, accounting for framework differences. - * - * @deprecated `Handlers.extractRequestData` is deprecated and will be removed in v8. Use `extractRequestData` instead. - * - * @param req The request object from which to extract data - * @param keys An optional array of keys to include in the normalized data. - * @returns An object containing normalized request data - */ -export function extractRequestData(req: { [key: string]: any }, keys?: string[]): ExtractedNodeRequestData { - return _extractRequestData(req, { include: keys }); -} - -/** - * Options deciding what parts of the request to use when enhancing an event - * - * @deprecated `Handlers.ParseRequestOptions` is deprecated and will be removed in v8. Use - * `AddRequestDataToEventOptions` in `@sentry/utils` instead. - */ -export type ParseRequestOptions = AddRequestDataToEventOptions['include'] & { - serverName?: boolean; - version?: boolean; -}; - -/** - * Enriches passed event with request data. - * - * @deprecated `Handlers.parseRequest` is deprecated and will be removed in v8. Use `addRequestDataToEvent` instead. - * - * @param event Will be mutated and enriched with req data - * @param req Request object - * @param options object containing flags to enable functionality - * @hidden - */ -export function parseRequest(event: Event, req: ExpressRequest, options: ParseRequestOptions = {}): Event { - return addRequestDataToEvent(event, req, { include: options }); -} diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 6aa6b0499ca2..f6f83aef9352 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import { endSession, functionToStringIntegration, diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 34e00f06b9c6..56682446a1d8 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -179,7 +179,6 @@ describe('requestHandler', () => { const scope = getCurrentScope(); expect((scope as any)._sdkProcessingMetadata).toEqual({ request: req, - requestDataOptionsFromExpressHandler: requestHandlerOptions, }); }); }); diff --git a/packages/node/test/integrations/requestdata.test.ts b/packages/node/test/integrations/requestdata.test.ts deleted file mode 100644 index e73fe2fbda88..000000000000 --- a/packages/node/test/integrations/requestdata.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as http from 'http'; -import type { RequestDataIntegrationOptions } from '@sentry/core'; -import { setCurrentClient } from '@sentry/core'; -import { applyScopeDataToEvent } from '@sentry/core'; -import { getCurrentScope } from '@sentry/core'; -import { RequestData } from '@sentry/core'; -import type { Event, EventProcessor, PolymorphicRequest } from '@sentry/types'; -import * as sentryUtils from '@sentry/utils'; - -import { NodeClient } from '../../src/client'; -import { requestHandler } from '../../src/handlers'; -import { getDefaultNodeClientOptions } from '../helper/node-client-options'; - -const addRequestDataToEventSpy = jest.spyOn(sentryUtils, 'addRequestDataToEvent'); - -const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' }; -const method = 'wagging'; -const protocol = 'mutualsniffing'; -const hostname = 'the.dog.park'; -const path = '/by/the/trees/'; -const queryString = 'chase=me&please=thankyou'; - -function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIntegrationOptions): EventProcessor { - // eslint-disable-next-line deprecation/deprecation - const requestDataIntegration = new RequestData({ - ...integrationOptions, - }); - - const client = new NodeClient( - getDefaultNodeClientOptions({ - dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', - integrations: [requestDataIntegration], - }), - ); - setCurrentClient(client); - client.init(); - - const eventProcessors = client['_eventProcessors'] as EventProcessor[]; - const eventProcessor = eventProcessors.find(processor => processor.id === 'RequestData'); - - expect(eventProcessor).toBeDefined(); - - return eventProcessor!; -} - -describe('`RequestData` integration', () => { - let req: http.IncomingMessage, event: Event; - - beforeEach(() => { - req = { - headers, - method, - protocol, - hostname, - originalUrl: `${path}?${queryString}`, - } as unknown as http.IncomingMessage; - event = { sdkProcessingMetadata: { request: req } }; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('usage with express request handler and GCP wrapper', () => { - it('uses options from Express request handler', async () => { - const sentryRequestMiddleware = requestHandler({ include: { transaction: 'methodPath' } }); - const res = new http.ServerResponse(req); - const next = jest.fn(); - - const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' }); - - sentryRequestMiddleware(req, res, next); - - applyScopeDataToEvent(event, getCurrentScope().getScopeData()); - void requestDataEventProcessor(event, {}); - - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; - - // `transaction` matches the request middleware's option, not the integration's option - expect(passedOptions?.include).toEqual(expect.objectContaining({ transaction: 'methodPath' })); - }); - - it('uses options from GCP wrapper', async () => { - type GCPHandler = (req: PolymorphicRequest, res: http.ServerResponse) => void; - const mockGCPWrapper = (origHandler: GCPHandler, options: Record): GCPHandler => { - const wrappedHandler: GCPHandler = (req, res) => { - getCurrentScope().setSDKProcessingMetadata({ - request: req, - requestDataOptionsFromGCPWrapper: options, - }); - origHandler(req, res); - }; - return wrappedHandler; - }; - - const wrappedGCPFunction = mockGCPWrapper(jest.fn(), { include: { transaction: 'methodPath' } }); - const res = new http.ServerResponse(req); - - const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' }); - - wrappedGCPFunction(req, res); - - applyScopeDataToEvent(event, getCurrentScope().getScopeData()); - void requestDataEventProcessor(event, {}); - - const passedOptions = addRequestDataToEventSpy.mock.calls[0][2]; - - // `transaction` matches the GCP wrapper's option, not the integration's option - expect(passedOptions?.include).toEqual(expect.objectContaining({ transaction: 'methodPath' })); - }); - }); -}); diff --git a/packages/node/test/requestdata.test.ts b/packages/node/test/requestdata.test.ts deleted file mode 100644 index 6b4e5e13101a..000000000000 --- a/packages/node/test/requestdata.test.ts +++ /dev/null @@ -1,582 +0,0 @@ -/* eslint-disable deprecation/deprecation */ - -// TODO (v8 / #5257): Remove everything related to the deprecated functions and move tests into `@sentry/utils` - -import type * as net from 'net'; -import type { Event, PolymorphicRequest, TransactionSource, User } from '@sentry/types'; -import type { AddRequestDataToEventOptions } from '@sentry/utils'; -import { - addRequestDataToEvent, - extractPathForTransaction, - extractRequestData as newExtractRequestData, -} from '@sentry/utils'; - -import type { ExpressRequest } from '../src/requestDataDeprecated'; -import { extractRequestData as oldExtractRequestData, parseRequest } from '../src/requestDataDeprecated'; - -// TODO (v8 / #5257): Remove `describe.each` wrapper, remove `formatArgs` wrapper, reformat args in tests, and use only -// `addRequestDataToEvent` -describe.each([parseRequest, addRequestDataToEvent])( - 'backwards compatibility of `parseRequest` rename and move', - fn => { - /** Rearrage and cast args correctly for each version of the function */ - function formatArgs( - fn: typeof parseRequest | typeof addRequestDataToEvent, - event: Event, - req: any, - include?: AddRequestDataToEventOptions['include'], - ): Parameters | Parameters { - if (fn.name === 'parseRequest') { - return [event, req as ExpressRequest, include]; - } else { - return [event, req as PolymorphicRequest, { include }]; - } - } - - describe(fn, () => { - let mockEvent: Event; - let mockReq: { [key: string]: any }; - - beforeEach(() => { - mockEvent = {}; - mockReq = { - baseUrl: '/routerMountPath', - body: 'foo', - cookies: { test: 'test' }, - headers: { - host: 'mattrobenolt.com', - }, - method: 'POST', - originalUrl: '/routerMountPath/subpath/specificValue?querystringKey=querystringValue', - path: '/subpath/specificValue', - query: { - querystringKey: 'querystringValue', - }, - route: { - path: '/subpath/:parameterName', - stack: [ - { - name: 'parameterNameRouteHandler', - }, - ], - }, - url: '/subpath/specificValue?querystringKey=querystringValue', - user: { - custom_property: 'foo', - email: 'tobias@mail.com', - id: 123, - username: 'tobias', - }, - }; - }); - - describe(`${fn.name}.user properties`, () => { - const DEFAULT_USER_KEYS = ['id', 'username', 'email']; - const CUSTOM_USER_KEYS = ['custom_property']; - - test(`${fn.name}.user only contains the default properties from the user`, () => { - const [event, req, options] = formatArgs(fn, mockEvent, mockReq); - const parsedRequest: Event = fn(event, req, options); - - expect(Object.keys(parsedRequest.user as User)).toEqual(DEFAULT_USER_KEYS); - }); - - test(`${fn.name}.user only contains the custom properties specified in the options.user array`, () => { - const optionsWithCustomUserKeys = { - user: CUSTOM_USER_KEYS, - }; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq, optionsWithCustomUserKeys); - const parsedRequest: Event = fn(event, req, options); - - expect(Object.keys(parsedRequest.user as User)).toEqual(CUSTOM_USER_KEYS); - }); - - test(`${fn.name}.user doesnt blow up when someone passes non-object value`, () => { - const reqWithUser = { - ...mockReq, - user: 'wat', - }; - - const [event, req, options] = formatArgs(fn, mockEvent, reqWithUser); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.user).toBeUndefined(); - }); - }); - - describe(`${fn.name}.ip property`, () => { - test('can be extracted from req.ip', () => { - const mockReqWithIP = { - ...mockReq, - ip: '123', - }; - const optionsWithIP = { - ip: true, - }; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReqWithIP, optionsWithIP); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.user!.ip_address).toEqual('123'); - }); - - test('can extract from req.socket.remoteAddress', () => { - const reqWithIPInSocket = { - ...mockReq, - socket: { - remoteAddress: '321', - } as net.Socket, - }; - const optionsWithIP = { - ip: true, - }; - - const [event, req, options] = formatArgs(fn, mockEvent, reqWithIPInSocket, optionsWithIP); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.user!.ip_address).toEqual('321'); - }); - }); - - describe(`${fn.name}.request properties`, () => { - test(`${fn.name}.request only contains the default set of properties from the request`, () => { - const DEFAULT_REQUEST_PROPERTIES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url']; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq); - const parsedRequest: Event = fn(event, req, options); - - expect(Object.keys(parsedRequest.request!)).toEqual(DEFAULT_REQUEST_PROPERTIES); - }); - - test(`${fn.name}.request only contains the specified properties in the options.request array`, () => { - const INCLUDED_PROPERTIES = ['data', 'headers', 'query_string', 'url']; - const optionsWithRequestIncludes = { - request: INCLUDED_PROPERTIES, - }; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq, optionsWithRequestIncludes); - const parsedRequest: Event = fn(event, req, options); - - expect(Object.keys(parsedRequest.request!)).toEqual(INCLUDED_PROPERTIES); - }); - - test.each([ - [undefined, true], - ['GET', false], - ['HEAD', false], - ])( - `${fn.name}.request skips \`body\` property for GET and HEAD requests - %s method`, - (method, shouldIncludeBodyData) => { - const reqWithMethod = { ...mockReq, method }; - - const [event, req, options] = formatArgs(fn, mockEvent, reqWithMethod); - const parsedRequest: Event = fn(event, req, options); - - if (shouldIncludeBodyData) { - expect(parsedRequest.request).toHaveProperty('data'); - } else { - expect(parsedRequest.request).not.toHaveProperty('data'); - } - }, - ); - }); - - describe(`${fn.name}.transaction property`, () => { - test('extracts method and full route path by default`', () => { - const [event, req, options] = formatArgs(fn, mockEvent, mockReq); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.transaction).toEqual('POST /routerMountPath/subpath/:parameterName'); - }); - - test('extracts method and full path by default when mountpoint is `/`', () => { - mockReq.originalUrl = mockReq.originalUrl.replace('/routerMountpath', ''); - mockReq.baseUrl = ''; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq); - const parsedRequest: Event = fn(event, req, options); - - // `subpath/` is the full path here, because there's no router mount path - expect(parsedRequest.transaction).toEqual('POST /subpath/:parameterName'); - }); - - test('fallback to method and `originalUrl` if route is missing', () => { - delete mockReq.route; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.transaction).toEqual('POST /routerMountPath/subpath/specificValue'); - }); - - test('can extract path only instead if configured', () => { - const optionsWithPathTransaction = { transaction: 'path' } as const; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq, optionsWithPathTransaction); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.transaction).toEqual('/routerMountPath/subpath/:parameterName'); - }); - - test('can extract handler name instead if configured', () => { - const optionsWithHandlerTransaction = { transaction: 'handler' } as const; - - const [event, req, options] = formatArgs(fn, mockEvent, mockReq, optionsWithHandlerTransaction); - const parsedRequest: Event = fn(event, req, options); - - expect(parsedRequest.transaction).toEqual('parameterNameRouteHandler'); - }); - }); - }); - }, -); - -// TODO (v8 / #5257): Remove `describe.each` wrapper, remove `formatArgs` wrapper, reformat args in tests, use only -// `newExtractRequestData`, and rename `newExtractRequestData` to just `extractRequestData` -Object.defineProperty(oldExtractRequestData, 'name', { - value: 'oldExtractRequestData', -}); -Object.defineProperty(newExtractRequestData, 'name', { - value: 'newExtractRequestData', -}); -describe.each([oldExtractRequestData, newExtractRequestData])( - 'backwards compatibility of `extractRequestData` move', - fn => { - /** Rearrage and cast args correctly for each version of the function */ - function formatArgs( - fn: typeof oldExtractRequestData | typeof newExtractRequestData, - req: any, - include?: string[], - ): Parameters | Parameters { - if (fn.name === 'oldExtractRequestData') { - return [req as ExpressRequest, include] as Parameters; - } else { - return [req as PolymorphicRequest, { include }] as Parameters; - } - } - - describe(fn, () => { - describe('default behaviour', () => { - test('node', () => { - const mockReq = { - headers: { host: 'example.com' }, - method: 'GET', - socket: { encrypted: true }, - originalUrl: '/', - }; - - const [req, options] = formatArgs(fn, mockReq); - - expect(fn(req, options as any)).toEqual({ - cookies: {}, - headers: { - host: 'example.com', - }, - method: 'GET', - query_string: undefined, - url: 'https://example.com/', - }); - }); - - test('degrades gracefully without request data', () => { - const mockReq = {}; - - const [req, options] = formatArgs(fn, mockReq); - - expect(fn(req, options as any)).toEqual({ - cookies: {}, - headers: {}, - method: undefined, - query_string: undefined, - url: 'http://', - }); - }); - }); - - describe('headers', () => { - it('removes the `Cookie` header from requestdata.headers, if `cookies` is not set in the options', () => { - const mockReq = { - cookies: { foo: 'bar' }, - headers: { cookie: 'foo=bar', otherHeader: 'hello' }, - }; - const optionsWithCookies = ['headers']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCookies); - - expect(fn(req, options as any)).toStrictEqual({ - headers: { otherHeader: 'hello' }, - }); - }); - - it('includes the `Cookie` header in requestdata.headers, if `cookies` is set in the options', () => { - const mockReq = { - cookies: { foo: 'bar' }, - headers: { cookie: 'foo=bar', otherHeader: 'hello' }, - }; - const optionsWithCookies = ['headers', 'cookies']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCookies); - - expect(fn(req, options as any)).toStrictEqual({ - headers: { otherHeader: 'hello', cookie: 'foo=bar' }, - cookies: { foo: 'bar' }, - }); - }); - }); - - describe('cookies', () => { - it('uses `req.cookies` if available', () => { - const mockReq = { - cookies: { foo: 'bar' }, - }; - const optionsWithCookies = ['cookies']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCookies); - - expect(fn(req, options as any)).toEqual({ - cookies: { foo: 'bar' }, - }); - }); - - it('parses the cookie header', () => { - const mockReq = { - headers: { - cookie: 'foo=bar;', - }, - }; - const optionsWithCookies = ['cookies']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCookies); - - expect(fn(req, options as any)).toEqual({ - cookies: { foo: 'bar' }, - }); - }); - - it('falls back if no cookies are defined', () => { - const mockReq = {}; - const optionsWithCookies = ['cookies']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCookies); - - expect(fn(req, options as any)).toEqual({ - cookies: {}, - }); - }); - }); - - describe('data', () => { - it('includes data from `req.body` if available', () => { - const mockReq = { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: 'foo=bar', - }; - const optionsWithData = ['data']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithData); - - expect(fn(req, options as any)).toEqual({ - data: 'foo=bar', - }); - }); - - it('encodes JSON body contents back to a string', () => { - const mockReq = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: { foo: 'bar' }, - }; - const optionsWithData = ['data']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithData); - - expect(fn(req, options as any)).toEqual({ - data: '{"foo":"bar"}', - }); - }); - }); - - describe('query_string', () => { - it('parses the query parms from the url', () => { - const mockReq = { - headers: { host: 'example.com' }, - secure: true, - originalUrl: '/?foo=bar', - }; - const optionsWithQueryString = ['query_string']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithQueryString); - - expect(fn(req, options as any)).toEqual({ - query_string: 'foo=bar', - }); - }); - - it('gracefully degrades if url cannot be determined', () => { - const mockReq = {}; - const optionsWithQueryString = ['query_string']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithQueryString); - - expect(fn(req, options as any)).toEqual({ - query_string: undefined, - }); - }); - }); - - describe('url', () => { - test('express/koa', () => { - const mockReq = { - host: 'example.com', - protocol: 'https', - url: '/', - }; - const optionsWithURL = ['url']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithURL); - - expect(fn(req, options as any)).toEqual({ - url: 'https://example.com/', - }); - }); - - test('node', () => { - const mockReq = { - headers: { host: 'example.com' }, - socket: { encrypted: true }, - originalUrl: '/', - }; - const optionsWithURL = ['url']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithURL); - - expect(fn(req, options as any)).toEqual({ - url: 'https://example.com/', - }); - }); - }); - - describe('custom key', () => { - it('includes the custom key if present', () => { - const mockReq = { - httpVersion: '1.1', - }; - const optionsWithCustomKey = ['httpVersion']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCustomKey); - - expect(fn(req, options as any)).toEqual({ - httpVersion: '1.1', - }); - }); - - it('gracefully degrades if the custom key is missing', () => { - const mockReq = {}; - const optionsWithCustomKey = ['httpVersion']; - - const [req, options] = formatArgs(fn, mockReq, optionsWithCustomKey); - - expect(fn(req, options as any)).toEqual({}); - }); - }); - }); - }, -); - -describe('extractPathForTransaction', () => { - it.each([ - [ - 'extracts a parameterized route and method if available', - { - method: 'get', - baseUrl: '/api/users', - route: { path: '/:id/details' }, - originalUrl: '/api/users/123/details', - } as PolymorphicRequest, - { path: true, method: true }, - 'GET /api/users/:id/details', - 'route' as TransactionSource, - ], - [ - 'ignores the method if specified', - { - method: 'get', - baseUrl: '/api/users', - route: { path: '/:id/details' }, - originalUrl: '/api/users/123/details', - } as PolymorphicRequest, - { path: true, method: false }, - '/api/users/:id/details', - 'route' as TransactionSource, - ], - [ - 'ignores the path if specified', - { - method: 'get', - baseUrl: '/api/users', - route: { path: '/:id/details' }, - originalUrl: '/api/users/123/details', - } as PolymorphicRequest, - { path: false, method: true }, - 'GET', - 'route' as TransactionSource, - ], - [ - 'returns an empty string if everything should be ignored', - { - method: 'get', - baseUrl: '/api/users', - route: { path: '/:id/details' }, - originalUrl: '/api/users/123/details', - } as PolymorphicRequest, - { path: false, method: false }, - '', - 'route' as TransactionSource, - ], - [ - 'falls back to the raw URL if no parameterized route is available', - { - method: 'get', - baseUrl: '/api/users', - originalUrl: '/api/users/123/details', - } as PolymorphicRequest, - { path: true, method: true }, - 'GET /api/users/123/details', - 'url' as TransactionSource, - ], - ])( - '%s', - ( - _: string, - req: PolymorphicRequest, - options: { path?: boolean; method?: boolean }, - expectedRoute: string, - expectedSource: TransactionSource, - ) => { - const [route, source] = extractPathForTransaction(req, options); - - expect(route).toEqual(expectedRoute); - expect(source).toEqual(expectedSource); - }, - ); - - it('overrides the requests information with a custom route if specified', () => { - const req = { - method: 'get', - baseUrl: '/api/users', - route: { path: '/:id/details' }, - originalUrl: '/api/users/123/details', - } as PolymorphicRequest; - - const [route, source] = extractPathForTransaction(req, { - path: true, - method: true, - customRoute: '/other/path/:id/details', - }); - - expect(route).toEqual('GET /other/path/:id/details'); - expect(source).toEqual('route'); - }); -}); diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts index e02093acf438..fe5279ddc6cf 100644 --- a/packages/serverless/src/gcpfunction/http.ts +++ b/packages/serverless/src/gcpfunction/http.ts @@ -5,7 +5,6 @@ import { handleCallbackErrors, setHttpStatus, } from '@sentry/core'; -import type { AddRequestDataToEventOptions } from '@sentry/node'; import { continueTrace, startSpanManual } from '@sentry/node'; import { getCurrentScope } from '@sentry/node'; import { captureException, flush } from '@sentry/node'; @@ -15,24 +14,6 @@ import { DEBUG_BUILD } from '../debug-build'; import { domainify, markEventUnhandled, proxyFunction } from './../utils'; import type { HttpFunction, WrapperOptions } from './general'; -// TODO (v8 / #5257): Remove this whole old/new business and just use the new stuff -type ParseRequestOptions = AddRequestDataToEventOptions['include'] & { - serverName?: boolean; - version?: boolean; -}; - -interface OldHttpFunctionWrapperOptions extends WrapperOptions { - /** - * @deprecated Use `addRequestDataToEventOptions` instead. - */ - parseRequestOptions: ParseRequestOptions; -} -interface NewHttpFunctionWrapperOptions extends WrapperOptions { - addRequestDataToEventOptions: AddRequestDataToEventOptions; -} - -export type HttpFunctionWrapperOptions = OldHttpFunctionWrapperOptions | NewHttpFunctionWrapperOptions; - /** * Wraps an HTTP function handler adding it error capture and tracing capabilities. * @@ -40,10 +21,7 @@ export type HttpFunctionWrapperOptions = OldHttpFunctionWrapperOptions | NewHttp * @param options Options * @returns HTTP handler */ -export function wrapHttpFunction( - fn: HttpFunction, - wrapOptions: Partial = {}, -): HttpFunction { +export function wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial = {}): HttpFunction { const wrap = (f: HttpFunction): HttpFunction => domainify(_wrapHttpFunction(f, wrapOptions)); let overrides: Record | undefined; @@ -59,17 +37,8 @@ export function wrapHttpFunction( } /** */ -function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial = {}): HttpFunction { - // TODO (v8 / #5257): Switch to using `addRequestDataToEventOptions` - // eslint-disable-next-line deprecation/deprecation - const { parseRequestOptions } = wrapOptions as OldHttpFunctionWrapperOptions; - - const options: HttpFunctionWrapperOptions = { - flushTimeout: 2000, - // TODO (v8 / xxx): Remove this line, since `addRequestDataToEventOptions` will be included in the spread of `wrapOptions` - addRequestDataToEventOptions: parseRequestOptions ? { include: parseRequestOptions } : {}, - ...wrapOptions, - }; +function _wrapHttpFunction(fn: HttpFunction, options: Partial): HttpFunction { + const flushTimeout = options.flushTimeout || 2000; return (req, res) => { const reqMethod = (req.method || '').toUpperCase(); const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || ''); @@ -90,7 +59,6 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial { getCurrentScope().setSDKProcessingMetadata({ request: req, - requestDataOptionsFromGCPWrapper: options.addRequestDataToEventOptions, }); if (span instanceof Transaction) { @@ -111,7 +79,7 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial { DEBUG_BUILD && logger.error(e); }) diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index cde69e6b22d2..798284a6d334 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -231,7 +231,7 @@ describe('GCPFunction', () => { const handler: HttpFunction = (_req, res) => { res.end(); }; - const wrappedHandler = wrapHttpFunction(handler, { addRequestDataToEventOptions: { include: { ip: true } } }); + const wrappedHandler = wrapHttpFunction(handler); await handleHttp(wrappedHandler); @@ -247,7 +247,6 @@ describe('GCPFunction', () => { headers: { host: 'hostname', 'content-type': 'application/json' }, body: { foo: 'bar' }, }, - requestDataOptionsFromGCPWrapper: { include: { ip: true } }, }); }); diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index fbcf8b38f02d..49b92b2e218a 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -205,12 +205,6 @@ export interface TransactionMetadata { /** For transactions tracing server-side request handling, the request being tracked. */ request?: PolymorphicRequest; - /** Compatibility shim for transitioning to the `RequestData` integration. The options passed to our Express request - * handler controlling what request data is added to the event. - * TODO (v8): This should go away - */ - requestDataOptionsFromExpressHandler?: { [key: string]: unknown }; - /** For transactions tracing server-side request handling, the path of the request being tracked. */ /** TODO: If we rm -rf `instrumentServer`, this can go, too */ requestPath?: string; diff --git a/packages/utils/src/requestdata.ts b/packages/utils/src/requestdata.ts index 85b748dadaba..7b8c61d27750 100644 --- a/packages/utils/src/requestdata.ts +++ b/packages/utils/src/requestdata.ts @@ -189,8 +189,6 @@ export function extractRequestData( req: PolymorphicRequest, options?: { include?: string[]; - // TODO(v8): Remove this paramater - deps?: InjectedNodeDeps; }, ): ExtractedNodeRequestData { const { include = DEFAULT_REQUEST_INCLUDES } = options || {}; @@ -258,7 +256,6 @@ export function extractRequestData( // query string: // node: req.url (raw) // express, koa, nextjs: req.query - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access requestData.query_string = extractQueryParams(req); break; } @@ -309,8 +306,8 @@ export function addRequestDataToEvent( if (include.request) { const extractedRequestData = Array.isArray(include.request) - ? extractRequestData(req, { include: include.request, deps: options && options.deps }) - : extractRequestData(req, { deps: options && options.deps }); + ? extractRequestData(req, { include: include.request }) + : extractRequestData(req); event.request = { ...event.request, diff --git a/packages/utils/test/requestdata.test.ts b/packages/utils/test/requestdata.test.ts new file mode 100644 index 000000000000..3bd5ac507268 --- /dev/null +++ b/packages/utils/test/requestdata.test.ts @@ -0,0 +1,493 @@ +import type * as net from 'net'; +import type { Event, PolymorphicRequest, TransactionSource, User } from '@sentry/types'; +import { addRequestDataToEvent, extractPathForTransaction, extractRequestData } from '@sentry/utils'; + +describe('addRequestDataToEvent', () => { + let mockEvent: Event; + let mockReq: { [key: string]: any }; + + beforeEach(() => { + mockEvent = {}; + mockReq = { + baseUrl: '/routerMountPath', + body: 'foo', + cookies: { test: 'test' }, + headers: { + host: 'example.org', + }, + method: 'POST', + originalUrl: '/routerMountPath/subpath/specificValue?querystringKey=querystringValue', + path: '/subpath/specificValue', + query: { + querystringKey: 'querystringValue', + }, + route: { + path: '/subpath/:parameterName', + stack: [ + { + name: 'parameterNameRouteHandler', + }, + ], + }, + url: '/subpath/specificValue?querystringKey=querystringValue', + user: { + custom_property: 'foo', + email: 'tobias@mail.com', + id: 123, + username: 'tobias', + }, + }; + }); + + describe('addRequestDataToEvent user properties', () => { + const DEFAULT_USER_KEYS = ['id', 'username', 'email']; + const CUSTOM_USER_KEYS = ['custom_property']; + + test('user only contains the default properties from the user', () => { + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq); + expect(Object.keys(parsedRequest.user as User)).toEqual(DEFAULT_USER_KEYS); + }); + + test('user only contains the custom properties specified in the options.user array', () => { + const optionsWithCustomUserKeys = { + include: { + user: CUSTOM_USER_KEYS, + }, + }; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq, optionsWithCustomUserKeys); + + expect(Object.keys(parsedRequest.user as User)).toEqual(CUSTOM_USER_KEYS); + }); + + test('setting user doesnt blow up when someone passes non-object value', () => { + const reqWithUser = { + ...mockReq, + // intentionally setting user to a non-object value, hence the as any cast + user: 'wat', + } as any; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, reqWithUser); + + expect(parsedRequest.user).toBeUndefined(); + }); + }); + + describe('addRequestDataToEvent ip property', () => { + test('can be extracted from req.ip', () => { + const mockReqWithIP = { + ...mockReq, + ip: '123', + }; + const optionsWithIP = { + include: { + ip: true, + }, + }; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReqWithIP, optionsWithIP); + + expect(parsedRequest.user!.ip_address).toEqual('123'); + }); + + test('can extract from req.socket.remoteAddress', () => { + const reqWithIPInSocket = { + ...mockReq, + socket: { + remoteAddress: '321', + } as net.Socket, + }; + const optionsWithIP = { + include: { + ip: true, + }, + }; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, reqWithIPInSocket, optionsWithIP); + + expect(parsedRequest.user!.ip_address).toEqual('321'); + }); + }); + + describe('request properties', () => { + test('request only contains the default set of properties from the request', () => { + const DEFAULT_REQUEST_PROPERTIES = ['cookies', 'data', 'headers', 'method', 'query_string', 'url']; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq); + + expect(Object.keys(parsedRequest.request!)).toEqual(DEFAULT_REQUEST_PROPERTIES); + }); + + test('request only contains the specified properties in the options.request array', () => { + const INCLUDED_PROPERTIES = ['data', 'headers', 'query_string', 'url']; + const optionsWithRequestIncludes = { + include: { + request: INCLUDED_PROPERTIES, + }, + }; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq, optionsWithRequestIncludes); + + expect(Object.keys(parsedRequest.request!)).toEqual(INCLUDED_PROPERTIES); + }); + + test.each([ + [undefined, true], + ['GET', false], + ['HEAD', false], + ])('request skips `body` property for GET and HEAD requests - %s method', (method, shouldIncludeBodyData) => { + const reqWithMethod = { ...mockReq, method }; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, reqWithMethod); + + if (shouldIncludeBodyData) { + expect(parsedRequest.request).toHaveProperty('data'); + } else { + expect(parsedRequest.request).not.toHaveProperty('data'); + } + }); + }); + + describe('transaction property', () => { + test('extracts method and full route path by default`', () => { + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq); + + expect(parsedRequest.transaction).toEqual('POST /routerMountPath/subpath/:parameterName'); + }); + + test('extracts method and full path by default when mountpoint is `/`', () => { + mockReq.originalUrl = mockReq.originalUrl.replace('/routerMountpath', ''); + mockReq.baseUrl = ''; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq); + + // `subpath/` is the full path here, because there's no router mount path + expect(parsedRequest.transaction).toEqual('POST /subpath/:parameterName'); + }); + + test('fallback to method and `originalUrl` if route is missing', () => { + delete mockReq.route; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq); + + expect(parsedRequest.transaction).toEqual('POST /routerMountPath/subpath/specificValue'); + }); + + test('can extract path only instead if configured', () => { + const optionsWithPathTransaction = { + include: { + transaction: 'path', + }, + } as const; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq, optionsWithPathTransaction); + + expect(parsedRequest.transaction).toEqual('/routerMountPath/subpath/:parameterName'); + }); + + test('can extract handler name instead if configured', () => { + const optionsWithHandlerTransaction = { + include: { + transaction: 'handler', + }, + } as const; + + const parsedRequest: Event = addRequestDataToEvent(mockEvent, mockReq, optionsWithHandlerTransaction); + + expect(parsedRequest.transaction).toEqual('parameterNameRouteHandler'); + }); + }); +}); + +describe('extractRequestData', () => { + describe('default behaviour', () => { + test('node', () => { + const mockReq = { + headers: { host: 'example.com' }, + method: 'GET', + socket: { encrypted: true }, + originalUrl: '/', + }; + + expect(extractRequestData(mockReq)).toEqual({ + cookies: {}, + headers: { + host: 'example.com', + }, + method: 'GET', + query_string: undefined, + url: 'https://example.com/', + }); + }); + + test('degrades gracefully without request data', () => { + const mockReq = {}; + + expect(extractRequestData(mockReq)).toEqual({ + cookies: {}, + headers: {}, + method: undefined, + query_string: undefined, + url: 'http://', + }); + }); + }); + + describe('headers', () => { + it('removes the `Cookie` header from requestdata.headers, if `cookies` is not set in the options', () => { + const mockReq = { + cookies: { foo: 'bar' }, + headers: { cookie: 'foo=bar', otherHeader: 'hello' }, + }; + const options = { include: ['headers'] }; + + expect(extractRequestData(mockReq, options)).toStrictEqual({ + headers: { otherHeader: 'hello' }, + }); + }); + + it('includes the `Cookie` header in requestdata.headers, if `cookies` is set in the options', () => { + const mockReq = { + cookies: { foo: 'bar' }, + headers: { cookie: 'foo=bar', otherHeader: 'hello' }, + }; + const optionsWithCookies = { include: ['headers', 'cookies'] }; + + expect(extractRequestData(mockReq, optionsWithCookies)).toStrictEqual({ + headers: { otherHeader: 'hello', cookie: 'foo=bar' }, + cookies: { foo: 'bar' }, + }); + }); + }); + + describe('cookies', () => { + it('uses `req.cookies` if available', () => { + const mockReq = { + cookies: { foo: 'bar' }, + }; + const optionsWithCookies = { include: ['cookies'] }; + + expect(extractRequestData(mockReq, optionsWithCookies)).toEqual({ + cookies: { foo: 'bar' }, + }); + }); + + it('parses the cookie header', () => { + const mockReq = { + headers: { + cookie: 'foo=bar;', + }, + }; + const optionsWithCookies = { include: ['cookies'] }; + + expect(extractRequestData(mockReq, optionsWithCookies)).toEqual({ + cookies: { foo: 'bar' }, + }); + }); + + it('falls back if no cookies are defined', () => { + const mockReq = {}; + const optionsWithCookies = { include: ['cookies'] }; + + expect(extractRequestData(mockReq, optionsWithCookies)).toEqual({ + cookies: {}, + }); + }); + }); + + describe('data', () => { + it('includes data from `req.body` if available', () => { + const mockReq = { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'foo=bar', + }; + const optionsWithData = { include: ['data'] }; + + expect(extractRequestData(mockReq, optionsWithData)).toEqual({ + data: 'foo=bar', + }); + }); + + it('encodes JSON body contents back to a string', () => { + const mockReq = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: { foo: 'bar' }, + }; + const optionsWithData = { include: ['data'] }; + + expect(extractRequestData(mockReq, optionsWithData)).toEqual({ + data: '{"foo":"bar"}', + }); + }); + }); + + describe('query_string', () => { + it('parses the query parms from the url', () => { + const mockReq = { + headers: { host: 'example.com' }, + secure: true, + originalUrl: '/?foo=bar', + }; + const optionsWithQueryString = { include: ['query_string'] }; + + expect(extractRequestData(mockReq, optionsWithQueryString)).toEqual({ + query_string: 'foo=bar', + }); + }); + + it('gracefully degrades if url cannot be determined', () => { + const mockReq = {}; + const optionsWithQueryString = { include: ['query_string'] }; + + expect(extractRequestData(mockReq, optionsWithQueryString)).toEqual({ + query_string: undefined, + }); + }); + }); + + describe('url', () => { + test('express/koa', () => { + const mockReq = { + host: 'example.com', + protocol: 'https', + url: '/', + }; + const optionsWithURL = { include: ['url'] }; + + expect(extractRequestData(mockReq, optionsWithURL)).toEqual({ + url: 'https://example.com/', + }); + }); + + test('node', () => { + const mockReq = { + headers: { host: 'example.com' }, + socket: { encrypted: true }, + originalUrl: '/', + }; + const optionsWithURL = { include: ['url'] }; + + expect(extractRequestData(mockReq, optionsWithURL)).toEqual({ + url: 'https://example.com/', + }); + }); + }); + + describe('custom key', () => { + it('includes the custom key if present', () => { + const mockReq = { + httpVersion: '1.1', + } as any; + const optionsWithCustomKey = { include: ['httpVersion'] }; + + expect(extractRequestData(mockReq, optionsWithCustomKey)).toEqual({ + httpVersion: '1.1', + }); + }); + + it('gracefully degrades if the custom key is missing', () => { + const mockReq = {} as any; + const optionsWithCustomKey = { include: ['httpVersion'] }; + + expect(extractRequestData(mockReq, optionsWithCustomKey)).toEqual({}); + }); + }); +}); + +describe('extractPathForTransaction', () => { + it.each([ + [ + 'extracts a parameterized route and method if available', + { + method: 'get', + baseUrl: '/api/users', + route: { path: '/:id/details' }, + originalUrl: '/api/users/123/details', + } as PolymorphicRequest, + { path: true, method: true }, + 'GET /api/users/:id/details', + 'route' as TransactionSource, + ], + [ + 'ignores the method if specified', + { + method: 'get', + baseUrl: '/api/users', + route: { path: '/:id/details' }, + originalUrl: '/api/users/123/details', + } as PolymorphicRequest, + { path: true, method: false }, + '/api/users/:id/details', + 'route' as TransactionSource, + ], + [ + 'ignores the path if specified', + { + method: 'get', + baseUrl: '/api/users', + route: { path: '/:id/details' }, + originalUrl: '/api/users/123/details', + } as PolymorphicRequest, + { path: false, method: true }, + 'GET', + 'route' as TransactionSource, + ], + [ + 'returns an empty string if everything should be ignored', + { + method: 'get', + baseUrl: '/api/users', + route: { path: '/:id/details' }, + originalUrl: '/api/users/123/details', + } as PolymorphicRequest, + { path: false, method: false }, + '', + 'route' as TransactionSource, + ], + [ + 'falls back to the raw URL if no parameterized route is available', + { + method: 'get', + baseUrl: '/api/users', + originalUrl: '/api/users/123/details', + } as PolymorphicRequest, + { path: true, method: true }, + 'GET /api/users/123/details', + 'url' as TransactionSource, + ], + ])( + '%s', + ( + _: string, + req: PolymorphicRequest, + options: { path?: boolean; method?: boolean }, + expectedRoute: string, + expectedSource: TransactionSource, + ) => { + const [route, source] = extractPathForTransaction(req, options); + + expect(route).toEqual(expectedRoute); + expect(source).toEqual(expectedSource); + }, + ); + + it('overrides the requests information with a custom route if specified', () => { + const req = { + method: 'get', + baseUrl: '/api/users', + route: { path: '/:id/details' }, + originalUrl: '/api/users/123/details', + } as PolymorphicRequest; + + const [route, source] = extractPathForTransaction(req, { + path: true, + method: true, + customRoute: '/other/path/:id/details', + }); + + expect(route).toEqual('GET /other/path/:id/details'); + expect(source).toEqual('route'); + }); +}); From cbf64ef36a303e337d4d9f7526bdb3739d5dbeb1 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 09:26:42 +0100 Subject: [PATCH 081/173] feat(node-experimental): Add missing re-exports (#10679) Noticed these were not exported yet there. --- packages/node-experimental/src/index.ts | 14 ++++++++++++-- .../node-experimental/src/otel/contextManager.ts | 1 + packages/node-experimental/src/sdk/hub.ts | 14 +++----------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index e4dc265b1adc..d94718abcabb 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -21,7 +21,7 @@ export const Integrations = { ...NodeExperimentalIntegrations, }; -export { init } from './sdk/init'; +export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations'; export * as Handlers from './sdk/handlers'; export type { Span } from './types'; @@ -40,7 +40,8 @@ export { setIsolationScope, setCurrentScope, } from './sdk/api'; -export { getCurrentHub, makeMain } from './sdk/hub'; +// eslint-disable-next-line deprecation/deprecation +export { getCurrentHub } from './sdk/hub'; export { addBreadcrumb, @@ -88,6 +89,14 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + setCurrentClient, + Scope, + setMeasurement, + continueTrace, + cron, + parameterize, + // eslint-disable-next-line deprecation/deprecation + makeMain, } from '@sentry/node'; export type { @@ -108,4 +117,5 @@ export type { Stacktrace, Thread, User, + NodeOptions, } from '@sentry/node'; diff --git a/packages/node-experimental/src/otel/contextManager.ts b/packages/node-experimental/src/otel/contextManager.ts index a7154fb96390..92cfb5b51ddd 100644 --- a/packages/node-experimental/src/otel/contextManager.ts +++ b/packages/node-experimental/src/otel/contextManager.ts @@ -34,6 +34,7 @@ export class SentryContextManager extends AsyncLocalStorageContextManager { const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope }; // We also need to "mock" the hub on the context, as the original @sentry/opentelemetry uses that... + // eslint-disable-next-line deprecation/deprecation const mockHub = { ...getCurrentHub(), getScope: () => scopes.scope }; const ctx1 = setHubOnContext(context, mockHub); diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index d9928c73905e..75c34c07839f 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -29,11 +29,13 @@ import type { SentryCarrier } from './types'; /** Ensure the global hub is our proxied hub. */ export function setupGlobalHub(): void { const carrier = getGlobalCarrier(); + // eslint-disable-next-line deprecation/deprecation carrier.hub = getCurrentHub(); } /** * This is for legacy reasons, and returns a proxy object instead of a hub to be used. + * @deprecated Use the methods directly. */ export function getCurrentHub(): Hub { return { @@ -124,17 +126,6 @@ export function getCurrentHub(): Hub { }; } -/** - * Replaces the current main hub with the passed one on the global object - * - * @returns The old replaced hub - */ -export function makeMain(hub: Hub): Hub { - // eslint-disable-next-line no-console - console.warn('makeMain is a noop in @sentry/node-experimental. Use `setCurrentClient` instead.'); - return hub; -} - /** * Sends the current Session on the scope */ @@ -152,6 +143,7 @@ function _sendSessionUpdate(): void { * Set a mocked hub on the current carrier. */ export function setLegacyHubOnCarrier(carrier: SentryCarrier): boolean { + // eslint-disable-next-line deprecation/deprecation carrier.hub = getCurrentHub(); return true; } From dc8726a83ec966a89506f65c88f38c2450d4798c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 10:18:55 +0100 Subject: [PATCH 082/173] feat: Implement new Async Context Strategy (#10647) This updates the ACS to not rely on hubs (only) anymore. Now, instead of the ACS providing only `getCurrentHub` and `runWithAsyncContext`, this is changed a bit: 1. There is always a strategy, even if running in browser or similar. we just use the default (=stack) strategy in that case. 2. The ACS always returns a hub/scope/etc, not `undefined` - so the strategy must take care of falling back to the global hub itself (to make it easier to implement hub<>scope interop). The ACS defines the following methods now: * `getCurrentScope` * `getIsolationScope` * `withScope` * `withSetScope` - a variant that takes a scope and makes it the active one. I decided to make this a dedicated method on the ACS instead of overloading `withScope` because the types for that are rather tricky to repeat in strategies... * `withIsolationScope` * `withSetIsolationScope` - not we do not use this yet, but to keep the door open for us this is already required. * `getCurrentHub` --> for now, for backwards compatibility The methods themselves are pretty straightforward now! We basically always create a new hub, and decide if we want to fork the current/isolation scope based on what method has been used. We still use a hub everywhere under the hood for now. --- .../pages/api/async-context-edge-endpoint.ts | 6 +- .../scopes/isolationScope/scenario.ts | 2 +- packages/astro/src/server/middleware.ts | 4 +- packages/astro/test/client/sdk.test.ts | 10 +- packages/astro/test/server/middleware.test.ts | 8 +- packages/astro/test/server/sdk.test.ts | 1 + packages/browser/src/exports.ts | 1 - packages/browser/test/unit/index.test.ts | 8 +- packages/bun/src/index.ts | 1 + packages/bun/src/integrations/bunserver.ts | 4 +- packages/core/src/asyncContext.ts | 34 ++- packages/core/src/breadcrumbs.ts | 3 +- packages/core/src/currentScopes.ts | 87 ++++++- packages/core/src/exports.ts | 80 +----- packages/core/src/hub.ts | 72 ++++-- packages/core/src/index.ts | 12 +- packages/core/src/integration.ts | 2 +- .../core/src/integrations/functiontostring.ts | 2 +- packages/core/src/metrics/exports.ts | 2 +- packages/core/src/scope.ts | 1 + packages/core/src/server-runtime-client.ts | 12 +- packages/core/src/sessionflusher.ts | 8 +- .../src/tracing/dynamicSamplingContext.ts | 2 +- packages/core/src/tracing/trace.ts | 105 ++++---- packages/core/src/utils/hasTracingEnabled.ts | 3 +- packages/core/test/lib/async-context.test.ts | 15 -- packages/core/test/lib/base.test.ts | 1 + packages/deno/src/index.ts | 1 + .../src/common/wrapApiHandlerWithSentry.ts | 7 +- .../wrapApiHandlerWithSentryVercelCrons.ts | 4 +- .../src/common/wrapPageComponentWithSentry.ts | 6 +- packages/nextjs/test/clientSdk.test.ts | 1 + packages/nextjs/test/serverSdk.test.ts | 2 +- packages/node-experimental/src/index.ts | 11 +- .../src/integrations/http.ts | 10 +- .../src/integrations/index.ts | 12 +- .../src/otel/asyncContextStrategy.ts | 105 +++++++- .../src/otel/contextManager.ts | 43 +++- packages/node-experimental/src/sdk/api.ts | 69 +----- packages/node-experimental/src/sdk/globals.ts | 31 +-- packages/node-experimental/src/sdk/hub.ts | 25 +- packages/node-experimental/src/sdk/init.ts | 20 +- packages/node-experimental/src/sdk/scope.ts | 72 +----- packages/node-experimental/src/sdk/types.ts | 36 --- .../src/utils/contextData.ts | 6 + .../test/helpers/mockSdkInit.ts | 12 +- .../test/integration/breadcrumbs.test.ts | 4 +- .../test/integration/scope.test.ts | 2 +- .../node-experimental/test/sdk/init.test.ts | 3 +- packages/node/src/async/domain.ts | 91 ++++++- packages/node/src/async/hooks.ts | 82 ++++++- packages/node/src/handlers.ts | 20 +- packages/node/src/index.ts | 1 + packages/node/test/async/domain.test.ts | 102 +++++--- packages/node/test/async/hooks.test.ts | 94 +++++--- packages/node/test/client.test.ts | 155 ++++++------ packages/node/test/handlers.test.ts | 228 ++++++++++-------- packages/node/test/index.test.ts | 25 +- .../node/test/integrations/undici.test.ts | 14 +- .../manual/webpack-async-context/index.js | 2 +- .../opentelemetry/src/asyncContextStrategy.ts | 70 +++++- packages/opentelemetry/src/constants.ts | 4 + packages/opentelemetry/src/contextManager.ts | 36 ++- .../test/asyncContextStrategy.test.ts | 21 +- .../opentelemetry/test/helpers/mockSdkInit.ts | 16 +- packages/remix/src/index.server.ts | 1 + packages/remix/src/utils/instrumentServer.ts | 7 +- .../remix/src/utils/serverAdapters/express.ts | 14 +- packages/remix/test/index.client.test.ts | 1 + packages/remix/test/index.server.test.ts | 4 +- .../test/integration/eventProcessors.test.ts | 1 + packages/serverless/src/index.ts | 1 + packages/serverless/src/utils.ts | 4 +- packages/sveltekit/src/server/handle.ts | 5 +- packages/sveltekit/src/server/index.ts | 1 + packages/sveltekit/test/client/sdk.test.ts | 1 + packages/sveltekit/test/server/sdk.test.ts | 1 + .../browser/browserTracingIntegration.test.ts | 1 + packages/vercel-edge/src/async.ts | 82 ++++++- packages/vercel-edge/src/index.ts | 1 + 80 files changed, 1215 insertions(+), 839 deletions(-) delete mode 100644 packages/core/test/lib/async-context.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts index 2ee051870a29..6dc023fdf1ed 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts @@ -5,16 +5,16 @@ export const config = { }; export default async function handler() { - // Without `runWithAsyncContext` and a working async context strategy the two spans created by `Sentry.startSpan()` would be nested. + // Without a working async context strategy the two spans created by `Sentry.startSpan()` would be nested. - const outerSpanPromise = Sentry.runWithAsyncContext(() => { + const outerSpanPromise = Sentry.withIsolationScope(() => { return Sentry.startSpan({ name: 'outer-span' }, () => { return new Promise(resolve => setTimeout(resolve, 300)); }); }); setTimeout(() => { - Sentry.runWithAsyncContext(() => { + Sentry.withIsolationScope(() => { return Sentry.startSpan({ name: 'inner-span' }, () => { return new Promise(resolve => setTimeout(resolve, 100)); }); diff --git a/dev-packages/node-integration-tests/suites/public-api/scopes/isolationScope/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/scopes/isolationScope/scenario.ts index 7d78c276880b..d38b96b91d4e 100644 --- a/dev-packages/node-integration-tests/suites/public-api/scopes/isolationScope/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/scopes/isolationScope/scenario.ts @@ -21,7 +21,7 @@ Sentry.withScope(scope => { Sentry.captureMessage('inner'); }); -Sentry.runWithAsyncContext(() => { +Sentry.withIsolationScope(() => { Sentry.getIsolationScope().setExtra('ff', 'ff'); Sentry.getCurrentScope().setExtra('gg', 'gg'); Sentry.captureMessage('inner_async_context'); diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index cd59d2f73344..7c0246174202 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -5,8 +5,8 @@ import { getActiveSpan, getClient, getCurrentScope, - runWithAsyncContext, startSpan, + withIsolationScope, } from '@sentry/node'; import type { Client, Scope, Span } from '@sentry/types'; import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils'; @@ -74,7 +74,7 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseH if (getActiveSpan()) { return instrumentRequest(ctx, next, handlerOptions); } - return runWithAsyncContext(() => { + return withIsolationScope(() => { return instrumentRequest(ctx, next, handlerOptions); }); }; diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index 6dcf2e984873..311287bfc533 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -2,10 +2,10 @@ import type { BrowserClient } from '@sentry/browser'; import { getActiveSpan } from '@sentry/browser'; import { browserTracingIntegration } from '@sentry/browser'; import * as SentryBrowser from '@sentry/browser'; -import { SDK_VERSION, WINDOW, getClient } from '@sentry/browser'; +import { SDK_VERSION, getClient } from '@sentry/browser'; import { vi } from 'vitest'; -import { getIsolationScope } from '@sentry/core'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { init } from '../../../astro/src/client/sdk'; const browserInit = vi.spyOn(SentryBrowser, 'init'); @@ -14,7 +14,11 @@ describe('Sentry client SDK', () => { describe('init', () => { afterEach(() => { vi.clearAllMocks(); - WINDOW.__SENTRY__.hub = undefined; + + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + getIsolationScope().clear(); + getGlobalScope().clear(); }); it('adds Astro metadata to the SDK options', () => { diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index a83de3cb0eb2..442e144bccb8 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -260,10 +260,10 @@ describe('sentryMiddleware', () => { }); describe('async context isolation', () => { - const runWithAsyncContextSpy = vi.spyOn(SentryNode, 'runWithAsyncContext'); + const withIsolationScopeSpy = vi.spyOn(SentryNode, 'withIsolationScope'); afterEach(() => { vi.clearAllMocks(); - runWithAsyncContextSpy.mockRestore(); + withIsolationScopeSpy.mockRestore(); }); it('starts a new async context if no span is active', async () => { @@ -279,7 +279,7 @@ describe('sentryMiddleware', () => { // this is fine, it's not required to pass in this test } - expect(runWithAsyncContextSpy).toHaveBeenCalledTimes(1); + expect(withIsolationScopeSpy).toHaveBeenCalledTimes(1); }); it("doesn't start a new async context if a span is active", async () => { @@ -297,7 +297,7 @@ describe('sentryMiddleware', () => { // this is fine, it's not required to pass in this test } - expect(runWithAsyncContextSpy).not.toHaveBeenCalled(); + expect(withIsolationScopeSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/astro/test/server/sdk.test.ts b/packages/astro/test/server/sdk.test.ts index d07f95263c97..1b042ee5331a 100644 --- a/packages/astro/test/server/sdk.test.ts +++ b/packages/astro/test/server/sdk.test.ts @@ -14,6 +14,7 @@ describe('Sentry server SDK', () => { SentryNode.getGlobalScope().clear(); SentryNode.getIsolationScope().clear(); SentryNode.getCurrentScope().clear(); + SentryNode.getCurrentScope().setClient(undefined); }); it('adds Astro metadata to the SDK options', () => { diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index e9ac6816c3c1..85c85415ebce 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -41,7 +41,6 @@ export { getGlobalScope, Hub, // eslint-disable-next-line deprecation/deprecation - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, Scope, diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index b2c844976c1d..46104ba79b0f 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -1,4 +1,4 @@ -import { InboundFilters, SDK_VERSION, getReportDialogEndpoint } from '@sentry/core'; +import { InboundFilters, SDK_VERSION, getGlobalScope, getIsolationScope, getReportDialogEndpoint } from '@sentry/core'; import type { WrappedFunction } from '@sentry/types'; import * as utils from '@sentry/utils'; @@ -39,7 +39,11 @@ describe('SentryBrowser', () => { const beforeSend = jest.fn(event => event); beforeEach(() => { - WINDOW.__SENTRY__ = { hub: undefined, logger: undefined, globalEventProcessors: [] }; + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + init({ beforeSend, dsn, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 39056b751226..22040d39b059 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -47,6 +47,7 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index e262cd4e70a4..5075d2f8f250 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -7,9 +7,9 @@ import { convertIntegrationFnToClass, defineIntegration, getCurrentScope, - runWithAsyncContext, setHttpStatus, startSpan, + withIsolationScope, } from '@sentry/core'; import type { IntegrationFn } from '@sentry/types'; import { getSanitizedUrlString, parseUrl } from '@sentry/utils'; @@ -53,7 +53,7 @@ export function instrumentBunServe(): void { function instrumentBunServeOptions(serveOptions: Parameters[0]): void { serveOptions.fetch = new Proxy(serveOptions.fetch, { apply(fetchTarget, fetchThisArg, fetchArgs: Parameters) { - return runWithAsyncContext(() => { + return withIsolationScope(() => { const request = fetchArgs[0]; const upperCaseMethod = request.method.toUpperCase(); if (upperCaseMethod === 'OPTIONS' || upperCaseMethod === 'HEAD') { diff --git a/packages/core/src/asyncContext.ts b/packages/core/src/asyncContext.ts index 2a4176f7f4cd..9ff2fff996f0 100644 --- a/packages/core/src/asyncContext.ts +++ b/packages/core/src/asyncContext.ts @@ -1,4 +1,5 @@ import type { Hub, Integration } from '@sentry/types'; +import type { Scope } from '@sentry/types'; import { GLOBAL_OBJ } from '@sentry/utils'; /** @@ -8,14 +9,39 @@ import { GLOBAL_OBJ } from '@sentry/utils'; */ export interface AsyncContextStrategy { /** - * Gets the current async context. Returns undefined if there is no current async context. + * Gets the currently active hub. */ - getCurrentHub: () => Hub | undefined; + getCurrentHub: () => Hub; /** - * Runs the supplied callback in its own async context. + * Fork the isolation scope inside of the provided callback. */ - runWithAsyncContext(callback: () => T): T; + withIsolationScope: (callback: (isolationScope: Scope) => T) => T; + + /** + * Fork the current scope inside of the provided callback. + */ + withScope: (callback: (isolationScope: Scope) => T) => T; + + /** + * Set the provided scope as the current scope inside of the provided callback. + */ + withSetScope: (scope: Scope, callback: (scope: Scope) => T) => T; + + /** + * Set the provided isolation as the current isolation scope inside of the provided callback. + */ + withSetIsolationScope: (isolationScope: Scope, callback: (isolationScope: Scope) => T) => T; + + /** + * Get the currently active scope. + */ + getCurrentScope: () => Scope; + + /** + * Get the currently active isolation scope. + */ + getIsolationScope: () => Scope; } /** diff --git a/packages/core/src/breadcrumbs.ts b/packages/core/src/breadcrumbs.ts index 85e28c030370..1cfad5d08fea 100644 --- a/packages/core/src/breadcrumbs.ts +++ b/packages/core/src/breadcrumbs.ts @@ -1,7 +1,6 @@ import type { Breadcrumb, BreadcrumbHint } from '@sentry/types'; import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils'; -import { getIsolationScope } from './currentScopes'; -import { getClient } from './exports'; +import { getClient, getIsolationScope } from './currentScopes'; /** * Default maximum number of breadcrumbs added to an event. Can be overwritten diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index bf93e310d2dd..a58aaf836be5 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -1,5 +1,7 @@ import type { Scope } from '@sentry/types'; -import { getCurrentHub } from './hub'; +import type { Client } from '@sentry/types'; +import { getMainCarrier } from './asyncContext'; +import { getAsyncContextStrategy } from './hub'; import { Scope as ScopeClass } from './scope'; /** @@ -12,8 +14,9 @@ let globalScope: Scope | undefined; * Get the currently active scope. */ export function getCurrentScope(): Scope { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getScope(); + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + return acs.getCurrentScope(); } /** @@ -21,8 +24,9 @@ export function getCurrentScope(): Scope { * The isolation scope is active for the current exection context. */ export function getIsolationScope(): Scope { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getIsolationScope(); + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + return acs.getIsolationScope(); } /** @@ -45,3 +49,76 @@ export function getGlobalScope(): Scope { export function setGlobalScope(scope: Scope | undefined): void { globalScope = scope; } + +/** + * Creates a new scope with and executes the given operation within. + * The scope is automatically removed once the operation + * finishes or throws. + */ +export function withScope(callback: (scope: Scope) => T): T; +/** + * Set the given scope as the active scope in the callback. + */ +export function withScope(scope: Scope | undefined, callback: (scope: Scope) => T): T; +/** + * Either creates a new active scope, or sets the given scope as active scope in the given callback. + */ +export function withScope( + ...rest: [callback: (scope: Scope) => T] | [scope: Scope | undefined, callback: (scope: Scope) => T] +): T { + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + + // If a scope is defined, we want to make this the active scope instead of the default one + if (rest.length === 2) { + const [scope, callback] = rest; + + if (!scope) { + return acs.withScope(callback); + } + + return acs.withSetScope(scope, callback); + } + + return acs.withScope(rest[0]); +} + +/** + * Attempts to fork the current isolation scope and the current scope based on the current async context strategy. If no + * async context strategy is set, the isolation scope and the current scope will not be forked (this is currently the + * case, for example, in the browser). + * + * Usage of this function in environments without async context strategy is discouraged and may lead to unexpected behaviour. + * + * This function is intended for Sentry SDK and SDK integration development. It is not recommended to be used in "normal" + * applications directly because it comes with pitfalls. Use at your own risk! + * + * @param callback The callback in which the passed isolation scope is active. (Note: In environments without async + * context strategy, the currently active isolation scope may change within execution of the callback.) + * @returns The same value that `callback` returns. + */ +export function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + return acs.withIsolationScope(callback); +} + +/** + * Runs the supplied callback in its own async context. Async Context strategies are defined per SDK. + * + * @param callback The callback to run in its own async context + * @param options Options to pass to the async context strategy + * @returns The result of the callback + * + * @deprecated Use `Sentry.withScope()` instead. + */ +export function runWithAsyncContext(callback: () => T): T { + return withScope(() => callback()); +} + +/** + * Get the currently active client. + */ +export function getClient(): C | undefined { + return getCurrentScope().getClient(); +} diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index d43e3efe26e2..eb6fe37a88ec 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,7 +1,6 @@ import type { CaptureContext, CheckIn, - Client, CustomSamplingContext, Event, EventHint, @@ -22,11 +21,10 @@ import type { import { GLOBAL_OBJ, isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from './constants'; -import { getCurrentScope, getIsolationScope } from './currentScopes'; +import { getClient, getCurrentScope, getIsolationScope, withScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Hub } from './hub'; -import { getCurrentHub, runWithAsyncContext } from './hub'; -import type { Scope } from './scope'; +import { getCurrentHub } from './hub'; import { closeSession, makeSession, updateSession } from './session'; import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent'; import { parseEventHintOrCaptureContext } from './utils/prepareEvent'; @@ -131,73 +129,6 @@ export function setUser(user: User | null): ReturnType { getIsolationScope().setUser(user); } -/** - * Creates a new scope with and executes the given operation within. - * The scope is automatically removed once the operation - * finishes or throws. - * - * This is essentially a convenience function for: - * - * pushScope(); - * callback(); - * popScope(); - */ -export function withScope(callback: (scope: ScopeInterface) => T): T; -/** - * Set the given scope as the active scope in the callback. - */ -export function withScope(scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T): T; -/** - * Either creates a new active scope, or sets the given scope as active scope in the given callback. - */ -export function withScope( - ...rest: - | [callback: (scope: ScopeInterface) => T] - | [scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T] -): T { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub() as Hub; - - // If a scope is defined, we want to make this the active scope instead of the default one - if (rest.length === 2) { - const [scope, callback] = rest; - if (!scope) { - // eslint-disable-next-line deprecation/deprecation - return hub.withScope(callback); - } - - // eslint-disable-next-line deprecation/deprecation - return hub.withScope(() => { - // eslint-disable-next-line deprecation/deprecation - hub.getStackTop().scope = scope as Scope; - return callback(scope as Scope); - }); - } - - // eslint-disable-next-line deprecation/deprecation - return hub.withScope(rest[0]); -} - -/** - * Attempts to fork the current isolation scope and the current scope based on the current async context strategy. If no - * async context strategy is set, the isolation scope and the current scope will not be forked (this is currently the - * case, for example, in the browser). - * - * Usage of this function in environments without async context strategy is discouraged and may lead to unexpected behaviour. - * - * This function is intended for Sentry SDK and SDK integration development. It is not recommended to be used in "normal" - * applications directly because it comes with pitfalls. Use at your own risk! - * - * @param callback The callback in which the passed isolation scope is active. (Note: In environments without async - * context strategy, the currently active isolation scope may change within execution of the callback.) - * @returns The same value that `callback` returns. - */ -export function withIsolationScope(callback: (isolationScope: ScopeInterface) => T): T { - return runWithAsyncContext(() => { - return callback(getIsolationScope()); - }); -} - /** * Forks the current scope and sets the provided span as active span in the context of the provided callback. * @@ -341,13 +272,6 @@ export async function close(timeout?: number): Promise { return Promise.resolve(false); } -/** - * Get the currently active client. - */ -export function getClient(): C | undefined { - return getCurrentScope().getClient(); -} - /** * Returns true if Sentry has been properly initialized. */ diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 44392b1beb90..f092af47f512 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -654,22 +654,32 @@ export function getCurrentHub(): HubInterface { return acs.getCurrentHub() || getGlobalHub(); } -/** - * Runs the supplied callback in its own async context. Async Context strategies are defined per SDK. - * - * @param callback The callback to run in its own async context - * @param options Options to pass to the async context strategy - * @returns The result of the callback - */ -export function runWithAsyncContext(callback: () => T): T { - // Get main carrier (global for every environment) - const carrier = getMainCarrier(); +let defaultCurrentScope: Scope | undefined; +let defaultIsolationScope: Scope | undefined; - const acs = getAsyncContextStrategy(carrier); - return acs.runWithAsyncContext(callback); +/** Get the default current scope. */ +export function getDefaultCurrentScope(): Scope { + if (!defaultCurrentScope) { + defaultCurrentScope = new Scope(); + } + + return defaultCurrentScope; +} + +/** Get the default isolation scope. */ +export function getDefaultIsolationScope(): Scope { + if (!defaultIsolationScope) { + defaultIsolationScope = new Scope(); + } + + return defaultIsolationScope; } -function getGlobalHub(): HubInterface { +/** + * Get the global hub. + * This will be removed during the v8 cycle and is only here to make migration easier. + */ +export function getGlobalHub(): HubInterface { const registry = getMainCarrier(); // If there's no hub, or its an old API, assign a new one @@ -680,7 +690,7 @@ function getGlobalHub(): HubInterface { getHubFromCarrier(registry).isOlderThan(API_VERSION) ) { // eslint-disable-next-line deprecation/deprecation - setHubOnCarrier(registry, new Hub()); + setHubOnCarrier(registry, new Hub(undefined, getDefaultCurrentScope(), getDefaultIsolationScope())); } // Return hub that lives on a global object @@ -749,11 +759,41 @@ export function getAsyncContextStrategy(carrier: Carrier): AsyncContextStrategy return getHubStackAsyncContextStrategy(); } +function withScope(callback: (scope: ScopeInterface) => T): T { + // eslint-disable-next-line deprecation/deprecation + return getGlobalHub().withScope(callback); +} + +function withSetScope(scope: ScopeInterface, callback: (scope: ScopeInterface) => T): T { + const hub = getGlobalHub() as Hub; + // eslint-disable-next-line deprecation/deprecation + return hub.withScope(() => { + // eslint-disable-next-line deprecation/deprecation + hub.getStackTop().scope = scope as Scope; + return callback(scope); + }); +} + +function withIsolationScope(callback: (isolationScope: ScopeInterface) => T): T { + // eslint-disable-next-line deprecation/deprecation + return getGlobalHub().withScope(() => { + // eslint-disable-next-line deprecation/deprecation + return callback(getGlobalHub().getIsolationScope()); + }); +} + +/* eslint-disable deprecation/deprecation */ function getHubStackAsyncContextStrategy(): AsyncContextStrategy { return { getCurrentHub: getGlobalHub, - runWithAsyncContext: (callback: () => T): T => { - return callback(); + withIsolationScope, + withScope, + withSetScope, + withSetIsolationScope: (_isolationScope: ScopeInterface, callback: (isolationScope: ScopeInterface) => T) => { + return withIsolationScope(callback); }, + getCurrentScope: () => getGlobalHub().getScope(), + getIsolationScope: () => getGlobalHub().getIsolationScope(), }; } +/* eslint-enable deprecation/deprecation */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cfcc08fef5ee..b1979d53ed40 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,9 +25,6 @@ export { setTag, setTags, setUser, - withScope, - withIsolationScope, - getClient, isInitialized, startSession, endSession, @@ -44,13 +41,20 @@ export { makeMain, setHubOnCarrier, ensureHubOnCarrier, - runWithAsyncContext, + getGlobalHub, + getDefaultCurrentScope, + getDefaultIsolationScope, } from './hub'; export { getCurrentScope, getIsolationScope, getGlobalScope, setGlobalScope, + withScope, + withIsolationScope, + // eslint-disable-next-line deprecation/deprecation + runWithAsyncContext, + getClient, } from './currentScopes'; export { getMainCarrier, diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 80400455363e..1e0273d72f2b 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -9,10 +9,10 @@ import type { Options, } from '@sentry/types'; import { arrayify, logger } from '@sentry/utils'; +import { getClient } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import { addGlobalEventProcessor } from './eventProcessors'; -import { getClient } from './exports'; import { getCurrentHub } from './hub'; declare module '@sentry/types' { diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index 0f3e9f08b59e..0f97226b6708 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -1,6 +1,6 @@ import type { Client, Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; import { getOriginalFunction } from '@sentry/utils'; -import { getClient } from '../exports'; +import { getClient } from '../currentScopes'; import { convertIntegrationFnToClass, defineIntegration } from '../integration'; let originalFunctionToString: () => void; diff --git a/packages/core/src/metrics/exports.ts b/packages/core/src/metrics/exports.ts index 6eae1d91ac8e..5af06d9d509d 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -2,8 +2,8 @@ import type { ClientOptions, MeasurementUnit, Primitive } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { BaseClient } from '../baseclient'; import { getCurrentScope } from '../currentScopes'; +import { getClient } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import { getClient } from '../exports'; import { spanToJSON } from '../utils/spanUtils'; import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; import { MetricsAggregator, metricsAggregatorIntegration } from './integration'; diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 6368bbcdf6f9..8f73f68060f3 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -420,6 +420,7 @@ export class Scope implements ScopeInterface { * @inheritDoc */ public clear(): this { + // client is not cleared here on purpose! this._breadcrumbs = []; this._tags = {}; this._extra = {}; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index e4dfb135abdf..5fbfa2c6aed6 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -15,8 +15,8 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; +import { getClient, getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { getClient } from './exports'; import { MetricsAggregator } from './metrics/aggregator'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; @@ -80,13 +80,13 @@ export class ServerRuntimeClient< /** * @inheritDoc */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + // eslint-disable-next-line @typescript-eslint/no-explicit-any public captureException(exception: any, hint?: EventHint, scope?: Scope): string | undefined { // Check if the flag `autoSessionTracking` is enabled, and if `_sessionFlusher` exists because it is initialised only // when the `requestHandler` middleware is used, and hence the expectation is to have SessionAggregates payload // sent to the Server only when the `requestHandler` middleware is used - if (this._options.autoSessionTracking && this._sessionFlusher && scope) { - const requestSession = scope.getRequestSession(); + if (this._options.autoSessionTracking && this._sessionFlusher) { + const requestSession = getIsolationScope().getRequestSession(); // Necessary checks to ensure this is code block is executed only within a request // Should override the status only if `requestSession.status` is `Ok`, which is its initial stage @@ -105,14 +105,14 @@ export class ServerRuntimeClient< // Check if the flag `autoSessionTracking` is enabled, and if `_sessionFlusher` exists because it is initialised only // when the `requestHandler` middleware is used, and hence the expectation is to have SessionAggregates payload // sent to the Server only when the `requestHandler` middleware is used - if (this._options.autoSessionTracking && this._sessionFlusher && scope) { + if (this._options.autoSessionTracking && this._sessionFlusher) { const eventType = event.type || 'exception'; const isException = eventType === 'exception' && event.exception && event.exception.values && event.exception.values.length > 0; // If the event is of type Exception, then a request session should be captured if (isException) { - const requestSession = scope.getRequestSession(); + const requestSession = getIsolationScope().getRequestSession(); // Ensure that this is happening within the bounds of a request, and make sure not to override // Session Status if Errored / Crashed diff --git a/packages/core/src/sessionflusher.ts b/packages/core/src/sessionflusher.ts index 4a1e518321fb..604a654a6b01 100644 --- a/packages/core/src/sessionflusher.ts +++ b/packages/core/src/sessionflusher.ts @@ -6,7 +6,7 @@ import type { SessionFlusherLike, } from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; -import { getCurrentScope } from './currentScopes'; +import { getIsolationScope } from './currentScopes'; type ReleaseHealthAttributes = { environment?: string; @@ -74,14 +74,14 @@ export class SessionFlusher implements SessionFlusherLike { if (!this._isEnabled) { return; } - const scope = getCurrentScope(); - const requestSession = scope.getRequestSession(); + const isolationScope = getIsolationScope(); + const requestSession = isolationScope.getRequestSession(); if (requestSession && requestSession.status) { this._incrementSessionStatusCount(requestSession.status, new Date()); // This is not entirely necessarily but is added as a safe guard to indicate the bounds of a request and so in // case captureRequestSession is called more than once to prevent double count - scope.setRequestSession(undefined); + isolationScope.setRequestSession(undefined); /* eslint-enable @typescript-eslint/no-unsafe-member-access */ } } diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index d7fe3872b42f..ff4599a42a68 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -2,7 +2,7 @@ import type { Client, DynamicSamplingContext, Span, Transaction } from '@sentry/ import { dropUndefinedKeys } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; -import { getClient } from '../exports'; +import { getClient } from '../currentScopes'; import { getRootSpan } from '../utils/getRootSpan'; import { spanIsSampled, spanToJSON } from '../utils/spanUtils'; diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index f2c82857543d..8e94e7acd891 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,11 +1,10 @@ import type { Hub, Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types'; import { addNonEnumerableProperty, dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; -import { getCurrentScope, getIsolationScope } from '../currentScopes'; +import { getCurrentScope, getIsolationScope, withScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import { withScope } from '../exports'; -import { getCurrentHub, runWithAsyncContext } from '../hub'; +import { getCurrentHub } from '../hub'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; @@ -24,33 +23,31 @@ import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; export function startSpan(context: StartSpanOptions, callback: (span: Span | undefined) => T): T { const ctx = normalizeContext(context); - return runWithAsyncContext(() => { - return withScope(context.scope, scope => { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + return withScope(context.scope, scope => { + // eslint-disable-next-line deprecation/deprecation + const hub = getCurrentHub(); + // eslint-disable-next-line deprecation/deprecation + const parentSpan = scope.getSpan(); - const shouldSkipSpan = context.onlyIfParent && !parentSpan; - const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); + const shouldSkipSpan = context.onlyIfParent && !parentSpan; + const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(activeSpan); - - return handleCallbackErrors( - () => callback(activeSpan), - () => { - // Only update the span status if it hasn't been changed yet - if (activeSpan) { - const { status } = spanToJSON(activeSpan); - if (!status || status === 'ok') { - activeSpan.setStatus('internal_error'); - } + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(activeSpan); + + return handleCallbackErrors( + () => callback(activeSpan), + () => { + // Only update the span status if it hasn't been changed yet + if (activeSpan) { + const { status } = spanToJSON(activeSpan); + if (!status || status === 'ok') { + activeSpan.setStatus('internal_error'); } - }, - () => activeSpan && activeSpan.end(), - ); - }); + } + }, + () => activeSpan && activeSpan.end(), + ); }); } @@ -71,36 +68,34 @@ export function startSpanManual( ): T { const ctx = normalizeContext(context); - return runWithAsyncContext(() => { - return withScope(context.scope, scope => { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + return withScope(context.scope, scope => { + // eslint-disable-next-line deprecation/deprecation + const hub = getCurrentHub(); + // eslint-disable-next-line deprecation/deprecation + const parentSpan = scope.getSpan(); - const shouldSkipSpan = context.onlyIfParent && !parentSpan; - const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); + const shouldSkipSpan = context.onlyIfParent && !parentSpan; + const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(activeSpan); - - function finishAndSetSpan(): void { - activeSpan && activeSpan.end(); - } - - return handleCallbackErrors( - () => callback(activeSpan, finishAndSetSpan), - () => { - // Only update the span status if it hasn't been changed yet, and the span is not yet finished - if (activeSpan && activeSpan.isRecording()) { - const { status } = spanToJSON(activeSpan); - if (!status || status === 'ok') { - activeSpan.setStatus('internal_error'); - } + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(activeSpan); + + function finishAndSetSpan(): void { + activeSpan && activeSpan.end(); + } + + return handleCallbackErrors( + () => callback(activeSpan, finishAndSetSpan), + () => { + // Only update the span status if it hasn't been changed yet, and the span is not yet finished + if (activeSpan && activeSpan.isRecording()) { + const { status } = spanToJSON(activeSpan); + if (!status || status === 'ok') { + activeSpan.setStatus('internal_error'); } - }, - ); - }); + } + }, + ); }); } @@ -271,7 +266,7 @@ export const continueTrace: ContinueTrace = ( return transactionContext; } - return runWithAsyncContext(() => { + return withScope(() => { return callback(transactionContext); }); }; diff --git a/packages/core/src/utils/hasTracingEnabled.ts b/packages/core/src/utils/hasTracingEnabled.ts index 94c5ab13e5ae..a4a854edf314 100644 --- a/packages/core/src/utils/hasTracingEnabled.ts +++ b/packages/core/src/utils/hasTracingEnabled.ts @@ -1,6 +1,5 @@ import type { Options } from '@sentry/types'; - -import { getClient } from '../exports'; +import { getClient } from '../currentScopes'; // Treeshakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean | undefined; diff --git a/packages/core/test/lib/async-context.test.ts b/packages/core/test/lib/async-context.test.ts deleted file mode 100644 index a0e9bb16ab36..000000000000 --- a/packages/core/test/lib/async-context.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getCurrentHub, runWithAsyncContext } from '../../src'; - -describe('runWithAsyncContext()', () => { - it('without strategy hubs should be equal', () => { - runWithAsyncContext(() => { - // eslint-disable-next-line deprecation/deprecation - const hub1 = getCurrentHub(); - runWithAsyncContext(() => { - // eslint-disable-next-line deprecation/deprecation - const hub2 = getCurrentHub(); - expect(hub1).toBe(hub2); - }); - }); - }); -}); diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index f07b0738e508..b48d3b0e5eec 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -64,6 +64,7 @@ describe('BaseClient', () => { TestClient.instance = undefined; setGlobalScope(undefined); getCurrentScope().clear(); + getCurrentScope().setClient(undefined); getIsolationScope().clear(); }); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index a82f1389ae4f..c3abd46f0b1e 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -46,6 +46,7 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts index 6aa8de0cbac0..1c217ac9cbf6 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts @@ -3,10 +3,9 @@ import { addTracingExtensions, captureException, continueTrace, - getCurrentScope, - runWithAsyncContext, setHttpStatus, startSpanManual, + withIsolationScope, } from '@sentry/core'; import { consoleSandbox, isString, logger, objectify, stripUrlQueryAndFragment } from '@sentry/utils'; @@ -55,7 +54,7 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz addTracingExtensions(); - return runWithAsyncContext(() => { + return withIsolationScope(isolationScope => { return continueTrace( { // TODO(v8): Make it so that continue trace will allow null as sentryTrace value and remove this fallback here @@ -82,7 +81,7 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz const reqMethod = `${(req.method || 'GET').toUpperCase()} `; - getCurrentScope().setSDKProcessingMetadata({ request: req }); + isolationScope.setSDKProcessingMetadata({ request: req }); return startSpanManual( { diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts index ae7bb19be43e..ec8791f95584 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts +++ b/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts @@ -1,4 +1,4 @@ -import { addTracingExtensions, captureCheckIn, runWithAsyncContext } from '@sentry/core'; +import { addTracingExtensions, captureCheckIn, withIsolationScope } from '@sentry/core'; import type { NextApiRequest } from 'next'; import type { VercelCronsConfig } from './types'; @@ -19,7 +19,7 @@ export function wrapApiHandlerWithSentryVercelCrons { - return runWithAsyncContext(() => { + return withIsolationScope(() => { if (!args || !args[0]) { return originalFunction.apply(thisArg, args); } diff --git a/packages/nextjs/src/common/wrapPageComponentWithSentry.ts b/packages/nextjs/src/common/wrapPageComponentWithSentry.ts index 2051d015b0c4..90d7f739d531 100644 --- a/packages/nextjs/src/common/wrapPageComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapPageComponentWithSentry.ts @@ -1,4 +1,4 @@ -import { addTracingExtensions, captureException, getCurrentScope, runWithAsyncContext } from '@sentry/core'; +import { addTracingExtensions, captureException, getCurrentScope, withIsolationScope } from '@sentry/core'; import { extractTraceparentData } from '@sentry/utils'; interface FunctionComponent { @@ -25,7 +25,7 @@ export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | C if (isReactClassComponent(pageComponent)) { return class SentryWrappedPageComponent extends pageComponent { public render(...args: unknown[]): unknown { - return runWithAsyncContext(() => { + return withIsolationScope(() => { const scope = getCurrentScope(); // We extract the sentry trace data that is put in the component props by datafetcher wrappers const sentryTraceData = @@ -60,7 +60,7 @@ export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | C } else if (typeof pageComponent === 'function') { return new Proxy(pageComponent, { apply(target, thisArg, argArray: [{ _sentryTraceData?: string } | undefined]) { - return runWithAsyncContext(() => { + return withIsolationScope(() => { const scope = getCurrentScope(); // We extract the sentry trace data that is put in the component props by datafetcher wrappers const sentryTraceData = argArray?.[0]?._sentryTraceData; diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 113117d519b7..2a19b74d5fcd 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -40,6 +40,7 @@ describe('Client init()', () => { getGlobalScope().clear(); getIsolationScope().clear(); getCurrentScope().clear(); + getCurrentScope().setClient(undefined); }); it('inits the React SDK', () => { diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index ea998dedfe44..f89e0f771349 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -22,8 +22,8 @@ describe('Server init()', () => { SentryNode.getGlobalScope().clear(); SentryNode.getIsolationScope().clear(); SentryNode.getCurrentScope().clear(); + SentryNode.getCurrentScope().setClient(undefined); - GLOBAL_OBJ.__SENTRY__.hub = undefined; delete process.env.VERCEL; }); diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index d94718abcabb..bd87436e36eb 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -32,13 +32,7 @@ export { captureException, captureEvent, captureMessage, - withScope, - withIsolationScope, withActiveSpan, - getCurrentScope, - getIsolationScope, - setIsolationScope, - setCurrentScope, } from './sdk/api'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; @@ -60,6 +54,7 @@ export { createTransport, flush, Hub, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, SDK_VERSION, getSpanStatusFromHttpCode, @@ -97,6 +92,10 @@ export { parameterize, // eslint-disable-next-line deprecation/deprecation makeMain, + getCurrentScope, + getIsolationScope, + withScope, + withIsolationScope, } from '@sentry/node'; export type { diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 0e24798ef173..319f2da4f60f 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -4,12 +4,18 @@ import { SpanKind } from '@opentelemetry/api'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { addBreadcrumb, defineIntegration, hasTracingEnabled, isSentryRequestUrl } from '@sentry/core'; +import { + addBreadcrumb, + defineIntegration, + getIsolationScope, + hasTracingEnabled, + isSentryRequestUrl, +} from '@sentry/core'; import { _INTERNAL, getClient, getSpanKind, setSpanMetadata } from '@sentry/opentelemetry'; import type { EventProcessor, Hub, Integration, IntegrationFn } from '@sentry/types'; import { stringMatchesSomePattern } from '@sentry/utils'; -import { getIsolationScope, setIsolationScope } from '../sdk/api'; +import { setIsolationScope } from '../sdk/scope'; import type { NodeExperimentalClient } from '../types'; import { addOriginToSpan } from '../utils/addOriginToSpan'; import { getRequestUrl } from '../utils/getRequestUrl'; diff --git a/packages/node-experimental/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts index ac9c0775bead..d9c238f68141 100644 --- a/packages/node-experimental/src/integrations/index.ts +++ b/packages/node-experimental/src/integrations/index.ts @@ -1,15 +1,7 @@ import { Integrations as NodeIntegrations } from '@sentry/node'; -const { - Console, - OnUncaughtException, - OnUnhandledRejection, - Modules, - ContextLines, - Context, - RequestData, - // eslint-disable-next-line deprecation/deprecation -} = NodeIntegrations; +const { Console, OnUncaughtException, OnUnhandledRejection, Modules, ContextLines, Context, RequestData } = + NodeIntegrations; export { Console, OnUncaughtException, OnUnhandledRejection, Modules, ContextLines, Context, RequestData }; diff --git a/packages/node-experimental/src/otel/asyncContextStrategy.ts b/packages/node-experimental/src/otel/asyncContextStrategy.ts index 10b4ea6d7b91..477f4a7c7e83 100644 --- a/packages/node-experimental/src/otel/asyncContextStrategy.ts +++ b/packages/node-experimental/src/otel/asyncContextStrategy.ts @@ -1,29 +1,116 @@ import * as api from '@opentelemetry/api'; +import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core'; +import type { Hub, Scope } from '@sentry/types'; -import { setAsyncContextStrategy } from '../sdk/globals'; -import { getCurrentHub } from './../sdk/hub'; +import { getCurrentHub as _getCurrentHub } from './../sdk/hub'; import type { CurrentScopes } from './../sdk/types'; -import { getScopesFromContext } from './../utils/contextData'; +import { + SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, + getScopesFromContext, +} from './../utils/contextData'; /** * Sets the async context strategy to use follow the OTEL context under the hood. * We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts) */ export function setOpenTelemetryContextAsyncContextStrategy(): void { - function getScopes(): CurrentScopes | undefined { + function getScopes(): CurrentScopes { const ctx = api.context.active(); - return getScopesFromContext(ctx); + const scopes = getScopesFromContext(ctx); + + if (scopes) { + return scopes; + } + + // fallback behavior: + // if, for whatever reason, we can't find scopes on the context here, we have to fix this somehow + return { + scope: getDefaultCurrentScope(), + isolationScope: getDefaultIsolationScope(), + }; + } + + function getCurrentHub(): Hub { + // eslint-disable-next-line deprecation/deprecation + const hub = _getCurrentHub(); + return { + ...hub, + getScope: () => { + const scopes = getScopes(); + return scopes.scope; + }, + getIsolationScope: () => { + const scopes = getScopes(); + return scopes.isolationScope; + }, + }; } - /* This is more or less a NOOP - we rely on the OTEL context manager for this */ - function runWithAsyncContext(callback: () => T): T { + function withScope(callback: (scope: Scope) => T): T { const ctx = api.context.active(); // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not + // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` return api.context.with(ctx, () => { - return callback(); + return callback(getCurrentScope()); + }); + } + + function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_SET_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which picks up this scope as the current scope + return api.context.with(ctx.setValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, scope), () => { + return callback(scope); + }); + } + + function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not + return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { + return callback(getIsolationScope()); }); } - setAsyncContextStrategy({ getScopes, getCurrentHub, runWithAsyncContext }); + function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not + return api.context.with(ctx.setValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, isolationScope), () => { + return callback(getIsolationScope()); + }); + } + + function getCurrentScope(): Scope { + return getScopes().scope; + } + + function getIsolationScope(): Scope { + return getScopes().isolationScope; + } + + setAsyncContextStrategy({ + getCurrentHub, + withScope, + withSetScope, + withSetIsolationScope, + withIsolationScope, + getCurrentScope, + getIsolationScope, + }); } diff --git a/packages/node-experimental/src/otel/contextManager.ts b/packages/node-experimental/src/otel/contextManager.ts index 92cfb5b51ddd..82a3a51b344b 100644 --- a/packages/node-experimental/src/otel/contextManager.ts +++ b/packages/node-experimental/src/otel/contextManager.ts @@ -1,11 +1,18 @@ import type { Context } from '@opentelemetry/api'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; +import { getCurrentScope, getIsolationScope } from '@sentry/core'; import { setHubOnContext } from '@sentry/opentelemetry'; +import type { Scope } from '@sentry/types'; import { getCurrentHub } from '../sdk/hub'; -import { getCurrentScope, getIsolationScope } from './../sdk/api'; import type { CurrentScopes } from './../sdk/types'; -import { getScopesFromContext, setScopesOnContext } from './../utils/contextData'; +import { + SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, + getScopesFromContext, + setScopesOnContext, +} from './../utils/contextData'; /** * This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager. @@ -25,21 +32,35 @@ export class SentryContextManager extends AsyncLocalStorageContextManager { thisArg?: ThisParameterType, ...args: A ): ReturnType { - const previousScopes = getScopesFromContext(context); + const currentScopes = getScopesFromContext(context); + const currentScope = currentScopes?.scope || getCurrentScope(); + const currentIsolationScope = currentScopes?.isolationScope || getIsolationScope(); - const currentScope = previousScopes ? previousScopes.scope : getCurrentScope(); - const isolationScope = previousScopes ? previousScopes.isolationScope : getIsolationScope(); + const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true; + const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) as Scope | undefined; + const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) as Scope | undefined; - const newCurrentScope = currentScope.clone(); - const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope }; + const newCurrentScope = scope || currentScope.clone(); + const newIsolationScope = + isolationScope || (shouldForkIsolationScope ? currentIsolationScope.clone() : currentIsolationScope); + const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope: newIsolationScope }; - // We also need to "mock" the hub on the context, as the original @sentry/opentelemetry uses that... - // eslint-disable-next-line deprecation/deprecation - const mockHub = { ...getCurrentHub(), getScope: () => scopes.scope }; + const mockHub = { + // eslint-disable-next-line deprecation/deprecation + ...getCurrentHub(), + getScope: () => newCurrentScope, + getIsolationScope: () => newIsolationScope, + }; const ctx1 = setHubOnContext(context, mockHub); const ctx2 = setScopesOnContext(ctx1, scopes); - return super.with(ctx2, fn, thisArg, ...args); + // Remove the unneeded values again + const ctx3 = ctx2 + .deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY); + + return super.with(ctx3, fn, thisArg, ...args); } } diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index f4731f9f126c..5e877360cc43 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -2,51 +2,23 @@ import type { Span } from '@opentelemetry/api'; import { context, trace } from '@opentelemetry/api'; -import type { CaptureContext, Event, EventHint, Scope, SeverityLevel } from '@sentry/types'; -import { getContextFromScope, getScopesFromContext, setScopesOnContext } from '../utils/contextData'; +import { getCurrentScope } from '@sentry/core'; +import type { CaptureContext, Client, Event, EventHint, Scope, SeverityLevel } from '@sentry/types'; import type { ExclusiveEventHintOrCaptureContext } from '../utils/prepareEvent'; import { parseEventHintOrCaptureContext } from '../utils/prepareEvent'; -import { getClient, getCurrentScope, getIsolationScope } from './scope'; -export { getCurrentScope, getIsolationScope, getClient }; -export { setCurrentScope, setIsolationScope } from './scope'; +/** Get the currently active client. */ +export function getClient(): C { + const currentScope = getCurrentScope(); -/** - * Creates a new scope with and executes the given operation within. - * The scope is automatically removed once the operation - * finishes or throws. - * - * This is essentially a convenience function for: - * - * pushScope(); - * callback(); - * popScope(); - */ -export function withScope(callback: (scope: Scope) => T): T; -/** - * Set the given scope as the active scope in the callback. - */ -export function withScope(scope: Scope | undefined, callback: (scope: Scope) => T): T; -/** - * Either creates a new active scope, or sets the given scope as active scope in the given callback. - */ -export function withScope( - ...rest: [callback: (scope: Scope) => T] | [scope: Scope | undefined, callback: (scope: Scope) => T] -): T { - // If a scope is defined, we want to make this the active scope instead of the default one - if (rest.length === 2) { - const [scope, callback] = rest; - if (!scope) { - return context.with(context.active(), () => callback(getCurrentScope())); - } - - const ctx = getContextFromScope(scope); - return context.with(ctx || context.active(), () => callback(getCurrentScope())); + const client = currentScope.getClient(); + if (client) { + return client as C; } - const callback = rest[0]; - return context.with(context.active(), () => callback(getCurrentScope())); + // TODO otherwise ensure we use a noop client + return {} as C; } /** @@ -61,27 +33,6 @@ export function withActiveSpan(span: Span, callback: (scope: Scope) => T): T return context.with(newContextWithActiveSpan, () => callback(getCurrentScope())); } -/** - * For a new isolation scope from the current isolation scope, - * and make it the current isolation scope in the given callback. - */ -export function withIsolationScope(callback: (isolationScope: Scope) => T): T { - const ctx = context.active(); - const currentScopes = getScopesFromContext(ctx); - const scopes = currentScopes - ? { ...currentScopes } - : { - scope: getCurrentScope(), - isolationScope: getIsolationScope(), - }; - - scopes.isolationScope = scopes.isolationScope.clone(); - - return context.with(setScopesOnContext(ctx, scopes), () => { - return callback(getIsolationScope()); - }); -} - /** Record an exception and send it to Sentry. */ export function captureException(exception: unknown, hint?: ExclusiveEventHintOrCaptureContext): string { return getCurrentScope().captureException(exception, parseEventHintOrCaptureContext(hint)); diff --git a/packages/node-experimental/src/sdk/globals.ts b/packages/node-experimental/src/sdk/globals.ts index a91f07cd206d..27428960bcf0 100644 --- a/packages/node-experimental/src/sdk/globals.ts +++ b/packages/node-experimental/src/sdk/globals.ts @@ -1,38 +1,19 @@ +import { getMainCarrier } from '@sentry/core'; import type { Hub } from '@sentry/types'; -import { GLOBAL_OBJ, logger } from '@sentry/utils'; +import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import type { AsyncContextStrategy, SentryCarrier } from './types'; - -/** Update the async context strategy */ -export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void { - const carrier = getGlobalCarrier(); - carrier.acs = strategy; -} - -/** - * Returns the global shim registry. - **/ -export function getGlobalCarrier(): SentryCarrier { - GLOBAL_OBJ.__SENTRY__ = GLOBAL_OBJ.__SENTRY__ || { - extensions: {}, - // For legacy reasons... - globalEventProcessors: [], - }; - - return GLOBAL_OBJ.__SENTRY__; -} - /** * Calls global extension method and binding current instance to the function call */ // @ts-expect-error Function lacks ending return statement and return type does not include 'undefined'. ts(2366) // eslint-disable-next-line @typescript-eslint/no-explicit-any export function callExtensionMethod(hub: Hub, method: string, ...args: any[]): T { - const carrier = getGlobalCarrier(); + const carrier = getMainCarrier(); + const sentry = carrier.__SENTRY__ || {}; - if (carrier.extensions && typeof carrier.extensions[method] === 'function') { - return carrier.extensions[method].apply(hub, args); + if (sentry.extensions && typeof sentry.extensions[method] === 'function') { + return sentry.extensions[method].apply(hub, args); } DEBUG_BUILD && logger.warn(`Extension method ${method} couldn't be found, doing nothing.`); } diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 75c34c07839f..420026fc180e 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -13,6 +13,8 @@ import type { import { addBreadcrumb, endSession, + getCurrentScope, + getIsolationScope, setContext, setExtra, setExtras, @@ -20,18 +22,10 @@ import { setTags, setUser, startSession, + withScope, } from '@sentry/core'; -import { captureEvent, getClient, getCurrentScope, withScope } from './api'; -import { callExtensionMethod, getGlobalCarrier } from './globals'; -import { getIsolationScope } from './scope'; -import type { SentryCarrier } from './types'; - -/** Ensure the global hub is our proxied hub. */ -export function setupGlobalHub(): void { - const carrier = getGlobalCarrier(); - // eslint-disable-next-line deprecation/deprecation - carrier.hub = getCurrentHub(); -} +import { captureEvent, getClient } from './api'; +import { callExtensionMethod } from './globals'; /** * This is for legacy reasons, and returns a proxy object instead of a hub to be used. @@ -138,12 +132,3 @@ function _sendSessionUpdate(): void { client.captureSession(session); } } - -/** - * Set a mocked hub on the current carrier. - */ -export function setLegacyHubOnCarrier(carrier: SentryCarrier): boolean { - // eslint-disable-next-line deprecation/deprecation - carrier.hub = getCurrentHub(); - return true; -} diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 519eb151752a..e7fc35a3f4b4 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -1,4 +1,11 @@ -import { endSession, getIntegrationsToSetup, hasTracingEnabled, startSession } from '@sentry/core'; +import { + endSession, + getCurrentScope, + getIntegrationsToSetup, + getIsolationScope, + hasTracingEnabled, + startSession, +} from '@sentry/core'; import { defaultIntegrations as defaultNodeIntegrations, defaultStackParser, @@ -22,10 +29,7 @@ import { httpIntegration } from '../integrations/http'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; import { setOpenTelemetryContextAsyncContextStrategy } from '../otel/asyncContextStrategy'; import type { NodeExperimentalClientOptions, NodeExperimentalOptions } from '../types'; -import { getClient, getCurrentScope, getIsolationScope } from './api'; import { NodeExperimentalClient } from './client'; -import { getGlobalCarrier } from './globals'; -import { setLegacyHubOnCarrier } from './hub'; import { initOtel } from './initOtel'; const ignoredDefaultIntegrations = ['Http', 'Undici']; @@ -66,6 +70,8 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { } } + setOpenTelemetryContextAsyncContextStrategy(); + const scope = getCurrentScope(); scope.update(options.initialScope); @@ -84,8 +90,6 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { updateScopeFromEnvVariables(); if (options.spotlight) { - const client = getClient(); - // force integrations to be setup even if no DSN was set // If they have already been added before, they will be ignored anyhow const integrations = client.getOptions().integrations; @@ -101,13 +105,9 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { // Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration initOtel(); - setOpenTelemetryContextAsyncContextStrategy(); } function getClientOptions(options: NodeExperimentalOptions): NodeExperimentalClientOptions { - const carrier = getGlobalCarrier(); - setLegacyHubOnCarrier(carrier); - if (options.defaultIntegrations === undefined) { options.defaultIntegrations = getDefaultIntegrations(options); } diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index f5a44652f884..3ee4ce16f3a4 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -1,72 +1,14 @@ -import { Scope as ScopeClass, getGlobalScope } from '@sentry/core'; -import type { Client } from '@sentry/types'; +import { context } from '@opentelemetry/api'; import type { Scope } from '@sentry/types'; - -import { getGlobalCarrier } from './globals'; -import type { CurrentScopes, SentryCarrier } from './types'; - -/** Get the current scope. */ -export function getCurrentScope(): Scope { - return getScopes().scope as Scope; -} - -/** - * Set the current scope on the execution context. - * This should mostly only be called in Sentry.init() - */ -export function setCurrentScope(scope: Scope): void { - getScopes().scope = scope; -} - -/** Get the currently active isolation scope. */ -export function getIsolationScope(): Scope { - return getScopes().isolationScope as Scope; -} +import { getScopesFromContext } from '../utils/contextData'; /** - * Set the currently active isolation scope. - * Use this with caution! As it updates the isolation scope for the current execution context. + * Update the active isolation scope. + * Should be used with caution! */ export function setIsolationScope(isolationScope: Scope): void { - getScopes().isolationScope = isolationScope; -} - -/** Get the currently active client. */ -export function getClient(): C { - const currentScope = getCurrentScope(); - const isolationScope = getIsolationScope(); - const globalScope = getGlobalScope(); - - const client = currentScope.getClient() || isolationScope.getClient() || globalScope.getClient(); - if (client) { - return client as C; + const scopes = getScopesFromContext(context.active()); + if (scopes) { + scopes.isolationScope = isolationScope; } - - // TODO otherwise ensure we use a noop client - return {} as C; -} - -function getScopes(): CurrentScopes { - const carrier = getGlobalCarrier(); - - if (carrier.acs && carrier.acs.getScopes) { - const scopes = carrier.acs.getScopes(); - - if (scopes) { - return scopes; - } - } - - return getGlobalCurrentScopes(carrier); -} - -function getGlobalCurrentScopes(carrier: SentryCarrier): CurrentScopes { - if (!carrier.scopes) { - carrier.scopes = { - scope: new ScopeClass(), - isolationScope: new ScopeClass(), - }; - } - - return carrier.scopes; } diff --git a/packages/node-experimental/src/sdk/types.ts b/packages/node-experimental/src/sdk/types.ts index c493353fe12c..686e67787916 100644 --- a/packages/node-experimental/src/sdk/types.ts +++ b/packages/node-experimental/src/sdk/types.ts @@ -4,8 +4,6 @@ import type { Contexts, EventProcessor, Extras, - Hub, - Integration, Primitive, PropagationContext, Scope, @@ -31,37 +29,3 @@ export interface CurrentScopes { scope: Scope; isolationScope: Scope; } - -/** - * Strategy used to track async context. - */ -export interface AsyncContextStrategy { - /** - * Gets the current async context. Returns undefined if there is no current async context. - */ - getScopes: () => CurrentScopes | undefined; - - /** This is here for legacy reasons. */ - getCurrentHub: () => Hub; - - /** - * Runs the supplied callback in its own async context. - */ - runWithAsyncContext(callback: () => T): T; -} - -export interface SentryCarrier { - scopes?: CurrentScopes; - acs?: AsyncContextStrategy; - - // hub is here for legacy reasons - hub?: Hub; - - extensions?: { - /** Extension methods for the hub, which are bound to the current Hub instance */ - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: Function; - }; - - integrations?: Integration[]; -} diff --git a/packages/node-experimental/src/utils/contextData.ts b/packages/node-experimental/src/utils/contextData.ts index cb77d37a9ae0..2309e57ea478 100644 --- a/packages/node-experimental/src/utils/contextData.ts +++ b/packages/node-experimental/src/utils/contextData.ts @@ -6,6 +6,12 @@ import type { CurrentScopes } from '../sdk/types'; export const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes'); +export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope'); + +export const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope'); + +export const SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_isolation_scope'); + const SCOPE_CONTEXT_MAP = new WeakMap(); /** diff --git a/packages/node-experimental/test/helpers/mockSdkInit.ts b/packages/node-experimental/test/helpers/mockSdkInit.ts index 9cc7692463d5..33e452a0a6c9 100644 --- a/packages/node-experimental/test/helpers/mockSdkInit.ts +++ b/packages/node-experimental/test/helpers/mockSdkInit.ts @@ -1,6 +1,6 @@ import { ProxyTracerProvider, context, propagation, trace } from '@opentelemetry/api'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { GLOBAL_OBJ } from '@sentry/utils'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { init } from '../../src/sdk/init'; import type { NodeExperimentalClientOptions } from '../../src/types'; @@ -8,12 +8,10 @@ import type { NodeExperimentalClientOptions } from '../../src/types'; const PUBLIC_DSN = 'https://username@domain/123'; export function resetGlobals(): void { - GLOBAL_OBJ.__SENTRY__ = { - extensions: {}, - hub: undefined, - globalEventProcessors: [], - logger: undefined, - }; + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + getIsolationScope().clear(); + getGlobalScope().clear(); } export function mockSdkInit(options?: Partial) { diff --git a/packages/node-experimental/test/integration/breadcrumbs.test.ts b/packages/node-experimental/test/integration/breadcrumbs.test.ts index e3862962338f..db3eef9e52fb 100644 --- a/packages/node-experimental/test/integration/breadcrumbs.test.ts +++ b/packages/node-experimental/test/integration/breadcrumbs.test.ts @@ -1,6 +1,6 @@ -import { addBreadcrumb, captureException, withScope } from '@sentry/core'; +import { addBreadcrumb, captureException, withIsolationScope, withScope } from '@sentry/core'; import { startSpan } from '@sentry/opentelemetry'; -import { getClient, withIsolationScope } from '../../src/sdk/api'; +import { getClient } from '../../src/sdk/api'; import type { NodeExperimentalClient } from '../../src/types'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index e800de4d41d0..8f73f5487b2b 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -457,7 +457,7 @@ describe('Integration | Scope', () => { // client is attached to current scope expect(currentScope.getClient()).toBeDefined(); - // current scope remains intact + expect(Sentry.getCurrentScope()).toBe(currentScope); expect(currentScope.getScopeData().tags).toEqual({ tag1: 'val1', tag2: 'val2' }); }); diff --git a/packages/node-experimental/test/sdk/init.test.ts b/packages/node-experimental/test/sdk/init.test.ts index d65734a6ad5c..5474f3c1ca07 100644 --- a/packages/node-experimental/test/sdk/init.test.ts +++ b/packages/node-experimental/test/sdk/init.test.ts @@ -1,8 +1,8 @@ import type { Integration } from '@sentry/types'; import * as auto from '../../src/integrations/getAutoPerformanceIntegrations'; +import { getClient } from '../../src/sdk/api'; import { init } from '../../src/sdk/init'; -import { getClient } from '../../src/sdk/scope'; import { cleanupOtel } from '../helpers/mockSdkInit'; // eslint-disable-next-line no-var @@ -37,6 +37,7 @@ describe('init()', () => { init({ dsn: PUBLIC_DSN, defaultIntegrations: false }); const client = getClient(); + expect(client.getOptions()).toEqual( expect.objectContaining({ integrations: [], diff --git a/packages/node/src/async/domain.ts b/packages/node/src/async/domain.ts index ecf5400bc58b..a39c281e5412 100644 --- a/packages/node/src/async/domain.ts +++ b/packages/node/src/async/domain.ts @@ -1,14 +1,16 @@ import * as domain from 'domain'; import type { Carrier } from '@sentry/core'; +import { getGlobalHub } from '@sentry/core'; +import { Hub as HubClass } from '@sentry/core'; import { ensureHubOnCarrier, getHubFromCarrier, setAsyncContextStrategy, setHubOnCarrier } from '@sentry/core'; -import type { Hub } from '@sentry/types'; +import type { Client, Hub, Scope } from '@sentry/types'; function getActiveDomain(): T | undefined { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any return (domain as any).active as T | undefined; } -function getCurrentHub(): Hub | undefined { +function getCurrentDomainHub(): Hub | undefined { const activeDomain = getActiveDomain(); // If there's no active domain, just return undefined and the global hub will be used @@ -21,19 +23,20 @@ function getCurrentHub(): Hub | undefined { return getHubFromCarrier(activeDomain); } -function createNewHub(parent: Hub | undefined): Hub { - const carrier: Carrier = {}; - ensureHubOnCarrier(carrier, parent); - return getHubFromCarrier(carrier); +function getCurrentHub(): Hub { + return getCurrentDomainHub() || getGlobalHub(); } -function runWithAsyncContext(callback: () => T): T { - const activeDomain = getActiveDomain(); - +function withExecutionContext( + client: Client | undefined, + scope: Scope, + isolationScope: Scope, + callback: () => T, +): T { const local = domain.create() as domain.Domain & Carrier; - const parentHub = activeDomain ? getHubFromCarrier(activeDomain) : undefined; - const newHub = createNewHub(parentHub); + // eslint-disable-next-line deprecation/deprecation + const newHub = new HubClass(client, scope, isolationScope); setHubOnCarrier(local, newHub); return local.bind(() => { @@ -41,9 +44,73 @@ function runWithAsyncContext(callback: () => T): T { })(); } +function withScope(callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope(); + /* eslint-enable deprecation/deprecation */ + + return withExecutionContext(client, scope, isolationScope, () => { + return callback(scope); + }); +} + +function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const isolationScope = parentHub.getIsolationScope(); + /* eslint-enable deprecation/deprecation */ + + return withExecutionContext(client, scope, isolationScope, () => { + return callback(scope); + }); +} + +function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope().clone(); + /* eslint-enable deprecation/deprecation */ + + return withExecutionContext(client, scope, isolationScope, () => { + return callback(isolationScope); + }); +} + +function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + /* eslint-enable deprecation/deprecation */ + + return withExecutionContext(client, scope, isolationScope, () => { + return callback(scope); + }); +} + /** * Sets the async context strategy to use Node.js domains. */ export function setDomainAsyncContextStrategy(): void { - setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext }); + setAsyncContextStrategy({ + getCurrentHub, + withScope, + withSetScope, + withIsolationScope, + withSetIsolationScope, + // eslint-disable-next-line deprecation/deprecation + getCurrentScope: () => getCurrentHub().getScope(), + // eslint-disable-next-line deprecation/deprecation + getIsolationScope: () => getCurrentHub().getIsolationScope(), + }); } diff --git a/packages/node/src/async/hooks.ts b/packages/node/src/async/hooks.ts index b0fea9535001..16a5c8ee4364 100644 --- a/packages/node/src/async/hooks.ts +++ b/packages/node/src/async/hooks.ts @@ -1,6 +1,6 @@ -import type { Carrier } from '@sentry/core'; -import { ensureHubOnCarrier, getHubFromCarrier, setAsyncContextStrategy } from '@sentry/core'; -import type { Hub } from '@sentry/types'; +import { Hub as HubClass, getGlobalHub } from '@sentry/core'; +import { setAsyncContextStrategy } from '@sentry/core'; +import type { Hub, Scope } from '@sentry/types'; import * as async_hooks from 'async_hooks'; interface AsyncLocalStorage { @@ -23,25 +23,81 @@ export function setHooksAsyncContextStrategy(): void { asyncStorage = new (async_hooks as NewerAsyncHooks).AsyncLocalStorage(); } - function getCurrentHub(): Hub | undefined { + function getCurrentHooksHub(): Hub | undefined { return asyncStorage.getStore(); } - function createNewHub(parent: Hub | undefined): Hub { - const carrier: Carrier = {}; - ensureHubOnCarrier(carrier, parent); - return getHubFromCarrier(carrier); + function getCurrentHub(): Hub { + return getCurrentHooksHub() || getGlobalHub(); } - function runWithAsyncContext(callback: () => T): T { - const existingHub = getCurrentHub(); + function withScope(callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); - const newHub = createNewHub(existingHub); + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ return asyncStorage.run(newHub, () => { - return callback(); + return callback(scope); }); } - setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext }); + function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const isolationScope = parentHub.getIsolationScope(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(scope); + }); + } + + function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope().clone(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(isolationScope); + }); + } + + function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(isolationScope); + }); + } + + setAsyncContextStrategy({ + getCurrentHub, + withScope, + withSetScope, + withIsolationScope, + withSetIsolationScope, + // eslint-disable-next-line deprecation/deprecation + getCurrentScope: () => getCurrentHub().getScope(), + // eslint-disable-next-line deprecation/deprecation + getIsolationScope: () => getCurrentHub().getIsolationScope(), + }); } diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 89bbb85cc91a..0ac97daae7c7 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -8,10 +8,11 @@ import { getActiveSpan, getClient, getCurrentScope, + getIsolationScope, hasTracingEnabled, - runWithAsyncContext, setHttpStatus, startTransaction, + withIsolationScope, withScope, } from '@sentry/core'; import type { Span } from '@sentry/types'; @@ -130,9 +131,9 @@ export function requestHandler( client.initSessionFlusher(); // If Scope contains a Single mode Session, it is removed in favor of using Session Aggregates mode - const scope = getCurrentScope(); - if (scope.getSession()) { - scope.setSession(); + const isolationScope = getIsolationScope(); + if (isolationScope.getSession()) { + isolationScope.setSession(); } } @@ -155,16 +156,15 @@ export function requestHandler( }); }; } - runWithAsyncContext(() => { - const scope = getCurrentScope(); - scope.setSDKProcessingMetadata({ + return withIsolationScope(isolationScope => { + isolationScope.setSDKProcessingMetadata({ request: req, }); const client = getClient(); if (isAutoSessionTrackingEnabled(client)) { // Set `status` of `RequestSession` to Ok, at the beginning of the request - scope.setRequestSession({ status: 'ok' }); + isolationScope.setRequestSession({ status: 'ok' }); } res.once('finish', () => { @@ -237,7 +237,7 @@ export function errorHandler(options?: { // The request should already have been stored in `scope.sdkProcessingMetadata` by `sentryRequestMiddleware`, // but on the off chance someone is using `sentryErrorMiddleware` without `sentryRequestMiddleware`, it doesn't // hurt to be sure - _scope.setSDKProcessingMetadata({ request: _req }); + getIsolationScope().setSDKProcessingMetadata({ request: _req }); // For some reason we need to set the transaction on the scope again // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -256,7 +256,7 @@ export function errorHandler(options?: { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const isSessionAggregatesMode = (client as any)._sessionFlusher !== undefined; if (isSessionAggregatesMode) { - const requestSession = _scope.getRequestSession(); + const requestSession = getIsolationScope().getRequestSession(); // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within // the bounds of a request, and if so the status is updated diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 341d9fdbb9a0..e45278fa2706 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -46,6 +46,7 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/test/async/domain.test.ts b/packages/node/test/async/domain.test.ts index 9473a81a7c22..79dd835b0fbb 100644 --- a/packages/node/test/async/domain.test.ts +++ b/packages/node/test/async/domain.test.ts @@ -1,14 +1,14 @@ /* eslint-disable deprecation/deprecation */ -import { Hub, makeMain } from '@sentry/core'; +import { Hub, getCurrentHub, getCurrentScope, makeMain, setAsyncContextStrategy, withScope } from '@sentry/core'; import { getIsolationScope, withIsolationScope } from '@sentry/core'; -import { getCurrentHub, runWithAsyncContext, setAsyncContextStrategy } from '@sentry/core'; +import type { Scope } from '@sentry/types'; import { setDomainAsyncContextStrategy } from '../../src/async/domain'; describe('setDomainAsyncContextStrategy()', () => { beforeEach(() => { const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation + makeMain(hub); }); @@ -77,30 +77,41 @@ describe('setDomainAsyncContextStrategy()', () => { }); }); - describe('with runWithAsyncContext()', () => { + describe('with withScope()', () => { test('hub scope inheritance', () => { setDomainAsyncContextStrategy(); const globalHub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - globalHub.setExtra('a', 'b'); + const initialIsolationScope = getIsolationScope(); + const initialScope = getCurrentScope(); + + initialScope.setExtra('a', 'b'); - runWithAsyncContext(() => { + withScope(scope => { const hub1 = getCurrentHub(); + expect(hub1).not.toBe(globalHub); expect(hub1).toEqual(globalHub); - // eslint-disable-next-line deprecation/deprecation - hub1.setExtra('c', 'd'); - expect(hub1).not.toEqual(globalHub); + expect(hub1.getScope()).toBe(scope); + expect(getCurrentScope()).toBe(scope); + expect(scope).not.toBe(initialScope); + + scope.setExtra('c', 'd'); + + expect(hub1.getIsolationScope()).toBe(initialIsolationScope); + expect(getIsolationScope()).toBe(initialIsolationScope); - runWithAsyncContext(() => { + withScope(scope2 => { const hub2 = getCurrentHub(); + expect(hub2).not.toBe(hub1); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); - // eslint-disable-next-line deprecation/deprecation - hub2.setExtra('e', 'f'); - expect(hub2).not.toEqual(hub1); + expect(scope2).toEqual(scope); + expect(scope2).not.toBe(scope); + + scope.setExtra('e', 'f'); + expect(scope2).not.toEqual(scope); }); }); }); @@ -108,52 +119,65 @@ describe('setDomainAsyncContextStrategy()', () => { test('async hub scope inheritance', async () => { setDomainAsyncContextStrategy(); - async function addRandomExtra(hub: Hub, key: string): Promise { + async function addRandomExtra(scope: Scope, key: string): Promise { return new Promise(resolve => { setTimeout(() => { - // eslint-disable-next-line deprecation/deprecation - hub.setExtra(key, Math.random()); + scope.setExtra(key, Math.random()); resolve(); }, 100); }); } - const globalHub = getCurrentHub() as Hub; - await addRandomExtra(globalHub, 'a'); + const globalHub = getCurrentHub(); + const initialIsolationScope = getIsolationScope(); + const initialScope = getCurrentScope(); + + await addRandomExtra(initialScope, 'a'); - await runWithAsyncContext(async () => { - const hub1 = getCurrentHub() as Hub; + await withScope(async scope => { + const hub1 = getCurrentHub(); + expect(hub1).not.toBe(globalHub); expect(hub1).toEqual(globalHub); - await addRandomExtra(hub1, 'b'); - expect(hub1).not.toEqual(globalHub); + expect(hub1.getScope()).toBe(scope); + expect(getCurrentScope()).toBe(scope); + expect(scope).not.toBe(initialScope); - await runWithAsyncContext(async () => { + await addRandomExtra(scope, 'b'); + + expect(hub1.getIsolationScope()).toBe(initialIsolationScope); + expect(getIsolationScope()).toBe(initialIsolationScope); + + await withScope(async scope2 => { const hub2 = getCurrentHub(); + expect(hub2).not.toBe(hub1); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); - await addRandomExtra(hub1, 'c'); - expect(hub2).not.toEqual(hub1); + expect(scope2).toEqual(scope); + expect(scope2).not.toBe(scope); + + await addRandomExtra(scope2, 'c'); + expect(scope2).not.toEqual(scope); }); }); }); - test('hub single instance', () => { + test('context single instance', () => { setDomainAsyncContextStrategy(); - runWithAsyncContext(() => { - const hub = getCurrentHub(); - expect(hub).toBe(getCurrentHub()); + const globalHub = getCurrentHub(); + withScope(() => { + expect(globalHub).not.toBe(getCurrentHub()); }); }); - test('within a domain not reused', () => { + test('context within a context not reused', () => { setDomainAsyncContextStrategy(); - runWithAsyncContext(() => { + withScope(() => { const hub1 = getCurrentHub(); - runWithAsyncContext(() => { + withScope(() => { const hub2 = getCurrentHub(); expect(hub1).not.toBe(hub2); }); @@ -166,11 +190,11 @@ describe('setDomainAsyncContextStrategy()', () => { let d1done = false; let d2done = false; - runWithAsyncContext(() => { + withScope(() => { const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation + hub.getStack().push({ client: 'process' } as any); - // eslint-disable-next-line deprecation/deprecation + expect(hub.getStack()[1]).toEqual({ client: 'process' }); // Just in case so we don't have to worry which one finishes first // (although it always should be d2) @@ -182,11 +206,11 @@ describe('setDomainAsyncContextStrategy()', () => { }, 0); }); - runWithAsyncContext(() => { + withScope(() => { const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation + hub.getStack().push({ client: 'local' } as any); - // eslint-disable-next-line deprecation/deprecation + expect(hub.getStack()[1]).toEqual({ client: 'local' }); setTimeout(() => { d2done = true; diff --git a/packages/node/test/async/hooks.test.ts b/packages/node/test/async/hooks.test.ts index cdfbe95b8e7e..bf2e0443fc24 100644 --- a/packages/node/test/async/hooks.test.ts +++ b/packages/node/test/async/hooks.test.ts @@ -2,19 +2,21 @@ import { Hub, getCurrentHub, + getCurrentScope, getIsolationScope, makeMain, - runWithAsyncContext, setAsyncContextStrategy, withIsolationScope, + withScope, } from '@sentry/core'; +import type { Scope } from '@sentry/types'; import { setHooksAsyncContextStrategy } from '../../src/async/hooks'; describe('setHooksAsyncContextStrategy()', () => { beforeEach(() => { const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation + makeMain(hub); }); @@ -83,30 +85,41 @@ describe('setHooksAsyncContextStrategy()', () => { }); }); - describe('with runWithAsyncContext()', () => { + describe('with withScope()', () => { test('hub scope inheritance', () => { setHooksAsyncContextStrategy(); const globalHub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - globalHub.setExtra('a', 'b'); + const initialIsolationScope = getIsolationScope(); + const initialScope = getCurrentScope(); + + initialScope.setExtra('a', 'b'); - runWithAsyncContext(() => { + withScope(scope => { const hub1 = getCurrentHub(); + expect(hub1).not.toBe(globalHub); expect(hub1).toEqual(globalHub); - // eslint-disable-next-line deprecation/deprecation - hub1.setExtra('c', 'd'); - expect(hub1).not.toEqual(globalHub); + expect(hub1.getScope()).toBe(scope); + expect(getCurrentScope()).toBe(scope); + expect(scope).not.toBe(initialScope); + + scope.setExtra('c', 'd'); + + expect(hub1.getIsolationScope()).toBe(initialIsolationScope); + expect(getIsolationScope()).toBe(initialIsolationScope); - runWithAsyncContext(() => { + withScope(scope2 => { const hub2 = getCurrentHub(); + expect(hub2).not.toBe(hub1); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); - // eslint-disable-next-line deprecation/deprecation - hub2.setExtra('e', 'f'); - expect(hub2).not.toEqual(hub1); + expect(scope2).toEqual(scope); + expect(scope2).not.toBe(scope); + + scope.setExtra('e', 'f'); + expect(scope2).not.toEqual(scope); }); }); }); @@ -114,33 +127,46 @@ describe('setHooksAsyncContextStrategy()', () => { test('async hub scope inheritance', async () => { setHooksAsyncContextStrategy(); - async function addRandomExtra(hub: Hub, key: string): Promise { + async function addRandomExtra(scope: Scope, key: string): Promise { return new Promise(resolve => { setTimeout(() => { - // eslint-disable-next-line deprecation/deprecation - hub.setExtra(key, Math.random()); + scope.setExtra(key, Math.random()); resolve(); }, 100); }); } - const globalHub = getCurrentHub() as Hub; - await addRandomExtra(globalHub, 'a'); + const globalHub = getCurrentHub(); + const initialIsolationScope = getIsolationScope(); + const initialScope = getCurrentScope(); + + await addRandomExtra(initialScope, 'a'); - await runWithAsyncContext(async () => { - const hub1 = getCurrentHub() as Hub; + await withScope(async scope => { + const hub1 = getCurrentHub(); + expect(hub1).not.toBe(globalHub); expect(hub1).toEqual(globalHub); - await addRandomExtra(hub1, 'b'); - expect(hub1).not.toEqual(globalHub); + expect(hub1.getScope()).toBe(scope); + expect(getCurrentScope()).toBe(scope); + expect(scope).not.toBe(initialScope); - await runWithAsyncContext(async () => { + await addRandomExtra(scope, 'b'); + + expect(hub1.getIsolationScope()).toBe(initialIsolationScope); + expect(getIsolationScope()).toBe(initialIsolationScope); + + await withScope(async scope2 => { const hub2 = getCurrentHub(); + expect(hub2).not.toBe(hub1); expect(hub2).toEqual(hub1); expect(hub2).not.toEqual(globalHub); - await addRandomExtra(hub1, 'c'); - expect(hub2).not.toEqual(hub1); + expect(scope2).toEqual(scope); + expect(scope2).not.toBe(scope); + + await addRandomExtra(scope2, 'c'); + expect(scope2).not.toEqual(scope); }); }); }); @@ -149,7 +175,7 @@ describe('setHooksAsyncContextStrategy()', () => { setHooksAsyncContextStrategy(); const globalHub = getCurrentHub(); - runWithAsyncContext(() => { + withScope(() => { expect(globalHub).not.toBe(getCurrentHub()); }); }); @@ -157,9 +183,9 @@ describe('setHooksAsyncContextStrategy()', () => { test('context within a context not reused', () => { setHooksAsyncContextStrategy(); - runWithAsyncContext(() => { + withScope(() => { const hub1 = getCurrentHub(); - runWithAsyncContext(() => { + withScope(() => { const hub2 = getCurrentHub(); expect(hub1).not.toBe(hub2); }); @@ -172,11 +198,11 @@ describe('setHooksAsyncContextStrategy()', () => { let d1done = false; let d2done = false; - runWithAsyncContext(() => { + withScope(() => { const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation + hub.getStack().push({ client: 'process' } as any); - // eslint-disable-next-line deprecation/deprecation + expect(hub.getStack()[1]).toEqual({ client: 'process' }); // Just in case so we don't have to worry which one finishes first // (although it always should be d2) @@ -188,11 +214,11 @@ describe('setHooksAsyncContextStrategy()', () => { }, 0); }); - runWithAsyncContext(() => { + withScope(() => { const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation + hub.getStack().push({ client: 'local' } as any); - // eslint-disable-next-line deprecation/deprecation + expect(hub.getStack()[1]).toEqual({ client: 'local' }); setTimeout(() => { d2done = true; diff --git a/packages/node/test/client.test.ts b/packages/node/test/client.test.ts index d868fb6eddb1..64c1251d1ce2 100644 --- a/packages/node/test/client.test.ts +++ b/packages/node/test/client.test.ts @@ -1,8 +1,10 @@ import * as os from 'os'; -import { Scope, SessionFlusher } from '@sentry/core'; +import { SessionFlusher, getCurrentScope, getGlobalScope, getIsolationScope, withIsolationScope } from '@sentry/core'; import type { Event, EventHint } from '@sentry/types'; +import type { Scope } from '@sentry/types'; import { NodeClient } from '../src'; +import { setNodeAsyncContextStrategy } from '../src/async'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -13,19 +15,30 @@ describe('NodeClient', () => { afterEach(() => { if ('_sessionFlusher' in client) clearInterval((client as any)._sessionFlusher._intervalId); jest.restoreAllMocks(); + + getIsolationScope().clear(); + getGlobalScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + }); + + beforeEach(() => { + setNodeAsyncContextStrategy(); }); describe('captureException', () => { test('when autoSessionTracking is enabled, and requestHandler is not used -> requestStatus should not be set', () => { const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); - client.captureException(new Error('test exception'), undefined, scope); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + client.captureException(new Error('test exception')); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); test('when autoSessionTracking is disabled -> requestStatus should not be set', () => { @@ -35,13 +48,14 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); - client.captureException(new Error('test exception'), undefined, scope); + client.captureException(new Error('test exception')); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); test('when autoSessionTracking is enabled + requestSession status is Crashed -> requestStatus should not be overridden', () => { @@ -51,13 +65,14 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'crashed' }); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'crashed' }); - client.captureException(new Error('test exception'), undefined, scope); + client.captureException(new Error('test exception')); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('crashed'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('crashed'); + }); }); test('when autoSessionTracking is enabled + error occurs within request bounds -> requestStatus should be set to Errored', () => { @@ -67,28 +82,37 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); - client.captureException(new Error('test exception'), undefined, scope); + client.captureException(new Error('test exception')); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('errored'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('errored'); + }); }); - test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', () => { + test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', done => { const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.4' }); client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); + let isolationScope: Scope; + withIsolationScope(_isolationScope => { + _isolationScope.setRequestSession({ status: 'ok' }); + isolationScope = _isolationScope; + }); - client.captureException(new Error('test exception'), undefined, scope); + client.captureException(new Error('test exception')); - const requestSession = scope.getRequestSession(); - expect(requestSession).toEqual(undefined); + setImmediate(() => { + const requestSession = isolationScope.getRequestSession(); + expect(requestSession).toEqual({ status: 'ok' }); + done(); + }); }); }); @@ -100,16 +124,12 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); - client.captureEvent( - { message: 'message', exception: { values: [{ type: 'exception type 1' }] } }, - undefined, - scope, - ); - - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); test('When captureEvent is called with an exception, requestSession status should be set to Errored', () => { @@ -119,13 +139,14 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); - client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }, {}, scope); + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('errored'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('errored'); + }); }); test('When captureEvent is called without an exception, requestSession status should not be set to Errored', () => { @@ -135,13 +156,14 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); - client.captureEvent({ message: 'message' }, {}, scope); + client.captureEvent({ message: 'message' }); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); test('When captureEvent is called with an exception but outside of a request, then requestStatus should not be set', () => { @@ -151,15 +173,12 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - - client.captureEvent( - { message: 'message', exception: { values: [{ type: 'exception type 1' }] } }, - undefined, - scope, - ); + withIsolationScope(isolationScope => { + isolationScope.clear(); + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); - expect(scope.getRequestSession()).toEqual(undefined); + expect(isolationScope.getRequestSession()).toEqual(undefined); + }); }); test('When captureEvent is called with a transaction, then requestSession status should not be set', () => { @@ -169,28 +188,28 @@ describe('NodeClient', () => { // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); - client.captureEvent({ message: 'message', type: 'transaction' }, undefined, scope); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message', type: 'transaction' }); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); test('When captureEvent is called with an exception but requestHandler is not used, then requestSession status should not be set', () => { const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, autoSessionTracking: true, release: '1.3' }); client = new NodeClient(options); - const scope = new Scope(); - scope.setRequestSession({ status: 'ok' }); - client.captureEvent( - { message: 'message', exception: { values: [{ type: 'exception type 1' }] } }, - undefined, - scope, - ); + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); - const requestSession = scope.getRequestSession(); - expect(requestSession!.status).toEqual('ok'); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); }); }); diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 56682446a1d8..8c3d7ccad90c 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -2,34 +2,32 @@ import * as http from 'http'; import * as sentryCore from '@sentry/core'; import { Hub, - Scope, + Scope as ScopeClass, Transaction, getClient, getCurrentScope, + getIsolationScope, + getMainCarrier, makeMain, - setAsyncContextStrategy, + mergeScopeData, spanToJSON, } from '@sentry/core'; -import type { Event, PropagationContext } from '@sentry/types'; +import type { Event, PropagationContext, Scope } from '@sentry/types'; import { SentryError } from '@sentry/utils'; import { NodeClient } from '../src/client'; import { errorHandler, requestHandler, tracingHandler } from '../src/handlers'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; -function mockAsyncContextStrategy(getHub: () => Hub): void { - function getCurrentHub(): Hub | undefined { - return getHub(); - } - - function runWithAsyncContext(fn: (hub: Hub) => T): T { - return fn(getHub()); - } - - setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext }); -} - describe('requestHandler', () => { + beforeEach(() => { + // Ensure we reset a potentially set acs to use the default + const sentry = getMainCarrier().__SENTRY__; + if (sentry) { + sentry.acs = undefined; + } + }); + const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' }; const method = 'wagging'; const protocol = 'mutualsniffing'; @@ -63,34 +61,44 @@ describe('requestHandler', () => { jest.restoreAllMocks(); }); - it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', () => { + it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); - - sentryRequestMiddleware(req, res, next); + let isolationScope: Scope; + sentryRequestMiddleware(req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - const scope = getCurrentScope(); - expect(scope?.getRequestSession()).toEqual({ status: 'ok' }); + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); }); - it('autoSessionTracking is disabled, does not set requestSession, when handling a request', () => { + it('autoSessionTracking is disabled, does not set requestSession, when handling a request', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); - - sentryRequestMiddleware(req, res, next); + let isolationScope: Scope; + sentryRequestMiddleware(req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - const scope = getCurrentScope(); - expect(scope?.getRequestSession()).toBeUndefined(); + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual(undefined); + done(); + }); }); it('autoSessionTracking is enabled, calls _captureRequestSession, on response finish', done => { @@ -98,19 +106,21 @@ describe('requestHandler', () => { client = new NodeClient(options); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); - - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); const captureRequestSession = jest.spyOn(client, '_captureRequestSession'); - sentryRequestMiddleware(req, res, next); + let isolationScope: Scope; + sentryRequestMiddleware(req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - const scope = getCurrentScope(); res.emit('finish'); setImmediate(() => { - expect(scope?.getRequestSession()).toEqual({ status: 'ok' }); + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); expect(captureRequestSession).toHaveBeenCalled(); done(); }); @@ -121,18 +131,21 @@ describe('requestHandler', () => { client = new NodeClient(options); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); - - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); const captureRequestSession = jest.spyOn(client, '_captureRequestSession'); - sentryRequestMiddleware(req, res, next); - const scope = getCurrentScope(); + let isolationScope: Scope; + sentryRequestMiddleware(req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + res.emit('finish'); setImmediate(() => { - expect(scope?.getRequestSession()).toBeUndefined(); + expect(isolationScope.getRequestSession()).toBeUndefined(); expect(captureRequestSession).not.toHaveBeenCalled(); done(); }); @@ -165,20 +178,31 @@ describe('requestHandler', () => { }); }); - it('stores request and request data options in `sdkProcessingMetadata`', () => { + it('stores request and request data options in `sdkProcessingMetadata`', done => { // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(getDefaultNodeClientOptions())); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); const requestHandlerOptions = { include: { ip: false } }; const sentryRequestMiddleware = requestHandler(requestHandlerOptions); - sentryRequestMiddleware(req, res, next); + let isolationScope: Scope; + let currentScope: Scope; + sentryRequestMiddleware(req, res, () => { + isolationScope = getIsolationScope(); + currentScope = getCurrentScope(); + return next(); + }); - const scope = getCurrentScope(); - expect((scope as any)._sdkProcessingMetadata).toEqual({ - request: req, + setImmediate(() => { + const scopeData = isolationScope.getScopeData(); + mergeScopeData(scopeData, currentScope.getScopeData()); + + expect(scopeData.sdkProcessingMetadata).toEqual({ + request: req, + }); + done(); }); }); }); @@ -207,7 +231,6 @@ describe('tracingHandler', () => { // eslint-disable-next-line deprecation/deprecation makeMain(hub); - mockAsyncContextStrategy(() => hub); req = { headers, method, @@ -224,8 +247,7 @@ describe('tracingHandler', () => { }); function getPropagationContext(): PropagationContext { - // @ts-expect-error accesing private property for test - return getCurrentScope()._propagationContext; + return getCurrentScope().getScopeData().propagationContext; } it('creates a transaction when handling a request', () => { @@ -329,7 +351,8 @@ describe('tracingHandler', () => { const options = getDefaultNodeClientOptions({ tracesSampler }); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); hub.run(() => { sentryTracingMiddleware(req, res, next); @@ -352,9 +375,8 @@ describe('tracingHandler', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(new NodeClient(options)); - - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); sentryTracingMiddleware(req, res, next); @@ -532,41 +554,55 @@ describe('errorHandler()', () => { if ('_sessionFlusher' in client) clearInterval((client as any)._sessionFlusher._intervalId); jest.restoreAllMocks(); }); - it('when autoSessionTracking is disabled, does not set requestSession status on Crash', () => { + it('when autoSessionTracking is disabled, does not set requestSession status on Crash', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); client = new NodeClient(options); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = getCurrentScope(); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); jest.spyOn(client, '_captureRequestSession'); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - scope?.setRequestSession({ status: 'ok' }); - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, next); - const requestSession = scope?.getRequestSession(); - expect(requestSession).toEqual({ status: 'ok' }); + getIsolationScope().setRequestSession({ status: 'ok' }); + + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); }); - it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', () => { + it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); client = new NodeClient(options); - - const scope = getCurrentScope(); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); jest.spyOn(client, '_captureRequestSession'); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - scope?.setRequestSession({ status: 'ok' }); - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, next); - const requestSession = scope?.getRequestSession(); - expect(requestSession).toEqual({ status: 'ok' }); + getIsolationScope().setRequestSession({ status: 'ok' }); + + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); }); it('when autoSessionTracking is enabled, should set requestSession status to Crashed when an unhandled error occurs within the bounds of a request', () => { @@ -575,62 +611,66 @@ describe('errorHandler()', () => { // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); + const scope = new ScopeClass(); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); - mockAsyncContextStrategy(() => hub); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); jest.spyOn(client, '_captureRequestSession'); hub.run(() => { - scope?.setRequestSession({ status: 'ok' }); + getIsolationScope().setRequestSession({ status: 'ok' }); sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { - const scope = getCurrentScope(); - const requestSession = scope?.getRequestSession(); - expect(requestSession).toEqual({ status: 'crashed' }); + expect(getIsolationScope().getRequestSession()).toEqual({ status: 'crashed' }); }); }); }); - it('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', () => { + it('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); client = new NodeClient(options); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); + const scope = new ScopeClass(); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client, scope); + // eslint-disable-next-line deprecation/deprecation + makeMain(hub); jest.spyOn(client, '_captureRequestSession'); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, next); - const requestSession = scope?.getRequestSession(); - expect(requestSession).toEqual(undefined); + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual(undefined); + done(); + }); }); - it('stores request in `sdkProcessingMetadata`', () => { + it('stores request in `sdkProcessingMetadata`', done => { const options = getDefaultNodeClientOptions({}); client = new NodeClient(options); // eslint-disable-next-line deprecation/deprecation const hub = new Hub(client); - mockAsyncContextStrategy(() => hub); // eslint-disable-next-line deprecation/deprecation makeMain(hub); - // `sentryErrorMiddleware` uses `withScope`, and we need access to the temporary scope it creates, so monkeypatch - // `captureException` in order to examine the scope as it exists inside the `withScope` callback - // eslint-disable-next-line deprecation/deprecation - hub.captureException = function (this: Hub, _exception: any) { - // eslint-disable-next-line deprecation/deprecation - const scope = this.getScope(); - expect((scope as any)._sdkProcessingMetadata.request).toEqual(req); - } as any; - - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, next); + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - expect.assertions(1); + setImmediate(() => { + expect(isolationScope.getScopeData().sdkProcessingMetadata.request).toEqual(req); + done(); + }); }); }); diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index b0b0000839f2..70061fe1d01c 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -1,13 +1,14 @@ import { LinkedErrors, SDK_VERSION, + getGlobalScope, + getIsolationScope, getMainCarrier, initAndBind, - runWithAsyncContext, setCurrentClient, + withIsolationScope, } from '@sentry/core'; import type { EventHint, Integration } from '@sentry/types'; -import { GLOBAL_OBJ } from '@sentry/utils'; import type { Event } from '../src'; import { contextLinesIntegration } from '../src'; @@ -42,13 +43,14 @@ const dsn = 'https://53039209a22b4ec1bcc296a3c9fdecd6@sentry.io/4291'; declare var global: any; describe('SentryNode', () => { - beforeEach(() => { - GLOBAL_OBJ.__SENTRY__ = { hub: undefined, logger: undefined, globalEventProcessors: [] }; - init({ dsn }); - }); - beforeEach(() => { jest.clearAllMocks(); + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + + init({ dsn }); }); describe('getContext() / setContext()', () => { @@ -304,7 +306,7 @@ describe('SentryNode', () => { setNodeAsyncContextStrategy(); const client = new NodeClient(options); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); setCurrentClient(client); @@ -372,6 +374,11 @@ class MockIntegration implements Integration { describe('SentryNode initialization', () => { beforeEach(() => { jest.clearAllMocks(); + + getGlobalScope().clear(); + getIsolationScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); }); test('global.SENTRY_RELEASE is used to set release on initialization if available', () => { @@ -501,8 +508,6 @@ describe('SentryNode initialization', () => { beforeEach(() => { process.env.SENTRY_TRACE = '12312012123120121231201212312012-1121201211212012-0'; process.env.SENTRY_BAGGAGE = 'sentry-release=1.0.0,sentry-environment=production'; - - getCurrentScope().clear(); }); afterEach(() => { diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index e5aef807190f..0e34294b1d1c 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -1,7 +1,15 @@ import * as http from 'http'; -import { Transaction, getActiveSpan, getClient, getCurrentScope, setCurrentClient, startSpan } from '@sentry/core'; +import { + Transaction, + getActiveSpan, + getClient, + getCurrentScope, + setCurrentClient, + startSpan, + withIsolationScope, +} from '@sentry/core'; import { spanToTraceHeader } from '@sentry/core'; -import { Hub, makeMain, runWithAsyncContext } from '@sentry/core'; +import { Hub, makeMain } from '@sentry/core'; import { fetch } from 'undici'; import { NodeClient } from '../../src/client'; @@ -215,7 +223,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { it.skip('attaches the sentry trace and baggage headers if there is an active span', async () => { expect.assertions(3); - await runWithAsyncContext(async () => { + await withIsolationScope(async () => { await startSpan({ name: 'outer-span' }, async outerSpan => { expect(outerSpan).toBeInstanceOf(Transaction); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/test/manual/webpack-async-context/index.js b/packages/node/test/manual/webpack-async-context/index.js index c8a9de6e7d41..143ae07e56f8 100644 --- a/packages/node/test/manual/webpack-async-context/index.js +++ b/packages/node/test/manual/webpack-async-context/index.js @@ -45,7 +45,7 @@ Sentry.init({ Sentry.setTag('a', 'b'); -Sentry.runWithAsyncContext(() => { +Sentry.withIsolationScope(() => { Sentry.setTag('a', 'x'); Sentry.captureMessage('inside'); }); diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index 83ffdf8b8fe1..230bd1e21210 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -1,7 +1,12 @@ import * as api from '@opentelemetry/api'; -import type { Hub } from '@sentry/core'; +import { getGlobalHub } from '@sentry/core'; import { setAsyncContextStrategy } from '@sentry/core'; -import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY } from './constants'; +import type { Hub, Scope } from '@sentry/types'; +import { + SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, +} from './constants'; import { getHubFromContext } from './utils/contextData'; @@ -10,7 +15,7 @@ import { getHubFromContext } from './utils/contextData'; * We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts) */ export function setOpenTelemetryContextAsyncContextStrategy(): void { - function getCurrentHub(): Hub | undefined { + function getCurrentFromContext(): Hub | undefined { const ctx = api.context.active(); // Returning undefined means the global hub will be used @@ -18,8 +23,11 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { return getHubFromContext(ctx) as Hub | undefined; } - /* This is more or less a NOOP - we rely on the OTEL context manager for this */ - function runWithAsyncContext(callback: () => T): T { + function getCurrentHub(): Hub { + return getCurrentFromContext() || getGlobalHub(); + } + + function withScope(callback: (scope: Scope) => T): T { const ctx = api.context.active(); // We depend on the otelContextManager to handle the context/hub @@ -27,10 +35,58 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { // the OTEL context manager, which uses the presence of this key to determine if it should // fork the isolation scope, or not // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` + return api.context.with(ctx, () => { + // eslint-disable-next-line deprecation/deprecation + return callback(getCurrentHub().getScope()); + }); + } + + function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_SET_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which picks up this scope as the current scope + return api.context.with(ctx.setValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, scope), () => { + return callback(scope); + }); + } + + function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { - return callback(); + // eslint-disable-next-line deprecation/deprecation + return callback(getCurrentHub().getIsolationScope()); + }); + } + + function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { + const ctx = api.context.active(); + + // We depend on the otelContextManager to handle the context/hub + // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by + // the OTEL context manager, which uses the presence of this key to determine if it should + // fork the isolation scope, or not + return api.context.with(ctx.setValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, isolationScope), () => { + // eslint-disable-next-line deprecation/deprecation + return callback(getCurrentHub().getIsolationScope()); }); } - setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext }); + setAsyncContextStrategy({ + getCurrentHub, + withScope, + withSetScope, + withIsolationScope, + withSetIsolationScope, + // eslint-disable-next-line deprecation/deprecation + getCurrentScope: () => getCurrentHub().getScope(), + // eslint-disable-next-line deprecation/deprecation + getIsolationScope: () => getCurrentHub().getIsolationScope(), + }); } diff --git a/packages/opentelemetry/src/constants.ts b/packages/opentelemetry/src/constants.ts index d38f8a91f121..03beb221ccf0 100644 --- a/packages/opentelemetry/src/constants.ts +++ b/packages/opentelemetry/src/constants.ts @@ -10,3 +10,7 @@ export const SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY = createContextKey('SENTRY_P export const SENTRY_HUB_CONTEXT_KEY = createContextKey('sentry_hub'); export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope'); + +export const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope'); + +export const SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_isolation_scope'); diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index a1fa8b171fa4..c840fa8887d4 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -1,22 +1,32 @@ import type { Context, ContextManager } from '@opentelemetry/api'; import { Hub } from '@sentry/core'; import { getCurrentHub } from '@sentry/core'; -import type { Hub as HubInterface } from '@sentry/types'; -import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY } from './constants'; +import type { Hub as HubInterface, Scope } from '@sentry/types'; +import { + SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, + SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, +} from './constants'; import { setHubOnContext } from './utils/contextData'; -function createNewHub(parent: HubInterface | undefined, shouldForkIsolationScope: boolean): Hub { +function createNewHub( + parent: HubInterface | undefined, + scope: Scope | undefined, + isolationScope: Scope | undefined, + shouldForkIsolationScope: boolean, +): Hub { if (parent) { // eslint-disable-next-line deprecation/deprecation const client = parent.getClient(); // eslint-disable-next-line deprecation/deprecation - const scope = parent.getScope(); - // eslint-disable-next-line deprecation/deprecation - const isolationScope = parent.getIsolationScope(); + const currentScope = scope || parent.getScope().clone(); + const currentIsolationScope = + // eslint-disable-next-line deprecation/deprecation + isolationScope || (shouldForkIsolationScope ? parent.getIsolationScope().clone() : parent.getIsolationScope()); // eslint-disable-next-line deprecation/deprecation - return new Hub(client, scope.clone(), shouldForkIsolationScope ? isolationScope.clone() : isolationScope); + return new Hub(client, currentScope, currentIsolationScope); } // eslint-disable-next-line deprecation/deprecation @@ -59,13 +69,21 @@ export function wrapContextManagerClass { const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true; + const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) as Scope | undefined; + const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) as Scope | undefined; // eslint-disable-next-line deprecation/deprecation const existingHub = getCurrentHub(); - const newHub = createNewHub(existingHub, shouldForkIsolationScope); + const newHub = createNewHub(existingHub, scope, isolationScope, shouldForkIsolationScope); return super.with( - setHubOnContext(context.deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY), newHub), + setHubOnContext( + context + .deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY), + newHub, + ), fn, thisArg, ...args, diff --git a/packages/opentelemetry/test/asyncContextStrategy.test.ts b/packages/opentelemetry/test/asyncContextStrategy.test.ts index 8fd783758277..7bc7363a407e 100644 --- a/packages/opentelemetry/test/asyncContextStrategy.test.ts +++ b/packages/opentelemetry/test/asyncContextStrategy.test.ts @@ -1,7 +1,8 @@ import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { Hub } from '@sentry/core'; +import { withIsolationScope } from '@sentry/core'; import { getCurrentHub } from '@sentry/core'; -import { runWithAsyncContext, setAsyncContextStrategy } from '@sentry/core'; +import { setAsyncContextStrategy } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../src/asyncContextStrategy'; import { TestClient, getDefaultTestClientOptions } from './helpers/TestClient'; @@ -33,7 +34,7 @@ describe('asyncContextStrategy', () => { // eslint-disable-next-line deprecation/deprecation globalHub.setExtra('a', 'b'); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); expect(hub1).toEqual(globalHub); @@ -42,7 +43,7 @@ describe('asyncContextStrategy', () => { hub1.setExtra('c', 'd'); expect(hub1).not.toEqual(globalHub); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub2).toEqual(hub1); @@ -70,7 +71,7 @@ describe('asyncContextStrategy', () => { const globalHub = getCurrentHub() as Hub; await addRandomExtra(globalHub, 'a'); - await runWithAsyncContext(async () => { + await withIsolationScope(async () => { // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub() as Hub; expect(hub1).toEqual(globalHub); @@ -78,7 +79,7 @@ describe('asyncContextStrategy', () => { await addRandomExtra(hub1, 'b'); expect(hub1).not.toEqual(globalHub); - await runWithAsyncContext(async () => { + await withIsolationScope(async () => { // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub2).toEqual(hub1); @@ -93,17 +94,17 @@ describe('asyncContextStrategy', () => { test('context single instance', () => { // eslint-disable-next-line deprecation/deprecation const globalHub = getCurrentHub(); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation expect(globalHub).not.toBe(getCurrentHub()); }); }); test('context within a context not reused', () => { - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub1 = getCurrentHub(); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub2 = getCurrentHub(); expect(hub1).not.toBe(hub2); @@ -115,7 +116,7 @@ describe('asyncContextStrategy', () => { let d1done = false; let d2done = false; - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub() as Hub; // eslint-disable-next-line deprecation/deprecation @@ -132,7 +133,7 @@ describe('asyncContextStrategy', () => { }); }); - runWithAsyncContext(() => { + withIsolationScope(() => { // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub() as Hub; // eslint-disable-next-line deprecation/deprecation diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index cf362c731fe4..8a6758775b27 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -1,8 +1,8 @@ import { ProxyTracerProvider, context, propagation, trace } from '@opentelemetry/api'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { ClientOptions, Options } from '@sentry/types'; -import { GLOBAL_OBJ } from '@sentry/utils'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy'; import { init as initTestClient } from './TestClient'; import { initOtel } from './initOtel'; @@ -23,13 +23,15 @@ function init(options: Partial | undefined = {}): void { setOpenTelemetryContextAsyncContextStrategy(); } +function resetGlobals(): void { + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + getIsolationScope().clear(); + getGlobalScope().clear(); +} + export function mockSdkInit(options?: Partial) { - GLOBAL_OBJ.__SENTRY__ = { - extensions: {}, - hub: undefined, - globalEventProcessors: [], - logger: undefined, - }; + resetGlobals(); init({ dsn: PUBLIC_DSN, ...options }); } diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 1c5cfe5be87e..9990595125e6 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -88,6 +88,7 @@ export { getModuleFromFilename, createGetModuleFromFilename, hapiErrorPlugin, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 7e1822aa8612..19e541fd4ebc 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -7,10 +7,10 @@ import { getCurrentScope, getDynamicSamplingContextFromSpan, hasTracingEnabled, - runWithAsyncContext, setHttpStatus, spanToJSON, spanToTraceHeader, + withIsolationScope, } from '@sentry/core'; import { captureException, getCurrentHub } from '@sentry/node'; import type { Hub, Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; @@ -455,11 +455,10 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui return origRequestHandler.call(this, request, loadContext); } - return runWithAsyncContext(async () => { + return withIsolationScope(async isolationScope => { // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const options = getClient()?.getOptions(); - const scope = getCurrentScope(); let normalizedRequest: Record = request; @@ -472,7 +471,7 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui const url = new URL(request.url); const [name, source] = getTransactionName(routes, url, pkg); - scope.setSDKProcessingMetadata({ + isolationScope.setSDKProcessingMetadata({ request: { ...normalizedRequest, route: { diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index 8f05de780381..dea4357e07db 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -1,11 +1,4 @@ -import { - getClient, - getCurrentHub, - getCurrentScope, - hasTracingEnabled, - runWithAsyncContext, - setHttpStatus, -} from '@sentry/core'; +import { getClient, getCurrentHub, hasTracingEnabled, setHttpStatus, withIsolationScope } from '@sentry/core'; import { flush } from '@sentry/node'; import type { Transaction } from '@sentry/types'; import { extractRequestData, fill, isString, logger } from '@sentry/utils'; @@ -52,7 +45,7 @@ function wrapExpressRequestHandler( } } - await runWithAsyncContext(async () => { + await withIsolationScope(async isolationScope => { // eslint-disable-next-line @typescript-eslint/unbound-method res.end = wrapEndMethod(res.end); @@ -60,9 +53,8 @@ function wrapExpressRequestHandler( // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const options = getClient()?.getOptions(); - const scope = getCurrentScope(); - scope.setSDKProcessingMetadata({ request }); + isolationScope.setSDKProcessingMetadata({ request }); if (!options || !hasTracingEnabled(options) || !request.url || !request.method) { return origRequestHandler.call(this, req, res, next); diff --git a/packages/remix/test/index.client.test.ts b/packages/remix/test/index.client.test.ts index ab93d9e34a3b..cfa0561ec43b 100644 --- a/packages/remix/test/index.client.test.ts +++ b/packages/remix/test/index.client.test.ts @@ -11,6 +11,7 @@ describe('Client init()', () => { SentryReact.getGlobalScope().clear(); SentryReact.getIsolationScope().clear(); SentryReact.getCurrentScope().clear(); + SentryReact.getCurrentScope().setClient(undefined); }); it('inits the React SDK', () => { diff --git a/packages/remix/test/index.server.test.ts b/packages/remix/test/index.server.test.ts index a244d8c2a3f2..79c138203012 100644 --- a/packages/remix/test/index.server.test.ts +++ b/packages/remix/test/index.server.test.ts @@ -1,5 +1,4 @@ import * as SentryNode from '@sentry/node'; -import { GLOBAL_OBJ } from '@sentry/utils'; import { Integrations, init } from '../src/index.server'; @@ -12,8 +11,7 @@ describe('Server init()', () => { SentryNode.getGlobalScope().clear(); SentryNode.getIsolationScope().clear(); SentryNode.getCurrentScope().clear(); - - GLOBAL_OBJ.__SENTRY__.hub = undefined; + SentryNode.getCurrentScope().setClient(undefined); }); it('inits the Node SDK', () => { diff --git a/packages/replay/test/integration/eventProcessors.test.ts b/packages/replay/test/integration/eventProcessors.test.ts index 41219c75ae6c..b683e1ac4279 100644 --- a/packages/replay/test/integration/eventProcessors.test.ts +++ b/packages/replay/test/integration/eventProcessors.test.ts @@ -11,6 +11,7 @@ useFakeTimers(); describe('Integration | eventProcessors', () => { beforeEach(() => { getCurrentScope().clear(); + getCurrentScope().setClient(undefined); }); afterEach(() => { diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 8fbc2ea7da27..564e0bd35776 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -82,6 +82,7 @@ export { getModuleFromFilename, createGetModuleFromFilename, metrics, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, consoleIntegration, onUncaughtExceptionIntegration, diff --git a/packages/serverless/src/utils.ts b/packages/serverless/src/utils.ts index 9a3eb95682a7..ba3082b7e262 100644 --- a/packages/serverless/src/utils.ts +++ b/packages/serverless/src/utils.ts @@ -1,4 +1,4 @@ -import { runWithAsyncContext } from '@sentry/core'; +import { withIsolationScope } from '@sentry/core'; import type { Scope } from '@sentry/types'; import { addExceptionMechanism } from '@sentry/utils'; @@ -7,7 +7,7 @@ import { addExceptionMechanism } from '@sentry/utils'; * @returns function which runs in the newly created domain or in the existing one */ export function domainify(fn: (...args: A) => R): (...args: A) => R | void { - return (...args) => runWithAsyncContext(() => fn(...args)); + return (...args) => withIsolationScope(() => fn(...args)); } /** diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 42f95c905ca5..e06838cca413 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -5,8 +5,9 @@ import { getDynamicSamplingContextFromSpan, setHttpStatus, spanToTraceHeader, + withIsolationScope, } from '@sentry/core'; -import { getActiveTransaction, runWithAsyncContext, startSpan } from '@sentry/core'; +import { getActiveTransaction, startSpan } from '@sentry/core'; import { captureException } from '@sentry/node'; /* eslint-disable @sentry-internal/sdk/no-optional-chaining */ import type { Span } from '@sentry/types'; @@ -153,7 +154,7 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { if (getActiveSpan()) { return instrumentHandle(input, options); } - return runWithAsyncContext(() => { + return withIsolationScope(() => { return instrumentHandle(input, options); }); }; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 2fe1efd50a05..e31d15f50eb1 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -82,6 +82,7 @@ export { createGetModuleFromFilename, hapiErrorPlugin, metrics, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts index d71c938539df..9e969c08504d 100644 --- a/packages/sveltekit/test/client/sdk.test.ts +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -23,6 +23,7 @@ describe('Sentry client SDK', () => { getGlobalScope().clear(); getIsolationScope().clear(); getCurrentScope().clear(); + getCurrentScope().setClient(undefined); }); it('adds SvelteKit metadata to the SDK options', () => { diff --git a/packages/sveltekit/test/server/sdk.test.ts b/packages/sveltekit/test/server/sdk.test.ts index 8f6f5c1b606f..f6f5e96e1620 100644 --- a/packages/sveltekit/test/server/sdk.test.ts +++ b/packages/sveltekit/test/server/sdk.test.ts @@ -14,6 +14,7 @@ describe('Sentry server SDK', () => { SentryNode.getGlobalScope().clear(); SentryNode.getIsolationScope().clear(); SentryNode.getCurrentScope().clear(); + SentryNode.getCurrentScope().setClient(undefined); }); it('adds SvelteKit metadata to the SDK options', () => { diff --git a/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts b/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts index 6ae36a8b9304..7171d41592bd 100644 --- a/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts +++ b/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts @@ -36,6 +36,7 @@ afterAll(() => { describe('browserTracingIntegration', () => { afterEach(() => { getCurrentScope().clear(); + getCurrentScope().setClient(undefined); }); it('works with tracing enabled', () => { diff --git a/packages/vercel-edge/src/async.ts b/packages/vercel-edge/src/async.ts index ac112edd97e1..11b6b790df1a 100644 --- a/packages/vercel-edge/src/async.ts +++ b/packages/vercel-edge/src/async.ts @@ -1,6 +1,6 @@ -import type { Carrier } from '@sentry/core'; -import { ensureHubOnCarrier, getHubFromCarrier, setAsyncContextStrategy } from '@sentry/core'; -import type { Hub } from '@sentry/types'; +import { Hub as HubClass, getGlobalHub } from '@sentry/core'; +import { setAsyncContextStrategy } from '@sentry/core'; +import type { Hub, Scope } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; @@ -32,25 +32,81 @@ export function setAsyncLocalStorageAsyncContextStrategy(): void { asyncStorage = new MaybeGlobalAsyncLocalStorage(); } - function getCurrentHub(): Hub | undefined { + function getCurrentAsyncStorageHub(): Hub | undefined { return asyncStorage.getStore(); } - function createNewHub(parent: Hub | undefined): Hub { - const carrier: Carrier = {}; - ensureHubOnCarrier(carrier, parent); - return getHubFromCarrier(carrier); + function getCurrentHub(): Hub { + return getCurrentAsyncStorageHub() || getGlobalHub(); } - function runWithAsyncContext(callback: () => T): T { - const existingHub = getCurrentHub(); + function withScope(callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); - const newHub = createNewHub(existingHub); + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ return asyncStorage.run(newHub, () => { - return callback(); + return callback(scope); }); } - setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext }); + function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const isolationScope = parentHub.getIsolationScope(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(scope); + }); + } + + function withIsolationScope(callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const isolationScope = parentHub.getIsolationScope().clone(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(isolationScope); + }); + } + + function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { + const parentHub = getCurrentHub(); + + /* eslint-disable deprecation/deprecation */ + const client = parentHub.getClient(); + const scope = parentHub.getScope().clone(); + const newHub = new HubClass(client, scope, isolationScope); + /* eslint-enable deprecation/deprecation */ + + return asyncStorage.run(newHub, () => { + return callback(isolationScope); + }); + } + + setAsyncContextStrategy({ + getCurrentHub, + withScope, + withSetScope, + withIsolationScope, + withSetIsolationScope, + // eslint-disable-next-line deprecation/deprecation + getCurrentScope: () => getCurrentHub().getScope(), + // eslint-disable-next-line deprecation/deprecation + getIsolationScope: () => getCurrentHub().getIsolationScope(), + }); } diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 4173aede1a23..6374b534705e 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -46,6 +46,7 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, + // eslint-disable-next-line deprecation/deprecation runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation From b38bccd7be021c767f14ad65ea304dee184d8c32 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 12:08:17 +0100 Subject: [PATCH 083/173] ref(node-experimental): Remove deprecated class integrations (#10675) Remove deprecated class integrations from node-experimental. --- packages/node-experimental/src/index.ts | 10 -- .../src/integrations/http.ts | 154 +----------------- .../src/integrations/index.ts | 20 --- .../src/integrations/node-fetch.ts | 113 +------------ packages/node-experimental/src/sdk/init.ts | 9 - .../src/sdk/spanProcessor.ts | 41 +---- .../test/integration/transactions.test.ts | 138 +--------------- 7 files changed, 10 insertions(+), 475 deletions(-) delete mode 100644 packages/node-experimental/src/integrations/index.ts diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index bd87436e36eb..2473b3d9aa64 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -1,6 +1,3 @@ -import { Integrations as CoreIntegrations } from '@sentry/core'; - -import * as NodeExperimentalIntegrations from './integrations'; export { expressIntegration } from './integrations/express'; export { fastifyIntegration } from './integrations/fastify'; export { graphqlIntegration } from './integrations/graphql'; @@ -14,13 +11,6 @@ export { nativeNodeFetchIntegration } from './integrations/node-fetch'; export { postgresIntegration } from './integrations/postgres'; export { prismaIntegration } from './integrations/prisma'; -/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ -export const Integrations = { - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, - ...NodeExperimentalIntegrations, -}; - export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations'; export * as Handlers from './sdk/handlers'; diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 319f2da4f60f..ca4f69f6fe86 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -4,19 +4,11 @@ import { SpanKind } from '@opentelemetry/api'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; -import { - addBreadcrumb, - defineIntegration, - getIsolationScope, - hasTracingEnabled, - isSentryRequestUrl, -} from '@sentry/core'; +import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl } from '@sentry/core'; import { _INTERNAL, getClient, getSpanKind, setSpanMetadata } from '@sentry/opentelemetry'; -import type { EventProcessor, Hub, Integration, IntegrationFn } from '@sentry/types'; -import { stringMatchesSomePattern } from '@sentry/utils'; +import type { IntegrationFn } from '@sentry/types'; import { setIsolationScope } from '../sdk/scope'; -import type { NodeExperimentalClient } from '../types'; import { addOriginToSpan } from '../utils/addOriginToSpan'; import { getRequestUrl } from '../utils/getRequestUrl'; @@ -111,148 +103,6 @@ const _httpIntegration = ((options: HttpOptions = {}) => { export const httpIntegration = defineIntegration(_httpIntegration); -interface OldHttpOptions { - /** - * Whether breadcrumbs should be recorded for requests - * Defaults to true - */ - breadcrumbs?: boolean; - - /** - * Whether tracing spans should be created for requests - * Defaults to false - */ - spans?: boolean; - - /** - * Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs matching the given patterns. - */ - ignoreOutgoingRequests?: (string | RegExp)[]; -} - -/** - * Http instrumentation based on @opentelemetry/instrumentation-http. - * This instrumentation does two things: - * * Create breadcrumbs for outgoing requests - * * Create spans for outgoing requests - * - * Note that this integration is also needed for the Express integration to work! - * - * @deprecated Use `httpIntegration()` instead. - */ -export class Http implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Http'; - - /** - * @inheritDoc - */ - public name: string; - - /** - * If spans for HTTP requests should be captured. - */ - public shouldCreateSpansForRequests: boolean; - - private _unload?: () => void; - private readonly _breadcrumbs: boolean; - // If this is undefined, use default behavior based on client settings - private readonly _spans: boolean | undefined; - private _ignoreOutgoingRequests: (string | RegExp)[]; - - /** - * @inheritDoc - */ - public constructor(options: OldHttpOptions = {}) { - // eslint-disable-next-line deprecation/deprecation - this.name = Http.id; - this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; - this._spans = typeof options.spans === 'undefined' ? undefined : options.spans; - - this._ignoreOutgoingRequests = options.ignoreOutgoingRequests || []; - - // Properly set in setupOnce based on client settings - this.shouldCreateSpansForRequests = false; - } - - /** - * @inheritDoc - */ - public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void { - // No need to instrument if we don't want to track anything - if (!this._breadcrumbs && this._spans === false) { - return; - } - - const client = getClient(); - const clientOptions = client?.getOptions(); - - // This is used in the sampler function - this.shouldCreateSpansForRequests = - typeof this._spans === 'boolean' ? this._spans : hasTracingEnabled(clientOptions); - - // Register instrumentations we care about - this._unload = registerInstrumentations({ - instrumentations: [ - new HttpInstrumentation({ - ignoreOutgoingRequestHook: request => { - const url = getRequestUrl(request); - - if (!url) { - return false; - } - - if (isSentryRequestUrl(url, getClient())) { - return true; - } - - if (this._ignoreOutgoingRequests.length && stringMatchesSomePattern(url, this._ignoreOutgoingRequests)) { - return true; - } - - return false; - }, - - ignoreIncomingRequestHook: request => { - const method = request.method?.toUpperCase(); - // We do not capture OPTIONS/HEAD requests as transactions - if (method === 'OPTIONS' || method === 'HEAD') { - return true; - } - - return false; - }, - - requireParentforOutgoingSpans: true, - requireParentforIncomingSpans: false, - requestHook: (span, req) => { - _updateSpan(span, req); - - // Update the isolation scope, isolate this request - if (getSpanKind(span) === SpanKind.SERVER) { - setIsolationScope(getIsolationScope().clone()); - } - }, - responseHook: (span, res) => { - if (this._breadcrumbs) { - _addRequestBreadcrumb(span, res); - } - }, - }), - ], - }); - } - - /** - * Unregister this integration. - */ - public unregister(): void { - this._unload?.(); - } -} - /** Update the span with data we need. */ function _updateSpan(span: Span, request: ClientRequest | IncomingMessage): void { addOriginToSpan(span, 'auto.http.otel.http'); diff --git a/packages/node-experimental/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts deleted file mode 100644 index d9c238f68141..000000000000 --- a/packages/node-experimental/src/integrations/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Integrations as NodeIntegrations } from '@sentry/node'; - -const { Console, OnUncaughtException, OnUnhandledRejection, Modules, ContextLines, Context, RequestData } = - NodeIntegrations; - -export { Console, OnUncaughtException, OnUnhandledRejection, Modules, ContextLines, Context, RequestData }; - -/* eslint-disable deprecation/deprecation */ -export { Express } from './express'; -export { Http } from './http'; -export { NodeFetch } from './node-fetch'; -export { Fastify } from './fastify'; -export { GraphQL } from './graphql'; -export { Mongo } from './mongo'; -export { Mongoose } from './mongoose'; -export { Mysql } from './mysql'; -export { Mysql2 } from './mysql2'; -export { Nest } from './nest'; -export { Postgres } from './postgres'; -export { Prisma } from './prisma'; diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node-experimental/src/integrations/node-fetch.ts index 35bf982d286e..94eca67c29ba 100644 --- a/packages/node-experimental/src/integrations/node-fetch.ts +++ b/packages/node-experimental/src/integrations/node-fetch.ts @@ -2,14 +2,12 @@ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import type { Instrumentation } from '@opentelemetry/instrumentation'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { addBreadcrumb, defineIntegration, hasTracingEnabled } from '@sentry/core'; -import { _INTERNAL, getClient, getSpanKind } from '@sentry/opentelemetry'; -import type { Integration, IntegrationFn } from '@sentry/types'; +import { addBreadcrumb, defineIntegration } from '@sentry/core'; +import { _INTERNAL, getSpanKind } from '@sentry/opentelemetry'; +import type { IntegrationFn } from '@sentry/types'; import { parseSemver } from '@sentry/utils'; -import type { NodeExperimentalClient } from '../types'; import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; const NODE_VERSION: ReturnType = parseSemver(process.versions.node); @@ -77,111 +75,6 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => { export const nativeNodeFetchIntegration = defineIntegration(_nativeNodeFetchIntegration); -interface OldNodeFetchOptions { - /** - * Whether breadcrumbs should be recorded for requests - * Defaults to true - */ - breadcrumbs?: boolean; - - /** - * Whether tracing spans should be created for requests - * Defaults to false - */ - spans?: boolean; -} - -/** - * Fetch instrumentation based on opentelemetry-instrumentation-fetch. - * This instrumentation does two things: - * * Create breadcrumbs for outgoing requests - * * Create spans for outgoing requests - * - * @deprecated Use `nativeNodeFetchIntegration()` instead. - */ -export class NodeFetch extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'NodeFetch'; - - /** - * @inheritDoc - */ - public name: string; - - /** - * If spans for HTTP requests should be captured. - */ - public shouldCreateSpansForRequests: boolean; - - private readonly _breadcrumbs: boolean; - // If this is undefined, use default behavior based on client settings - private readonly _spans: boolean | undefined; - - /** - * @inheritDoc - */ - public constructor(options: OldNodeFetchOptions = {}) { - super(options); - - // eslint-disable-next-line deprecation/deprecation - this.name = NodeFetch.id; - this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; - this._spans = typeof options.spans === 'undefined' ? undefined : options.spans; - - // Properly set in setupOnce based on client settings - this.shouldCreateSpansForRequests = false; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - // Only add NodeFetch if Node >= 16, as previous versions do not support it - if (!NODE_VERSION.major || NODE_VERSION.major < 16) { - return; - } - - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { FetchInstrumentation } = require('opentelemetry-instrumentation-fetch-node'); - return [ - new FetchInstrumentation({ - onRequest: ({ span }: { span: Span }) => { - _updateSpan(span); - - if (this._breadcrumbs) { - _addRequestBreadcrumb(span); - } - }, - }), - ]; - } catch (error) { - // Could not load instrumentation - } - } - - /** - * @inheritDoc - */ - public setupOnce(): void { - super.setupOnce(); - - const client = getClient(); - const clientOptions = client?.getOptions(); - - // This is used in the sampler function - this.shouldCreateSpansForRequests = - typeof this._spans === 'boolean' ? this._spans : hasTracingEnabled(clientOptions); - } - - /** - * Unregister this integration. - */ - public unregister(): void { - this._unload?.(); - } -} - /** Update the span with data we need. */ function _updateSpan(span: Span): void { addOriginToSpan(span, 'auto.http.otel.node_fetch'); diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index e7fc35a3f4b4..11c98b72c89b 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -7,7 +7,6 @@ import { startSession, } from '@sentry/core'; import { - defaultIntegrations as defaultNodeIntegrations, defaultStackParser, getDefaultIntegrations as getDefaultNodeIntegrations, getSentryRelease, @@ -34,14 +33,6 @@ import { initOtel } from './initOtel'; const ignoredDefaultIntegrations = ['Http', 'Undici']; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations: Integration[] = [ - // eslint-disable-next-line deprecation/deprecation - ...defaultNodeIntegrations.filter(i => !ignoredDefaultIntegrations.includes(i.name)), - httpIntegration(), - nativeNodeFetchIntegration(), -]; - /** Get the default integrations for the Node Experimental SDK. */ export function getDefaultIntegrations(options: Options): Integration[] { return [ diff --git a/packages/node-experimental/src/sdk/spanProcessor.ts b/packages/node-experimental/src/sdk/spanProcessor.ts index 4cc782d804f1..c7254ac23808 100644 --- a/packages/node-experimental/src/sdk/spanProcessor.ts +++ b/packages/node-experimental/src/sdk/spanProcessor.ts @@ -1,41 +1,6 @@ -import { SpanKind } from '@opentelemetry/api'; -import type { Span } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SentrySpanProcessor, getClient } from '@sentry/opentelemetry'; - -import type { Http } from '../integrations/http'; -import type { NodeFetch } from '../integrations/node-fetch'; -import type { NodeExperimentalClient } from '../types'; +import { SentrySpanProcessor } from '@sentry/opentelemetry'; /** - * Implement custom code to avoid sending spans in certain cases. + * Nothing custom here anymore! We can remove this eventually... */ -export class NodeExperimentalSentrySpanProcessor extends SentrySpanProcessor { - /** @inheritDoc */ - protected _shouldSendSpanToSentry(span: Span): boolean { - const client = getClient(); - // eslint-disable-next-line deprecation/deprecation - const httpIntegration = client ? client.getIntegrationByName('Http') : undefined; - // eslint-disable-next-line deprecation/deprecation - const fetchIntegration = client ? client.getIntegrationByName('NodeFetch') : undefined; - - // If we encounter a client or server span with url & method, we assume this comes from the http instrumentation - // In this case, if `shouldCreateSpansForRequests` is false, we want to _record_ the span but not _sample_ it, - // So we can generate a breadcrumb for it but no span will be sent - // TODO v8: Remove this - if ( - (span.kind === SpanKind.CLIENT || span.kind === SpanKind.SERVER) && - span.attributes[SemanticAttributes.HTTP_URL] && - span.attributes[SemanticAttributes.HTTP_METHOD] - ) { - const shouldCreateSpansForRequests = - span.attributes['http.client'] === 'fetch' - ? fetchIntegration?.shouldCreateSpansForRequests - : httpIntegration?.shouldCreateSpansForRequests; - - return shouldCreateSpansForRequests !== false; - } - - return true; - } -} +export class NodeExperimentalSentrySpanProcessor extends SentrySpanProcessor {} diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index 7de51c7811e6..72dd4966bc41 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -1,14 +1,11 @@ -import { SpanKind, TraceFlags, context, trace } from '@opentelemetry/api'; +import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import { spanToJSON } from '@sentry/core'; import { SentrySpanProcessor, getClient, setPropagationContextOnContext } from '@sentry/opentelemetry'; -import type { Integration, PropagationContext, TransactionEvent } from '@sentry/types'; +import type { PropagationContext, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; import * as Sentry from '../../src'; -import { startSpan } from '../../src'; -import type { Http, NodeFetch } from '../../src/integrations'; import type { NodeExperimentalClient } from '../../src/types'; import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; @@ -679,135 +676,4 @@ describe('Integration | Transactions', () => { ]), ); }); - - it('does not create spans for http requests if disabled in http integration', async () => { - const beforeSendTransaction = jest.fn(() => null); - - mockSdkInit({ enableTracing: true, beforeSendTransaction }); - - jest.useFakeTimers(); - - const client = getClient() as NodeExperimentalClient; - - jest.spyOn(client, 'getIntegrationByName').mockImplementation(name => { - if (name === 'Http') { - return { - shouldCreateSpansForRequests: false, - // eslint-disable-next-line deprecation/deprecation - } as Http; - } - - return {} as Integration; - }); - - client.tracer.startActiveSpan( - 'test op', - { - kind: SpanKind.CLIENT, - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_URL]: 'https://example.com', - }, - }, - span => { - startSpan({ name: 'inner 1' }, () => { - startSpan({ name: 'inner 2' }, () => {}); - }); - - span.end(); - }, - ); - - void client.flush(); - jest.advanceTimersByTime(5_000); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(0); - - // Now try a non-HTTP span - client.tracer.startActiveSpan( - 'test op 2', - { - kind: SpanKind.CLIENT, - attributes: {}, - }, - span => { - startSpan({ name: 'inner 1' }, () => { - startSpan({ name: 'inner 2' }, () => {}); - }); - - span.end(); - }, - ); - - void client.flush(); - jest.advanceTimersByTime(5_000); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - }); - - it('does not create spans for fetch requests if disabled in fetch integration', async () => { - const beforeSendTransaction = jest.fn(() => null); - - mockSdkInit({ enableTracing: true, beforeSendTransaction }); - - jest.useFakeTimers(); - - const client = getClient() as NodeExperimentalClient; - - jest.spyOn(client, 'getIntegrationByName').mockImplementation(name => { - if (name === 'NodeFetch') { - return { - shouldCreateSpansForRequests: false, - // eslint-disable-next-line deprecation/deprecation - } as NodeFetch; - } - - return {} as Integration; - }); - - client.tracer.startActiveSpan( - 'test op', - { - kind: SpanKind.CLIENT, - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_URL]: 'https://example.com', - 'http.client': 'fetch', - }, - }, - span => { - startSpan({ name: 'inner 1' }, () => { - startSpan({ name: 'inner 2' }, () => {}); - }); - - span.end(); - }, - ); - - void client.flush(); - jest.advanceTimersByTime(5_000); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(0); - - // Now try a non-HTTP span - client.tracer.startActiveSpan( - 'test op 2', - { - kind: SpanKind.CLIENT, - attributes: {}, - }, - span => { - startSpan({ name: 'inner 1' }, () => { - startSpan({ name: 'inner 2' }, () => {}); - }); - - span.end(); - }, - ); - - void client.flush(); - jest.advanceTimersByTime(5_000); - - expect(beforeSendTransaction).toHaveBeenCalledTimes(1); - }); }); From dc5383da2aac7f52985abcb2776aa078dabf6130 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 12:08:55 +0100 Subject: [PATCH 084/173] ref(core): Rename `Span` class to `SentrySpan` (#10687) And stop exporting it from packages other than core. --- MIGRATION.md | 5 ++ .../index.bundle.tracing.replay.feedback.ts | 3 +- .../src/index.bundle.tracing.replay.ts | 3 +- packages/browser/src/index.bundle.tracing.ts | 3 +- packages/core/src/tracing/idletransaction.ts | 8 +-- packages/core/src/tracing/index.ts | 2 +- .../src/tracing/{span.ts => sentrySpan.ts} | 8 +-- packages/core/src/tracing/transaction.ts | 4 +- packages/core/src/utils/spanUtils.ts | 8 +-- packages/core/test/lib/tracing/span.test.ts | 66 +++++++++---------- .../core/test/lib/tracing/spanstatus.test.ts | 6 +- packages/core/test/lib/tracing/trace.test.ts | 12 ++-- packages/core/test/lib/utils/getRootSpan.ts | 8 +-- .../core/test/lib/utils/spanUtils.test.ts | 20 +++--- packages/nextjs/src/edge/index.ts | 2 +- packages/nextjs/src/index.types.ts | 1 - .../test/helpers/createSpan.ts | 6 +- packages/node/test/integrations/http.test.ts | 16 ++--- .../test/spanprocessor.test.ts | 2 +- .../tracing-internal/src/exports/index.ts | 1 - .../test/browser/metrics/utils.test.ts | 5 +- packages/tracing/src/index.ts | 15 ----- packages/tracing/test/idletransaction.test.ts | 10 +-- .../test/integrations/apollo-nestjs.test.ts | 4 +- .../tracing/test/integrations/apollo.test.ts | 4 +- .../tracing/test/integrations/graphql.test.ts | 4 +- .../test/integrations/node/mongo.test.ts | 4 +- .../test/integrations/node/postgres.test.ts | 6 +- packages/tracing/test/span.test.ts | 65 +++++++++--------- 29 files changed, 143 insertions(+), 158 deletions(-) rename packages/core/src/tracing/{span.ts => sentrySpan.ts} (99%) diff --git a/MIGRATION.md b/MIGRATION.md index 261ee10d0bf6..d4a7827ed755 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -55,6 +55,11 @@ The following previously deprecated API has been removed from the `@sentry/nextj - `IS_BUILD` - `isBuild` +## Removal of `Span` class export from SDK packages + +In v8, we are no longer exporting the `Span` class from SDK packages (e.g. `@sentry/browser` or `@sentry/node`). +Internally, this class is now called `SentrySpan`, and it is no longer meant to be used by users directly. + ## Removal of Severity Enum In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type. In v8 we removed the `Severity` diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 4a015f0dd9fe..8a4a05dba6b6 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,5 +1,5 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/feedback'; -import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay, replayIntegration } from '@sentry/replay'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; @@ -27,7 +27,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - Span, addExtensionMethods, }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index c880f97bd84f..20bbf135ace5 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,5 +1,5 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; import { Replay, replayIntegration } from '@sentry/replay'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; @@ -27,7 +27,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - Span, addExtensionMethods, }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index e645de683dd5..5b1dc7f5d2de 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,6 +1,6 @@ // This is exported so the loader does not fail when switching off Replay import { Feedback, Replay, feedbackIntegration, replayIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing, Span, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; import * as Sentry from './index.bundle.base'; @@ -27,7 +27,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - Span, addExtensionMethods, }; export * from './index.bundle.base'; diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index 492415306c58..3e9381edc83b 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -4,8 +4,8 @@ import { logger, timestampInSeconds } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; -import type { Span } from './span'; -import { SpanRecorder } from './span'; +import type { SentrySpan } from './sentrySpan'; +import { SpanRecorder } from './sentrySpan'; import { Transaction } from './transaction'; export const TRACING_DEFAULTS = { @@ -41,7 +41,7 @@ export class IdleTransactionSpanRecorder extends SpanRecorder { /** * @inheritDoc */ - public add(span: Span): void { + public add(span: SentrySpan): void { // We should make sure we do not push and pop activities for // the transaction that this span recorder belongs to. if (span.spanContext().spanId !== this.transactionSpanId) { @@ -178,7 +178,7 @@ export class IdleTransaction extends Transaction { } // eslint-disable-next-line deprecation/deprecation - this.spanRecorder.spans = this.spanRecorder.spans.filter((span: Span) => { + this.spanRecorder.spans = this.spanRecorder.spans.filter((span: SentrySpan) => { // If we are dealing with the transaction itself, we just return it if (span.spanContext().spanId === this.spanContext().spanId) { return true; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index 38fc5df28e11..1efa436f7548 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -1,7 +1,7 @@ export { startIdleTransaction, addTracingExtensions } from './hubextensions'; export { IdleTransaction, TRACING_DEFAULTS } from './idletransaction'; export type { BeforeFinishCallback } from './idletransaction'; -export { Span } from './span'; +export { SentrySpan } from './sentrySpan'; export { Transaction } from './transaction'; // eslint-disable-next-line deprecation/deprecation export { getActiveTransaction } from './utils'; diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/sentrySpan.ts similarity index 99% rename from packages/core/src/tracing/span.ts rename to packages/core/src/tracing/sentrySpan.ts index 5ded23cc386d..425ded18713b 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -37,7 +37,7 @@ import { setHttpStatus } from './spanstatus'; * @hidden */ export class SpanRecorder { - public spans: Span[]; + public spans: SentrySpan[]; private readonly _maxlen: number; @@ -52,7 +52,7 @@ export class SpanRecorder { * trace tree (i.e.the first n spans with the smallest * start_timestamp). */ - public add(span: Span): void { + public add(span: SentrySpan): void { if (this.spans.length > this._maxlen) { // eslint-disable-next-line deprecation/deprecation span.spanRecorder = undefined; @@ -65,7 +65,7 @@ export class SpanRecorder { /** * Span contains all data about a span */ -export class Span implements SpanInterface { +export class SentrySpan implements SpanInterface { /** * Tags for the span. * @deprecated Use `spanToJSON(span).atttributes` instead. @@ -386,7 +386,7 @@ export class Span implements SpanInterface { public startChild( spanContext?: Pick>, ): SpanInterface { - const childSpan = new Span({ + const childSpan = new SentrySpan({ ...spanContext, parentSpanId: this._spanId, sampled: this._sampled, diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 53105363e3ea..cdea78e1880b 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -19,11 +19,11 @@ import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; -import { Span as SpanClass, SpanRecorder } from './span'; +import { SentrySpan, SpanRecorder } from './sentrySpan'; import { getCapturedScopesOnSpan } from './trace'; /** JSDoc */ -export class Transaction extends SpanClass implements TransactionInterface { +export class Transaction extends SentrySpan implements TransactionInterface { /** * The reference to the current hub. */ diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index cbf8ce6d7f5b..a23559576b9e 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -1,6 +1,6 @@ import type { Span, SpanJSON, SpanTimeInput, TraceContext } from '@sentry/types'; import { dropUndefinedKeys, generateSentryTraceHeader, timestampInSeconds } from '@sentry/utils'; -import type { Span as SpanClass } from '../tracing/span'; +import type { SentrySpan } from '../tracing/sentrySpan'; // These are aligned with OpenTelemetry trace flags export const TRACE_FLAG_NONE = 0x0; @@ -72,7 +72,7 @@ function ensureTimestampInSeconds(timestamp: number): number { * TODO v8: When we remove the deprecated stuff from `span.ts`, we can remove the circular dependency again. */ export function spanToJSON(span: Span): Partial { - if (spanIsSpanClass(span)) { + if (spanIsSentrySpan(span)) { return span.getSpanJSON(); } @@ -90,8 +90,8 @@ export function spanToJSON(span: Span): Partial { * Sadly, due to circular dependency checks we cannot actually import the Span class here and check for instanceof. * :( So instead we approximate this by checking if it has the `getSpanJSON` method. */ -function spanIsSpanClass(span: Span): span is SpanClass { - return typeof (span as SpanClass).getSpanJSON === 'function'; +function spanIsSentrySpan(span: Span): span is SentrySpan { + return typeof (span as SentrySpan).getSpanJSON === 'function'; } /** diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index b3c08987d4b2..16b92a4a283f 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -1,30 +1,30 @@ import { timestampInSeconds } from '@sentry/utils'; -import { Span } from '../../../src'; +import { SentrySpan } from '../../../src'; import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON } from '../../../src/utils/spanUtils'; describe('span', () => { describe('name', () => { /* eslint-disable deprecation/deprecation */ it('works with name', () => { - const span = new Span({ name: 'span name' }); + const span = new SentrySpan({ name: 'span name' }); expect(span.name).toEqual('span name'); expect(span.description).toEqual('span name'); }); it('works with description', () => { - const span = new Span({ description: 'span name' }); + const span = new SentrySpan({ description: 'span name' }); expect(span.name).toEqual('span name'); expect(span.description).toEqual('span name'); }); it('works without name', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.name).toEqual(''); expect(span.description).toEqual(undefined); }); it('allows to update the name via setter', () => { - const span = new Span({ name: 'span name' }); + const span = new SentrySpan({ name: 'span name' }); expect(span.name).toEqual('span name'); expect(span.description).toEqual('span name'); @@ -35,7 +35,7 @@ describe('span', () => { }); it('allows to update the name via setName', () => { - const span = new Span({ name: 'span name' }); + const span = new SentrySpan({ name: 'span name' }); expect(span.name).toEqual('span name'); expect(span.description).toEqual('span name'); @@ -47,7 +47,7 @@ describe('span', () => { }); it('allows to update the name via updateName', () => { - const span = new Span({ name: 'span name' }); + const span = new SentrySpan({ name: 'span name' }); expect(span.name).toEqual('span name'); expect(span.description).toEqual('span name'); @@ -61,7 +61,7 @@ describe('span', () => { describe('setAttribute', () => { it('allows to set attributes', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('str', 'bar'); span.setAttribute('num', 1); @@ -84,13 +84,13 @@ describe('span', () => { strArray: ['aa', 'bb'], boolArray: [true, false], arrayWithUndefined: [1, undefined, 2], - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); }); it('deletes attributes when setting to `undefined`', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('str', 'bar'); @@ -103,7 +103,7 @@ describe('span', () => { }); it('disallows invalid attribute types', () => { - const span = new Span(); + const span = new SentrySpan(); /** @ts-expect-error this is invalid */ span.setAttribute('str', {}); @@ -118,12 +118,12 @@ describe('span', () => { describe('setAttributes', () => { it('allows to set attributes', () => { - const span = new Span(); + const span = new SentrySpan(); const initialAttributes = span['_attributes']; expect(initialAttributes).toEqual({ - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); @@ -176,7 +176,7 @@ describe('span', () => { }); it('deletes attributes when setting to `undefined`', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('str', 'bar'); @@ -191,7 +191,7 @@ describe('span', () => { describe('end', () => { it('works without endTimestamp', () => { - const span = new Span(); + const span = new SentrySpan(); const now = timestampInSeconds(); span.end(); @@ -199,7 +199,7 @@ describe('span', () => { }); it('works with endTimestamp in seconds', () => { - const span = new Span(); + const span = new SentrySpan(); const timestamp = timestampInSeconds() - 1; span.end(timestamp); @@ -207,7 +207,7 @@ describe('span', () => { }); it('works with endTimestamp in milliseconds', () => { - const span = new Span(); + const span = new SentrySpan(); const timestamp = Date.now() - 1000; span.end(timestamp); @@ -215,7 +215,7 @@ describe('span', () => { }); it('works with endTimestamp in array form', () => { - const span = new Span(); + const span = new SentrySpan(); const seconds = Math.floor(timestampInSeconds() - 1); span.end([seconds, 0]); @@ -225,7 +225,7 @@ describe('span', () => { it('skips if span is already ended', () => { const startTimestamp = timestampInSeconds() - 5; const endTimestamp = timestampInSeconds() - 1; - const span = new Span({ startTimestamp, endTimestamp }); + const span = new SentrySpan({ startTimestamp, endTimestamp }); span.end(); @@ -235,24 +235,24 @@ describe('span', () => { describe('isRecording', () => { it('returns true for sampled span', () => { - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); expect(span.isRecording()).toEqual(true); }); it('returns false for sampled, finished span', () => { - const span = new Span({ sampled: true, endTimestamp: Date.now() }); + const span = new SentrySpan({ sampled: true, endTimestamp: Date.now() }); expect(span.isRecording()).toEqual(false); }); it('returns false for unsampled span', () => { - const span = new Span({ sampled: false }); + const span = new SentrySpan({ sampled: false }); expect(span.isRecording()).toEqual(false); }); }); describe('spanContext', () => { it('works with default span', () => { - const span = new Span(); + const span = new SentrySpan(); expect(span.spanContext()).toEqual({ spanId: span['_spanId'], traceId: span['_traceId'], @@ -261,7 +261,7 @@ describe('span', () => { }); it('works sampled span', () => { - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); expect(span.spanContext()).toEqual({ spanId: span['_spanId'], traceId: span['_traceId'], @@ -270,7 +270,7 @@ describe('span', () => { }); it('works unsampled span', () => { - const span = new Span({ sampled: false }); + const span = new SentrySpan({ sampled: false }); expect(span.spanContext()).toEqual({ spanId: span['_spanId'], traceId: span['_traceId'], @@ -282,22 +282,22 @@ describe('span', () => { // Ensure that attributes & data are merged together describe('_getData', () => { it('works without data & attributes', () => { - const span = new Span(); + const span = new SentrySpan(); expect(span['_getData']()).toEqual({ - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); }); it('works with data only', () => { - const span = new Span(); + const span = new SentrySpan(); // eslint-disable-next-line deprecation/deprecation span.setData('foo', 'bar'); expect(span['_getData']()).toEqual({ foo: 'bar', - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); expect(span['_getData']()).toStrictEqual({ @@ -308,12 +308,12 @@ describe('span', () => { }); it('works with attributes only', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('foo', 'bar'); expect(span['_getData']()).toEqual({ foo: 'bar', - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); // eslint-disable-next-line deprecation/deprecation @@ -321,7 +321,7 @@ describe('span', () => { }); it('merges data & attributes', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('foo', 'foo'); span.setAttribute('bar', 'bar'); // eslint-disable-next-line deprecation/deprecation @@ -333,7 +333,7 @@ describe('span', () => { foo: 'foo', bar: 'bar', baz: 'baz', - // origin is set by default to 'manual' in the Span constructor + // origin is set by default to 'manual' in the SentrySpan constructor 'sentry.origin': 'manual', }); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/test/lib/tracing/spanstatus.test.ts b/packages/core/test/lib/tracing/spanstatus.test.ts index 559aa101c642..de3411ee2a77 100644 --- a/packages/core/test/lib/tracing/spanstatus.test.ts +++ b/packages/core/test/lib/tracing/spanstatus.test.ts @@ -1,4 +1,4 @@ -import { Span, setHttpStatus, spanToJSON } from '../../../src/index'; +import { SentrySpan, setHttpStatus, spanToJSON } from '../../../src/index'; describe('setHttpStatus', () => { it.each([ @@ -16,7 +16,7 @@ describe('setHttpStatus', () => { [504, 'deadline_exceeded'], [520, 'internal_error'], ])('applies the correct span status and http status code to the span (%s - $%s)', (code, status) => { - const span = new Span({ name: 'test' }); + const span = new SentrySpan({ name: 'test' }); setHttpStatus(span!, code); @@ -28,7 +28,7 @@ describe('setHttpStatus', () => { }); it("doesn't set the status for an unknown http status code", () => { - const span = new Span({ name: 'test' }); + const span = new SentrySpan({ name: 'test' }); setHttpStatus(span!, 600); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index ea4e9b2d43ca..d95f713147ec 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -11,7 +11,7 @@ import { withScope, } from '../../../src'; import { - Span, + SentrySpan, continueTrace, getActiveSpan, startInactiveSpan, @@ -279,11 +279,11 @@ describe('startSpan', () => { }); it('creates & finishes span', async () => { - let _span: Span | undefined; + let _span: SentrySpan | undefined; startSpan({ name: 'GET users/[id]' }, span => { expect(span).toBeDefined(); expect(spanToJSON(span!).timestamp).toBeUndefined(); - _span = span as Span; + _span = span as SentrySpan; }); expect(_span).toBeDefined(); @@ -314,7 +314,7 @@ describe('startSpan', () => { const initialScope = getCurrentScope(); const manualScope = initialScope.clone(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); @@ -475,7 +475,7 @@ describe('startSpanManual', () => { const initialScope = getCurrentScope(); const manualScope = initialScope.clone(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); @@ -580,7 +580,7 @@ describe('startInactiveSpan', () => { const initialScope = getCurrentScope(); const manualScope = initialScope.clone(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); diff --git a/packages/core/test/lib/utils/getRootSpan.ts b/packages/core/test/lib/utils/getRootSpan.ts index eba622a2d884..dcb33ac83e8c 100644 --- a/packages/core/test/lib/utils/getRootSpan.ts +++ b/packages/core/test/lib/utils/getRootSpan.ts @@ -1,8 +1,8 @@ -import { Span, Transaction, getRootSpan } from '../../../src'; +import { SentrySpan, Transaction, getRootSpan } from '../../../src'; describe('getRootSpan', () => { - it('returns the root span of a span (Span)', () => { - const root = new Span({ name: 'test' }); + it('returns the root span of a span (SentrySpan)', () => { + const root = new SentrySpan({ name: 'test' }); // @ts-expect-error this is highly illegal and shouldn't happen IRL // eslint-disable-next-line deprecation/deprecation root.transaction = root; @@ -29,7 +29,7 @@ describe('getRootSpan', () => { }); it('returns undefined if span has no root span', () => { - const span = new Span({ name: 'test' }); + const span = new SentrySpan({ name: 'test' }); expect(getRootSpan(span)).toBe(undefined); }); diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index 0afb59530623..6453681e7867 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -1,14 +1,14 @@ import { TRACEPARENT_REGEXP, timestampInSeconds } from '@sentry/utils'; -import { Span, spanToTraceHeader } from '../../../src'; +import { SentrySpan, spanToTraceHeader } from '../../../src'; import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../../../src/utils/spanUtils'; describe('spanToTraceHeader', () => { test('simple', () => { - const span = new Span(); + const span = new SentrySpan(); expect(spanToTraceHeader(span)).toMatch(TRACEPARENT_REGEXP); }); test('with sample', () => { - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); expect(spanToTraceHeader(span)).toMatch(TRACEPARENT_REGEXP); }); }); @@ -49,7 +49,7 @@ describe('spanTimeInputToSeconds', () => { describe('spanToJSON', () => { it('works with a simple span', () => { - const span = new Span(); + const span = new SentrySpan(); expect(spanToJSON(span)).toEqual({ span_id: span.spanContext().spanId, trace_id: span.spanContext().traceId, @@ -62,7 +62,7 @@ describe('spanToJSON', () => { }); it('works with a full span', () => { - const span = new Span({ + const span = new SentrySpan({ name: 'test name', op: 'test op', parentSpanId: '1234', @@ -107,7 +107,7 @@ describe('spanToJSON', () => { start_timestamp: 123, }; }, - } as unknown as Span; + } as unknown as SentrySpan; expect(spanToJSON(span)).toEqual({ span_id: 'span_id', @@ -119,20 +119,20 @@ describe('spanToJSON', () => { it('returns empty object if span does not have getter methods', () => { // eslint-disable-next-line - const span = new Span().toJSON(); + const span = new SentrySpan().toJSON(); - expect(spanToJSON(span as unknown as Span)).toEqual({}); + expect(spanToJSON(span as unknown as SentrySpan)).toEqual({}); }); }); describe('spanIsSampled', () => { test('sampled', () => { - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); expect(spanIsSampled(span)).toBe(true); }); test('not sampled', () => { - const span = new Span({ sampled: false }); + const span = new SentrySpan({ sampled: false }); expect(spanIsSampled(span)).toBe(false); }); }); diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 735dc40d3188..d63aa9ec0256 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -48,7 +48,7 @@ export function withSentryConfig(exportedUserNextConfig: T): T { } export * from '@sentry/vercel-edge'; -export { Span, Transaction } from '@sentry/core'; +export { Transaction } from '@sentry/core'; export * from '../common'; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 7facc2d580b2..967b84c2c4c4 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -33,7 +33,6 @@ export declare const ErrorBoundary: typeof clientSdk.ErrorBoundary; export declare const showReportDialog: typeof clientSdk.showReportDialog; export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary; -export declare const Span: typeof edgeSdk.Span; export declare const Transaction: typeof edgeSdk.Transaction; export { withSentryConfig } from './config'; diff --git a/packages/node-experimental/test/helpers/createSpan.ts b/packages/node-experimental/test/helpers/createSpan.ts index 38c4ed96f3a8..af96dde1e994 100644 --- a/packages/node-experimental/test/helpers/createSpan.ts +++ b/packages/node-experimental/test/helpers/createSpan.ts @@ -1,13 +1,13 @@ import type { Context, SpanContext } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import type { Tracer } from '@opentelemetry/sdk-trace-base'; -import { Span } from '@opentelemetry/sdk-trace-base'; +import { SentrySpan } from '@opentelemetry/sdk-trace-base'; import { uuid4 } from '@sentry/utils'; export function createSpan( name?: string, { spanId, parentSpanId }: { spanId?: string; parentSpanId?: string } = {}, -): Span { +): SentrySpan { const spanProcessor = { onStart: () => {}, onEnd: () => {}, @@ -26,5 +26,5 @@ export function createSpan( }; // eslint-disable-next-line deprecation/deprecation - return new Span(tracer, {} as Context, name || 'test', spanContext, SpanKind.INTERNAL, parentSpanId); + return new SentrySpan(tracer, {} as Context, name || 'test', spanContext, SpanKind.INTERNAL, parentSpanId); } diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index b7e2149c74c9..2af325d5ddbd 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,6 +1,6 @@ import * as http from 'http'; import * as https from 'https'; -import type { Span } from '@sentry/core'; +import type { SentrySpan } from '@sentry/core'; import { Transaction } from '@sentry/core'; import { getCurrentScope, makeMain, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; import { Hub, addTracingExtensions } from '@sentry/core'; @@ -79,7 +79,7 @@ describe('tracing', () => { const transaction = createTransactionOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; http.get('http://dogs.are.great/'); @@ -97,7 +97,7 @@ describe('tracing', () => { const transaction = createTransactionOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; http.get('http://squirrelchasers.ingest.sentry.io/api/12312012/store/'); @@ -288,7 +288,7 @@ describe('tracing', () => { const transaction = createTransactionOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; http.get('http://dogs.are.great/spaniel?tail=wag&cute=true#learn-more'); @@ -313,7 +313,7 @@ describe('tracing', () => { const transaction = createTransactionOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; http.request({ method: 'GET', host: 'dogs.are.great', path: '/spaniel?tail=wag&cute=true#learn-more' }); @@ -343,7 +343,7 @@ describe('tracing', () => { const transaction = createTransactionOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; http.get(`http://${auth}@dogs.are.great/`); @@ -405,7 +405,7 @@ describe('tracing', () => { const transaction = createTransactionAndPutOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; const request = http.get(url); @@ -515,7 +515,7 @@ describe('tracing', () => { const transaction = createTransactionAndPutOnScope(); // eslint-disable-next-line deprecation/deprecation - const spans = (transaction as unknown as Span).spanRecorder?.spans as Span[]; + const spans = (transaction as unknown as SentrySpan).spanRecorder?.spans as SentrySpan[]; const request = http.get(url); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 768c12369130..c5e54b8d642d 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -6,7 +6,7 @@ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import type { SpanStatusType } from '@sentry/core'; import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; -import { Span as SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; +import { SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; import { NodeClient } from '@sentry/node'; import { resolvedSyncPromise } from '@sentry/utils'; diff --git a/packages/tracing-internal/src/exports/index.ts b/packages/tracing-internal/src/exports/index.ts index 955598675691..132a4417570e 100644 --- a/packages/tracing-internal/src/exports/index.ts +++ b/packages/tracing-internal/src/exports/index.ts @@ -3,7 +3,6 @@ export { getActiveTransaction, hasTracingEnabled, IdleTransaction, - Span, // eslint-disable-next-line deprecation/deprecation SpanStatus, startIdleTransaction, diff --git a/packages/tracing-internal/test/browser/metrics/utils.test.ts b/packages/tracing-internal/test/browser/metrics/utils.test.ts index 428d91edc058..1d6a5621296f 100644 --- a/packages/tracing-internal/test/browser/metrics/utils.test.ts +++ b/packages/tracing-internal/test/browser/metrics/utils.test.ts @@ -1,5 +1,4 @@ -import { spanToJSON } from '@sentry/core'; -import { Span, Transaction } from '../../../src'; +import { SentrySpan, Transaction, spanToJSON } from '@sentry/core'; import { _startChild } from '../../../src/browser/metrics/utils'; describe('_startChild()', () => { @@ -11,7 +10,7 @@ describe('_startChild()', () => { op: 'script', }); - expect(span).toBeInstanceOf(Span); + expect(span).toBeInstanceOf(SentrySpan); expect(spanToJSON(span).description).toBe('evaluation'); // eslint-disable-next-line deprecation/deprecation expect(span.op).toBe('script'); diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts index 7192736b0f62..2887963e1149 100644 --- a/packages/tracing/src/index.ts +++ b/packages/tracing/src/index.ts @@ -13,7 +13,6 @@ import { Mysql, Postgres, Prisma, - Span as SpanT, SpanStatus as SpanStatusT, TRACEPARENT_REGEXP as TRACEPARENT_REGEXP_T, Transaction as TransactionT, @@ -86,20 +85,6 @@ export const Transaction = TransactionT; */ export type Transaction = TransactionT; -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `Span` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -export const Span = SpanT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `Span` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -export type Span = SpanT; - /** * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. */ diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/tracing/test/idletransaction.test.ts index f210330c7049..b1c1f498e46d 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/tracing/test/idletransaction.test.ts @@ -14,7 +14,7 @@ import { startSpanManual, } from '@sentry/core'; -import { IdleTransaction, Span, getClient } from '../../core/src'; +import { IdleTransaction, SentrySpan, getClient } from '../../core/src'; import { IdleTransactionSpanRecorder } from '../../core/src/tracing/idletransaction'; import { getDefaultBrowserClientOptions } from './testutils'; @@ -231,11 +231,11 @@ describe('IdleTransaction', () => { expect(spans).toHaveLength(3); expect(spans[0].spanContext().spanId).toBe(transaction.spanContext().spanId); - // Regular Span - should not modified + // Regular SentrySpan - should not modified expect(spans[1].spanContext().spanId).toBe(regularSpan.spanContext().spanId); expect(spans[1]['_endTime']).not.toBe(spanToJSON(transaction).timestamp); - // Cancelled Span - has endtimestamp of transaction + // Cancelled SentrySpan - has endtimestamp of transaction expect(spans[2].spanContext().spanId).toBe(cancelledSpan.spanContext().spanId); expect(spans[2].status).toBe('cancelled'); expect(spans[2]['_endTime']).toBe(spanToJSON(transaction).timestamp); @@ -522,7 +522,7 @@ describe('IdleTransactionSpanRecorder', () => { expect(mockPushActivity).toHaveBeenCalledTimes(0); expect(mockPopActivity).toHaveBeenCalledTimes(0); - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); expect(spanRecorder.spans).toHaveLength(0); spanRecorder.add(span); @@ -543,7 +543,7 @@ describe('IdleTransactionSpanRecorder', () => { const mockPopActivity = jest.fn(); const spanRecorder = new IdleTransactionSpanRecorder(mockPushActivity, mockPopActivity, '', 10); - const span = new Span({ sampled: true, startTimestamp: 765, endTimestamp: 345 }); + const span = new SentrySpan({ sampled: true, startTimestamp: 765, endTimestamp: 345 }); spanRecorder.add(span); expect(mockPushActivity).toHaveBeenCalledTimes(0); diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts index 51f82f210e59..7761b44f39c5 100644 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ b/packages/tracing/test/integrations/apollo-nestjs.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import { Hub, Scope, SentrySpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -80,7 +80,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new SpanClass(); + parentSpan = new SentrySpan(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index 5de017275080..75b905a77a41 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import { Hub, Scope, SentrySpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -80,7 +80,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new SpanClass(); + parentSpan = new SentrySpan(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index 7189a148130d..e3ef5361e01b 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import { Hub, Scope, SentrySpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -42,7 +42,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new SpanClass(); + parentSpan = new SentrySpan(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(scope, 'setSpan'); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index 4a42a096de69..e1cee110e7b9 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import { Hub, Scope, SentrySpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -64,7 +64,7 @@ describe('patchOperation()', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new SpanClass(); + parentSpan = new SentrySpan(); childSpan = parentSpan.startChild(); testClient = getTestClient({}); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index 800657d20fa1..c3f528d117b0 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -1,6 +1,6 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, Span as SpanClass } from '@sentry/core'; +import { Hub, Scope, SentrySpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { loadModule, logger } from '@sentry/utils'; import pg from 'pg'; @@ -64,7 +64,7 @@ describe('setupOnce', () => { beforeEach(() => { scope = new Scope(); - parentSpan = new SpanClass(); + parentSpan = new SentrySpan(); childSpan = parentSpan.startChild(); jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); @@ -135,7 +135,7 @@ describe('setupOnce', () => { it('does not attempt resolution when module is passed directly', async () => { const scope = new Scope(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new SpanClass()); + jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new SentrySpan()); new Integrations.Postgres({ module: mockModule }).setupOnce( () => undefined, diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 0574bdae7da3..8975b3762a64 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -3,6 +3,7 @@ import { BrowserClient } from '@sentry/browser'; import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SentrySpan, getClient, getCurrentHub, getCurrentScope, @@ -13,10 +14,10 @@ import { } from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; -import { Span, TRACEPARENT_REGEXP, Transaction } from '../src'; +import { TRACEPARENT_REGEXP, Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; -describe('Span', () => { +describe('SentrySpan', () => { beforeEach(() => { getGlobalScope().clear(); getIsolationScope().clear(); @@ -32,9 +33,9 @@ describe('Span', () => { jest.clearAllMocks(); }); - describe('new Span', () => { + describe('new SentrySpan', () => { test('simple', () => { - const span = new Span({ sampled: true }); + const span = new SentrySpan({ sampled: true }); const span2 = span.startChild(); expect((span2 as any).parentSpanId).toBe((span as any).spanId); expect((span2 as any).traceId).toBe((span as any).traceId); @@ -42,13 +43,13 @@ describe('Span', () => { }); test('sets instrumenter to `sentry` if not specified in constructor', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.instrumenter).toBe('sentry'); }); test('allows to set instrumenter in constructor', () => { - const span = new Span({ instrumenter: 'otel' }); + const span = new SentrySpan({ instrumenter: 'otel' }); expect(span.instrumenter).toBe('otel'); }); @@ -73,14 +74,14 @@ describe('Span', () => { const span2 = transaction.startChild(); const span3 = span2.startChild(); span3.end(); - expect(transaction.spanRecorder).toBe((span2 as Span).spanRecorder); - expect(transaction.spanRecorder).toBe((span3 as Span).spanRecorder); + expect(transaction.spanRecorder).toBe((span2 as SentrySpan).spanRecorder); + expect(transaction.spanRecorder).toBe((span3 as SentrySpan).spanRecorder); }); }); describe('setters', () => { test('setTag', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.tags.foo).toBeUndefined(); span.setTag('foo', 'bar'); expect(span.tags.foo).toBe('bar'); @@ -89,7 +90,7 @@ describe('Span', () => { }); test('setData', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.data.foo).toBeUndefined(); span.setData('foo', null); expect(span.data.foo).toBe(null); @@ -100,7 +101,7 @@ describe('Span', () => { }); test('setName', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.description).toBeUndefined(); span.updateName('foo'); expect(span.description).toBe('foo'); @@ -109,14 +110,14 @@ describe('Span', () => { describe('status', () => { test('setStatus', () => { - const span = new Span({}); + const span = new SentrySpan({}); span.setStatus('permission_denied'); expect((span.getTraceContext() as any).status).toBe('permission_denied'); }); // TODO (v8): Remove test('setHttpStatus', () => { - const span = new Span({}); + const span = new SentrySpan({}); span.setHttpStatus(404); expect((span.getTraceContext() as any).status).toBe('not_found'); expect(span.tags['http.status_code']).toBe('404'); @@ -125,7 +126,7 @@ describe('Span', () => { // TODO (v8): Remove test('isSuccess', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(span.isSuccess()).toBe(false); expect(spanToJSON(span).status).not.toBe('ok'); span.setHttpStatus(200); @@ -151,25 +152,25 @@ describe('Span', () => { describe('toTraceparent', () => { test('simple', () => { - expect(new Span().toTraceparent()).toMatch(TRACEPARENT_REGEXP); + expect(new SentrySpan().toTraceparent()).toMatch(TRACEPARENT_REGEXP); }); test('with sample', () => { - expect(new Span({ sampled: true }).toTraceparent()).toMatch(TRACEPARENT_REGEXP); + expect(new SentrySpan({ sampled: true }).toTraceparent()).toMatch(TRACEPARENT_REGEXP); }); }); describe('toJSON', () => { test('simple', () => { const span = JSON.parse( - JSON.stringify(new Span({ traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', spanId: 'bbbbbbbbbbbbbbbb' })), + JSON.stringify(new SentrySpan({ traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', spanId: 'bbbbbbbbbbbbbbbb' })), ); expect(span).toHaveProperty('span_id', 'bbbbbbbbbbbbbbbb'); expect(span).toHaveProperty('trace_id', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); }); test('with parent', () => { - const spanA = new Span({ traceId: 'a', spanId: 'b' }) as any; - const spanB = new Span({ traceId: 'c', spanId: 'd', sampled: false, parentSpanId: spanA.spanId }); + const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; + const spanB = new SentrySpan({ traceId: 'c', spanId: 'd', sampled: false, parentSpanId: spanA.spanId }); const serialized = JSON.parse(JSON.stringify(spanB)); expect(serialized).toHaveProperty('parent_span_id', 'b'); expect(serialized).toHaveProperty('span_id', 'd'); @@ -177,8 +178,8 @@ describe('Span', () => { }); test('should drop all `undefined` values', () => { - const spanA = new Span({ traceId: 'a', spanId: 'b' }) as any; - const spanB = new Span({ + const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; + const spanB = new SentrySpan({ parentSpanId: spanA.spanId, spanId: 'd', traceId: 'c', @@ -199,7 +200,7 @@ describe('Span', () => { describe('finish', () => { test('simple', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(spanToJSON(span).timestamp).toBeUndefined(); span.end(); expect(spanToJSON(span).timestamp).toBeGreaterThan(1); @@ -332,14 +333,14 @@ describe('Span', () => { describe('end', () => { test('simple', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(spanToJSON(span).timestamp).toBeUndefined(); span.end(); expect(spanToJSON(span).timestamp).toBeGreaterThan(1); }); test('with endTime in seconds', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(spanToJSON(span).timestamp).toBeUndefined(); const endTime = Date.now() / 1000; span.end(endTime); @@ -347,7 +348,7 @@ describe('Span', () => { }); test('with endTime in milliseconds', () => { - const span = new Span({}); + const span = new SentrySpan({}); expect(spanToJSON(span).timestamp).toBeUndefined(); const endTime = Date.now(); span.end(endTime); @@ -481,27 +482,27 @@ describe('Span', () => { describe('getTraceContext', () => { test('should have status attribute undefined if no status tag is available', () => { - const span = new Span({}); + const span = new SentrySpan({}); const context = span.getTraceContext(); expect((context as any).status).toBeUndefined(); }); test('should have success status extracted from tags', () => { - const span = new Span({}); + const span = new SentrySpan({}); span.setStatus('ok'); const context = span.getTraceContext(); expect((context as any).status).toBe('ok'); }); test('should have failure status extracted from tags', () => { - const span = new Span({}); + const span = new SentrySpan({}); span.setStatus('resource_exhausted'); const context = span.getTraceContext(); expect((context as any).status).toBe('resource_exhausted'); }); test('should drop all `undefined` values', () => { - const spanB = new Span({ spanId: 'd', traceId: 'c' }); + const spanB = new SentrySpan({ spanId: 'd', traceId: 'c' }); const context = spanB.getTraceContext(); expect(context).toStrictEqual({ span_id: 'd', @@ -523,7 +524,7 @@ describe('Span', () => { description: 'test', op: 'op', }; - const span = new Span(originalContext); + const span = new SentrySpan(originalContext); const newContext = span.toContext(); @@ -551,7 +552,7 @@ describe('Span', () => { tag0: 'hello', }, }; - const span = new Span(originalContext); + const span = new SentrySpan(originalContext); span.updateWithContext({ traceId: 'c', @@ -577,7 +578,7 @@ describe('Span', () => { tags: { tag0: 'hello' }, data: { data0: 'foo' }, }; - const span = new Span(originalContext); + const span = new SentrySpan(originalContext); const newContext = { ...span.toContext(), From 9c21df49f6bb175bd599a183142c310ed0bba693 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 14:54:59 +0100 Subject: [PATCH 085/173] feat(node-experimental): Properly set request & session on http requests (#10676) This ensures that for incoming http requests in node-experimental, we automatically set the request & the session on the isolation scope. --- packages/node-experimental/src/integrations/http.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index ca4f69f6fe86..bc01d3dad071 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -83,7 +83,10 @@ const _httpIntegration = ((options: HttpOptions = {}) => { // Update the isolation scope, isolate this request if (getSpanKind(span) === SpanKind.SERVER) { - setIsolationScope(getIsolationScope().clone()); + const isolationScope = getIsolationScope().clone(); + isolationScope.setSDKProcessingMetadata({ request: req }); + isolationScope.setRequestSession({ status: 'ok' }); + setIsolationScope(isolationScope); } }, responseHook: (span, res) => { From 22ebb3bd5788afad35dcdc521d2d41f077e5588f Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 15:07:21 +0100 Subject: [PATCH 086/173] ref(core): Skip hub in top level `captureXXX` methods (#10688) Directly call these on `getCurrentScope()` instead. --- packages/core/src/exports.ts | 9 +++---- .../node/test/onunhandledrejection.test.ts | 25 ++++++++----------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index eb6fe37a88ec..12744baffb7a 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -41,8 +41,7 @@ export function captureException( exception: any, hint?: ExclusiveEventHintOrCaptureContext, ): string { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().captureException(exception, parseEventHintOrCaptureContext(hint)); + return getCurrentScope().captureException(exception, parseEventHintOrCaptureContext(hint)); } /** @@ -57,8 +56,7 @@ export function captureMessage(message: string, captureContext?: CaptureContext // arity of the `captureMessage(message, level)` method. const level = typeof captureContext === 'string' ? captureContext : undefined; const context = typeof captureContext !== 'string' ? { captureContext } : undefined; - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().captureMessage(message, level, context); + return getCurrentScope().captureMessage(message, level, context); } /** @@ -69,8 +67,7 @@ export function captureMessage(message: string, captureContext?: CaptureContext * @returns the id of the captured event. */ export function captureEvent(event: Event, hint?: EventHint): string { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().captureEvent(event, hint); + return getCurrentScope().captureEvent(event, hint); } /** diff --git a/packages/node/test/onunhandledrejection.test.ts b/packages/node/test/onunhandledrejection.test.ts index b62da7c02fe0..f20e7bd0274d 100644 --- a/packages/node/test/onunhandledrejection.test.ts +++ b/packages/node/test/onunhandledrejection.test.ts @@ -1,5 +1,5 @@ -import { Hub } from '@sentry/core'; -import type { NodeClient } from '../src/client'; +import * as SentryCore from '@sentry/core'; +import type { Client } from '@sentry/types'; import { makeUnhandledPromiseHandler, onUnhandledRejectionIntegration } from '../src/integrations/onunhandledrejection'; @@ -7,25 +7,20 @@ import { makeUnhandledPromiseHandler, onUnhandledRejectionIntegration } from '.. global.console.warn = () => null; global.console.error = () => null; -const client = { getOptions: () => ({}) } as unknown as NodeClient; - -jest.mock('@sentry/core', () => { - // we just want to short-circuit it, so dont worry about types - const original = jest.requireActual('@sentry/core'); - return { - ...original, - getClient: () => client, - }; -}); - describe('unhandled promises', () => { test('install global listener', () => { + const client = { getOptions: () => ({}) } as unknown as Client; + SentryCore.setCurrentClient(client); + const integration = onUnhandledRejectionIntegration(); integration.setup!(client); expect(process.listeners('unhandledRejection')).toHaveLength(1); }); test('makeUnhandledPromiseHandler', () => { + const client = { getOptions: () => ({}) } as unknown as Client; + SentryCore.setCurrentClient(client); + const promise = { domain: { sentryContext: { @@ -36,7 +31,7 @@ describe('unhandled promises', () => { }, }; - const captureException = jest.spyOn(Hub.prototype, 'captureException'); + const captureException = jest.spyOn(SentryCore, 'captureException').mockImplementation(() => 'test'); const handler = makeUnhandledPromiseHandler(client, { mode: 'warn', @@ -44,7 +39,7 @@ describe('unhandled promises', () => { handler('bla', promise); - expect(captureException.mock.calls[0][1]).toEqual({ + expect(captureException).toHaveBeenCalledWith('bla', { originalException: { domain: { sentryContext: { From 3b1d83653516476cae34d6c58938a9835331b2f9 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Feb 2024 15:22:15 +0100 Subject: [PATCH 087/173] feat(opentelemetry): Merge node-experimental changes into opentelemetry (#10689) This PR moves the formerly experimental stuff that we did in node-experimental into the opentelemetry package. This means that the generic OTEL stuff is there and can in theory also be used with otel-browser (if a user would want that), and our node package will eventually be built on top of that. Mostly, this means we can delete a bunch of code! --- packages/node-experimental/src/index.ts | 13 +- .../src/otel/asyncContextStrategy.ts | 116 ------ .../src/otel/contextManager.ts | 59 +--- packages/node-experimental/src/sdk/api.ts | 39 +- packages/node-experimental/src/sdk/hub.ts | 3 +- packages/node-experimental/src/sdk/init.ts | 2 +- .../node-experimental/src/sdk/initOtel.ts | 5 +- packages/node-experimental/src/sdk/scope.ts | 2 +- .../src/sdk/spanProcessor.ts | 6 - .../src/utils/contextData.ts | 43 --- .../opentelemetry/src/asyncContextStrategy.ts | 64 ++-- packages/opentelemetry/src/constants.ts | 2 + packages/opentelemetry/src/contextManager.ts | 80 ++--- .../opentelemetry/src/custom/getCurrentHub.ts | 134 +++++++ packages/opentelemetry/src/index.ts | 13 +- packages/opentelemetry/src/sampler.ts | 1 - packages/opentelemetry/src/trace.ts | 16 +- packages/opentelemetry/src/types.ts | 5 + .../opentelemetry/src/utils/contextData.ts | 38 +- .../test/asyncContextStrategy.test.ts | 334 +++++++++++++----- .../opentelemetry/test/helpers/TestClient.ts | 10 +- .../opentelemetry/test/helpers/mockSdkInit.ts | 2 +- .../test/integration/transactions.test.ts | 2 +- 23 files changed, 539 insertions(+), 450 deletions(-) delete mode 100644 packages/node-experimental/src/otel/asyncContextStrategy.ts delete mode 100644 packages/node-experimental/src/sdk/spanProcessor.ts delete mode 100644 packages/node-experimental/src/utils/contextData.ts create mode 100644 packages/opentelemetry/src/custom/getCurrentHub.ts diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 2473b3d9aa64..22e29a146df2 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -16,14 +16,8 @@ export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanc export * as Handlers from './sdk/handlers'; export type { Span } from './types'; -export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan } from '@sentry/opentelemetry'; -export { - getClient, - captureException, - captureEvent, - captureMessage, - withActiveSpan, -} from './sdk/api'; +export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; +export { getClient } from './sdk/api'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; @@ -86,6 +80,9 @@ export { getIsolationScope, withScope, withIsolationScope, + captureException, + captureEvent, + captureMessage, } from '@sentry/node'; export type { diff --git a/packages/node-experimental/src/otel/asyncContextStrategy.ts b/packages/node-experimental/src/otel/asyncContextStrategy.ts deleted file mode 100644 index 477f4a7c7e83..000000000000 --- a/packages/node-experimental/src/otel/asyncContextStrategy.ts +++ /dev/null @@ -1,116 +0,0 @@ -import * as api from '@opentelemetry/api'; -import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core'; -import type { Hub, Scope } from '@sentry/types'; - -import { getCurrentHub as _getCurrentHub } from './../sdk/hub'; -import type { CurrentScopes } from './../sdk/types'; -import { - SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, - SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, - SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, - getScopesFromContext, -} from './../utils/contextData'; - -/** - * Sets the async context strategy to use follow the OTEL context under the hood. - * We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts) - */ -export function setOpenTelemetryContextAsyncContextStrategy(): void { - function getScopes(): CurrentScopes { - const ctx = api.context.active(); - const scopes = getScopesFromContext(ctx); - - if (scopes) { - return scopes; - } - - // fallback behavior: - // if, for whatever reason, we can't find scopes on the context here, we have to fix this somehow - return { - scope: getDefaultCurrentScope(), - isolationScope: getDefaultIsolationScope(), - }; - } - - function getCurrentHub(): Hub { - // eslint-disable-next-line deprecation/deprecation - const hub = _getCurrentHub(); - return { - ...hub, - getScope: () => { - const scopes = getScopes(); - return scopes.scope; - }, - getIsolationScope: () => { - const scopes = getScopes(); - return scopes.isolationScope; - }, - }; - } - - function withScope(callback: (scope: Scope) => T): T { - const ctx = api.context.active(); - - // We depend on the otelContextManager to handle the context/hub - // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by - // the OTEL context manager, which uses the presence of this key to determine if it should - // fork the isolation scope, or not - // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` - return api.context.with(ctx, () => { - return callback(getCurrentScope()); - }); - } - - function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { - const ctx = api.context.active(); - - // We depend on the otelContextManager to handle the context/hub - // We set the `SENTRY_FORK_SET_SCOPE_CONTEXT_KEY` context value, which is picked up by - // the OTEL context manager, which picks up this scope as the current scope - return api.context.with(ctx.setValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, scope), () => { - return callback(scope); - }); - } - - function withIsolationScope(callback: (isolationScope: Scope) => T): T { - const ctx = api.context.active(); - - // We depend on the otelContextManager to handle the context/hub - // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by - // the OTEL context manager, which uses the presence of this key to determine if it should - // fork the isolation scope, or not - return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { - return callback(getIsolationScope()); - }); - } - - function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { - const ctx = api.context.active(); - - // We depend on the otelContextManager to handle the context/hub - // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by - // the OTEL context manager, which uses the presence of this key to determine if it should - // fork the isolation scope, or not - return api.context.with(ctx.setValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, isolationScope), () => { - return callback(getIsolationScope()); - }); - } - - function getCurrentScope(): Scope { - return getScopes().scope; - } - - function getIsolationScope(): Scope { - return getScopes().isolationScope; - } - - setAsyncContextStrategy({ - getCurrentHub, - withScope, - withSetScope, - withSetIsolationScope, - withIsolationScope, - getCurrentScope, - getIsolationScope, - }); -} diff --git a/packages/node-experimental/src/otel/contextManager.ts b/packages/node-experimental/src/otel/contextManager.ts index 82a3a51b344b..e0e7da218326 100644 --- a/packages/node-experimental/src/otel/contextManager.ts +++ b/packages/node-experimental/src/otel/contextManager.ts @@ -1,18 +1,5 @@ -import type { Context } from '@opentelemetry/api'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; -import { getCurrentScope, getIsolationScope } from '@sentry/core'; -import { setHubOnContext } from '@sentry/opentelemetry'; -import type { Scope } from '@sentry/types'; -import { getCurrentHub } from '../sdk/hub'; - -import type { CurrentScopes } from './../sdk/types'; -import { - SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, - SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, - SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, - getScopesFromContext, - setScopesOnContext, -} from './../utils/contextData'; +import { wrapContextManagerClass } from '@sentry/opentelemetry'; /** * This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager. @@ -21,46 +8,4 @@ import { * Note that we currently only support AsyncHooks with this, * but since this should work for Node 14+ anyhow that should be good enough. */ -export class SentryContextManager extends AsyncLocalStorageContextManager { - /** - * Overwrite with() of the original AsyncLocalStorageContextManager - * to ensure we also create a new hub per context. - */ - public with ReturnType>( - context: Context, - fn: F, - thisArg?: ThisParameterType, - ...args: A - ): ReturnType { - const currentScopes = getScopesFromContext(context); - const currentScope = currentScopes?.scope || getCurrentScope(); - const currentIsolationScope = currentScopes?.isolationScope || getIsolationScope(); - - const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true; - const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) as Scope | undefined; - const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) as Scope | undefined; - - const newCurrentScope = scope || currentScope.clone(); - const newIsolationScope = - isolationScope || (shouldForkIsolationScope ? currentIsolationScope.clone() : currentIsolationScope); - const scopes: CurrentScopes = { scope: newCurrentScope, isolationScope: newIsolationScope }; - - const mockHub = { - // eslint-disable-next-line deprecation/deprecation - ...getCurrentHub(), - getScope: () => newCurrentScope, - getIsolationScope: () => newIsolationScope, - }; - - const ctx1 = setHubOnContext(context, mockHub); - const ctx2 = setScopesOnContext(ctx1, scopes); - - // Remove the unneeded values again - const ctx3 = ctx2 - .deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) - .deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) - .deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY); - - return super.with(ctx3, fn, thisArg, ...args); - } -} +export const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager); diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index 5e877360cc43..0f0a4de321aa 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -1,12 +1,7 @@ // PUBLIC APIS -import type { Span } from '@opentelemetry/api'; -import { context, trace } from '@opentelemetry/api'; import { getCurrentScope } from '@sentry/core'; -import type { CaptureContext, Client, Event, EventHint, Scope, SeverityLevel } from '@sentry/types'; - -import type { ExclusiveEventHintOrCaptureContext } from '../utils/prepareEvent'; -import { parseEventHintOrCaptureContext } from '../utils/prepareEvent'; +import type { Client } from '@sentry/types'; /** Get the currently active client. */ export function getClient(): C { @@ -20,35 +15,3 @@ export function getClient(): C { // TODO otherwise ensure we use a noop client return {} as C; } - -/** - * Forks the current scope and sets the provided span as active span in the context of the provided callback. - * - * @param span Spans started in the context of the provided callback will be children of this span. - * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. - * @returns the value returned from the provided callback function. - */ -export function withActiveSpan(span: Span, callback: (scope: Scope) => T): T { - const newContextWithActiveSpan = trace.setSpan(context.active(), span); - return context.with(newContextWithActiveSpan, () => callback(getCurrentScope())); -} - -/** Record an exception and send it to Sentry. */ -export function captureException(exception: unknown, hint?: ExclusiveEventHintOrCaptureContext): string { - return getCurrentScope().captureException(exception, parseEventHintOrCaptureContext(hint)); -} - -/** Record a message and send it to Sentry. */ -export function captureMessage(message: string, captureContext?: CaptureContext | SeverityLevel): string { - // This is necessary to provide explicit scopes upgrade, without changing the original - // arity of the `captureMessage(message, level)` method. - const level = typeof captureContext === 'string' ? captureContext : undefined; - const context = typeof captureContext !== 'string' ? { captureContext } : undefined; - - return getCurrentScope().captureMessage(message, level, context); -} - -/** Capture a generic event and send it to Sentry. */ -export function captureEvent(event: Event, hint?: EventHint): string { - return getCurrentScope().captureEvent(event, hint); -} diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 420026fc180e..ef652ba01fbf 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -12,6 +12,7 @@ import type { import { addBreadcrumb, + captureEvent, endSession, getCurrentScope, getIsolationScope, @@ -24,7 +25,7 @@ import { startSession, withScope, } from '@sentry/core'; -import { captureEvent, getClient } from './api'; +import { getClient } from './api'; import { callExtensionMethod } from './globals'; /** diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 11c98b72c89b..c190c934776b 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -13,6 +13,7 @@ import { makeNodeTransport, spotlightIntegration, } from '@sentry/node'; +import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import type { Client, Integration, Options } from '@sentry/types'; import { consoleSandbox, @@ -26,7 +27,6 @@ import { DEBUG_BUILD } from '../debug-build'; import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations'; import { httpIntegration } from '../integrations/http'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; -import { setOpenTelemetryContextAsyncContextStrategy } from '../otel/asyncContextStrategy'; import type { NodeExperimentalClientOptions, NodeExperimentalOptions } from '../types'; import { NodeExperimentalClient } from './client'; import { initOtel } from './initOtel'; diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts index 1a078a1c013a..1303b59483ff 100644 --- a/packages/node-experimental/src/sdk/initOtel.ts +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -3,14 +3,13 @@ import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { SDK_VERSION } from '@sentry/core'; -import { SentryPropagator, SentrySampler, setupEventContextTrace } from '@sentry/opentelemetry'; +import { SentryPropagator, SentrySampler, SentrySpanProcessor, setupEventContextTrace } from '@sentry/opentelemetry'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { SentryContextManager } from '../otel/contextManager'; import type { NodeExperimentalClient } from '../types'; import { getClient } from './api'; -import { NodeExperimentalSentrySpanProcessor } from './spanProcessor'; /** * Initialize OpenTelemetry for Node. @@ -55,7 +54,7 @@ export function setupOtel(client: NodeExperimentalClient): BasicTracerProvider { }), forceFlushTimeoutMillis: 500, }); - provider.addSpanProcessor(new NodeExperimentalSentrySpanProcessor()); + provider.addSpanProcessor(new SentrySpanProcessor()); // Initialize the provider provider.register({ diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node-experimental/src/sdk/scope.ts index 3ee4ce16f3a4..0633c1f57f43 100644 --- a/packages/node-experimental/src/sdk/scope.ts +++ b/packages/node-experimental/src/sdk/scope.ts @@ -1,6 +1,6 @@ import { context } from '@opentelemetry/api'; +import { getScopesFromContext } from '@sentry/opentelemetry'; import type { Scope } from '@sentry/types'; -import { getScopesFromContext } from '../utils/contextData'; /** * Update the active isolation scope. diff --git a/packages/node-experimental/src/sdk/spanProcessor.ts b/packages/node-experimental/src/sdk/spanProcessor.ts deleted file mode 100644 index c7254ac23808..000000000000 --- a/packages/node-experimental/src/sdk/spanProcessor.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SentrySpanProcessor } from '@sentry/opentelemetry'; - -/** - * Nothing custom here anymore! We can remove this eventually... - */ -export class NodeExperimentalSentrySpanProcessor extends SentrySpanProcessor {} diff --git a/packages/node-experimental/src/utils/contextData.ts b/packages/node-experimental/src/utils/contextData.ts deleted file mode 100644 index 2309e57ea478..000000000000 --- a/packages/node-experimental/src/utils/contextData.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Context } from '@opentelemetry/api'; -import { createContextKey } from '@opentelemetry/api'; -import type { Scope } from '@sentry/types'; - -import type { CurrentScopes } from '../sdk/types'; - -export const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes'); - -export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope'); - -export const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope'); - -export const SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_isolation_scope'); - -const SCOPE_CONTEXT_MAP = new WeakMap(); - -/** - * Try to get the current scopes from the given OTEL context. - * This requires a Context Manager that was wrapped with getWrappedContextManager. - */ -export function getScopesFromContext(context: Context): CurrentScopes | undefined { - return context.getValue(SENTRY_SCOPES_CONTEXT_KEY) as CurrentScopes | undefined; -} - -/** - * Set the current scopes on an OTEL context. - * This will return a forked context with the Propagation Context set. - */ -export function setScopesOnContext(context: Context, scopes: CurrentScopes): Context { - // So we can look up the context from the scope later - SCOPE_CONTEXT_MAP.set(scopes.scope, context); - SCOPE_CONTEXT_MAP.set(scopes.isolationScope, context); - - return context.setValue(SENTRY_SCOPES_CONTEXT_KEY, scopes); -} - -/** - * Get the context related to a scope. - * TODO v8: Use this for the `trace` functions. - * */ -export function getContextFromScope(scope: Scope): Context | undefined { - return SCOPE_CONTEXT_MAP.get(scope); -} diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index 230bd1e21210..397bd55f2685 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -1,30 +1,51 @@ import * as api from '@opentelemetry/api'; -import { getGlobalHub } from '@sentry/core'; -import { setAsyncContextStrategy } from '@sentry/core'; +import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core'; import type { Hub, Scope } from '@sentry/types'; + import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, } from './constants'; - -import { getHubFromContext } from './utils/contextData'; +import { getCurrentHub as _getCurrentHub } from './custom/getCurrentHub'; +import type { CurrentScopes } from './types'; +import { getScopesFromContext } from './utils/contextData'; /** * Sets the async context strategy to use follow the OTEL context under the hood. * We handle forking a hub inside of our custom OTEL Context Manager (./otelContextManager.ts) */ export function setOpenTelemetryContextAsyncContextStrategy(): void { - function getCurrentFromContext(): Hub | undefined { + function getScopes(): CurrentScopes { const ctx = api.context.active(); + const scopes = getScopesFromContext(ctx); + + if (scopes) { + return scopes; + } - // Returning undefined means the global hub will be used - // Need to cast from @sentry/type's `Hub` to @sentry/core's `Hub` - return getHubFromContext(ctx) as Hub | undefined; + // fallback behavior: + // if, for whatever reason, we can't find scopes on the context here, we have to fix this somehow + return { + scope: getDefaultCurrentScope(), + isolationScope: getDefaultIsolationScope(), + }; } function getCurrentHub(): Hub { - return getCurrentFromContext() || getGlobalHub(); + // eslint-disable-next-line deprecation/deprecation + const hub = _getCurrentHub(); + return { + ...hub, + getScope: () => { + const scopes = getScopes(); + return scopes.scope; + }, + getIsolationScope: () => { + const scopes = getScopes(); + return scopes.isolationScope; + }, + }; } function withScope(callback: (scope: Scope) => T): T { @@ -36,8 +57,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { // fork the isolation scope, or not // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` return api.context.with(ctx, () => { - // eslint-disable-next-line deprecation/deprecation - return callback(getCurrentHub().getScope()); + return callback(getCurrentScope()); }); } @@ -60,8 +80,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { // the OTEL context manager, which uses the presence of this key to determine if it should // fork the isolation scope, or not return api.context.with(ctx.setValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, true), () => { - // eslint-disable-next-line deprecation/deprecation - return callback(getCurrentHub().getIsolationScope()); + return callback(getIsolationScope()); }); } @@ -73,20 +92,25 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { // the OTEL context manager, which uses the presence of this key to determine if it should // fork the isolation scope, or not return api.context.with(ctx.setValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, isolationScope), () => { - // eslint-disable-next-line deprecation/deprecation - return callback(getCurrentHub().getIsolationScope()); + return callback(getIsolationScope()); }); } + function getCurrentScope(): Scope { + return getScopes().scope; + } + + function getIsolationScope(): Scope { + return getScopes().isolationScope; + } + setAsyncContextStrategy({ getCurrentHub, withScope, withSetScope, - withIsolationScope, withSetIsolationScope, - // eslint-disable-next-line deprecation/deprecation - getCurrentScope: () => getCurrentHub().getScope(), - // eslint-disable-next-line deprecation/deprecation - getIsolationScope: () => getCurrentHub().getIsolationScope(), + withIsolationScope, + getCurrentScope, + getIsolationScope, }); } diff --git a/packages/opentelemetry/src/constants.ts b/packages/opentelemetry/src/constants.ts index 03beb221ccf0..4bb29782978b 100644 --- a/packages/opentelemetry/src/constants.ts +++ b/packages/opentelemetry/src/constants.ts @@ -9,6 +9,8 @@ export const SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY = createContextKey('SENTRY_P /** Context Key to hold a Hub. */ export const SENTRY_HUB_CONTEXT_KEY = createContextKey('sentry_hub'); +export const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes'); + export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_isolation_scope'); export const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope'); diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index c840fa8887d4..aed172533eea 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -1,41 +1,14 @@ import type { Context, ContextManager } from '@opentelemetry/api'; -import { Hub } from '@sentry/core'; -import { getCurrentHub } from '@sentry/core'; -import type { Hub as HubInterface, Scope } from '@sentry/types'; +import { getCurrentScope, getIsolationScope } from '@sentry/core'; +import type { Scope } from '@sentry/types'; + import { SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, } from './constants'; - -import { setHubOnContext } from './utils/contextData'; - -function createNewHub( - parent: HubInterface | undefined, - scope: Scope | undefined, - isolationScope: Scope | undefined, - shouldForkIsolationScope: boolean, -): Hub { - if (parent) { - // eslint-disable-next-line deprecation/deprecation - const client = parent.getClient(); - // eslint-disable-next-line deprecation/deprecation - const currentScope = scope || parent.getScope().clone(); - const currentIsolationScope = - // eslint-disable-next-line deprecation/deprecation - isolationScope || (shouldForkIsolationScope ? parent.getIsolationScope().clone() : parent.getIsolationScope()); - - // eslint-disable-next-line deprecation/deprecation - return new Hub(client, currentScope, currentIsolationScope); - } - - // eslint-disable-next-line deprecation/deprecation - return new Hub(); -} - -// Typescript complains if we do not use `...args: any[]` for the mixin, with: -// A mixin class must have a constructor with a single rest parameter of type 'any[]'.ts(2545) -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { getCurrentHub } from './custom/getCurrentHub'; +import { getScopesFromContext, setHubOnContext, setScopesOnContext } from './utils/contextData'; /** * Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub. @@ -46,7 +19,7 @@ function createNewHub( * const contextManager = new SentryContextManager(); */ export function wrapContextManagerClass( - ContextManagerClass: new (...args: any[]) => ContextManagerInstance, + ContextManagerClass: new (...args: unknown[]) => ContextManagerInstance, ): typeof ContextManagerClass { /** * This is a custom ContextManager for OpenTelemetry, which extends the default AsyncLocalStorageContextManager. @@ -68,29 +41,38 @@ export function wrapContextManagerClass, ...args: A ): ReturnType { + const currentScopes = getScopesFromContext(context); + const currentScope = currentScopes?.scope || getCurrentScope(); + const currentIsolationScope = currentScopes?.isolationScope || getIsolationScope(); + const shouldForkIsolationScope = context.getValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) === true; const scope = context.getValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) as Scope | undefined; const isolationScope = context.getValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY) as Scope | undefined; - // eslint-disable-next-line deprecation/deprecation - const existingHub = getCurrentHub(); - const newHub = createNewHub(existingHub, scope, isolationScope, shouldForkIsolationScope); + const newCurrentScope = scope || currentScope.clone(); + const newIsolationScope = + isolationScope || (shouldForkIsolationScope ? currentIsolationScope.clone() : currentIsolationScope); + const scopes = { scope: newCurrentScope, isolationScope: newIsolationScope }; + + const mockHub = { + // eslint-disable-next-line deprecation/deprecation + ...getCurrentHub(), + getScope: () => newCurrentScope, + getIsolationScope: () => newIsolationScope, + }; + + const ctx1 = setHubOnContext(context, mockHub); + const ctx2 = setScopesOnContext(ctx1, scopes); + + // Remove the unneeded values again + const ctx3 = ctx2 + .deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) + .deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY); - return super.with( - setHubOnContext( - context - .deleteValue(SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY) - .deleteValue(SENTRY_FORK_SET_SCOPE_CONTEXT_KEY) - .deleteValue(SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY), - newHub, - ), - fn, - thisArg, - ...args, - ); + return super.with(ctx3, fn, thisArg, ...args); } } return SentryContextManager as unknown as typeof ContextManagerClass; } -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/packages/opentelemetry/src/custom/getCurrentHub.ts b/packages/opentelemetry/src/custom/getCurrentHub.ts new file mode 100644 index 000000000000..3e4f6639d188 --- /dev/null +++ b/packages/opentelemetry/src/custom/getCurrentHub.ts @@ -0,0 +1,134 @@ +import type { + Client, + CustomSamplingContext, + EventHint, + Hub, + Integration, + IntegrationClass, + Scope, + SeverityLevel, + TransactionContext, +} from '@sentry/types'; + +import { + addBreadcrumb, + captureEvent, + endSession, + getClient, + getCurrentScope, + getIsolationScope, + setContext, + setExtra, + setExtras, + setTag, + setTags, + setUser, + startSession, + withScope, +} from '@sentry/core'; + +/** + * This is for legacy reasons, and returns a proxy object instead of a hub to be used. + * @deprecated Use the methods directly. + */ +export function getCurrentHub(): Hub { + return { + isOlderThan(_version: number): boolean { + return false; + }, + + bindClient(client: Client): void { + const scope = getCurrentScope(); + scope.setClient(client); + }, + + pushScope(): Scope { + // TODO: This does not work and is actually deprecated + return getCurrentScope(); + }, + + popScope(): boolean { + // TODO: This does not work and is actually deprecated + return false; + }, + + withScope, + getClient: () => getClient() as C | undefined, + getScope: getCurrentScope, + getIsolationScope, + captureException: (exception: unknown, hint?: EventHint) => { + return getCurrentScope().captureException(exception, hint); + }, + captureMessage: (message: string, level?: SeverityLevel, hint?: EventHint) => { + return getCurrentScope().captureMessage(message, level, hint); + }, + captureEvent, + addBreadcrumb, + setUser, + setTags, + setTag, + setExtra, + setExtras, + setContext, + + run(callback: (hub: Hub) => void): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return withScope(() => callback(this as any)); + }, + + getIntegration(integration: IntegrationClass): T | null { + // eslint-disable-next-line deprecation/deprecation + return getClient()?.getIntegration(integration) || null; + }, + + traceHeaders(): { [key: string]: string } { + // TODO: Do we need this?? + return {}; + }, + + startTransaction( + _context: TransactionContext, + _customSamplingContext?: CustomSamplingContext, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): any { + // eslint-disable-next-line no-console + console.warn('startTransaction is a noop in @sentry/opentelemetry. Use `startSpan` instead.'); + // We return an object here as hub.ts checks for the result of this + // and renders a different warning if this is empty + return {}; + }, + + startSession, + + endSession, + + captureSession(endSession?: boolean): void { + // both send the update and pull the session from the scope + if (endSession) { + // eslint-disable-next-line deprecation/deprecation + return this.endSession(); + } + + // only send the update + _sendSessionUpdate(); + }, + + shouldSendDefaultPii(): boolean { + const client = getClient(); + return Boolean(client ? client.getOptions().sendDefaultPii : false); + }, + }; +} + +/** + * Sends the current Session on the scope + */ +function _sendSessionUpdate(): void { + const scope = getCurrentScope(); + const client = getClient(); + + const session = scope.getSession(); + if (client && session) { + client.captureSession(session); + } +} diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index 4c3211ebd23c..f5ffb08199c8 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -14,7 +14,11 @@ export { getSpanScopes, } from './utils/spanData'; -export { getPropagationContextFromContext, setPropagationContextOnContext, setHubOnContext } from './utils/contextData'; +export { + getPropagationContextFromContext, + setPropagationContextOnContext, + getScopesFromContext, +} from './utils/contextData'; export { spanHasAttributes, @@ -28,10 +32,12 @@ export { export { isSentryRequestSpan } from './utils/isSentryRequest'; export { getActiveSpan, getRootSpan } from './utils/getActiveSpan'; -export { startSpan, startSpanManual, startInactiveSpan } from './trace'; +export { startSpan, startSpanManual, startInactiveSpan, withActiveSpan } from './trace'; // eslint-disable-next-line deprecation/deprecation export { setupGlobalHub } from './custom/hub'; +// eslint-disable-next-line deprecation/deprecation +export { getCurrentHub } from './custom/getCurrentHub'; export { addTracingExtensions } from './custom/hubextensions'; export { setupEventContextTrace } from './setupEventContextTrace'; @@ -42,8 +48,7 @@ export { SentrySpanProcessor } from './spanProcessor'; export { SentrySampler } from './sampler'; // Legacy -// eslint-disable-next-line deprecation/deprecation -export { getCurrentHub, getClient } from '@sentry/core'; +export { getClient } from '@sentry/core'; /** * The following internal utils are not considered public API and are subject to change. diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 0cd15ebb2daf..820c35ce371d 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -159,7 +159,6 @@ function getSampleRate( */ function isValidSampleRate(rate: unknown): boolean { // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck - // eslint-disable-next-line @typescript-eslint/no-explicit-any if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) { DEBUG_BUILD && logger.warn( diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index ed53b02a7c17..694f7a274e02 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -2,8 +2,8 @@ import type { Span, Tracer } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; import { SpanStatusCode, trace } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; -import { SDK_VERSION, getClient, handleCallbackErrors } from '@sentry/core'; -import type { Client } from '@sentry/types'; +import { SDK_VERSION, getClient, getCurrentScope, handleCallbackErrors } from '@sentry/core'; +import type { Client, Scope } from '@sentry/types'; import { InternalSentrySemanticAttributes } from './semanticAttributes'; import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; @@ -101,6 +101,18 @@ export function startInactiveSpan(spanContext: OpenTelemetrySpanContext): Span { return span; } +/** + * Forks the current scope and sets the provided span as active span in the context of the provided callback. + * + * @param span Spans started in the context of the provided callback will be children of this span. + * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. + * @returns the value returned from the provided callback function. + */ +export function withActiveSpan(span: Span, callback: (scope: Scope) => T): T { + const newContextWithActiveSpan = trace.setSpan(context.active(), span); + return context.with(newContextWithActiveSpan, () => callback(getCurrentScope())); +} + function getTracer(): Tracer { const client = getClient(); return (client && client.tracer) || trace.getTracer('@sentry/opentelemetry', SDK_VERSION); diff --git a/packages/opentelemetry/src/types.ts b/packages/opentelemetry/src/types.ts index d01d80b64e82..5abbdeeb4f26 100644 --- a/packages/opentelemetry/src/types.ts +++ b/packages/opentelemetry/src/types.ts @@ -34,3 +34,8 @@ export interface OpenTelemetrySpanContext { export type AbstractSpan = WriteableSpan | ReadableSpan; export type { Span }; + +export interface CurrentScopes { + scope: Scope; + isolationScope: Scope; +} diff --git a/packages/opentelemetry/src/utils/contextData.ts b/packages/opentelemetry/src/utils/contextData.ts index 899c55e3678d..fa4dd56db99a 100644 --- a/packages/opentelemetry/src/utils/contextData.ts +++ b/packages/opentelemetry/src/utils/contextData.ts @@ -1,7 +1,14 @@ import type { Context } from '@opentelemetry/api'; -import type { Hub, PropagationContext } from '@sentry/types'; +import type { Hub, PropagationContext, Scope } from '@sentry/types'; -import { SENTRY_HUB_CONTEXT_KEY, SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY } from '../constants'; +import { + SENTRY_HUB_CONTEXT_KEY, + SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, + SENTRY_SCOPES_CONTEXT_KEY, +} from '../constants'; +import type { CurrentScopes } from '../types'; + +const SCOPE_CONTEXT_MAP = new WeakMap(); /** * Try to get the Propagation Context from the given OTEL context. @@ -34,3 +41,30 @@ export function getHubFromContext(context: Context): Hub | undefined { export function setHubOnContext(context: Context, hub: Hub): Context { return context.setValue(SENTRY_HUB_CONTEXT_KEY, hub); } + +/** + * Try to get the current scopes from the given OTEL context. + * This requires a Context Manager that was wrapped with getWrappedContextManager. + */ +export function getScopesFromContext(context: Context): CurrentScopes | undefined { + return context.getValue(SENTRY_SCOPES_CONTEXT_KEY) as CurrentScopes | undefined; +} + +/** + * Set the current scopes on an OTEL context. + * This will return a forked context with the Propagation Context set. + */ +export function setScopesOnContext(context: Context, scopes: CurrentScopes): Context { + // So we can look up the context from the scope later + SCOPE_CONTEXT_MAP.set(scopes.scope, context); + + return context.setValue(SENTRY_SCOPES_CONTEXT_KEY, scopes); +} + +/** + * Get the context related to a scope. + * TODO v8: Use this for the `trace` functions. + * */ +export function getContextFromScope(scope: Scope): Context | undefined { + return SCOPE_CONTEXT_MAP.get(scope); +} diff --git a/packages/opentelemetry/test/asyncContextStrategy.test.ts b/packages/opentelemetry/test/asyncContextStrategy.test.ts index 7bc7363a407e..16eea942655e 100644 --- a/packages/opentelemetry/test/asyncContextStrategy.test.ts +++ b/packages/opentelemetry/test/asyncContextStrategy.test.ts @@ -1,9 +1,13 @@ import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import type { Hub } from '@sentry/core'; -import { withIsolationScope } from '@sentry/core'; -import { getCurrentHub } from '@sentry/core'; -import { setAsyncContextStrategy } from '@sentry/core'; +import { + getCurrentScope, + getIsolationScope, + setAsyncContextStrategy, + withIsolationScope, + withScope, +} from '@sentry/core'; +import type { Scope } from '@sentry/types'; import { setOpenTelemetryContextAsyncContextStrategy } from '../src/asyncContextStrategy'; import { TestClient, getDefaultTestClientOptions } from './helpers/TestClient'; import { setupOtel } from './helpers/initOtel'; @@ -13,6 +17,9 @@ describe('asyncContextStrategy', () => { let provider: BasicTracerProvider | undefined; beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + const options = getDefaultTestClientOptions(); const client = new TestClient(options); provider = setupOtel(client); @@ -28,123 +35,266 @@ describe('asyncContextStrategy', () => { setAsyncContextStrategy(undefined); }); - test('hub scope inheritance', () => { - // eslint-disable-next-line deprecation/deprecation - const globalHub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - globalHub.setExtra('a', 'b'); + test('scope inheritance', () => { + const initialScope = getCurrentScope(); + const initialIsolationScope = getIsolationScope(); + + initialScope.setExtra('a', 'a'); + initialIsolationScope.setExtra('aa', 'aa'); withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub1 = getCurrentHub(); - expect(hub1).toEqual(globalHub); - - // eslint-disable-next-line deprecation/deprecation - hub1.setExtra('c', 'd'); - expect(hub1).not.toEqual(globalHub); - - withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub2 = getCurrentHub(); - expect(hub2).toEqual(hub1); - expect(hub2).not.toEqual(globalHub); - - // eslint-disable-next-line deprecation/deprecation - hub2.setExtra('e', 'f'); - expect(hub2).not.toEqual(hub1); + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + scope1.setExtra('b', 'b'); + isolationScope1.setExtra('bb', 'bb'); + + withScope(() => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); + + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); + + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); + + scope2.setExtra('c', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b: 'b', + c: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb: 'bb', + }); }); }); }); - test('async hub scope inheritance', async () => { - async function addRandomExtra(hub: Hub, key: string): Promise { - return new Promise(resolve => { - setTimeout(() => { - // eslint-disable-next-line deprecation/deprecation - hub.setExtra(key, Math.random()); - resolve(); - }, 100); - }); + test('async scope inheritance', async () => { + const initialScope = getCurrentScope(); + const initialIsolationScope = getIsolationScope(); + + async function asycnSetExtra(scope: Scope, key: string, value: string): Promise { + await new Promise(resolve => setTimeout(resolve, 1)); + scope.setExtra(key, value); } - // eslint-disable-next-line deprecation/deprecation - const globalHub = getCurrentHub() as Hub; - await addRandomExtra(globalHub, 'a'); + initialScope.setExtra('a', 'a'); + initialIsolationScope.setExtra('aa', 'aa'); await withIsolationScope(async () => { - // eslint-disable-next-line deprecation/deprecation - const hub1 = getCurrentHub() as Hub; - expect(hub1).toEqual(globalHub); + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + await asycnSetExtra(scope1, 'b', 'b'); + await asycnSetExtra(isolationScope1, 'bb', 'bb'); + + await withScope(async () => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); - await addRandomExtra(hub1, 'b'); - expect(hub1).not.toEqual(globalHub); + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); - await withIsolationScope(async () => { - // eslint-disable-next-line deprecation/deprecation - const hub2 = getCurrentHub(); - expect(hub2).toEqual(hub1); - expect(hub2).not.toEqual(globalHub); + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); - await addRandomExtra(hub1, 'c'); - expect(hub2).not.toEqual(hub1); + await asycnSetExtra(scope2, 'c', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b: 'b', + c: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb: 'bb', + }); }); }); }); - test('context single instance', () => { - // eslint-disable-next-line deprecation/deprecation - const globalHub = getCurrentHub(); + test('concurrent scope contexts', () => { + const initialScope = getCurrentScope(); + const initialIsolationScope = getIsolationScope(); + + initialScope.setExtra('a', 'a'); + initialIsolationScope.setExtra('aa', 'aa'); + withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - expect(globalHub).not.toBe(getCurrentHub()); + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + scope1.setExtra('b', 'b'); + isolationScope1.setExtra('bb', 'bb'); + + withScope(() => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); + + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); + + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); + + scope2.setExtra('c', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b: 'b', + c: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb: 'bb', + }); + }); }); - }); - test('context within a context not reused', () => { withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub1 = getCurrentHub(); - withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub2 = getCurrentHub(); - expect(hub1).not.toBe(hub2); + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + scope1.setExtra('b2', 'b'); + isolationScope1.setExtra('bb2', 'bb'); + + withScope(() => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); + + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); + + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); + + scope2.setExtra('c2', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b2: 'b', + c2: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb2: 'bb', + }); }); }); }); - test('concurrent hub contexts', done => { - let d1done = false; - let d2done = false; + test('concurrent async scope contexts', async () => { + const initialScope = getCurrentScope(); + const initialIsolationScope = getIsolationScope(); - withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation - hub.getStack().push({ client: 'process' } as any); - // eslint-disable-next-line deprecation/deprecation - expect(hub.getStack()[1]).toEqual({ client: 'process' }); - // Just in case so we don't have to worry which one finishes first - // (although it always should be d2) - setTimeout(() => { - d1done = true; - if (d2done) { - done(); - } + async function asycnSetExtra(scope: Scope, key: string, value: string): Promise { + await new Promise(resolve => setTimeout(resolve, 1)); + scope.setExtra(key, value); + } + + initialScope.setExtra('a', 'a'); + initialIsolationScope.setExtra('aa', 'aa'); + + await withIsolationScope(async () => { + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + await asycnSetExtra(scope1, 'b', 'b'); + await asycnSetExtra(isolationScope1, 'bb', 'bb'); + + await withScope(async () => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); + + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); + + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); + + await asycnSetExtra(scope2, 'c', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b: 'b', + c: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb: 'bb', + }); }); }); - withIsolationScope(() => { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub() as Hub; - // eslint-disable-next-line deprecation/deprecation - hub.getStack().push({ client: 'local' } as any); - // eslint-disable-next-line deprecation/deprecation - expect(hub.getStack()[1]).toEqual({ client: 'local' }); - setTimeout(() => { - d2done = true; - if (d1done) { - done(); - } + await withIsolationScope(async () => { + const scope1 = getCurrentScope(); + const isolationScope1 = getIsolationScope(); + + expect(scope1).not.toBe(initialScope); + expect(isolationScope1).not.toBe(initialIsolationScope); + + expect(scope1.getScopeData()).toEqual(initialScope.getScopeData()); + expect(isolationScope1.getScopeData()).toEqual(initialIsolationScope.getScopeData()); + + scope1.setExtra('b2', 'b'); + isolationScope1.setExtra('bb2', 'bb'); + + await withScope(async () => { + const scope2 = getCurrentScope(); + const isolationScope2 = getIsolationScope(); + + expect(scope2).not.toBe(scope1); + expect(isolationScope2).toBe(isolationScope1); + + expect(scope2.getScopeData()).toEqual(scope1.getScopeData()); + + scope2.setExtra('c2', 'c'); + + expect(scope2.getScopeData().extra).toEqual({ + a: 'a', + b2: 'b', + c2: 'c', + }); + + expect(isolationScope2.getScopeData().extra).toEqual({ + aa: 'aa', + bb2: 'bb', + }); }); }); }); diff --git a/packages/opentelemetry/test/helpers/TestClient.ts b/packages/opentelemetry/test/helpers/TestClient.ts index d2ed75175b8e..d284df690fbc 100644 --- a/packages/opentelemetry/test/helpers/TestClient.ts +++ b/packages/opentelemetry/test/helpers/TestClient.ts @@ -1,4 +1,4 @@ -import { BaseClient, createTransport, initAndBind } from '@sentry/core'; +import { BaseClient, createTransport, getCurrentScope } from '@sentry/core'; import type { Client, ClientOptions, Event, Options, SeverityLevel } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; @@ -15,10 +15,8 @@ class BaseTestClient extends BaseClient { exception: { values: [ { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ type: exception.name, value: exception.message, - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ }, ], }, @@ -35,7 +33,11 @@ export const TestClient = wrapClientClass(BaseTestClient); export type TestClientInterface = Client & OpenTelemetryClient; export function init(options: Partial = {}): void { - initAndBind(TestClient, getDefaultTestClientOptions(options)); + const client = new TestClient(getDefaultTestClientOptions(options)); + + // The client is on the current scope, from where it generally is inherited + getCurrentScope().setClient(client); + client.init(); } export function getDefaultTestClientOptions(options: Partial = {}): ClientOptions { diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index 8a6758775b27..f77c37ce4034 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -18,9 +18,9 @@ function init(options: Partial | undefined = {}): void { ...options, }; + setOpenTelemetryContextAsyncContextStrategy(); initTestClient(fullOptions); initOtel(); - setOpenTelemetryContextAsyncContextStrategy(); } function resetGlobals(): void { diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 2da60af0195d..b32ca6324cb7 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -20,7 +20,7 @@ describe('Integration | Transactions', () => { it('correctly creates transaction & spans', async () => { const beforeSendTransaction = jest.fn(() => null); - mockSdkInit({ enableTracing: true, beforeSendTransaction }); + mockSdkInit({ enableTracing: true, beforeSendTransaction, debug: true }); const client = getClient() as TestClientInterface; From 2334f24372f3f852c8df86946a7c1cb7739c5bc0 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 16 Feb 2024 13:24:08 -0500 Subject: [PATCH 088/173] feat(v8/vue): Remove all deprecated exports from vue (#10533) --- packages/vue/src/index.ts | 8 +- packages/vue/src/integration.ts | 48 ++--- packages/vue/src/router.ts | 111 ++++------ packages/vue/src/tracing.ts | 14 +- packages/vue/test/router.test.ts | 359 +++---------------------------- 5 files changed, 93 insertions(+), 447 deletions(-) diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 0b9626ee185d..110b80d270a9 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,13 +1,7 @@ export * from '@sentry/browser'; export { init } from './sdk'; -// eslint-disable-next-line deprecation/deprecation -export { vueRouterInstrumentation } from './router'; export { browserTracingIntegration } from './browserTracingIntegration'; export { attachErrorHandler } from './errorhandler'; export { createTracingMixins } from './tracing'; -export { - vueIntegration, - // eslint-disable-next-line deprecation/deprecation - VueIntegration, -} from './integration'; +export { vueIntegration } from './integration'; diff --git a/packages/vue/src/integration.ts b/packages/vue/src/integration.ts index 8150ea6b95d6..4bd99e3d6c8c 100644 --- a/packages/vue/src/integration.ts +++ b/packages/vue/src/integration.ts @@ -1,8 +1,9 @@ -import { convertIntegrationFnToClass, defineIntegration, hasTracingEnabled } from '@sentry/core'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { defineIntegration, hasTracingEnabled } from '@sentry/core'; +import type { Client, IntegrationFn } from '@sentry/types'; import { GLOBAL_OBJ, arrayify, consoleSandbox } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; +import { DEBUG_BUILD } from './debug-build'; import { attachErrorHandler } from './errorhandler'; import { createTracingMixins } from './tracing'; import type { Options, Vue, VueOptions } from './types'; @@ -33,17 +34,6 @@ const _vueIntegration = ((integrationOptions: Partial = {}) => { export const vueIntegration = defineIntegration(_vueIntegration); -/** - * Initialize Vue error & performance tracking. - * - * @deprecated Use `vueIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const VueIntegration = convertIntegrationFnToClass( - INTEGRATION_NAME, - vueIntegration, -) as IntegrationClass; - function _setupIntegration(client: Client, integrationOptions: Partial): void { const options: Options = { ...DEFAULT_CONFIG, ...client.getOptions(), ...integrationOptions }; if (!options.Vue && !options.app) { @@ -67,23 +57,25 @@ Update your \`Sentry.init\` call with an appropriate config option: } const vueInit = (app: Vue, options: Options): void => { - // Check app is not mounted yet - should be mounted _after_ init()! - // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it - // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 - const appWithInstance = app as Vue & { - _instance?: { - isMounted?: boolean; + if (DEBUG_BUILD) { + // Check app is not mounted yet - should be mounted _after_ init()! + // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it + // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 + const appWithInstance = app as Vue & { + _instance?: { + isMounted?: boolean; + }; }; - }; - const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; - if (isMounted === true) { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', - ); - }); + const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; + if (isMounted === true) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', + ); + }); + } } attachErrorHandler(app, options); diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index b7f3fd0466b0..b16541b1ea29 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -1,24 +1,12 @@ -import { WINDOW, captureException } from '@sentry/browser'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; -import type { SpanAttributes, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; - -import { getActiveTransaction } from './tracing'; - -interface VueRouterInstrumationOptions { - /** - * What to use for route labels. - * By default, we use route.name (if set) and else the path. - * - * Default: 'name' - */ - routeLabel: 'name' | 'path'; -} - -export type VueRouterInstrumentation = ( - startTransaction: (context: TransactionContext) => T | undefined, - startTransactionOnPageLoad?: boolean, - startTransactionOnLocationChange?: boolean, -) => void; +import { captureException } from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getRootSpan, + spanToJSON, +} from '@sentry/core'; +import type { Span, SpanAttributes, TransactionContext, TransactionSource } from '@sentry/types'; // The following type is an intersection of the Route type from VueRouter v2, v3, and v4. // This is not great, but kinda necessary to make it work with all versions at the same time. @@ -43,57 +31,18 @@ interface VueRouter { beforeEach: (fn: (to: Route, from: Route, next?: () => void) => void) => void; } -/** - * Creates routing instrumentation for Vue Router v2, v3 and v4 - * - * You can optionally pass in an options object with the available option: - * * `routeLabel`: Set this to `route` to opt-out of using `route.name` for transaction names. - * - * @param router The Vue Router instance that is used - * - * @deprecated Use `browserTracingIntegration()` from `@sentry/vue` instead - this includes the vue router instrumentation. - */ -export function vueRouterInstrumentation( - router: VueRouter, - options: Partial = {}, -): VueRouterInstrumentation { - return ( - startTransaction: (context: TransactionContext) => Transaction | undefined, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, - ) => { - // We have to start the pageload transaction as early as possible (before the router's `beforeEach` hook - // is called) to not miss child spans of the pageload. - // We check that window & window.location exists in order to not run this code in SSR environments. - if (startTransactionOnPageLoad && WINDOW && WINDOW.location) { - startTransaction({ - name: WINDOW.location.pathname, - op: 'pageload', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }); - } - - instrumentVueRouter( - router, - { - routeLabel: options.routeLabel || 'name', - instrumentNavigation: startTransactionOnLocationChange, - instrumentPageLoad: startTransactionOnPageLoad, - }, - startTransaction, - ); - }; -} - /** * Instrument the Vue router to create navigation spans. */ export function instrumentVueRouter( router: VueRouter, options: { + /** + * What to use for route labels. + * By default, we use route.name (if set) and else the path. + * + * Default: 'name' + */ routeLabel: 'name' | 'path'; instrumentPageLoad: boolean; instrumentNavigation: boolean; @@ -139,17 +88,16 @@ export function instrumentVueRouter( } if (options.instrumentPageLoad && isPageLoadNavigation) { - // eslint-disable-next-line deprecation/deprecation - const pageloadTransaction = getActiveTransaction(); - if (pageloadTransaction) { - const existingAttributes = spanToJSON(pageloadTransaction).data || {}; + const activeRootSpan = getActiveRootSpan(); + if (activeRootSpan) { + const existingAttributes = spanToJSON(activeRootSpan).data || {}; if (existingAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] !== 'custom') { - pageloadTransaction.updateName(transactionName); - pageloadTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); + activeRootSpan.updateName(transactionName); + activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); } // Set router attributes on the existing pageload transaction - // This will the origin, and add params & query attributes - pageloadTransaction.setAttributes({ + // This will override the origin, and add params & query attributes + activeRootSpan.setAttributes({ ...attributes, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', }); @@ -158,6 +106,7 @@ export function instrumentVueRouter( if (options.instrumentNavigation && !isPageLoadNavigation) { attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = transactionSource; + attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = 'auto.navigation.vue'; startNavigationSpanFn({ name: transactionName, op: 'navigation', @@ -173,3 +122,17 @@ export function instrumentVueRouter( } }); } + +function getActiveRootSpan(): Span | undefined { + const span = getActiveSpan(); + const rootSpan = span ? getRootSpan(span) : undefined; + + if (!rootSpan) { + return undefined; + } + + const op = spanToJSON(rootSpan).op; + + // Only use this root span if it is a pageload or navigation span + return op === 'navigation' || op === 'pageload' ? rootSpan : undefined; +} diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 8e2c743db064..277acc959c3f 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -1,5 +1,5 @@ -import { getActiveSpan, getCurrentScope, startInactiveSpan } from '@sentry/browser'; -import type { Span, Transaction } from '@sentry/types'; +import { getActiveSpan, startInactiveSpan } from '@sentry/browser'; +import type { Span } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; @@ -32,16 +32,6 @@ const HOOKS: { [key in Operation]: Hook[] } = { update: ['beforeUpdate', 'updated'], }; -/** - * Grabs active transaction off scope. - * - * @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead. - */ -export function getActiveTransaction(): Transaction | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentScope().getTransaction(); -} - /** Finish top-level span and activity with a debounce configured using `timeout` option */ function finishRootSpan(vm: VueSentry, timestamp: number, timeout: number): void { if (vm.$_sentryRootSpanTimer) { diff --git a/packages/vue/test/router.test.ts b/packages/vue/test/router.test.ts index 7d45889be864..c3a786a8f887 100644 --- a/packages/vue/test/router.test.ts +++ b/packages/vue/test/router.test.ts @@ -1,12 +1,19 @@ import * as SentryBrowser from '@sentry/browser'; +import * as SentryCore from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import type { SpanAttributes, Transaction } from '@sentry/types'; +import type { Span, SpanAttributes } from '@sentry/types'; import type { Route } from '../src/router'; -import { instrumentVueRouter, vueRouterInstrumentation } from '../src/router'; -import * as vueTracing from '../src/tracing'; +import { instrumentVueRouter } from '../src/router'; const captureExceptionSpy = jest.spyOn(SentryBrowser, 'captureException'); +jest.mock('@sentry/core', () => { + const actual = jest.requireActual('@sentry/core'); + return { + ...actual, + getActiveSpan: jest.fn().mockReturnValue({}), + }; +}); const mockVueRouter = { onError: jest.fn void]>(), @@ -51,307 +58,6 @@ const testRoutes: Record = { }, }; -/* eslint-disable deprecation/deprecation */ -describe('vueRouterInstrumentation()', () => { - const mockStartTransaction = jest.fn(); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return instrumentation that instruments VueRouter.onError', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction); - - // check - expect(mockVueRouter.onError).toHaveBeenCalledTimes(1); - - const onErrorCallback = mockVueRouter.onError.mock.calls[0][0]; - - const testError = new Error(); - onErrorCallback(testError); - - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenCalledWith(testError, { mechanism: { handled: false } }); - }); - - it.each([ - ['normalRoute1', 'normalRoute2', '/accounts/:accountId', 'route'], - ['normalRoute2', 'namedRoute', 'login-screen', 'custom'], - ['normalRoute2', 'unmatchedRoute', '/e8733846-20ac-488c-9871-a5cbcb647294', 'url'], - ])( - 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for navigations', - (fromKey, toKey, transactionName, transactionSource) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenCalledWith({ - name: transactionName, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: transactionSource, - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - - expect(mockNext).toHaveBeenCalledTimes(1); - }, - ); - - it.each([ - ['initialPageloadRoute', 'normalRoute1', '/books/:bookId/chapter/:chapterId', 'route'], - ['initialPageloadRoute', 'namedRoute', 'login-screen', 'custom'], - ['initialPageloadRoute', 'unmatchedRoute', '/e8733846-20ac-488c-9871-a5cbcb647294', 'url'], - ])( - 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for pageloads', - (fromKey, toKey, transactionName, transactionSource) => { - const mockedTxn = { - updateName: jest.fn(), - setData: jest.fn(), - setAttribute: jest.fn(), - setAttributes: jest.fn(), - metadata: {}, - }; - const customMockStartTxn = { ...mockStartTransaction }.mockImplementation(_ => { - return mockedTxn; - }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); - - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(customMockStartTxn, true, true); - - // check for transaction start - expect(customMockStartTxn).toHaveBeenCalledTimes(1); - expect(customMockStartTxn).toHaveBeenCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - op: 'pageload', - }); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; - - beforeEachCallback(to, from, mockNext); - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - expect(mockedTxn.updateName).toHaveBeenCalledWith(transactionName); - expect(mockedTxn.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ - ...getAttributesForRoute(to), - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - }); - - expect(mockNext).toHaveBeenCalledTimes(1); - }, - ); - - it('allows to configure routeLabel=path', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'path' }); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/login', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); - - it('allows to configure routeLabel=name', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'name' }); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: 'login-screen', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); - - it("doesn't overwrite a pageload transaction name it was set to custom before the router resolved the route", () => { - const mockedTxn = { - updateName: jest.fn(), - setData: jest.fn(), - setAttribute: jest.fn(), - setAttributes: jest.fn(), - name: '', - toJSON: () => ({ - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }), - }; - const customMockStartTxn = { ...mockStartTransaction }.mockImplementation(_ => { - return mockedTxn; - }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); - - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(customMockStartTxn, true, true); - - // check for transaction start - expect(customMockStartTxn).toHaveBeenCalledTimes(1); - expect(customMockStartTxn).toHaveBeenCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - }, - op: 'pageload', - }); - - // now we give the transaction a custom name, thereby simulating what would - // happen when users use the `beforeNavigate` hook - mockedTxn.name = 'customTxnName'; - mockedTxn.toJSON = () => ({ - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - }); - - const to = testRoutes['normalRoute1']; - const from = testRoutes['initialPageloadRoute']; - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(to, from, mockNext); - - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - expect(mockedTxn.updateName).not.toHaveBeenCalled(); - expect(mockedTxn.setAttribute).not.toHaveBeenCalled(); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - ...getAttributesForRoute(to), - }); - expect(mockedTxn.name).toEqual('customTxnName'); - }); - - test.each([ - [undefined, 1], - [false, 0], - [true, 1], - ])( - 'should return instrumentation that considers the startTransactionOnPageLoad option = %p', - (startTransactionOnPageLoad, expectedCallsAmount) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction, startTransactionOnPageLoad, true); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute1'], testRoutes['initialPageloadRoute'], mockNext); - - expect(mockStartTransaction).toHaveBeenCalledTimes(expectedCallsAmount); - }, - ); - - test.each([ - [undefined, 1], - [false, 0], - [true, 1], - ])( - 'should return instrumentation that considers the startTransactionOnLocationChange option = %p', - (startTransactionOnLocationChange, expectedCallsAmount) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument (this will call startTrransaction once for pageloads but we can ignore that) - instrument(mockStartTransaction, true, startTransactionOnLocationChange); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute2'], testRoutes['normalRoute1'], mockNext); - - expect(mockStartTransaction).toHaveBeenCalledTimes(expectedCallsAmount + 1); - }, - ); - - it("doesn't throw when `next` is not available in the beforeEach callback (Vue Router 4)", () => { - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'path' }); - instrument(mockStartTransaction, true, true); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, undefined); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/login', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); -}); -/* eslint-enable deprecation/deprecation */ - describe('instrumentVueRouter()', () => { afterEach(() => { jest.clearAllMocks(); @@ -421,18 +127,17 @@ describe('instrumentVueRouter()', () => { ])( 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for pageloads', (fromKey, toKey, transactionName, transactionSource) => { - const mockedTxn = { + const mockRootSpan = { + getSpanJSON: jest.fn().mockReturnValue({ op: 'pageload' }), updateName: jest.fn(), - setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), - metadata: {}, }; - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); const mockStartSpan = jest.fn().mockImplementation(_ => { - return mockedTxn; + return mockRootSpan; }); instrumentVueRouter( mockVueRouter, @@ -451,9 +156,9 @@ describe('instrumentVueRouter()', () => { beforeEachCallback(to, from, mockNext); expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - expect(mockedTxn.updateName).toHaveBeenCalledWith(transactionName); - expect(mockedTxn.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ + expect(mockRootSpan.updateName).toHaveBeenCalledWith(transactionName); + expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); + expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', ...getAttributesForRoute(to), }); @@ -517,22 +222,22 @@ describe('instrumentVueRouter()', () => { }); it("doesn't overwrite a pageload transaction name it was set to custom before the router resolved the route", () => { - const mockedTxn = { + const mockRootSpan = { updateName: jest.fn(), - setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), name: '', - toJSON: () => ({ + getSpanJSON: () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }), }; const mockStartSpan = jest.fn().mockImplementation(_ => { - return mockedTxn; + return mockRootSpan; }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); instrumentVueRouter( mockVueRouter, @@ -545,8 +250,9 @@ describe('instrumentVueRouter()', () => { // now we give the transaction a custom name, thereby simulating what would // happen when users use the `beforeNavigate` hook - mockedTxn.name = 'customTxnName'; - mockedTxn.toJSON = () => ({ + mockRootSpan.name = 'customTxnName'; + mockRootSpan.getSpanJSON = () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, @@ -561,13 +267,13 @@ describe('instrumentVueRouter()', () => { expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - expect(mockedTxn.updateName).not.toHaveBeenCalled(); - expect(mockedTxn.setAttribute).not.toHaveBeenCalled(); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ + expect(mockRootSpan.updateName).not.toHaveBeenCalled(); + expect(mockRootSpan.setAttribute).not.toHaveBeenCalled(); + expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', ...getAttributesForRoute(to), }); - expect(mockedTxn.name).toEqual('customTxnName'); + expect(mockRootSpan.name).toEqual('customTxnName'); }); test.each([ @@ -576,19 +282,20 @@ describe('instrumentVueRouter()', () => { ])( 'should return instrumentation that considers the instrumentPageLoad = %p', (instrumentPageLoad, expectedCallsAmount) => { - const mockedTxn = { + const mockRootSpan = { updateName: jest.fn(), setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), name: '', toJSON: () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }), }; - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); const mockStartSpan = jest.fn(); instrumentVueRouter( @@ -603,7 +310,7 @@ describe('instrumentVueRouter()', () => { const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; beforeEachCallback(testRoutes['normalRoute1'], testRoutes['initialPageloadRoute'], mockNext); - expect(mockedTxn.updateName).toHaveBeenCalledTimes(expectedCallsAmount); + expect(mockRootSpan.updateName).toHaveBeenCalledTimes(expectedCallsAmount); expect(mockStartSpan).not.toHaveBeenCalled(); }, ); From a8966d13844352c20557ff09abc8af9a4bbb1980 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 16 Feb 2024 17:39:30 -0400 Subject: [PATCH 089/173] feat(core): Decouple metrics aggregation from client (#10628) This is a re-opening of #10596 which was closed automatically after the branch is was based off was closed. Part of the work for #10595 This PR decouples metrics aggregation from the client and stores the aggregator globally the first time it is required. This PR does not remove `captureAggregateMetrics` from the `BaseClient` or make `sendEnvelope` public as this can occur in a later PR. This will remove the remaining metrics code from the most basic tree-shaken Sentry configuration. The default (node) metrics implementation remains in `@sentry/core` because it's required for Deno and Vercel-Edge which don't ref. `@sentry/node`. There's a load of extra deletions and cleanup that can occur for v8 when the integration is deleted. For example `BrowserMetricsAggregator` can probably move to `@sentry/browser`. --- MIGRATION.md | 4 ++ .../suites/metrics/init.js | 16 +++++ .../suites/metrics/test.ts | 17 +++++ .../utils/helpers.ts | 24 ++++++- .../tracing/metric-summaries/scenario.js | 3 - packages/astro/src/index.types.ts | 1 + packages/browser/src/exports.ts | 3 +- packages/browser/src/metrics.ts | 45 ++++++++++++++ packages/bun/src/index.ts | 2 +- packages/core/src/baseclient.ts | 26 ++++---- packages/core/src/index.ts | 3 + packages/core/src/metrics/exports-default.ts | 46 ++++++++++++++ packages/core/src/metrics/exports.ts | 62 ++++++++++++------- packages/core/src/metrics/integration.ts | 31 ---------- packages/core/src/server-runtime-client.ts | 5 -- packages/deno/src/index.ts | 2 +- packages/nextjs/src/index.types.ts | 2 + packages/node/src/index.ts | 2 +- packages/remix/src/index.types.ts | 2 + packages/sveltekit/src/index.types.ts | 2 + packages/types/src/client.ts | 20 ++++++ 21 files changed, 238 insertions(+), 80 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/metrics/init.js create mode 100644 dev-packages/browser-integration-tests/suites/metrics/test.ts create mode 100644 packages/browser/src/metrics.ts create mode 100644 packages/core/src/metrics/exports-default.ts delete mode 100644 packages/core/src/metrics/integration.ts diff --git a/MIGRATION.md b/MIGRATION.md index d4a7827ed755..1e307d7d47fe 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,9 @@ # Upgrading from 7.x to 8.x +## Removal of the `MetricsAggregator` integration class and `metricsAggregatorIntegration` + +The SDKs now support metrics features without any additional configuration. + ## Updated behaviour of `tracePropagationTargets` in the browser (HTTP tracing headers & CORS) We updated the behaviour of the SDKs when no `tracePropagationTargets` option was defined. As a reminder, you can diff --git a/dev-packages/browser-integration-tests/suites/metrics/init.js b/dev-packages/browser-integration-tests/suites/metrics/init.js new file mode 100644 index 000000000000..8a5032c05eef --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/metrics/init.js @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', +}); + +Sentry.metrics.increment('increment'); +Sentry.metrics.increment('increment'); +Sentry.metrics.distribution('distribution', 42); +Sentry.metrics.distribution('distribution', 45); +Sentry.metrics.gauge('gauge', 5); +Sentry.metrics.gauge('gauge', 15); +Sentry.metrics.set('set', 'nope'); +Sentry.metrics.set('set', 'another'); diff --git a/dev-packages/browser-integration-tests/suites/metrics/test.ts b/dev-packages/browser-integration-tests/suites/metrics/test.ts new file mode 100644 index 000000000000..d73235d876c8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/metrics/test.ts @@ -0,0 +1,17 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, properEnvelopeRequestParser } from '../../utils/helpers'; + +sentryTest('collects metrics', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + const statsdBuffer = await getFirstSentryEnvelopeRequest(page, url, properEnvelopeRequestParser); + const statsdString = new TextDecoder().decode(statsdBuffer); + // Replace all the Txxxxxx to remove the timestamps + const normalisedStatsdString = statsdString.replace(/T\d+\n?/g, 'T000000'); + + expect(normalisedStatsdString).toEqual( + 'increment@none:2|c|T000000distribution@none:42:45|d|T000000gauge@none:15:5:15:20:2|g|T000000set@none:3387254:3443787523|s|T000000', + ); +}); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 6027695746e9..861f05b9f641 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -1,5 +1,6 @@ import type { Page, Request } from '@playwright/test'; -import type { EnvelopeItemType, Event, EventEnvelopeHeaders } from '@sentry/types'; +import type { EnvelopeItem, EnvelopeItemType, Event, EventEnvelopeHeaders } from '@sentry/types'; +import { parseEnvelope } from '@sentry/utils'; export const envelopeUrlRegex = /\.sentry\.io\/api\/\d+\/envelope\//; @@ -21,6 +22,27 @@ export const envelopeRequestParser = (request: Request | null, envelo return envelopeParser(request)[envelopeIndex] as T; }; +/** + * The above envelope parser does not follow the envelope spec... + * ...but modifying it to follow the spec breaks a lot of the test which rely on the current indexing behavior. + * + * This parser is a temporary solution to allow us to test metrics with statsd envelopes. + * + * Eventually, all the tests should be migrated to use this 'proper' envelope parser! + */ +export const properEnvelopeParser = (request: Request | null): EnvelopeItem[] => { + // https://develop.sentry.dev/sdk/envelopes/ + const envelope = request?.postData() || ''; + + const [, items] = parseEnvelope(envelope, new TextEncoder(), new TextDecoder()); + + return items; +}; + +export const properEnvelopeRequestParser = (request: Request | null, envelopeIndex = 1): T => { + return properEnvelopeParser(request)[0][envelopeIndex] as T; +}; + export const envelopeHeaderRequestParser = (request: Request | null): EventEnvelopeHeaders => { // https://develop.sentry.dev/sdk/envelopes/ const envelope = request?.postData() || ''; diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js index ef68afb06576..e2921db180af 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -6,9 +6,6 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - _experiments: { - metricsAggregator: true, - }, }); // Stop the process from exiting before the transaction is sent diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index ce55efac54bb..b19c6af22637 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -26,4 +26,5 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; export default sentryAstro; diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 85c85415ebce..b4f58f09ac50 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -64,7 +64,6 @@ export { FunctionToString, // eslint-disable-next-line deprecation/deprecation InboundFilters, - metrics, functionToStringIntegration, inboundFiltersIntegration, parameterize, @@ -77,6 +76,8 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; +export * from './metrics'; + export { WINDOW } from './helpers'; export { BrowserClient } from './client'; export { makeFetchTransport, makeXHRTransport } from './transports'; diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts new file mode 100644 index 000000000000..6a7792a16c67 --- /dev/null +++ b/packages/browser/src/metrics.ts @@ -0,0 +1,45 @@ +import type { MetricData } from '@sentry/core'; +import { BrowserMetricsAggregator, metrics as metricsCore } from '@sentry/core'; + +/** + * Adds a value to a counter metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function increment(name: string, value: number = 1, data?: MetricData): void { + metricsCore.increment(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a distribution metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function distribution(name: string, value: number, data?: MetricData): void { + metricsCore.distribution(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a set metric. Value must be a string or integer. + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function set(name: string, value: number | string, data?: MetricData): void { + metricsCore.set(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a gauge metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function gauge(name: string, value: number, data?: MetricData): void { + metricsCore.gauge(BrowserMetricsAggregator, name, value, data); +} + +export const metrics = { + increment, + distribution, + set, + gauge, +}; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 22040d39b059..a1b052eae2a4 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -71,7 +71,7 @@ export { startInactiveSpan, startSpanManual, continueTrace, - metrics, + metricsDefault as metrics, functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index fadd11958fc9..2d802c95d938 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -17,7 +17,6 @@ import type { Integration, IntegrationClass, MetricBucketItem, - MetricsAggregator, Outcome, ParameterizedString, SdkMetadata, @@ -93,13 +92,6 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca * } */ export abstract class BaseClient implements Client { - /** - * A reference to a metrics aggregator - * - * @experimental Note this is alpha API. It may experience breaking changes in the future. - */ - public metricsAggregator?: MetricsAggregator; - /** Options passed to the SDK. */ protected readonly _options: O; @@ -280,9 +272,7 @@ export abstract class BaseClient implements Client { public flush(timeout?: number): PromiseLike { const transport = this._transport; if (transport) { - if (this.metricsAggregator) { - this.metricsAggregator.flush(); - } + this.emit('flush'); return this._isClientDoneProcessing(timeout).then(clientFinished => { return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed); }); @@ -297,9 +287,7 @@ export abstract class BaseClient implements Client { public close(timeout?: number): PromiseLike { return this.flush(timeout).then(result => { this.getOptions().enabled = false; - if (this.metricsAggregator) { - this.metricsAggregator.close(); - } + this.emit('close'); return result; }); } @@ -495,6 +483,10 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; + public on(hook: 'flush', callback: () => void): void; + + public on(hook: 'close', callback: () => void): void; + /** @inheritdoc */ public on(hook: string, callback: unknown): void { if (!this._hooks[hook]) { @@ -541,6 +533,12 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'startNavigationSpan', options: StartSpanOptions): void; + /** @inheritdoc */ + public emit(hook: 'flush'): void; + + /** @inheritdoc */ + public emit(hook: 'close'): void; + /** @inheritdoc */ public emit(hook: string, ...rest: unknown[]): void { if (this._hooks[hook]) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b1979d53ed40..8e69f6aba5af 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -114,6 +114,9 @@ export { linkedErrorsIntegration } from './integrations/linkederrors'; export { moduleMetadataIntegration } from './integrations/metadata'; export { requestDataIntegration } from './integrations/requestdata'; export { metrics } from './metrics/exports'; +export type { MetricData } from './metrics/exports'; +export { metricsDefault } from './metrics/exports-default'; +export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; /** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ const Integrations = INTEGRATIONS; diff --git a/packages/core/src/metrics/exports-default.ts b/packages/core/src/metrics/exports-default.ts new file mode 100644 index 000000000000..f331bd3de3f1 --- /dev/null +++ b/packages/core/src/metrics/exports-default.ts @@ -0,0 +1,46 @@ +import { MetricsAggregator } from './aggregator'; +import type { MetricData } from './exports'; +import { metrics as metricsCore } from './exports'; + +/** + * Adds a value to a counter metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function increment(name: string, value: number = 1, data?: MetricData): void { + metricsCore.increment(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a distribution metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function distribution(name: string, value: number, data?: MetricData): void { + metricsCore.distribution(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a set metric. Value must be a string or integer. + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function set(name: string, value: number | string, data?: MetricData): void { + metricsCore.set(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a gauge metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function gauge(name: string, value: number, data?: MetricData): void { + metricsCore.gauge(MetricsAggregator, name, value, data); +} + +export const metricsDefault = { + increment, + distribution, + set, + gauge, +}; diff --git a/packages/core/src/metrics/exports.ts b/packages/core/src/metrics/exports.ts index 5af06d9d509d..4587c26a8510 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -1,4 +1,9 @@ -import type { ClientOptions, MeasurementUnit, Primitive } from '@sentry/types'; +import type { + ClientOptions, + MeasurementUnit, + MetricsAggregator as MetricsAggregatorInterface, + Primitive, +} from '@sentry/types'; import { logger } from '@sentry/utils'; import type { BaseClient } from '../baseclient'; import { getCurrentScope } from '../currentScopes'; @@ -6,29 +11,46 @@ import { getClient } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { spanToJSON } from '../utils/spanUtils'; import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; -import { MetricsAggregator, metricsAggregatorIntegration } from './integration'; import type { MetricType } from './types'; -interface MetricData { +export interface MetricData { unit?: MeasurementUnit; tags?: Record; timestamp?: number; } +type MetricsAggregatorConstructor = { + new (client: BaseClient): MetricsAggregatorInterface; +}; + +/** + * Global metrics aggregator instance. + * + * This is initialized on the first call to any `Sentry.metric.*` method. + */ +let globalMetricsAggregator: MetricsAggregatorInterface | undefined; + function addToMetricsAggregator( + Aggregator: MetricsAggregatorConstructor, metricType: MetricType, name: string, value: number | string, data: MetricData | undefined = {}, ): void { const client = getClient>(); - const scope = getCurrentScope(); + if (!client) { + return; + } + + if (!globalMetricsAggregator) { + const aggregator = (globalMetricsAggregator = new Aggregator(client)); + + client.on('flush', () => aggregator.flush()); + client.on('close', () => aggregator.close()); + } + if (client) { - if (!client.metricsAggregator) { - DEBUG_BUILD && - logger.warn('No metrics aggregator enabled. Please add the MetricsAggregator integration to use metrics APIs'); - return; - } + const scope = getCurrentScope(); const { unit, tags, timestamp } = data; const { release, environment } = client.getOptions(); // eslint-disable-next-line deprecation/deprecation @@ -45,7 +67,7 @@ function addToMetricsAggregator( } DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`); - client.metricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp); + globalMetricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp); } } @@ -54,8 +76,8 @@ function addToMetricsAggregator( * * @experimental This API is experimental and might have breaking changes in the future. */ -export function increment(name: string, value: number = 1, data?: MetricData): void { - addToMetricsAggregator(COUNTER_METRIC_TYPE, name, value, data); +function increment(aggregator: MetricsAggregatorConstructor, name: string, value: number = 1, data?: MetricData): void { + addToMetricsAggregator(aggregator, COUNTER_METRIC_TYPE, name, value, data); } /** @@ -63,8 +85,8 @@ export function increment(name: string, value: number = 1, data?: MetricData): v * * @experimental This API is experimental and might have breaking changes in the future. */ -export function distribution(name: string, value: number, data?: MetricData): void { - addToMetricsAggregator(DISTRIBUTION_METRIC_TYPE, name, value, data); +function distribution(aggregator: MetricsAggregatorConstructor, name: string, value: number, data?: MetricData): void { + addToMetricsAggregator(aggregator, DISTRIBUTION_METRIC_TYPE, name, value, data); } /** @@ -72,8 +94,8 @@ export function distribution(name: string, value: number, data?: MetricData): vo * * @experimental This API is experimental and might have breaking changes in the future. */ -export function set(name: string, value: number | string, data?: MetricData): void { - addToMetricsAggregator(SET_METRIC_TYPE, name, value, data); +function set(aggregator: MetricsAggregatorConstructor, name: string, value: number | string, data?: MetricData): void { + addToMetricsAggregator(aggregator, SET_METRIC_TYPE, name, value, data); } /** @@ -81,8 +103,8 @@ export function set(name: string, value: number | string, data?: MetricData): vo * * @experimental This API is experimental and might have breaking changes in the future. */ -export function gauge(name: string, value: number, data?: MetricData): void { - addToMetricsAggregator(GAUGE_METRIC_TYPE, name, value, data); +function gauge(aggregator: MetricsAggregatorConstructor, name: string, value: number, data?: MetricData): void { + addToMetricsAggregator(aggregator, GAUGE_METRIC_TYPE, name, value, data); } export const metrics = { @@ -90,8 +112,4 @@ export const metrics = { distribution, set, gauge, - /** @deprecated Use `metrics.metricsAggregratorIntegration()` instead. */ - // eslint-disable-next-line deprecation/deprecation - MetricsAggregator, - metricsAggregatorIntegration, }; diff --git a/packages/core/src/metrics/integration.ts b/packages/core/src/metrics/integration.ts deleted file mode 100644 index af797bd8adf4..000000000000 --- a/packages/core/src/metrics/integration.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Client, ClientOptions, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; -import type { BaseClient } from '../baseclient'; -import { convertIntegrationFnToClass, defineIntegration } from '../integration'; -import { BrowserMetricsAggregator } from './browser-aggregator'; - -const INTEGRATION_NAME = 'MetricsAggregator'; - -const _metricsAggregatorIntegration = (() => { - return { - name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function - setup(client: BaseClient) { - client.metricsAggregator = new BrowserMetricsAggregator(client); - }, - }; -}) satisfies IntegrationFn; - -export const metricsAggregatorIntegration = defineIntegration(_metricsAggregatorIntegration); - -/** - * Enables Sentry metrics monitoring. - * - * @experimental This API is experimental and might having breaking changes in the future. - * @deprecated Use `metricsAggegratorIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const MetricsAggregator = convertIntegrationFnToClass( - INTEGRATION_NAME, - metricsAggregatorIntegration, -) as IntegrationClass void }>; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 5fbfa2c6aed6..81109efd2be9 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -17,7 +17,6 @@ import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; import { getClient, getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; -import { MetricsAggregator } from './metrics/aggregator'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; import { @@ -51,10 +50,6 @@ export class ServerRuntimeClient< addTracingExtensions(); super(options); - - if (options._experiments && options._experiments['metricsAggregator']) { - this.metricsAggregator = new MetricsAggregator(this); - } } /** diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index c3abd46f0b1e..8decb4e56ec3 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -69,7 +69,7 @@ export { startSpan, startInactiveSpan, startSpanManual, - metrics, + metricsDefault as metrics, inboundFiltersIntegration, linkedErrorsIntegration, functionToStringIntegration, diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 967b84c2c4c4..9d28ac85c9f3 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -35,6 +35,8 @@ export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary; export declare const Transaction: typeof edgeSdk.Transaction; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; + export { withSentryConfig } from './config'; /** diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index e45278fa2706..3cc1d560de39 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -71,11 +71,11 @@ export { startSpanManual, continueTrace, parameterize, - metrics, functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration, + metricsDefault as metrics, } from '@sentry/core'; export { diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index 2a801f954917..37256e1f2dbe 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -29,3 +29,5 @@ declare const runtime: 'client' | 'server'; export const close = runtime === 'client' ? clientSdk.close : serverSdk.close; export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush; + +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 868aff8de7f6..b162ef4c521f 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -48,3 +48,5 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; + +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 5a0ddcb38cb8..62956f0c00d0 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -263,6 +263,16 @@ export interface Client { */ on(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; + /** + * A hook that is called when the client is flushing + */ + on(hook: 'flush', callback: () => void): void; + + /** + * A hook that is called when the client is closing + */ + on(hook: 'close', callback: () => void): void; + /** * Fire a hook event for transaction start. * Expects to be given a transaction as the second argument. @@ -334,5 +344,15 @@ export interface Client { */ emit(hook: 'startNavigationSpan', options: StartSpanOptions): void; + /** + * Emit a hook event for client flush + */ + emit(hook: 'flush'): void; + + /** + * Emit a hook event for client close + */ + emit(hook: 'close'): void; + /* eslint-enable @typescript-eslint/unified-signatures */ } From ac39685f630ac89c58f3e011dd0aa0ee36ddee74 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 19 Feb 2024 09:26:41 +0100 Subject: [PATCH 090/173] ref: Migrate transaction source from metadata to attributes (#10674) --- .../suites/feedback/captureFeedback/test.ts | 1 + .../feedback/captureFeedbackAndReplay/test.ts | 5 +- .../envelope-header-transaction-name/init.js | 3 +- .../tests/propagation.test.ts | 4 ++ .../tests/transactions.test.ts | 1 + .../multiple-routers/complex-router/server.ts | 3 +- .../src/tracing/dynamicSamplingContext.ts | 5 +- packages/core/src/tracing/transaction.ts | 17 ++--- .../tracing/dynamicSamplingContext.test.ts | 14 ++-- packages/core/test/lib/tracing/trace.test.ts | 1 + .../core/test/lib/tracing/transaction.test.ts | 41 ++++++------ .../deno/test/__snapshots__/mod.test.ts.snap | 35 ---------- packages/deno/test/normalize.ts | 4 +- packages/ember/addon/index.ts | 16 ++++- .../common/withServerActionInstrumentation.ts | 6 +- .../test/integration/scope.test.ts | 2 +- .../test/integration/transactions.test.ts | 65 +++++++++++++------ packages/node/src/handlers.ts | 2 +- packages/node/test/integrations/http.test.ts | 5 +- .../opentelemetry-node/src/spanprocessor.ts | 4 +- .../test/spanprocessor.test.ts | 7 +- packages/opentelemetry/src/spanExporter.ts | 5 +- .../test/custom/transaction.test.ts | 2 - .../test/integration/scope.test.ts | 1 + .../test/integration/transactions.test.ts | 11 +++- packages/opentelemetry/test/trace.test.ts | 8 ++- packages/remix/src/client/performance.tsx | 8 +-- packages/remix/src/utils/instrumentServer.ts | 3 +- packages/sveltekit/src/client/load.ts | 11 ++-- packages/sveltekit/src/server/handle.ts | 3 +- packages/sveltekit/src/server/load.ts | 13 ++-- packages/sveltekit/test/client/load.test.ts | 10 +-- packages/sveltekit/test/server/handle.test.ts | 6 +- packages/sveltekit/test/server/load.test.ts | 21 +++--- .../src/browser/browserTracingIntegration.ts | 23 ++----- .../src/browser/browsertracing.ts | 23 ++----- .../tracing-internal/src/browser/router.ts | 9 ++- .../src/node/integrations/express.ts | 2 +- .../browser/browserTracingIntegration.test.ts | 4 ++ .../test/browser/browsertracing.test.ts | 19 ++++-- .../test/browser/router.test.ts | 5 +- packages/tracing/test/span.test.ts | 12 ++-- packages/tracing/test/transaction.test.ts | 19 ++++-- packages/types/src/span.ts | 4 +- packages/types/src/transaction.ts | 8 +-- packages/utils/src/requestdata.ts | 9 +-- 46 files changed, 252 insertions(+), 228 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts index 26ba665a418f..5d4ebe233c4d 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts @@ -44,6 +44,7 @@ sentryTest('should capture feedback (@sentry-internal/feedback import)', async ( const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request()); expect(feedbackEvent).toEqual({ type: 'feedback', + breadcrumbs: expect.any(Array), contexts: { feedback: { contact_email: 'janedoe@example.org', diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts index 1590c68652a3..057b5d43a1c8 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts @@ -50,15 +50,16 @@ sentryTest('should capture feedback (@sentry-internal/feedback import)', async ( expect(breadcrumbs).toEqual( expect.arrayContaining([ - { + expect.objectContaining({ category: 'sentry.feedback', data: { feedbackId: expect.any(String) }, - }, + }), ]), ); expect(feedbackEvent).toEqual({ type: 'feedback', + breadcrumbs: expect.any(Array), contexts: { feedback: { contact_email: 'janedoe@example.org', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index e28325fcf78e..579523ee2015 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -1,4 +1,5 @@ import * as Sentry from '@sentry/browser'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/browser'; import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; @@ -17,4 +18,4 @@ scope.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; }); -scope.getTransaction().setMetadata({ source: 'custom' }); +scope.getTransaction().setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts index 9b83e882a739..cbb2c889edec 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/propagation.test.ts @@ -65,6 +65,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.response.status_code': 200, 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.otel.http', + 'sentry.source': 'route', }, op: 'http.server', span_id: expect.any(String), @@ -89,6 +90,7 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => { 'http.response.status_code': 200, 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.otel.http', + 'sentry.source': 'route', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, @@ -162,6 +164,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_code': 200, 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.otel.http', + 'sentry.source': 'route', }, op: 'http.server', span_id: expect.any(String), @@ -186,6 +189,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { 'http.response.status_code': 200, 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.otel.http', + 'sentry.source': 'route', }, op: 'http.server', parent_span_id: outgoingHttpSpanId, diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts index d2ab9e3d664f..0429adca8623 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/tests/transactions.test.ts @@ -30,6 +30,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.response.status_code': 200, 'sentry.op': 'http.server', 'sentry.origin': 'auto.http.otel.http', + 'sentry.source': 'route', }, op: 'http.server', span_id: expect.any(String), diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts index 8df1b9c3d988..08b3a8f024cc 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import express from 'express'; const app = express(); @@ -9,7 +8,7 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/packages/core/src/tracing/dynamicSamplingContext.ts b/packages/core/src/tracing/dynamicSamplingContext.ts index ff4599a42a68..222763a8a781 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -3,6 +3,7 @@ import { dropUndefinedKeys } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; import { getClient } from '../currentScopes'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { getRootSpan } from '../utils/getRootSpan'; import { spanIsSampled, spanToJSON } from '../utils/spanUtils'; @@ -66,7 +67,7 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly { name: 'tx', metadata: { sampleRate: 0.56, - source: 'route', + }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, sampled: true, }); @@ -98,9 +100,11 @@ describe('getDynamicSamplingContextFromSpan', () => { const transaction = new Transaction({ name: 'tx', metadata: { - source: 'url', sampleRate: 0.56, }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, }); const dsc = getDynamicSamplingContextFromSpan(transaction); @@ -110,11 +114,11 @@ describe('getDynamicSamplingContextFromSpan', () => { test.each([ ['is included if transaction source is parameterized route/url', 'route'], ['is included if transaction source is a custom name', 'custom'], - ])('%s', (_: string, source) => { + ] as const)('%s', (_: string, source: TransactionSource) => { const transaction = startInactiveSpan({ name: 'tx', - metadata: { - ...(source && { source: source as TransactionSource }), + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index d95f713147ec..1d06ea009937 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -266,6 +266,7 @@ describe('startSpan', () => { data: { 'sentry.origin': 'auto.http.browser', 'sentry.sample_rate': 0, + 'sentry.source': 'custom', }, origin: 'auto.http.browser', description: 'GET users/[id]', diff --git a/packages/core/test/lib/tracing/transaction.test.ts b/packages/core/test/lib/tracing/transaction.test.ts index 415c0448f78e..9371bda548fd 100644 --- a/packages/core/test/lib/tracing/transaction.test.ts +++ b/packages/core/test/lib/tracing/transaction.test.ts @@ -1,4 +1,9 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Transaction } from '../../../src'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + Transaction, +} from '../../../src'; describe('transaction', () => { describe('name', () => { @@ -16,7 +21,7 @@ describe('transaction', () => { transaction.name = 'new name'; expect(transaction.name).toEqual('new name'); - expect(transaction.metadata.source).toEqual('custom'); + expect(transaction.attributes['sentry.source']).toEqual('custom'); }); it('allows to update the name via setName', () => { @@ -27,7 +32,7 @@ describe('transaction', () => { transaction.setName('new name'); expect(transaction.name).toEqual('new name'); - expect(transaction.metadata.source).toEqual('custom'); + expect(transaction.attributes['sentry.source']).toEqual('custom'); }); it('allows to update the name via updateName', () => { @@ -38,7 +43,7 @@ describe('transaction', () => { transaction.updateName('new name'); expect(transaction.name).toEqual('new name'); - expect(transaction.metadata.source).toEqual('route'); + expect(transaction.attributes['sentry.source']).toEqual('route'); }); /* eslint-enable deprecation/deprecation */ }); @@ -48,15 +53,13 @@ describe('transaction', () => { it('works with defaults', () => { const transaction = new Transaction({ name: 'span name' }); expect(transaction.metadata).toEqual({ - source: 'custom', spanMetadata: {}, }); }); it('allows to set metadata in constructor', () => { - const transaction = new Transaction({ name: 'span name', metadata: { source: 'url', request: {} } }); + const transaction = new Transaction({ name: 'span name', metadata: { request: {} } }); expect(transaction.metadata).toEqual({ - source: 'url', spanMetadata: {}, request: {}, }); @@ -71,37 +74,31 @@ describe('transaction', () => { [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 0.5, }, }); + expect(transaction.metadata).toEqual({ - source: 'url', sampleRate: 0.5, spanMetadata: {}, request: {}, }); - }); - - it('allows to update metadata via setMetadata', () => { - const transaction = new Transaction({ name: 'span name', metadata: { source: 'url', request: {} } }); - - transaction.setMetadata({ source: 'route' }); - expect(transaction.metadata).toEqual({ - source: 'route', - spanMetadata: {}, - request: {}, + expect(transaction.attributes).toEqual({ + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 0.5, }); }); - it('allows to update metadata via setAttribute', () => { - const transaction = new Transaction({ name: 'span name', metadata: { source: 'url', request: {} } }); + it('allows to update metadata via setMetadata', () => { + const transaction = new Transaction({ name: 'span name', metadata: {} }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + transaction.setMetadata({ request: {} }); expect(transaction.metadata).toEqual({ - source: 'route', spanMetadata: {}, request: {}, }); }); + /* eslint-enable deprecation/deprecation */ }); }); diff --git a/packages/deno/test/__snapshots__/mod.test.ts.snap b/packages/deno/test/__snapshots__/mod.test.ts.snap index d728072d38d6..417acf1e7639 100644 --- a/packages/deno/test/__snapshots__/mod.test.ts.snap +++ b/packages/deno/test/__snapshots__/mod.test.ts.snap @@ -41,41 +41,6 @@ snapshot[`captureException 1`] = ` }, stacktrace: { frames: [ - { - colno: 20, - filename: "ext:cli/40_testing.js", - function: "outerWrapped", - in_app: false, - lineno: 472, - }, - { - colno: 33, - filename: "ext:cli/40_testing.js", - function: "exitSanitizer", - in_app: false, - lineno: 458, - }, - { - colno: 31, - filename: "ext:cli/40_testing.js", - function: "resourceSanitizer", - in_app: false, - lineno: 410, - }, - { - colno: 33, - filename: "ext:cli/40_testing.js", - function: "asyncOpSanitizer", - in_app: false, - lineno: 177, - }, - { - colno: 11, - filename: "ext:cli/40_testing.js", - function: "innerWrapped", - in_app: false, - lineno: 526, - }, { colno: 27, context_line: " client.captureException(something());", diff --git a/packages/deno/test/normalize.ts b/packages/deno/test/normalize.ts index 64295932e00d..4dbbc8f3f6ec 100644 --- a/packages/deno/test/normalize.ts +++ b/packages/deno/test/normalize.ts @@ -152,9 +152,9 @@ function normalizeEvent(event: sentryTypes.Event): sentryTypes.Event { } if (event.exception?.values?.[0].stacktrace?.frames) { - // Exlcude Deno frames since these may change between versions + // Exclude Deno frames since these may change between versions event.exception.values[0].stacktrace.frames = event.exception.values[0].stacktrace.frames.filter( - frame => !frame.filename?.includes('deno:'), + frame => !frame.filename?.includes('deno:') && !frame.filename?.startsWith('ext:'), ); } diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index f403df54284e..5f5cbf054666 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -9,6 +9,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata } from '@sentry/core import { GLOBAL_OBJ } from '@sentry/utils'; import Ember from 'ember'; +import type { TransactionSource } from '@sentry/types'; import type { EmberSentryConfig, GlobalConfig, OwnConfig } from './types'; function _getSentryInitConfig(): EmberSentryConfig['sentry'] { @@ -68,11 +69,12 @@ export const instrumentRoutePerformance = (BaseRoute description: string, fn: X, args: Parameters, + source: TransactionSource, ): Promise> => { return startSpan( { attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'ember', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, op, name: description, @@ -94,15 +96,22 @@ export const instrumentRoutePerformance = (BaseRoute this.fullRouteName, super.beforeModel.bind(this), args, + 'custom', ); } public async model(...args: unknown[]): Promise { - return instrumentFunction('ui.ember.route.model', this.fullRouteName, super.model.bind(this), args); + return instrumentFunction('ui.ember.route.model', this.fullRouteName, super.model.bind(this), args, 'custom'); } public afterModel(...args: unknown[]): void | Promise { - return instrumentFunction('ui.ember.route.after_model', this.fullRouteName, super.afterModel.bind(this), args); + return instrumentFunction( + 'ui.ember.route.after_model', + this.fullRouteName, + super.afterModel.bind(this), + args, + 'custom', + ); } public setupController(...args: unknown[]): void | Promise { @@ -111,6 +120,7 @@ export const instrumentRoutePerformance = (BaseRoute this.fullRouteName, super.setupController.bind(this), args, + 'custom', ); } }, diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 1f4cdaf992c9..0c54a4f5b665 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -1,4 +1,4 @@ -import { getIsolationScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getIsolationScope } from '@sentry/core'; import { addTracingExtensions, captureException, @@ -94,8 +94,8 @@ async function withServerActionInstrumentationImplementation { diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index 8f73f5487b2b..3f6c8a441216 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -88,7 +88,7 @@ describe('Integration | Scope', () => { expect.objectContaining({ contexts: expect.objectContaining({ trace: { - data: { 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual' }, + data: { 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', 'sentry.source': 'custom' }, span_id: spanId, status: 'ok', trace_id: traceId, diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index 72dd4966bc41..f1b89e922641 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -29,7 +29,9 @@ describe('Integration | Transactions', () => { { op: 'test op', name: 'test name', - source: 'task', + attributes: { + [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', + }, origin: 'auto.test', metadata: { requestPath: 'test-path' }, }, @@ -85,6 +87,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), @@ -106,7 +109,6 @@ describe('Integration | Transactions', () => { transaction: 'test name', }), sampleRate: 1, - source: 'task', spanMetadata: expect.any(Object), requestPath: 'test-path', }), @@ -185,26 +187,34 @@ describe('Integration | Transactions', () => { Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); Sentry.withIsolationScope(() => { - Sentry.startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => { - Sentry.addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); + Sentry.startSpan( + { + op: 'test op', + name: 'test name', + origin: 'auto.test', + attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task' }, + }, + span => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); - span.setAttributes({ - 'test.outer': 'test value', - }); + span.setAttributes({ + 'test.outer': 'test value', + }); - const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); + const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); - Sentry.setTag('test.tag', 'test value'); + Sentry.setTag('test.tag', 'test value'); - Sentry.startSpan({ name: 'inner span 2' }, innerSpan => { - Sentry.addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); + Sentry.startSpan({ name: 'inner span 2' }, innerSpan => { + Sentry.addBreadcrumb({ message: 'test breadcrumb 3', timestamp: 123456 }); - innerSpan.setAttributes({ - 'test.inner': 'test value', + innerSpan.setAttributes({ + 'test.inner': 'test value', + }); }); - }); - }); + }, + ); }); Sentry.withIsolationScope(() => { @@ -251,6 +261,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), @@ -297,6 +308,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op b', 'sentry.origin': 'manual', + 'sentry.source': 'custom', }, op: 'test op b', span_id: expect.any(String), @@ -409,6 +421,7 @@ describe('Integration | Transactions', () => { data: { 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', + 'sentry.source': 'custom', }, span_id: expect.any(String), status: 'ok', @@ -452,6 +465,7 @@ describe('Integration | Transactions', () => { data: { 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', + 'sentry.source': 'custom', }, span_id: expect.any(String), status: 'ok', @@ -508,12 +522,20 @@ describe('Integration | Transactions', () => { context.with( trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), () => { - Sentry.startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, () => { - const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); + Sentry.startSpan( + { + op: 'test op', + name: 'test name', + origin: 'auto.test', + attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task' }, + }, + () => { + const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); - Sentry.startSpan({ name: 'inner span 2' }, () => {}); - }); + Sentry.startSpan({ name: 'inner span 2' }, () => {}); + }, + ); }, ); @@ -531,6 +553,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 0ac97daae7c7..8e1cb695c05b 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -72,7 +72,7 @@ export function tracingHandler(): ( op: 'http.server', origin: 'auto.http.node.tracingHandler', ...ctx, - data: { + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, metadata: { diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 2af325d5ddbd..af550ef70643 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,6 +1,7 @@ import * as http from 'http'; import * as https from 'https'; import type { SentrySpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { Transaction } from '@sentry/core'; import { getCurrentScope, makeMain, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; import { Hub, addTracingExtensions } from '@sentry/core'; @@ -162,7 +163,7 @@ describe('tracing', () => { it('adds the transaction name to the the baggage header if a valid transaction source is set', async () => { nock('http://dogs.are.great').get('/').reply(200); - createTransactionOnScope({}, { metadata: { source: 'route' } }); + createTransactionOnScope({}, { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' } }); const request = http.get({ host: 'http://dogs.are.great/', headers: { baggage: 'dog=great' } }); const baggageHeader = request.getHeader('baggage') as string; @@ -176,7 +177,7 @@ describe('tracing', () => { it('does not add the transaction name to the the baggage header if url transaction source is set', async () => { nock('http://dogs.are.great').get('/').reply(200); - createTransactionOnScope({}, { metadata: { source: 'url' } }); + createTransactionOnScope({}, { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } }); const request = http.get({ host: 'http://dogs.are.great/', headers: { baggage: 'dog=great' } }); const baggageHeader = request.getHeader('baggage') as string; diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 9996fc73111f..f79dc9f5b2e4 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -179,7 +179,9 @@ function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial { otelSpan.end(); // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.metadata.source).toBe('url'); + expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); }); }); @@ -687,7 +688,7 @@ describe('SentrySpanProcessor', () => { otelSpan.end(); // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.metadata.source).toBe('route'); + expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); }); }); @@ -703,7 +704,7 @@ describe('SentrySpanProcessor', () => { otelSpan.end(); // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.metadata.source).toBe('route'); + expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); }); }); diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 1f6792932dcd..e8baeb17d2cc 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -4,6 +4,7 @@ import { ExportResultCode } from '@opentelemetry/core'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; import type { Transaction } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentHub } from '@sentry/core'; import type { Scope, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types'; import { addNonEnumerableProperty, logger } from '@sentry/utils'; @@ -159,10 +160,12 @@ function createTransactionForOtelSpan(span: ReadableSpan): Transaction { status: mapStatus(span), startTimestamp: convertOtelTimeToSeconds(span.startTime), metadata: { - source, sampleRate: span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] as number | undefined, ...metadata, }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + }, data: removeSentryAttributes(data), origin, tags, diff --git a/packages/opentelemetry/test/custom/transaction.test.ts b/packages/opentelemetry/test/custom/transaction.test.ts index ae07939981cc..f56350abeb8e 100644 --- a/packages/opentelemetry/test/custom/transaction.test.ts +++ b/packages/opentelemetry/test/custom/transaction.test.ts @@ -24,7 +24,6 @@ describe('startTranscation', () => { expect(transaction.spanRecorder?.spans).toHaveLength(1); // eslint-disable-next-line deprecation/deprecation expect(transaction.metadata).toEqual({ - source: 'custom', spanMetadata: {}, }); @@ -55,7 +54,6 @@ describe('startTranscation', () => { expect(transaction).toBeInstanceOf(Transaction); // eslint-disable-next-line deprecation/deprecation expect(transaction.metadata).toEqual({ - source: 'custom', spanMetadata: {}, }); diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index cc4def42ddbc..594fa1bcf9ae 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -98,6 +98,7 @@ describe('Integration | Scope', () => { data: { 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', + 'sentry.source': 'custom', }, span_id: spanId, status: 'ok', diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index b32ca6324cb7..8292152b420e 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -1,6 +1,6 @@ import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { addBreadcrumb, getClient, setTag, withIsolationScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addBreadcrumb, getClient, setTag, withIsolationScope } from '@sentry/core'; import type { PropagationContext, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -31,9 +31,11 @@ describe('Integration | Transactions', () => { { op: 'test op', name: 'test name', - source: 'task', origin: 'auto.test', metadata: { requestPath: 'test-path' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', + }, }, span => { addBreadcrumb({ message: 'test breadcrumb 2', timestamp: 123456 }); @@ -86,6 +88,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), @@ -106,7 +109,6 @@ describe('Integration | Transactions', () => { transaction: 'test name', }), sampleRate: 1, - source: 'task', spanMetadata: expect.any(Object), requestPath: 'test-path', }), @@ -250,6 +252,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), @@ -296,6 +299,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op b', 'sentry.origin': 'manual', + 'sentry.source': 'custom', }, op: 'test op b', span_id: expect.any(String), @@ -377,6 +381,7 @@ describe('Integration | Transactions', () => { 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', + 'sentry.source': 'task', }, op: 'test op', span_id: expect.any(String), diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 5785eb7fdb00..fa6e1d09dad8 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -218,8 +218,10 @@ describe('trace', () => { name: 'outer', op: 'my-op', origin: 'auto.test.origin', - source: 'task', metadata: { requestPath: 'test-path' }, + attributes: { + [InternalSentrySemanticAttributes.SOURCE]: 'task', + }, }, span => { expect(span).toBeDefined(); @@ -335,7 +337,9 @@ describe('trace', () => { name: 'outer', op: 'my-op', origin: 'auto.test.origin', - source: 'task', + attributes: { + [InternalSentrySemanticAttributes.SOURCE]: 'task', + }, metadata: { requestPath: 'test-path' }, }); diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index 10e91837149d..347e8a2f73e8 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -78,8 +78,8 @@ export function startPageloadSpan(): void { op: 'pageload', origin: 'auto.pageload.remix', tags: DEFAULT_TAGS, - metadata: { - source: 'url', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }; @@ -103,8 +103,8 @@ function startNavigationSpan(matches: RouteMatch[]): void { op: 'navigation', origin: 'auto.navigation.remix', tags: DEFAULT_TAGS, - metadata: { - source: 'route', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, }; diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 19e541fd4ebc..7eb30e46af12 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getActiveTransaction, getClient, @@ -415,13 +416,13 @@ export function startRequestHandlerTransaction( op: 'http.server', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.remix', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, tags: { method: request.method, }, ...traceparentData, metadata: { - source, dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, }, }); diff --git a/packages/sveltekit/src/client/load.ts b/packages/sveltekit/src/client/load.ts index 3e454bdb364a..621015badbf1 100644 --- a/packages/sveltekit/src/client/load.ts +++ b/packages/sveltekit/src/client/load.ts @@ -1,4 +1,9 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, handleCallbackErrors, startSpan } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + handleCallbackErrors, + startSpan, +} from '@sentry/core'; import { captureException } from '@sentry/svelte'; import { addNonEnumerableProperty, objectify } from '@sentry/utils'; import type { LoadEvent } from '@sveltejs/kit'; @@ -86,12 +91,10 @@ export function wrapLoadWithSentry any>(origLoad: T) op: 'function.sveltekit.load', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, status: 'ok', - metadata: { - source: routeId ? 'route' : 'url', - }, }, () => handleCallbackErrors(() => wrappingTarget.apply(thisArg, [patchedEvent]), sendErrorToSentry), ); diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index e06838cca413..cc53f7541923 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -1,5 +1,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getCurrentScope, getDynamicSamplingContextFromSpan, @@ -179,12 +180,12 @@ async function instrumentHandle( op: 'http.server', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', }, name: `${event.request.method} ${event.route?.id || event.url.pathname}`, status: 'ok', ...traceparentData, metadata: { - source: event.route?.id ? 'route' : 'url', dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, }, }, diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts index 9728dcf47b5b..0d73b27a9d1b 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server/load.ts @@ -1,5 +1,10 @@ /* eslint-disable @sentry-internal/sdk/no-optional-chaining */ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getCurrentScope, startSpan } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getCurrentScope, + startSpan, +} from '@sentry/core'; import { captureException } from '@sentry/node'; import { addNonEnumerableProperty, objectify } from '@sentry/utils'; import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; @@ -69,12 +74,10 @@ export function wrapLoadWithSentry any>(origLoad: T) op: 'function.sveltekit.load', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, status: 'ok', - metadata: { - source: routeId ? 'route' : 'url', - }, }; try { @@ -138,11 +141,11 @@ export function wrapServerLoadWithSentry any>(origSe op: 'function.sveltekit.server.load', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, status: 'ok', metadata: { - source: routeId ? 'route' : 'url', dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, }, data: { diff --git a/packages/sveltekit/test/client/load.test.ts b/packages/sveltekit/test/client/load.test.ts index ca7c1d625224..3c625865cf1c 100644 --- a/packages/sveltekit/test/client/load.test.ts +++ b/packages/sveltekit/test/client/load.test.ts @@ -3,7 +3,7 @@ import type { Load } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { wrapLoadWithSentry } from '../../src/client/load'; const mockCaptureException = vi.spyOn(SentrySvelte, 'captureException').mockImplementation(() => 'xx'); @@ -109,13 +109,11 @@ describe('wrapLoadWithSentry', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, op: 'function.sveltekit.load', name: '/users/[id]', status: 'ok', - metadata: { - source: 'route', - }, }, expect.any(Function), ); @@ -139,13 +137,11 @@ describe('wrapLoadWithSentry', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, op: 'function.sveltekit.load', name: '/users/123', status: 'ok', - metadata: { - source: 'url', - }, }, expect.any(Function), ); diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 9bba3c99e68e..019eab5992d0 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,4 +1,4 @@ -import { addTracingExtensions } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions } from '@sentry/core'; import { NodeClient, setCurrentClient } from '@sentry/node'; import * as SentryNode from '@sentry/node'; import type { Transaction } from '@sentry/types'; @@ -133,7 +133,7 @@ describe('handleSentry', () => { expect(ref.name).toEqual('GET /users/[id]'); expect(ref.op).toEqual('http.server'); expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); - expect(ref.metadata.source).toEqual('route'); + expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); expect(ref.endTimestamp).toBeDefined(); expect(ref.spanRecorder.spans).toHaveLength(1); @@ -169,7 +169,7 @@ describe('handleSentry', () => { expect(ref.name).toEqual('GET /users/[id]'); expect(ref.op).toEqual('http.server'); expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); - expect(ref.metadata.source).toEqual('route'); + expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); expect(ref.endTimestamp).toBeDefined(); diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 388541a8d884..790ded799106 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, addTracingExtensions } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions } from '@sentry/core'; import * as SentryNode from '@sentry/node'; import type { Load, ServerLoad } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit'; @@ -199,13 +199,11 @@ describe('wrapLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, op: 'function.sveltekit.load', name: '/users/[id]', status: 'ok', - metadata: { - source: 'route', - }, }, expect.any(Function), ); @@ -220,13 +218,11 @@ describe('wrapLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, op: 'function.sveltekit.load', name: '/users/123', status: 'ok', - metadata: { - source: 'url', - }, }, expect.any(Function), ); @@ -256,6 +252,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, op: 'function.sveltekit.server.load', name: '/users/[id]', @@ -275,7 +272,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', }, - source: 'route', }, }, expect.any(Function), @@ -291,16 +287,15 @@ describe('wrapServerLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, op: 'function.sveltekit.server.load', name: '/users/[id]', status: 'ok', + metadata: {}, data: { 'http.method': 'GET', }, - metadata: { - source: 'route', - }, }, expect.any(Function), ); @@ -315,6 +310,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, op: 'function.sveltekit.server.load', name: '/users/[id]', @@ -327,7 +323,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { }, metadata: { dynamicSamplingContext: {}, - source: 'route', }, }, expect.any(Function), @@ -346,6 +341,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, op: 'function.sveltekit.server.load', name: '/users/123', @@ -365,7 +361,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { trace_id: '1234567890abcdef1234567890abcdef', transaction: 'dogpark', }, - source: 'url', }, }, expect.any(Function), diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index 95dee0c42f5e..ad70af5aa666 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -236,16 +236,15 @@ export const browserTracingIntegration = ((_options: Partial( startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, op: 'pageload', origin: 'auto.pageload.browser', - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, }); } @@ -58,7 +61,9 @@ export function instrumentRoutingWithDefaults( name: WINDOW.location.pathname, op: 'navigation', origin: 'auto.navigation.browser', - metadata: { source: 'url' }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, }); } }); diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index 7dabd973252e..617a7d981cf4 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -376,7 +376,7 @@ function instrumentRouter(appOrRouter: ExpressRouter): void { const transaction = res.__sentry_transaction; const attributes = (transaction && spanToJSON(transaction).data) || {}; - if (transaction && attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] !== 'custom') { + if (transaction && attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'url') { // If the request URL is '/' or empty, the reconstructed route will be empty. // Therefore, we fall back to setting the final route to '/' in this case. const finalRoute = req._reconstructedRoute || '/'; diff --git a/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts b/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts index 7171d41592bd..2655ae05dbe7 100644 --- a/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts +++ b/packages/tracing-internal/test/browser/browserTracingIntegration.test.ts @@ -233,6 +233,7 @@ describe('browserTracingIntegration', () => { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -266,6 +267,7 @@ describe('browserTracingIntegration', () => { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', testy: 'yes', }, span_id: expect.any(String), @@ -327,6 +329,7 @@ describe('browserTracingIntegration', () => { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, span_id: expect.any(String), start_timestamp: expect.any(Number), @@ -360,6 +363,7 @@ describe('browserTracingIntegration', () => { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test', [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', testy: 'yes', }, span_id: expect.any(String), diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index be16c8cf092e..fb93661607aa 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -1,5 +1,12 @@ /* eslint-disable deprecation/deprecation */ -import { TRACING_DEFAULTS, getClient, getCurrentHub, setCurrentClient, spanToJSON } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + TRACING_DEFAULTS, + getClient, + getCurrentHub, + setCurrentClient, + spanToJSON, +} from '@sentry/core'; import * as hubExtensions from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents, HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; @@ -240,7 +247,7 @@ describe('BrowserTracing', () => { const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.name).toBe('newName'); - expect(transaction.metadata.source).toBe('custom'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); }); @@ -252,13 +259,17 @@ describe('BrowserTracing', () => { createBrowserTracing(true, { beforeNavigate: mockBeforeNavigation, routingInstrumentation: (customStartTransaction: (obj: any) => void) => { - customStartTransaction({ name: 'a/path', op: 'pageload', metadata: { source: 'url' } }); + customStartTransaction({ + name: 'a/path', + op: 'pageload', + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' }, + }); }, }); const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(transaction.name).toBe('a/path'); - expect(transaction.metadata.source).toBe('url'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); }); diff --git a/packages/tracing-internal/test/browser/router.test.ts b/packages/tracing-internal/test/browser/router.test.ts index a27926ad9803..e7a12ea91506 100644 --- a/packages/tracing-internal/test/browser/router.test.ts +++ b/packages/tracing-internal/test/browser/router.test.ts @@ -1,3 +1,4 @@ +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import type { HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; @@ -45,7 +46,7 @@ conditionalTest({ min: 16 })('instrumentRoutingWithDefaults', () => { name: 'blank', op: 'pageload', origin: 'auto.pageload.browser', - metadata: { source: 'url' }, + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' }, startTimestamp: expect.any(Number), }); }); @@ -80,7 +81,7 @@ conditionalTest({ min: 16 })('instrumentRoutingWithDefaults', () => { name: 'blank', op: 'navigation', origin: 'auto.navigation.browser', - metadata: { source: 'url' }, + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' }, }); }); diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 8975b3762a64..f65ceb75eaf6 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -12,7 +12,7 @@ import { setCurrentClient, spanToJSON, } from '@sentry/core'; -import type { BaseTransportOptions, ClientOptions, TransactionSource } from '@sentry/types'; +import type { BaseTransportOptions, ClientOptions } from '@sentry/types'; import { TRACEPARENT_REGEXP, Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; @@ -667,8 +667,8 @@ describe('SentrySpan', () => { const transaction = new Transaction( { name: 'tx', - metadata: { - source: 'url', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }, getCurrentHub(), @@ -681,12 +681,12 @@ describe('SentrySpan', () => { test.each([ ['is included if transaction source is paremeterized route/url', 'route'], ['is included if transaction source is a custom name', 'custom'], - ])('%s', (_: string, source) => { + ] as const)('%s', (_, source) => { const transaction = new Transaction( { name: 'tx', - metadata: { - ...(source && { source: source as TransactionSource }), + attributes: { + ...(source && { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source }), }, }, getCurrentHub(), diff --git a/packages/tracing/test/transaction.test.ts b/packages/tracing/test/transaction.test.ts index f9e950dd9cb9..55d162becc21 100644 --- a/packages/tracing/test/transaction.test.ts +++ b/packages/tracing/test/transaction.test.ts @@ -16,17 +16,20 @@ describe('`Transaction` class', () => { describe('transaction name source', () => { it('sets source in constructor if provided', () => { - const transaction = new Transaction({ name: 'dogpark', metadata: { source: 'route' } }); + const transaction = new Transaction({ + name: 'dogpark', + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' }, + }); expect(transaction.name).toEqual('dogpark'); - expect(transaction.metadata.source).toEqual('route'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); }); it("sets source to be `'custom'` in constructor if not provided", () => { const transaction = new Transaction({ name: 'dogpark' }); expect(transaction.name).toEqual('dogpark'); - expect(transaction.metadata.source).toBe('custom'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); }); it("sets source to `'custom'` when assigning to `name` property", () => { @@ -34,7 +37,7 @@ describe('`Transaction` class', () => { transaction.name = 'ballpit'; expect(transaction.name).toEqual('ballpit'); - expect(transaction.metadata.source).toEqual('custom'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); it('sets instrumenter to be `sentry` in constructor if not provided', () => { @@ -55,7 +58,7 @@ describe('`Transaction` class', () => { transaction.setName('ballpit'); expect(transaction.name).toEqual('ballpit'); - expect(transaction.metadata.source).toEqual('custom'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); it('uses given `source` value', () => { @@ -63,7 +66,7 @@ describe('`Transaction` class', () => { transaction.setName('ballpit', 'route'); expect(transaction.name).toEqual('ballpit'); - expect(transaction.metadata.source).toEqual('route'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); }); }); @@ -74,7 +77,7 @@ describe('`Transaction` class', () => { transaction.updateName('ballpit'); expect(transaction.name).toEqual('ballpit'); - expect(transaction.metadata.source).toEqual('route'); + expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); }); }); }); @@ -170,6 +173,7 @@ describe('`Transaction` class', () => { data: { [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, span_id: transaction.spanId, trace_id: transaction.traceId, @@ -201,6 +205,7 @@ describe('`Transaction` class', () => { data: { [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, span_id: transaction.spanId, trace_id: transaction.traceId, diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 69704d497b8f..f9e29a188327 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -2,7 +2,7 @@ import type { TraceContext } from './context'; import type { Instrumenter } from './instrumenter'; import type { Primitive } from './misc'; import type { HrTime } from './opentelemetry'; -import type { Transaction } from './transaction'; +import type { Transaction, TransactionSource } from './transaction'; type SpanOriginType = 'manual' | 'auto'; type SpanOriginCategory = string; // e.g. http, db, ui, .... @@ -26,7 +26,7 @@ export type SpanAttributeValue = export type SpanAttributes = Partial<{ 'sentry.origin': string; 'sentry.op': string; - 'sentry.source': string; + 'sentry.source': TransactionSource; 'sentry.sample_rate': number; }> & Record; diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index 49b92b2e218a..df22d28de7fc 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -109,7 +109,7 @@ export interface Transaction extends TransactionContext, Omit Date: Mon, 19 Feb 2024 13:03:01 +0100 Subject: [PATCH 091/173] ref: Refactor remaining `makeMain` usage (#10713) Instead, use a combination of `setCurrentClient()` and `client.init()`. I also refactored the domain ACS to avoid using the hub on the carrier (removing this overall will happen in a follow up PR). I also removed the tracing `hub` test as that should be covered in other places and uses a lot of internal hub stuff etc. --- packages/gatsby/gatsby-browser.js | 7 +- packages/gatsby/test/gatsby-browser.test.ts | 31 +- packages/node/src/async/domain.ts | 20 +- packages/node/test/async/domain.test.ts | 8 +- packages/node/test/async/hooks.test.ts | 8 +- packages/node/test/handlers.test.ts | 119 ++-- packages/node/test/integrations/http.test.ts | 72 +- .../node/test/integrations/undici.test.ts | 29 +- packages/tracing/test/hub.test.ts | 637 ------------------ 9 files changed, 141 insertions(+), 790 deletions(-) delete mode 100644 packages/tracing/test/hub.test.ts diff --git a/packages/gatsby/gatsby-browser.js b/packages/gatsby/gatsby-browser.js index 3eff2faf439f..98621dbb43e9 100644 --- a/packages/gatsby/gatsby-browser.js +++ b/packages/gatsby/gatsby-browser.js @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import { init } from '@sentry/gatsby'; +import { getClient, init } from '@sentry/gatsby'; export function onClientEntry(_, pluginParams) { const isIntialized = isSentryInitialized(); @@ -32,10 +32,7 @@ export function onClientEntry(_, pluginParams) { } function isSentryInitialized() { - // Although `window` should exist because we're in the browser (where this script - // is run), and `__SENTRY__.hub` is created when importing the Gatsby SDK, double - // check that in case something weird happens. - return !!(window && window.__SENTRY__ && window.__SENTRY__.hub && window.__SENTRY__.hub.getClient()); + return !!getClient(); } function areSentryOptionsDefined(params) { diff --git a/packages/gatsby/test/gatsby-browser.test.ts b/packages/gatsby/test/gatsby-browser.test.ts index cf456a9d3e9d..117de1cdd0ec 100644 --- a/packages/gatsby/test/gatsby-browser.test.ts +++ b/packages/gatsby/test/gatsby-browser.test.ts @@ -1,8 +1,6 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - +import type { Client } from '@sentry/types'; import { onClientEntry } from '../gatsby-browser'; -import { browserTracingIntegration } from '../src/index'; +import { browserTracingIntegration, getCurrentScope, getIsolationScope, setCurrentClient } from '../src/index'; (global as any).__SENTRY_RELEASE__ = '683f3a6ab819d47d23abfca9a914c81f0524d35b'; (global as any).__SENTRY_DSN__ = 'https://examplePublicKey@o0.ingest.sentry.io/0'; @@ -50,24 +48,21 @@ describe('onClientEntry', () => { }); describe('inits Sentry once', () => { + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + getCurrentScope().setClient(undefined); + }); + afterEach(() => { - delete (window as any).__SENTRY__; (global.console.warn as jest.Mock).mockClear(); (global.console.error as jest.Mock).mockClear(); }); - function setMockedSentryInWindow() { - (window as any).__SENTRY__ = { - hub: { - getClient: () => ({ - // Empty object mocking the client - }), - }, - }; - } - it('initialized in injected config, without pluginParams', () => { - setMockedSentryInWindow(); + const client = {} as Client; + setCurrentClient(client); + onClientEntry(undefined, { plugins: [] }); // eslint-disable-next-line no-console expect(console.warn).not.toHaveBeenCalled(); @@ -77,7 +72,9 @@ describe('onClientEntry', () => { }); it('initialized in injected config, with pluginParams', () => { - setMockedSentryInWindow(); + const client = {} as Client; + setCurrentClient(client); + onClientEntry(undefined, { plugins: [], dsn: 'dsn', release: 'release' }); // eslint-disable-next-line no-console expect((console.warn as jest.Mock).mock.calls[0]).toMatchInlineSnapshot(` diff --git a/packages/node/src/async/domain.ts b/packages/node/src/async/domain.ts index a39c281e5412..904f949441de 100644 --- a/packages/node/src/async/domain.ts +++ b/packages/node/src/async/domain.ts @@ -1,26 +1,32 @@ import * as domain from 'domain'; -import type { Carrier } from '@sentry/core'; import { getGlobalHub } from '@sentry/core'; import { Hub as HubClass } from '@sentry/core'; -import { ensureHubOnCarrier, getHubFromCarrier, setAsyncContextStrategy, setHubOnCarrier } from '@sentry/core'; +import { setAsyncContextStrategy } from '@sentry/core'; import type { Client, Hub, Scope } from '@sentry/types'; +type DomainWithHub = domain.Domain & { + hub?: Hub; +}; + function getActiveDomain(): T | undefined { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any return (domain as any).active as T | undefined; } function getCurrentDomainHub(): Hub | undefined { - const activeDomain = getActiveDomain(); + const activeDomain = getActiveDomain(); // If there's no active domain, just return undefined and the global hub will be used if (!activeDomain) { return undefined; } - ensureHubOnCarrier(activeDomain); + if (activeDomain.hub) { + return activeDomain.hub; + } - return getHubFromCarrier(activeDomain); + activeDomain.hub = getCurrentHub(); + return activeDomain.hub; } function getCurrentHub(): Hub { @@ -33,11 +39,11 @@ function withExecutionContext( isolationScope: Scope, callback: () => T, ): T { - const local = domain.create() as domain.Domain & Carrier; + const local = domain.create() as DomainWithHub; // eslint-disable-next-line deprecation/deprecation const newHub = new HubClass(client, scope, isolationScope); - setHubOnCarrier(local, newHub); + local.hub = newHub; return local.bind(() => { return callback(); diff --git a/packages/node/test/async/domain.test.ts b/packages/node/test/async/domain.test.ts index 79dd835b0fbb..5e06695ed2f6 100644 --- a/packages/node/test/async/domain.test.ts +++ b/packages/node/test/async/domain.test.ts @@ -1,5 +1,6 @@ /* eslint-disable deprecation/deprecation */ -import { Hub, getCurrentHub, getCurrentScope, makeMain, setAsyncContextStrategy, withScope } from '@sentry/core'; +import type { Hub } from '@sentry/core'; +import { getCurrentHub, getCurrentScope, setAsyncContextStrategy, withScope } from '@sentry/core'; import { getIsolationScope, withIsolationScope } from '@sentry/core'; import type { Scope } from '@sentry/types'; @@ -7,9 +8,8 @@ import { setDomainAsyncContextStrategy } from '../../src/async/domain'; describe('setDomainAsyncContextStrategy()', () => { beforeEach(() => { - const hub = new Hub(); - - makeMain(hub); + getCurrentScope().clear(); + getIsolationScope().clear(); }); afterEach(() => { diff --git a/packages/node/test/async/hooks.test.ts b/packages/node/test/async/hooks.test.ts index bf2e0443fc24..8cbd575b4dd6 100644 --- a/packages/node/test/async/hooks.test.ts +++ b/packages/node/test/async/hooks.test.ts @@ -1,10 +1,9 @@ /* eslint-disable deprecation/deprecation */ +import type { Hub } from '@sentry/core'; import { - Hub, getCurrentHub, getCurrentScope, getIsolationScope, - makeMain, setAsyncContextStrategy, withIsolationScope, withScope, @@ -15,9 +14,8 @@ import { setHooksAsyncContextStrategy } from '../../src/async/hooks'; describe('setHooksAsyncContextStrategy()', () => { beforeEach(() => { - const hub = new Hub(); - - makeMain(hub); + getCurrentScope().clear(); + getIsolationScope().clear(); }); afterEach(() => { diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 8c3d7ccad90c..960d25e7bcad 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -2,15 +2,15 @@ import * as http from 'http'; import * as sentryCore from '@sentry/core'; import { Hub, - Scope as ScopeClass, Transaction, getClient, getCurrentScope, getIsolationScope, getMainCarrier, - makeMain, mergeScopeData, + setCurrentClient, spanToJSON, + withScope, } from '@sentry/core'; import type { Event, PropagationContext, Scope } from '@sentry/types'; import { SentryError } from '@sentry/utils'; @@ -21,10 +21,14 @@ import { getDefaultNodeClientOptions } from './helper/node-client-options'; describe('requestHandler', () => { beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + // Ensure we reset a potentially set acs to use the default const sentry = getMainCarrier().__SENTRY__; if (sentry) { sentry.acs = undefined; + sentry.hub = undefined; } }); @@ -38,7 +42,6 @@ describe('requestHandler', () => { const sentryRequestMiddleware = requestHandler(); let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; - let client: NodeClient; function createNoOpSpy() { const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it @@ -63,11 +66,8 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); - client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); let isolationScope: Scope; sentryRequestMiddleware(req, res, () => { @@ -83,11 +83,8 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not set requestSession, when handling a request', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); - client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); let isolationScope: Scope; sentryRequestMiddleware(req, res, () => { @@ -103,11 +100,8 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, calls _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); - client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); const captureRequestSession = jest.spyOn(client, '_captureRequestSession'); @@ -128,11 +122,8 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not call _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); - client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); const captureRequestSession = jest.spyOn(client, '_captureRequestSession'); @@ -179,10 +170,8 @@ describe('requestHandler', () => { }); it('stores request and request data options in `sdkProcessingMetadata`', done => { - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(new NodeClient(getDefaultNodeClientOptions())); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(getDefaultNodeClientOptions()); + setCurrentClient(client); const requestHandlerOptions = { include: { ip: false } }; const sentryRequestMiddleware = requestHandler(requestHandlerOptions); @@ -208,6 +197,18 @@ describe('requestHandler', () => { }); describe('tracingHandler', () => { + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + + // Ensure we reset a potentially set acs to use the default + const sentry = getMainCarrier().__SENTRY__; + if (sentry) { + sentry.acs = undefined; + sentry.hub = undefined; + } + }); + const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' }; const method = 'wagging'; const protocol = 'mutualsniffing'; @@ -218,7 +219,7 @@ describe('tracingHandler', () => { const sentryTracingMiddleware = tracingHandler(); - let hub: Hub, req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; + let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; function createNoOpSpy() { const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it @@ -226,10 +227,8 @@ describe('tracingHandler', () => { } beforeEach(() => { - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(new NodeClient(getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }))); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(getDefaultNodeClientOptions({ tracesSampleRate: 1.0 })); + setCurrentClient(client); req = { headers, @@ -349,12 +348,10 @@ describe('tracingHandler', () => { it('extracts request data for sampling context', () => { const tracesSampler = jest.fn(); const options = getDefaultNodeClientOptions({ tracesSampler }); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(new NodeClient(options)); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); - hub.run(() => { + withScope(() => { sentryTracingMiddleware(req, res, next); expect(tracesSampler).toHaveBeenCalledWith( @@ -373,10 +370,8 @@ describe('tracingHandler', () => { it('puts its transaction on the scope', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(new NodeClient(options)); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const client = new NodeClient(options); + setCurrentClient(client); sentryTracingMiddleware(req, res, next); @@ -521,6 +516,18 @@ describe('tracingHandler', () => { }); describe('errorHandler()', () => { + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + + // Ensure we reset a potentially set acs to use the default + const sentry = getMainCarrier().__SENTRY__; + if (sentry) { + sentry.acs = undefined; + sentry.hub = undefined; + } + }); + const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' }; const method = 'wagging'; const protocol = 'mutualsniffing'; @@ -561,10 +568,7 @@ describe('errorHandler()', () => { // by the`requestHandler`) client.initSessionFlusher(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); jest.spyOn(client, '_captureRequestSession'); @@ -585,11 +589,9 @@ describe('errorHandler()', () => { it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); + setCurrentClient(client); jest.spyOn(client, '_captureRequestSession'); - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); getIsolationScope().setRequestSession({ status: 'ok' }); @@ -611,15 +613,12 @@ describe('errorHandler()', () => { // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new ScopeClass(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + + setCurrentClient(client); jest.spyOn(client, '_captureRequestSession'); - hub.run(() => { + withScope(() => { getIsolationScope().setRequestSession({ status: 'ok' }); sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { expect(getIsolationScope().getRequestSession()).toEqual({ status: 'crashed' }); @@ -633,11 +632,7 @@ describe('errorHandler()', () => { // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new ScopeClass(); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client, scope); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); jest.spyOn(client, '_captureRequestSession'); @@ -656,11 +651,7 @@ describe('errorHandler()', () => { it('stores request in `sdkProcessingMetadata`', done => { const options = getDefaultNodeClientOptions({}); client = new NodeClient(options); - - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); let isolationScope: Scope; sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index af550ef70643..6dbc6b9c8b62 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,10 +1,11 @@ import * as http from 'http'; import * as https from 'https'; -import type { SentrySpan } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { getCurrentHub, getIsolationScope, setCurrentClient } from '@sentry/core'; import { Transaction } from '@sentry/core'; -import { getCurrentScope, makeMain, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; -import { Hub, addTracingExtensions } from '@sentry/core'; +import { getCurrentScope, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; +import { addTracingExtensions } from '@sentry/core'; import type { TransactionContext } from '@sentry/types'; import { TRACEPARENT_REGEXP, logger } from '@sentry/utils'; import * as nock from 'nock'; @@ -26,6 +27,11 @@ const originalHttpGet = http.get; const originalHttpRequest = http.request; describe('tracing', () => { + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + }); + afterEach(() => { // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(undefined); @@ -67,12 +73,8 @@ describe('tracing', () => { ...customOptions, }); const client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - // eslint-disable-next-line deprecation/deprecation - hub.bindClient(client); + setCurrentClient(client); + client.init(); } it("creates a span for each outgoing non-sentry request when there's a transaction on the scope", () => { @@ -271,14 +273,16 @@ describe('tracing', () => { environment: 'production', instrumenter: 'otel', }); + const client = new NodeClient(options); + setCurrentClient(client); // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(new NodeClient(options)); + const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation const integration = new HttpIntegration(); integration.setupOnce( () => {}, - () => hub, + () => hub as Hub, ); expect(loggerLogSpy).toBeCalledWith('HTTP Integration is skipped because of instrumenter configuration.'); @@ -373,12 +377,10 @@ describe('tracing', () => { }); const client = new NodeClient(options); + setCurrentClient(client); + client.init(); // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); - - return hub; + return getCurrentHub(); } function createTransactionAndPutOnScope() { @@ -401,7 +403,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); const transaction = createTransactionAndPutOnScope(); @@ -449,7 +451,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); createTransactionAndPutOnScope(); @@ -482,7 +484,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); createTransactionAndPutOnScope(); @@ -511,7 +513,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); const transaction = createTransactionAndPutOnScope(); @@ -559,7 +561,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); createTransactionAndPutOnScope(); @@ -592,7 +594,7 @@ describe('tracing', () => { httpIntegration.setupOnce( () => undefined, - () => hub, + () => hub as Hub, ); createTransactionAndPutOnScope(); @@ -625,10 +627,8 @@ describe('default protocols', () => { }, }); const client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); return p; } @@ -713,10 +713,8 @@ describe('httpIntegration', () => { environment: 'production', }); const client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('converts default options', () => { @@ -774,10 +772,8 @@ describe('httpIntegration', () => { describe('_shouldCreateSpans', () => { beforeEach(function () { - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + getCurrentScope().clear(); + getIsolationScope().clear(); }); it.each([ @@ -789,6 +785,8 @@ describe('_shouldCreateSpans', () => { [{ enableIfHasTracingEnabled: true }, { tracesSampleRate: 0 }, true], [{}, {}, true], ])('works with tracing=%p and clientOptions=%p', (tracing, clientOptions, expected) => { + const client = new NodeClient(getDefaultNodeClientOptions(clientOptions)); + setCurrentClient(client); const actual = _shouldCreateSpans(tracing, clientOptions); expect(actual).toEqual(expected); }); @@ -796,10 +794,8 @@ describe('_shouldCreateSpans', () => { describe('_getShouldCreateSpanForRequest', () => { beforeEach(function () { - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + getCurrentScope().clear(); + getIsolationScope().clear(); }); it.each([ diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 0e34294b1d1c..7119fb375b82 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -4,12 +4,13 @@ import { getActiveSpan, getClient, getCurrentScope, + getIsolationScope, + getMainCarrier, setCurrentClient, startSpan, withIsolationScope, } from '@sentry/core'; import { spanToTraceHeader } from '@sentry/core'; -import { Hub, makeMain } from '@sentry/core'; import { fetch } from 'undici'; import { NodeClient } from '../../src/client'; @@ -20,13 +21,10 @@ import { conditionalTest } from '../utils'; const SENTRY_DSN = 'https://0@0.ingest.sentry.io/0'; -let hub: Hub; - beforeAll(async () => { try { await setupTestServer(); } catch (e) { - // eslint-disable-next-line no-console const error = new Error('Undici integration tests are skipped because test server could not be set up.'); // This needs lib es2022 and newer so marking as any (error as any).cause = e; @@ -38,14 +36,22 @@ const DEFAULT_OPTIONS = getDefaultNodeClientOptions({ dsn: SENTRY_DSN, tracesSampler: () => true, integrations: [nativeNodeFetchintegration()], + debug: true, }); beforeEach(() => { + // Ensure we reset a potentially set acs to use the default + const sentry = getMainCarrier().__SENTRY__; + if (sentry) { + sentry.acs = undefined; + sentry.hub = undefined; + } + + getCurrentScope().clear(); + getIsolationScope().clear(); const client = new NodeClient(DEFAULT_OPTIONS); - // eslint-disable-next-line deprecation/deprecation - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); afterEach(() => { @@ -148,7 +154,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { }); }); - it('creates a span for invalid looking urls', async () => { + it('creates a span for invalid looking urls xxx', async () => { await startSpan({ name: 'outer-span' }, async outerSpan => { try { // Intentionally add // to the url @@ -395,10 +401,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { environment: 'production', }); const client = new NodeClient(options); - // eslint-disable-next-line deprecation/deprecation - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); }); it.each([ diff --git a/packages/tracing/test/hub.test.ts b/packages/tracing/test/hub.test.ts deleted file mode 100644 index b12966e6b3dc..000000000000 --- a/packages/tracing/test/hub.test.ts +++ /dev/null @@ -1,637 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { BrowserClient } from '@sentry/browser'; -import { Hub, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, makeMain } from '@sentry/core'; -import * as utilsModule from '@sentry/utils'; // for mocking -import { extractTraceparentData, logger } from '@sentry/utils'; - -import { BrowserTracing, TRACEPARENT_REGEXP, Transaction, addExtensionMethods } from '../src'; -import { - addDOMPropertiesToGlobal, - getDefaultBrowserClientOptions, - getSymbolObjectKeyByName, - testOnlyIfNodeVersionAtLeast, -} from './testutils'; - -addExtensionMethods(); - -const mathRandom = jest.spyOn(Math, 'random'); -jest.spyOn(Transaction.prototype, 'setAttribute'); -jest.spyOn(logger, 'warn'); -jest.spyOn(logger, 'log'); -jest.spyOn(logger, 'error'); -jest.spyOn(utilsModule, 'isNodeEnv'); - -addDOMPropertiesToGlobal(['XMLHttpRequest', 'Event', 'location', 'document']); - -describe('Hub', () => { - afterEach(() => { - jest.clearAllMocks(); - // Reset global carrier to the initial state - const hub = new Hub(); - makeMain(hub); - }); - - describe('getTransaction()', () => { - it('should find a transaction which has been set on the scope if sampled = true', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - transaction.sampled = true; - - hub.getScope().setSpan(transaction); - - expect(hub.getScope().getTransaction()).toBe(transaction); - }); - - it('should find a transaction which has been set on the scope if sampled = false', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); - - hub.getScope().setSpan(transaction); - - expect(hub.getScope().getTransaction()).toBe(transaction); - }); - - it("should not find an open transaction if it's not on the scope", () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(hub.getScope().getTransaction()).toBeUndefined(); - }); - }); - - describe('transaction sampling', () => { - describe('default sample context', () => { - it('should add transaction context data to default sample context', () => { - const tracesSampler = jest.fn(); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - - const transactionContext = { - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: true, - }; - - hub.startTransaction(transactionContext); - - expect(tracesSampler).toHaveBeenLastCalledWith(expect.objectContaining({ transactionContext })); - }); - - it("should add parent's sampling decision to default sample context", () => { - const tracesSampler = jest.fn(); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const parentSamplingDecsion = false; - - hub.startTransaction({ - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: parentSamplingDecsion, - }); - - expect(tracesSampler).toHaveBeenLastCalledWith( - expect.objectContaining({ parentSampled: parentSamplingDecsion }), - ); - }); - }); - - describe('sample()', () => { - it('should set sampled = false when tracing is disabled', () => { - const options = getDefaultBrowserClientOptions({}); - // neither tracesSampleRate nor tracesSampler is defined -> tracing disabled - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(false); - }); - - it('should set sampled = false if tracesSampleRate is 0', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(false); - }); - - it('should set sampled = true if tracesSampleRate is 1', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(true); - }); - - it('should set sampled = true if tracesSampleRate is 1 (without global hub)', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const hub = new Hub(new BrowserClient(options)); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(true); - }); - - it("should call tracesSampler if it's defined", () => { - const tracesSampler = jest.fn(); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - }); - - it('should set sampled = false if tracesSampler returns 0', () => { - const tracesSampler = jest.fn().mockReturnValue(0); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(false); - }); - - it('should set sampled = true if tracesSampler returns 1', () => { - const tracesSampler = jest.fn().mockReturnValue(1); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(true); - }); - - it('should set sampled = true if tracesSampler returns 1 (without global hub)', () => { - const tracesSampler = jest.fn().mockReturnValue(1); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(true); - }); - - it('should set sampled = true if enableTracing is true', () => { - const options = getDefaultBrowserClientOptions({ enableTracing: true }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(true); - }); - - it('should set sampled = false if enableTracing is true & tracesSampleRate is 0', () => { - const options = getDefaultBrowserClientOptions({ enableTracing: true, tracesSampleRate: 0 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(false); - }); - - it('should set sampled = false if enableTracing is false & tracesSampleRate is 0', () => { - const options = getDefaultBrowserClientOptions({ enableTracing: false, tracesSampleRate: 0 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(transaction.sampled).toBe(false); - }); - - it('should prefer tracesSampler returning false to enableTracing', () => { - // make the two options do opposite things to prove precedence - const tracesSampler = jest.fn().mockReturnValue(false); - const options = getDefaultBrowserClientOptions({ enableTracing: true, tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(false); - }); - - it('should prefer tracesSampler returning true to enableTracing', () => { - // make the two options do opposite things to prove precedence - const tracesSampler = jest.fn().mockReturnValue(true); - const options = getDefaultBrowserClientOptions({ enableTracing: false, tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(true); - }); - - it('should not try to override explicitly set positive sampling decision', () => { - // so that the decision otherwise would be false - const tracesSampler = jest.fn().mockReturnValue(0); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark', sampled: true }); - - expect(transaction.sampled).toBe(true); - }); - - it('should not try to override explicitly set negative sampling decision', () => { - // so that the decision otherwise would be true - const tracesSampler = jest.fn().mockReturnValue(1); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); - - expect(transaction.sampled).toBe(false); - }); - - it('should prefer tracesSampler to tracesSampleRate', () => { - // make the two options do opposite things to prove precedence - const tracesSampler = jest.fn().mockReturnValue(true); - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0, tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(true); - }); - - it('should tolerate tracesSampler returning a boolean', () => { - const tracesSampler = jest.fn().mockReturnValue(true); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - expect(tracesSampler).toHaveBeenCalled(); - expect(transaction.sampled).toBe(true); - }); - - it('should record sampling method when sampling decision is explicitly set', () => { - const tracesSampler = jest.fn().mockReturnValue(0.1121); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark', sampled: true }); - - expect(Transaction.prototype.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, 1); - }); - - it('should record sampling method and rate when sampling decision comes from tracesSampler', () => { - const tracesSampler = jest.fn().mockReturnValue(0.1121); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(Transaction.prototype.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, 0.1121); - }); - - it('should record sampling method when sampling decision is inherited', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.1121 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark', parentSampled: true }); - - // length 2 because origin and op are set as attributes on span initialization - expect(Transaction.prototype.setAttribute).toHaveBeenCalledTimes(2); - }); - - it('should record sampling method and rate when sampling decision comes from traceSampleRate', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.1121 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(Transaction.prototype.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, 0.1121); - }); - }); - - describe('isValidSampleRate()', () => { - it("should reject tracesSampleRates which aren't numbers or booleans", () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 'dogs!' as any }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be a boolean or a number')); - }); - - it('should reject tracesSampleRates which are NaN', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 'dogs!' as any }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be a boolean or a number')); - }); - - // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 - it('should reject tracesSampleRates less than 0', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: -26 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be between 0 and 1')); - }); - - // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 - it('should reject tracesSampleRates greater than 1', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 26 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be between 0 and 1')); - }); - - it("should reject tracesSampler return values which aren't numbers or booleans", () => { - const tracesSampler = jest.fn().mockReturnValue('dogs!'); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be a boolean or a number')); - }); - - it('should reject tracesSampler return values which are NaN', () => { - const tracesSampler = jest.fn().mockReturnValue(NaN); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be a boolean or a number')); - }); - - // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 - it('should reject tracesSampler return values less than 0', () => { - const tracesSampler = jest.fn().mockReturnValue(-12); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be between 0 and 1')); - }); - - // the rate might be a boolean, but for our purposes, false is equivalent to 0 and true is equivalent to 1 - it('should reject tracesSampler return values greater than 1', () => { - const tracesSampler = jest.fn().mockReturnValue(31); - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - hub.startTransaction({ name: 'dogpark' }); - - expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('Sample rate must be between 0 and 1')); - }); - }); - - it('should drop transactions with sampled = false', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0 }); - const client = new BrowserClient(options); - jest.spyOn(client, 'captureEvent'); - - const hub = new Hub(client); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - jest.spyOn(transaction, 'end'); - transaction.end(); - - expect(transaction.sampled).toBe(false); - expect(transaction.end).toReturnWith(undefined); - expect(client.captureEvent).not.toBeCalled(); - }); - - it('should drop transactions when using wrong instrumenter', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1, instrumenter: 'otel' }); - const client = new BrowserClient(options); - jest.spyOn(client, 'captureEvent'); - - const hub = new Hub(client); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - - jest.spyOn(transaction, 'end'); - transaction.end(); - - expect(transaction.sampled).toBe(false); - expect(transaction.end).toReturnWith(undefined); - expect(client.captureEvent).not.toBeCalled(); - expect(logger.error).toHaveBeenCalledWith( - `A transaction was started with instrumenter=\`sentry\`, but the SDK is configured with the \`otel\` instrumenter. -The transaction will not be sampled. Please use the otel instrumentation to start transactions.`, - ); - }); - - describe('sampling inheritance', () => { - it('should propagate sampling decision to child spans', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: Math.random() }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark' }); - const child = transaction.startChild({ op: 'ball.chase' }); - - expect(child.sampled).toBe(transaction.sampled); - }); - - // TODO the way we dig out the headers to test them doesn't work on Node < 10 - testOnlyIfNodeVersionAtLeast(10)( - 'should propagate positive sampling decision to child transactions in XHR header', - async () => { - const options = getDefaultBrowserClientOptions({ - dsn: 'https://1231@dogs.are.great/1121', - tracesSampleRate: 1, - integrations: [new BrowserTracing()], - }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - - const transaction = hub.startTransaction({ name: 'dogpark' }); - hub.getScope().setSpan(transaction); - - const request = new XMLHttpRequest(); - await new Promise(resolve => { - request.timeout = 1; - request.onloadend = request.ontimeout = resolve; - request.open('GET', '/chase-partners'); - request.send(''); - }); - - // this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the - // `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than - // value - const headers = (request as any)[getSymbolObjectKeyByName(request, 'impl') as symbol].flag.requestHeaders; - - // check that sentry-trace header is added to request - expect(headers).toEqual( - expect.objectContaining({ 'sentry-trace': expect.stringMatching(TRACEPARENT_REGEXP) }), - ); - - // check that sampling decision is passed down correctly - expect(transaction.sampled).toBe(true); - expect(extractTraceparentData(headers['sentry-trace'])!.parentSampled).toBe(true); - }, - ); - - // TODO the way we dig out the headers to test them doesn't work on Node < 10 - testOnlyIfNodeVersionAtLeast(10)( - 'should propagate negative sampling decision to child transactions in XHR header', - async () => { - const options = getDefaultBrowserClientOptions({ - dsn: 'https://1231@dogs.are.great/1121', - tracesSampleRate: 1, - integrations: [new BrowserTracing()], - }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - - const transaction = hub.startTransaction({ name: 'dogpark', sampled: false }); - hub.getScope().setSpan(transaction); - - const request = new XMLHttpRequest(); - await new Promise(resolve => { - request.timeout = 1; - request.onloadend = request.ontimeout = resolve; - request.open('GET', '/chase-partners'); - request.send(''); - }); - - // this looks weird, it's true, but it's really just `request.impl.flag.requestHeaders` - it's just that the - // `impl` key is a symbol rather than a string, and therefore needs to be referred to by reference rather than - // value - const headers = (request as any)[getSymbolObjectKeyByName(request, 'impl') as symbol].flag.requestHeaders; - - // check that sentry-trace header is added to request - expect(headers).toEqual( - expect.objectContaining({ 'sentry-trace': expect.stringMatching(TRACEPARENT_REGEXP) }), - ); - - // check that sampling decision is passed down correctly - expect(transaction.sampled).toBe(false); - expect(extractTraceparentData(headers['sentry-trace'])!.parentSampled).toBe(false); - }, - ); - - it('should propagate positive sampling decision to child transactions in fetch header', () => { - // TODO - }); - - it('should propagate negative sampling decision to child transactions in fetch header', () => { - // TODO - }); - - it("should inherit parent's positive sampling decision if tracesSampler is undefined", () => { - // we know that without inheritance we'll get sampled = false (since our "random" number won't be below the - // sample rate), so make parent's decision the opposite to prove that inheritance takes precedence over - // tracesSampleRate - mathRandom.mockReturnValueOnce(1); - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 0.5 }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const parentSamplingDecsion = true; - - const transaction = hub.startTransaction({ - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: parentSamplingDecsion, - }); - - expect(transaction.sampled).toBe(parentSamplingDecsion); - }); - - it("should inherit parent's negative sampling decision if tracesSampler is undefined", () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - // tracesSampleRate = 1 means every transaction should end up with sampled = true, so make parent's decision the - // opposite to prove that inheritance takes precedence over tracesSampleRate - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - const parentSamplingDecsion = false; - - const transaction = hub.startTransaction({ - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: parentSamplingDecsion, - }); - - expect(transaction.sampled).toBe(parentSamplingDecsion); - }); - - it("should ignore parent's positive sampling decision when tracesSampler is defined", () => { - // this tracesSampler causes every transaction to end up with sampled = true, so make parent's decision the - // opposite to prove that tracesSampler takes precedence over inheritance - const tracesSampler = () => true; - const parentSamplingDecsion = false; - - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - - const transaction = hub.startTransaction({ - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: parentSamplingDecsion, - }); - - expect(transaction.sampled).not.toBe(parentSamplingDecsion); - }); - - it("should ignore parent's negative sampling decision when tracesSampler is defined", () => { - // this tracesSampler causes every transaction to end up with sampled = false, so make parent's decision the - // opposite to prove that tracesSampler takes precedence over inheritance - const tracesSampler = () => false; - const parentSamplingDecsion = true; - - const options = getDefaultBrowserClientOptions({ tracesSampler }); - const hub = new Hub(new BrowserClient(options)); - makeMain(hub); - - const transaction = hub.startTransaction({ - name: 'dogpark', - parentSpanId: '12312012', - parentSampled: parentSamplingDecsion, - }); - - expect(transaction.sampled).not.toBe(parentSamplingDecsion); - }); - }); - }); - - describe('trimming transaction', () => { - describe('it should trim a transaction to the span timestamp if trimEnd is true', () => { - const options = getDefaultBrowserClientOptions({ - tracesSampleRate: 1, - dsn: 'https://username@domain/123', - }); - const client = new BrowserClient(options); - const hub = new Hub(client); - - const captureEventSpy = jest.spyOn(hub, 'captureEvent'); - - makeMain(hub); - const transaction = hub.startTransaction({ name: 'dogpark', startTimestamp: 1000, trimEnd: true }); - - transaction.startChild({ op: 'test', startTimestamp: 1200, endTimestamp: 1500 }); - - transaction.end(2000); - - expect(captureEventSpy).toHaveBeenCalledTimes(1); - expect(captureEventSpy.mock.calls[0][0].timestamp).toEqual(1500); - }); - }); -}); From 041c484c03543c4ccd509c02cc98399fba3b831d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 19 Feb 2024 13:05:32 +0100 Subject: [PATCH 092/173] fix: Export session API (#10711) --- packages/browser/src/exports.ts | 3 +++ packages/bun/src/index.ts | 3 +++ packages/deno/src/index.ts | 3 +++ packages/node/src/index.ts | 3 +++ packages/serverless/src/index.ts | 3 +++ 5 files changed, 15 insertions(+) diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index b4f58f09ac50..82a524f2bf76 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -67,6 +67,9 @@ export { functionToStringIntegration, inboundFiltersIntegration, parameterize, + startSession, + captureSession, + endSession, } from '@sentry/core'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index a1b052eae2a4..28ffa8f73d84 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -77,6 +77,9 @@ export { linkedErrorsIntegration, requestDataIntegration, parameterize, + startSession, + captureSession, + endSession, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 8decb4e56ec3..fd325263e84f 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -78,6 +78,9 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + startSession, + captureSession, + endSession, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 3cc1d560de39..713a2571f72e 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -76,6 +76,9 @@ export { linkedErrorsIntegration, requestDataIntegration, metricsDefault as metrics, + startSession, + captureSession, + endSession, } from '@sentry/core'; export { diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 564e0bd35776..f02117ce33c1 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -100,4 +100,7 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + startSession, + captureSession, + endSession, } from '@sentry/node'; From 7362857bc891291ebba24bf37caa660cc5df3507 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 19 Feb 2024 14:04:35 +0100 Subject: [PATCH 093/173] feat: Remove `hub` from global, `hub.run` & hub utilities (#10718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes `hub` on the global, as well as `getHubFromCarrier`, `hub.run()`, and some hub utilities: `getHubFromCarrier`, `ensureHubOnCarrier` and `setHubOnCarrier`. note that `makeMain` remains as a noop for now to not break vite-plugin v0.6.0 😿 We can remove this eventually. --- packages/astro/src/index.server.ts | 1 - packages/browser/src/exports.ts | 1 - packages/browser/test/unit/index.test.ts | 16 ++-- packages/bun/src/index.ts | 1 - packages/core/src/asyncContext.ts | 2 - packages/core/src/hub.ts | 94 ++----------------- packages/core/src/index.ts | 3 - packages/core/test/lib/global.test.ts | 40 -------- packages/deno/src/index.ts | 1 - packages/node-experimental/src/sdk/hub.ts | 5 - packages/node/src/index.ts | 1 - packages/node/test/handlers.test.ts | 3 - packages/node/test/index.test.ts | 9 +- .../node/test/integrations/undici.test.ts | 1 - .../opentelemetry/src/custom/getCurrentHub.ts | 5 - packages/remix/src/index.server.ts | 1 - packages/serverless/src/index.ts | 1 - packages/sveltekit/src/server/index.ts | 1 - packages/types/src/hub.ts | 9 -- packages/vercel-edge/src/index.ts | 1 - 20 files changed, 20 insertions(+), 176 deletions(-) delete mode 100644 packages/core/test/lib/global.test.ts diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 0c046e855e8d..7a8ff4427e12 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -16,7 +16,6 @@ export { captureCheckIn, withMonitor, createTransport, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 82a524f2bf76..301c681dc14a 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -31,7 +31,6 @@ export { close, createTransport, flush, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index 46104ba79b0f..6272adc653b8 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -58,21 +58,21 @@ describe('SentryBrowser', () => { describe('getContext() / setContext()', () => { it('should store/load extra', () => { getCurrentScope().setExtra('abc', { def: [1] }); - expect(global.__SENTRY__.hub._stack[0].scope._extra).toEqual({ + expect(getCurrentScope().getScopeData().extra).toEqual({ abc: { def: [1] }, }); }); it('should store/load tags', () => { getCurrentScope().setTag('abc', 'def'); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ + expect(getCurrentScope().getScopeData().tags).toEqual({ abc: 'def', }); }); it('should store/load user', () => { getCurrentScope().setUser({ id: 'def' }); - expect(global.__SENTRY__.hub._stack[0].scope._user).toEqual({ + expect(getCurrentScope().getScopeData().user).toEqual({ id: 'def', }); }); @@ -294,20 +294,20 @@ describe('SentryBrowser initialization', () => { it('should use window.SENTRY_RELEASE to set release on initialization if available', () => { global.SENTRY_RELEASE = { id: 'foobar' }; init({ dsn }); - expect(global.__SENTRY__.hub._stack[0].client.getOptions().release).toBe('foobar'); + expect(getClient()?.getOptions().release).toBe('foobar'); delete global.SENTRY_RELEASE; }); it('should use initialScope', () => { init({ dsn, initialScope: { tags: { a: 'b' } } }); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ a: 'b' }); + expect(getCurrentScope().getScopeData().tags).toEqual({ a: 'b' }); }); it('should use initialScope Scope', () => { const scope = new Scope(); scope.setTags({ a: 'b' }); init({ dsn, initialScope: scope }); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ a: 'b' }); + expect(getCurrentScope().getScopeData().tags).toEqual({ a: 'b' }); }); it('should use initialScope callback', () => { @@ -318,13 +318,13 @@ describe('SentryBrowser initialization', () => { return scope; }, }); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ a: 'b' }); + expect(getCurrentScope().getScopeData().tags).toEqual({ a: 'b' }); }); it('should have initialization proceed as normal if window.SENTRY_RELEASE is not set', () => { // This is mostly a happy-path test to ensure that the initialization doesn't throw an error. init({ dsn }); - expect(global.__SENTRY__.hub._stack[0].client.getOptions().release).toBeUndefined(); + expect(getClient()?.getOptions().release).toBeUndefined(); }); describe('SDK metadata', () => { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 28ffa8f73d84..6b06bd604244 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -35,7 +35,6 @@ export { flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/core/src/asyncContext.ts b/packages/core/src/asyncContext.ts index 9ff2fff996f0..fa47ce8aa020 100644 --- a/packages/core/src/asyncContext.ts +++ b/packages/core/src/asyncContext.ts @@ -53,7 +53,6 @@ export interface Carrier { } interface SentryCarrier { - hub?: Hub; acs?: AsyncContextStrategy; /** * Extra Hub properties injected by various SDKs @@ -96,7 +95,6 @@ export function getSentryCarrier(carrier: Carrier): SentryCarrier { if (!carrier.__SENTRY__) { carrier.__SENTRY__ = { extensions: {}, - hub: undefined, }; } return carrier.__SENTRY__; diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index f092af47f512..0fa6d239a26f 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -421,20 +421,6 @@ export class Hub implements HubInterface { this.getIsolationScope().setContext(name, context); } - /** - * @inheritDoc - */ - public run(callback: (hub: Hub) => void): void { - // eslint-disable-next-line deprecation/deprecation - const oldHub = makeMain(this); - try { - callback(this); - } finally { - // eslint-disable-next-line deprecation/deprecation - makeMain(oldHub); - } - } - /** * @inheritDoc * @deprecated Use `Sentry.getClient().getIntegrationByName()` instead. @@ -618,23 +604,8 @@ Sentry.init({...}); * @deprecated Use `setCurrentClient()` instead. */ export function makeMain(hub: HubInterface): HubInterface { - const registry = getMainCarrier(); - const oldHub = getHubFromCarrier(registry); - setHubOnCarrier(registry, hub); - return oldHub; -} - -/** - * This will set passed {@link Hub} on the passed object's __SENTRY__.hub attribute - * @param carrier object - * @param hub Hub - * @returns A boolean indicating success or failure - */ -export function setHubOnCarrier(carrier: Carrier, hub: HubInterface): boolean { - if (!carrier) return false; - const sentry = getSentryCarrier(carrier); - sentry.hub = hub; - return true; + // noop! + return hub; } /** @@ -681,69 +652,18 @@ export function getDefaultIsolationScope(): Scope { */ export function getGlobalHub(): HubInterface { const registry = getMainCarrier(); + const sentry = getSentryCarrier(registry) as { hub?: HubInterface }; // If there's no hub, or its an old API, assign a new one - - if ( - !hasHubOnCarrier(registry) || - // eslint-disable-next-line deprecation/deprecation - getHubFromCarrier(registry).isOlderThan(API_VERSION) - ) { - // eslint-disable-next-line deprecation/deprecation - setHubOnCarrier(registry, new Hub(undefined, getDefaultCurrentScope(), getDefaultIsolationScope())); - } - - // Return hub that lives on a global object - return getHubFromCarrier(registry); -} - -/** - * This will tell whether a carrier has a hub on it or not - * @param carrier object - */ -function hasHubOnCarrier(carrier: Carrier): boolean { - return !!getSentryCarrier(carrier).hub; -} - -/** - * This will create a new {@link Hub} and add to the passed object on - * __SENTRY__.hub. - * @param carrier object - * @hidden - */ -export function getHubFromCarrier(carrier: Carrier): HubInterface { - const sentry = getSentryCarrier(carrier); - if (!sentry.hub) { - // eslint-disable-next-line deprecation/deprecation - sentry.hub = new Hub(); + if (sentry.hub) { + return sentry.hub; } + // eslint-disable-next-line deprecation/deprecation + sentry.hub = new Hub(undefined, getDefaultCurrentScope(), getDefaultIsolationScope()); return sentry.hub; } -/** - * @private Private API with no semver guarantees! - * - * If the carrier does not contain a hub, a new hub is created with the global hub client and scope. - */ -export function ensureHubOnCarrier(carrier: Carrier, parent: HubInterface = getGlobalHub()): void { - // If there's no hub on current domain, or it's an old API, assign a new one - if ( - !hasHubOnCarrier(carrier) || - // eslint-disable-next-line deprecation/deprecation - getHubFromCarrier(carrier).isOlderThan(API_VERSION) - ) { - // eslint-disable-next-line deprecation/deprecation - const client = parent.getClient(); - // eslint-disable-next-line deprecation/deprecation - const scope = parent.getScope(); - // eslint-disable-next-line deprecation/deprecation - const isolationScope = parent.getIsolationScope(); - // eslint-disable-next-line deprecation/deprecation - setHubOnCarrier(carrier, new Hub(client, scope.clone() as Scope, isolationScope.clone() as Scope)); - } -} - /** * Get the current async context strategy. * If none has been setup, the default will be used. diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8e69f6aba5af..56d86d3426c9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -35,12 +35,9 @@ export { export { // eslint-disable-next-line deprecation/deprecation getCurrentHub, - getHubFromCarrier, Hub, // eslint-disable-next-line deprecation/deprecation makeMain, - setHubOnCarrier, - ensureHubOnCarrier, getGlobalHub, getDefaultCurrentScope, getDefaultIsolationScope, diff --git a/packages/core/test/lib/global.test.ts b/packages/core/test/lib/global.test.ts deleted file mode 100644 index 7e6bdf58ac9e..000000000000 --- a/packages/core/test/lib/global.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable deprecation/deprecation */ - -import { GLOBAL_OBJ } from '@sentry/utils'; - -import { Hub, getCurrentHub, getHubFromCarrier } from '../../src/hub'; - -describe('global', () => { - test('getGlobalHub', () => { - expect(getCurrentHub()).toBeTruthy(); - expect(GLOBAL_OBJ.__SENTRY__.hub).toBeTruthy(); - }); - - test('getHubFromCarrier', () => { - const bla = { a: 'b' }; - getHubFromCarrier(bla as any); - expect((bla as any).__SENTRY__.hub).toBeTruthy(); - expect((bla as any).__SENTRY__.hub).toBe((bla as any).__SENTRY__.hub); - getHubFromCarrier(bla as any); - }); - - test('getGlobalHub', () => { - const newestHub = new Hub(undefined, undefined, undefined, 999999); - GLOBAL_OBJ.__SENTRY__.hub = newestHub; - expect(getCurrentHub()).toBe(newestHub); - }); - - test('hub extension methods receive correct hub instance', () => { - const newestHub = new Hub(undefined, undefined, undefined, 999999); - GLOBAL_OBJ.__SENTRY__.hub = newestHub; - const fn = jest.fn().mockImplementation(function (...args: []) { - // @ts-expect-error 'this' implicitly has type 'any' because it does not have a type annotation - expect(this).toBe(newestHub); - expect(args).toEqual([1, 2, 3]); - }); - GLOBAL_OBJ.__SENTRY__.extensions = {}; - GLOBAL_OBJ.__SENTRY__.extensions.testy = fn; - (getCurrentHub() as any)._callExtensionMethod('testy', 1, 2, 3); - expect(fn).toBeCalled(); - }); -}); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index fd325263e84f..873cb4ba2b38 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -34,7 +34,6 @@ export { flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index ef652ba01fbf..4de88d4788c7 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -72,11 +72,6 @@ export function getCurrentHub(): Hub { setExtras, setContext, - run(callback: (hub: Hub) => void): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return withScope(() => callback(this as any)); - }, - getIntegration(integration: IntegrationClass): T | null { // eslint-disable-next-line deprecation/deprecation return getClient().getIntegration(integration); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 713a2571f72e..519e969af141 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -34,7 +34,6 @@ export { flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 960d25e7bcad..f64aa08a9129 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -28,7 +28,6 @@ describe('requestHandler', () => { const sentry = getMainCarrier().__SENTRY__; if (sentry) { sentry.acs = undefined; - sentry.hub = undefined; } }); @@ -205,7 +204,6 @@ describe('tracingHandler', () => { const sentry = getMainCarrier().__SENTRY__; if (sentry) { sentry.acs = undefined; - sentry.hub = undefined; } }); @@ -524,7 +522,6 @@ describe('errorHandler()', () => { const sentry = getMainCarrier().__SENTRY__; if (sentry) { sentry.acs = undefined; - sentry.hub = undefined; } }); diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 70061fe1d01c..3b9a52272b61 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -56,21 +56,22 @@ describe('SentryNode', () => { describe('getContext() / setContext()', () => { test('store/load extra', async () => { getCurrentScope().setExtra('abc', { def: [1] }); - expect(global.__SENTRY__.hub._stack[0].scope._extra).toEqual({ + + expect(getCurrentScope().getScopeData().extra).toEqual({ abc: { def: [1] }, }); }); test('store/load tags', async () => { getCurrentScope().setTag('abc', 'def'); - expect(global.__SENTRY__.hub._stack[0].scope._tags).toEqual({ + expect(getCurrentScope().getScopeData().tags).toEqual({ abc: 'def', }); }); test('store/load user', async () => { getCurrentScope().setUser({ id: 'def' }); - expect(global.__SENTRY__.hub._stack[0].scope._user).toEqual({ + expect(getCurrentScope().getScopeData().user).toEqual({ id: 'def', }); }); @@ -384,7 +385,7 @@ describe('SentryNode initialization', () => { test('global.SENTRY_RELEASE is used to set release on initialization if available', () => { global.SENTRY_RELEASE = { id: 'foobar' }; init({ dsn }); - expect(global.__SENTRY__.hub._stack[0].client.getOptions().release).toEqual('foobar'); + expect(getClient()?.getOptions().release).toEqual('foobar'); // Unsure if this is needed under jest. global.SENTRY_RELEASE = undefined; }); diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 7119fb375b82..9bd28d33c713 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -44,7 +44,6 @@ beforeEach(() => { const sentry = getMainCarrier().__SENTRY__; if (sentry) { sentry.acs = undefined; - sentry.hub = undefined; } getCurrentScope().clear(); diff --git a/packages/opentelemetry/src/custom/getCurrentHub.ts b/packages/opentelemetry/src/custom/getCurrentHub.ts index 3e4f6639d188..5df6e1e1d36f 100644 --- a/packages/opentelemetry/src/custom/getCurrentHub.ts +++ b/packages/opentelemetry/src/custom/getCurrentHub.ts @@ -71,11 +71,6 @@ export function getCurrentHub(): Hub { setExtras, setContext, - run(callback: (hub: Hub) => void): void { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return withScope(() => callback(this as any)); - }, - getIntegration(integration: IntegrationClass): T | null { // eslint-disable-next-line deprecation/deprecation return getClient()?.getIntegration(integration) || null; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 9990595125e6..3f106fc8eecf 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -23,7 +23,6 @@ export { createTransport, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index f02117ce33c1..03a8539bf8f4 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -35,7 +35,6 @@ export { getCurrentScope, getGlobalScope, getIsolationScope, - getHubFromCarrier, getSpanStatusFromHttpCode, setHttpStatus, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index e31d15f50eb1..44e073da8ece 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -17,7 +17,6 @@ export { createTransport, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index 61463b9e98b1..7eec8d42b91a 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -197,15 +197,6 @@ export interface Hub { */ setContext(name: string, context: { [key: string]: any } | null): void; - /** - * For the duration of the callback, this hub will be set as the global current Hub. - * This function is useful if you want to run your own client and hook into an already initialized one - * e.g.: Reporting issues to your own sentry when running in your component while still using the users configuration. - * - * TODO v8: This will be merged with `withScope()` - */ - run(callback: (hub: Hub) => void): void; - /** * Returns the integration if installed on the current client. * diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 6374b534705e..416be96797f1 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -34,7 +34,6 @@ export { flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, From c970772bfac08ca23d2542179f393bb66554c49f Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 19 Feb 2024 14:06:05 +0100 Subject: [PATCH 094/173] feat: Allow passing `null` to `withActiveSpan` (#10717) --- packages/core/src/exports.ts | 10 +++-- packages/core/test/lib/scope.test.ts | 9 ++++ .../node-experimental/test/sdk/api.test.ts | 42 +++++++++++++++++++ packages/opentelemetry/src/trace.ts | 10 +++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index 12744baffb7a..eb9a95652792 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -127,16 +127,18 @@ export function setUser(user: User | null): ReturnType { } /** - * Forks the current scope and sets the provided span as active span in the context of the provided callback. + * Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be + * passed `null` to start an entirely new span tree. * - * @param span Spans started in the context of the provided callback will be children of this span. + * @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed, + * spans started within the callback will not be attached to a parent span. * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. * @returns the value returned from the provided callback function. */ -export function withActiveSpan(span: Span, callback: (scope: ScopeInterface) => T): T { +export function withActiveSpan(span: Span | null, callback: (scope: ScopeInterface) => T): T { return withScope(scope => { // eslint-disable-next-line deprecation/deprecation - scope.setSpan(span); + scope.setSpan(span || undefined); return callback(scope); }); } diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 27609f8214bf..360cd34938e5 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -582,4 +582,13 @@ describe('withActiveSpan()', () => { }); }); }); + + it('when `null` is passed, no span should be active within the callback', () => { + expect.assertions(1); + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + expect(getActiveSpan()).toBeUndefined(); + }); + }); + }); }); diff --git a/packages/node-experimental/test/sdk/api.test.ts b/packages/node-experimental/test/sdk/api.test.ts index 730f1144e69d..5465d288def9 100644 --- a/packages/node-experimental/test/sdk/api.test.ts +++ b/packages/node-experimental/test/sdk/api.test.ts @@ -53,4 +53,46 @@ describe('withActiveSpan()', () => { expect.anything(), ); }); + + it('when `null` is passed, no span should be active within the callback', () => { + expect.assertions(1); + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + expect(getActiveSpan()).toBeUndefined(); + }); + }); + }); + + it('when `null` is passed, should start a new trace for new spans', async () => { + const beforeSendTransaction = jest.fn(() => null); + mockSdkInit({ enableTracing: true, beforeSendTransaction }); + const client = getClient(); + + startSpan({ name: 'parent-span' }, () => { + withActiveSpan(null, () => { + startSpan({ name: 'child-span' }, () => {}); + }); + }); + + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(2); + + // The child span should be a child of the inactive span + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + transaction: 'parent-span', + spans: expect.not.arrayContaining([expect.objectContaining({ description: 'child-span' })]), + }), + expect.anything(), + ); + + // The floating span should be a separate transaction + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + transaction: 'child-span', + }), + expect.anything(), + ); + }); }); diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 694f7a274e02..83f69d385dfc 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -102,14 +102,16 @@ export function startInactiveSpan(spanContext: OpenTelemetrySpanContext): Span { } /** - * Forks the current scope and sets the provided span as active span in the context of the provided callback. + * Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be + * passed `null` to start an entirely new span tree. * - * @param span Spans started in the context of the provided callback will be children of this span. + * @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed, + * spans started within the callback will not be attached to a parent span. * @param callback Execution context in which the provided span will be active. Is passed the newly forked scope. * @returns the value returned from the provided callback function. */ -export function withActiveSpan(span: Span, callback: (scope: Scope) => T): T { - const newContextWithActiveSpan = trace.setSpan(context.active(), span); +export function withActiveSpan(span: Span | null, callback: (scope: Scope) => T): T { + const newContextWithActiveSpan = span ? trace.setSpan(context.active(), span) : trace.deleteSpan(context.active()); return context.with(newContextWithActiveSpan, () => callback(getCurrentScope())); } From 4f92ff0215bfe3c5ecfc35e069962ff2c106b105 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 19 Feb 2024 15:02:36 +0100 Subject: [PATCH 095/173] ref(node-experimental): Copy transport & client to node-experimental (#10720) One thing less to depend on from node! The only thing missing to be moved from node then is handlers & non-performance integrations - getting there! --- packages/core/src/sdk.ts | 19 +- packages/node-experimental/.eslintrc.js | 3 + packages/node-experimental/package.json | 3 +- .../src/integrations/http.ts | 9 +- packages/node-experimental/src/proxy/base.ts | 151 ++++++ .../node-experimental/src/proxy/helpers.ts | 71 +++ packages/node-experimental/src/proxy/index.ts | 226 ++++++++ .../src/proxy/parse-proxy-response.ts | 137 +++++ packages/node-experimental/src/sdk/client.ts | 22 +- packages/node-experimental/src/sdk/init.ts | 18 +- .../node-experimental/src/sdk/initOtel.ts | 6 +- packages/node-experimental/src/sdk/types.ts | 31 -- .../src/transports/http-module.ts | 29 + .../node-experimental/src/transports/http.ts | 174 ++++++ .../node-experimental/src/transports/index.ts | 3 + packages/node-experimental/src/types.ts | 87 ++- .../test/helpers/createSpan.ts | 30 -- .../getDefaultNodePreviewClientOptions.ts | 7 +- .../test/helpers/mockSdkInit.ts | 4 +- .../test/integration/breadcrumbs.test.ts | 14 +- .../test/integration/scope.test.ts | 6 +- .../test/integration/transactions.test.ts | 10 +- .../node-experimental/test/sdk/client.test.ts | 494 +++++++++++++++++- .../test/transports/http.test.ts | 416 +++++++++++++++ .../test/transports/https.test.ts | 389 ++++++++++++++ .../test/transports/test-server-certs.ts | 48 ++ yarn.lock | 5 + 27 files changed, 2287 insertions(+), 125 deletions(-) create mode 100644 packages/node-experimental/src/proxy/base.ts create mode 100644 packages/node-experimental/src/proxy/helpers.ts create mode 100644 packages/node-experimental/src/proxy/index.ts create mode 100644 packages/node-experimental/src/proxy/parse-proxy-response.ts delete mode 100644 packages/node-experimental/src/sdk/types.ts create mode 100644 packages/node-experimental/src/transports/http-module.ts create mode 100644 packages/node-experimental/src/transports/http.ts create mode 100644 packages/node-experimental/src/transports/index.ts delete mode 100644 packages/node-experimental/test/helpers/createSpan.ts create mode 100644 packages/node-experimental/test/transports/http.test.ts create mode 100644 packages/node-experimental/test/transports/https.test.ts create mode 100644 packages/node-experimental/test/transports/test-server-certs.ts diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index ef5f51a87b13..3356774e14da 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -1,4 +1,4 @@ -import type { Client, ClientOptions } from '@sentry/types'; +import type { Client, ClientOptions, Hub as HubInterface } from '@sentry/types'; import { consoleSandbox, logger } from '@sentry/utils'; import { getCurrentScope } from './currentScopes'; @@ -43,10 +43,19 @@ export function initAndBind( * Make the given client the current client. */ export function setCurrentClient(client: Client): void { + getCurrentScope().setClient(client); + + // is there a hub too? // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub() as Hub; + const hub = getCurrentHub(); + if (isHubClass(hub)) { + // eslint-disable-next-line deprecation/deprecation + const top = hub.getStackTop(); + top.client = client; + } +} + +function isHubClass(hub: HubInterface): hub is Hub { // eslint-disable-next-line deprecation/deprecation - const top = hub.getStackTop(); - top.client = client; - top.scope.setClient(client); + return !!(hub as Hub).getStackTop; } diff --git a/packages/node-experimental/.eslintrc.js b/packages/node-experimental/.eslintrc.js index 9899ea1b73d8..bec6469d0e28 100644 --- a/packages/node-experimental/.eslintrc.js +++ b/packages/node-experimental/.eslintrc.js @@ -5,5 +5,8 @@ module.exports = { extends: ['../../.eslintrc.js'], rules: { '@sentry-internal/sdk/no-optional-chaining': 'off', + '@sentry-internal/sdk/no-nullish-coalescing': 'off', + '@sentry-internal/sdk/no-unsupported-es6-methods': 'off', + '@sentry-internal/sdk/no-class-field-initializers': 'off', }, }; diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json index 5ee77ee6fc00..e3805a603faa 100644 --- a/packages/node-experimental/package.json +++ b/packages/node-experimental/package.json @@ -53,7 +53,8 @@ "@sentry/node": "7.100.0", "@sentry/opentelemetry": "7.100.0", "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/utils": "7.100.0", + "@types/node": "14.18.63" }, "optionalDependencies": { "opentelemetry-instrumentation-fetch-node": "1.1.0" diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index bc01d3dad071..14b163e5a309 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -1,4 +1,4 @@ -import type { ClientRequest, IncomingMessage, ServerResponse } from 'http'; +import type { ClientRequest, ServerResponse } from 'http'; import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; @@ -9,6 +9,7 @@ import { _INTERNAL, getClient, getSpanKind, setSpanMetadata } from '@sentry/open import type { IntegrationFn } from '@sentry/types'; import { setIsolationScope } from '../sdk/scope'; +import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module'; import { addOriginToSpan } from '../utils/addOriginToSpan'; import { getRequestUrl } from '../utils/getRequestUrl'; @@ -107,16 +108,16 @@ const _httpIntegration = ((options: HttpOptions = {}) => { export const httpIntegration = defineIntegration(_httpIntegration); /** Update the span with data we need. */ -function _updateSpan(span: Span, request: ClientRequest | IncomingMessage): void { +function _updateSpan(span: Span, request: ClientRequest | HTTPModuleRequestIncomingMessage): void { addOriginToSpan(span, 'auto.http.otel.http'); if (getSpanKind(span) === SpanKind.SERVER) { - setSpanMetadata(span, { request }); + setSpanMetadata(span, { request: request as HTTPModuleRequestIncomingMessage }); } } /** Add a breadcrumb for outgoing requests. */ -function _addRequestBreadcrumb(span: Span, response: IncomingMessage | ServerResponse): void { +function _addRequestBreadcrumb(span: Span, response: HTTPModuleRequestIncomingMessage | ServerResponse): void { if (getSpanKind(span) !== SpanKind.CLIENT) { return; } diff --git a/packages/node-experimental/src/proxy/base.ts b/packages/node-experimental/src/proxy/base.ts new file mode 100644 index 000000000000..e1ef24c3092e --- /dev/null +++ b/packages/node-experimental/src/proxy/base.ts @@ -0,0 +1,151 @@ +/** + * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7 + * With the following licence: + * + * (The MIT License) + * + * Copyright (c) 2013 Nathan Rajlich * + * + * 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 the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions:* + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software.* + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable @typescript-eslint/member-ordering */ +/* eslint-disable jsdoc/require-jsdoc */ +import * as http from 'http'; +import type * as net from 'net'; +import type { Duplex } from 'stream'; +import type * as tls from 'tls'; + +export * from './helpers'; + +interface HttpConnectOpts extends net.TcpNetConnectOpts { + secureEndpoint: false; + protocol?: string; +} + +interface HttpsConnectOpts extends tls.ConnectionOptions { + secureEndpoint: true; + protocol?: string; + port: number; +} + +export type AgentConnectOpts = HttpConnectOpts | HttpsConnectOpts; + +const INTERNAL = Symbol('AgentBaseInternalState'); + +interface InternalState { + defaultPort?: number; + protocol?: string; + currentSocket?: Duplex; +} + +export abstract class Agent extends http.Agent { + private [INTERNAL]: InternalState; + + // Set by `http.Agent` - missing from `@types/node` + options!: Partial; + keepAlive!: boolean; + + constructor(opts?: http.AgentOptions) { + super(opts); + this[INTERNAL] = {}; + } + + abstract connect( + req: http.ClientRequest, + options: AgentConnectOpts, + ): Promise | Duplex | http.Agent; + + /** + * Determine whether this is an `http` or `https` request. + */ + isSecureEndpoint(options?: AgentConnectOpts): boolean { + if (options) { + // First check the `secureEndpoint` property explicitly, since this + // means that a parent `Agent` is "passing through" to this instance. + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + if (typeof (options as any).secureEndpoint === 'boolean') { + return options.secureEndpoint; + } + + // If no explicit `secure` endpoint, check if `protocol` property is + // set. This will usually be the case since using a full string URL + // or `URL` instance should be the most common usage. + if (typeof options.protocol === 'string') { + return options.protocol === 'https:'; + } + } + + // Finally, if no `protocol` property was set, then fall back to + // checking the stack trace of the current call stack, and try to + // detect the "https" module. + const { stack } = new Error(); + if (typeof stack !== 'string') return false; + return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1); + } + + createSocket(req: http.ClientRequest, options: AgentConnectOpts, cb: (err: Error | null, s?: Duplex) => void): void { + const connectOpts = { + ...options, + secureEndpoint: this.isSecureEndpoint(options), + }; + Promise.resolve() + .then(() => this.connect(req, connectOpts)) + .then(socket => { + if (socket instanceof http.Agent) { + // @ts-expect-error `addRequest()` isn't defined in `@types/node` + return socket.addRequest(req, connectOpts); + } + this[INTERNAL].currentSocket = socket; + // @ts-expect-error `createSocket()` isn't defined in `@types/node` + super.createSocket(req, options, cb); + }, cb); + } + + createConnection(): Duplex { + const socket = this[INTERNAL].currentSocket; + this[INTERNAL].currentSocket = undefined; + if (!socket) { + throw new Error('No socket was returned in the `connect()` function'); + } + return socket; + } + + get defaultPort(): number { + return this[INTERNAL].defaultPort ?? (this.protocol === 'https:' ? 443 : 80); + } + + set defaultPort(v: number) { + if (this[INTERNAL]) { + this[INTERNAL].defaultPort = v; + } + } + + get protocol(): string { + return this[INTERNAL].protocol ?? (this.isSecureEndpoint() ? 'https:' : 'http:'); + } + + set protocol(v: string) { + if (this[INTERNAL]) { + this[INTERNAL].protocol = v; + } + } +} diff --git a/packages/node-experimental/src/proxy/helpers.ts b/packages/node-experimental/src/proxy/helpers.ts new file mode 100644 index 000000000000..119ffd9317ce --- /dev/null +++ b/packages/node-experimental/src/proxy/helpers.ts @@ -0,0 +1,71 @@ +/** + * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7 + * With the following licence: + * + * (The MIT License) + * + * Copyright (c) 2013 Nathan Rajlich * + * + * 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 the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions:* + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software.* + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* eslint-disable jsdoc/require-jsdoc */ +import * as http from 'http'; +import * as https from 'https'; +import type { Readable } from 'stream'; +// TODO (v8): Remove this when Node < 12 is no longer supported +import type { URL } from 'url'; + +export type ThenableRequest = http.ClientRequest & { + then: Promise['then']; +}; + +export async function toBuffer(stream: Readable): Promise { + let length = 0; + const chunks: Buffer[] = []; + for await (const chunk of stream) { + length += (chunk as Buffer).length; + chunks.push(chunk); + } + return Buffer.concat(chunks, length); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function json(stream: Readable): Promise { + const buf = await toBuffer(stream); + const str = buf.toString('utf8'); + try { + return JSON.parse(str); + } catch (_err: unknown) { + const err = _err as Error; + err.message += ` (input: ${str})`; + throw err; + } +} + +export function req(url: string | URL, opts: https.RequestOptions = {}): ThenableRequest { + const href = typeof url === 'string' ? url : url.href; + const req = (href.startsWith('https:') ? https : http).request(url, opts) as ThenableRequest; + const promise = new Promise((resolve, reject) => { + req.once('response', resolve).once('error', reject).end() as unknown as ThenableRequest; + }); + req.then = promise.then.bind(promise); + return req; +} diff --git a/packages/node-experimental/src/proxy/index.ts b/packages/node-experimental/src/proxy/index.ts new file mode 100644 index 000000000000..4129a9f65cd7 --- /dev/null +++ b/packages/node-experimental/src/proxy/index.ts @@ -0,0 +1,226 @@ +/** + * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7 + * With the following licence: + * + * (The MIT License) + * + * Copyright (c) 2013 Nathan Rajlich * + * + * 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 the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions:* + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software.* + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import assert from 'assert'; +import type * as http from 'http'; +import type { OutgoingHttpHeaders } from 'http'; +import * as net from 'net'; +import * as tls from 'tls'; +// TODO (v8): Remove this when Node < 12 is no longer supported +import { URL } from 'url'; +import { logger } from '@sentry/utils'; +import { Agent } from './base'; +import type { AgentConnectOpts } from './base'; +import { parseProxyResponse } from './parse-proxy-response'; + +function debug(...args: unknown[]): void { + logger.log('[https-proxy-agent]', ...args); +} + +type Protocol = T extends `${infer Protocol}:${infer _}` ? Protocol : never; + +type ConnectOptsMap = { + http: Omit; + https: Omit; +}; + +type ConnectOpts = { + [P in keyof ConnectOptsMap]: Protocol extends P ? ConnectOptsMap[P] : never; +}[keyof ConnectOptsMap]; + +export type HttpsProxyAgentOptions = ConnectOpts & + http.AgentOptions & { + headers?: OutgoingHttpHeaders | (() => OutgoingHttpHeaders); + }; + +/** + * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to + * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests. + * + * Outgoing HTTP requests are first tunneled through the proxy server using the + * `CONNECT` HTTP request method to establish a connection to the proxy server, + * and then the proxy server connects to the destination target and issues the + * HTTP request from the proxy server. + * + * `https:` requests have their socket connection upgraded to TLS once + * the connection to the proxy server has been established. + */ +export class HttpsProxyAgent extends Agent { + static protocols = ['http', 'https'] as const; + + readonly proxy: URL; + proxyHeaders: OutgoingHttpHeaders | (() => OutgoingHttpHeaders); + connectOpts: net.TcpNetConnectOpts & tls.ConnectionOptions; + + constructor(proxy: Uri | URL, opts?: HttpsProxyAgentOptions) { + super(opts); + this.options = {}; + this.proxy = typeof proxy === 'string' ? new URL(proxy) : proxy; + this.proxyHeaders = opts?.headers ?? {}; + debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href); + + // Trim off the brackets from IPv6 addresses + const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ''); + const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.proxy.protocol === 'https:' ? 443 : 80; + this.connectOpts = { + // Attempt to negotiate http/1.1 for proxy servers that support http/2 + ALPNProtocols: ['http/1.1'], + ...(opts ? omit(opts, 'headers') : null), + host, + port, + }; + } + + /** + * Called when the node-core HTTP client library is creating a + * new HTTP request. + */ + async connect(req: http.ClientRequest, opts: AgentConnectOpts): Promise { + const { proxy } = this; + + if (!opts.host) { + throw new TypeError('No "host" provided'); + } + + // Create a socket connection to the proxy server. + let socket: net.Socket; + if (proxy.protocol === 'https:') { + debug('Creating `tls.Socket`: %o', this.connectOpts); + const servername = this.connectOpts.servername || this.connectOpts.host; + socket = tls.connect({ + ...this.connectOpts, + servername: servername && net.isIP(servername) ? undefined : servername, + }); + } else { + debug('Creating `net.Socket`: %o', this.connectOpts); + socket = net.connect(this.connectOpts); + } + + const headers: OutgoingHttpHeaders = + typeof this.proxyHeaders === 'function' ? this.proxyHeaders() : { ...this.proxyHeaders }; + const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host; + let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`; + + // Inject the `Proxy-Authorization` header if necessary. + if (proxy.username || proxy.password) { + const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`; + headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`; + } + + headers.Host = `${host}:${opts.port}`; + + if (!headers['Proxy-Connection']) { + headers['Proxy-Connection'] = this.keepAlive ? 'Keep-Alive' : 'close'; + } + for (const name of Object.keys(headers)) { + payload += `${name}: ${headers[name]}\r\n`; + } + + const proxyResponsePromise = parseProxyResponse(socket); + + socket.write(`${payload}\r\n`); + + const { connect, buffered } = await proxyResponsePromise; + req.emit('proxyConnect', connect); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Not EventEmitter in Node types + this.emit('proxyConnect', connect, req); + + if (connect.statusCode === 200) { + req.once('socket', resume); + + if (opts.secureEndpoint) { + // The proxy is connecting to a TLS server, so upgrade + // this socket connection to a TLS connection. + debug('Upgrading socket connection to TLS'); + const servername = opts.servername || opts.host; + return tls.connect({ + ...omit(opts, 'host', 'path', 'port'), + socket, + servername: net.isIP(servername) ? undefined : servername, + }); + } + + return socket; + } + + // Some other status code that's not 200... need to re-play the HTTP + // header "data" events onto the socket once the HTTP machinery is + // attached so that the node core `http` can parse and handle the + // error status code. + + // Close the original socket, and a new "fake" socket is returned + // instead, so that the proxy doesn't get the HTTP request + // written to it (which may contain `Authorization` headers or other + // sensitive data). + // + // See: https://hackerone.com/reports/541502 + socket.destroy(); + + const fakeSocket = new net.Socket({ writable: false }); + fakeSocket.readable = true; + + // Need to wait for the "socket" event to re-play the "data" events. + req.once('socket', (s: net.Socket) => { + debug('Replaying proxy buffer for failed request'); + assert(s.listenerCount('data') > 0); + + // Replay the "buffered" Buffer onto the fake `socket`, since at + // this point the HTTP module machinery has been hooked up for + // the user. + s.push(buffered); + s.push(null); + }); + + return fakeSocket; + } +} + +function resume(socket: net.Socket | tls.TLSSocket): void { + socket.resume(); +} + +function omit( + obj: T, + ...keys: K +): { + [K2 in Exclude]: T[K2]; +} { + const ret = {} as { + [K in keyof typeof obj]: (typeof obj)[K]; + }; + let key: keyof typeof obj; + for (key in obj) { + if (!keys.includes(key)) { + ret[key] = obj[key]; + } + } + return ret; +} diff --git a/packages/node-experimental/src/proxy/parse-proxy-response.ts b/packages/node-experimental/src/proxy/parse-proxy-response.ts new file mode 100644 index 000000000000..e351945e3c0f --- /dev/null +++ b/packages/node-experimental/src/proxy/parse-proxy-response.ts @@ -0,0 +1,137 @@ +/** + * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7 + * With the following licence: + * + * (The MIT License) + * + * Copyright (c) 2013 Nathan Rajlich * + * + * 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 the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions:* + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software.* + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable jsdoc/require-jsdoc */ +import type { IncomingHttpHeaders } from 'http'; +import type { Readable } from 'stream'; +import { logger } from '@sentry/utils'; + +function debug(...args: unknown[]): void { + logger.log('[https-proxy-agent:parse-proxy-response]', ...args); +} + +export interface ConnectResponse { + statusCode: number; + statusText: string; + headers: IncomingHttpHeaders; +} + +export function parseProxyResponse(socket: Readable): Promise<{ connect: ConnectResponse; buffered: Buffer }> { + return new Promise((resolve, reject) => { + // we need to buffer any HTTP traffic that happens with the proxy before we get + // the CONNECT response, so that if the response is anything other than an "200" + // response code, then we can re-play the "data" events on the socket once the + // HTTP parser is hooked up... + let buffersLength = 0; + const buffers: Buffer[] = []; + + function read() { + const b = socket.read(); + if (b) ondata(b); + else socket.once('readable', read); + } + + function cleanup() { + socket.removeListener('end', onend); + socket.removeListener('error', onerror); + socket.removeListener('readable', read); + } + + function onend() { + cleanup(); + debug('onend'); + reject(new Error('Proxy connection ended before receiving CONNECT response')); + } + + function onerror(err: Error) { + cleanup(); + debug('onerror %o', err); + reject(err); + } + + function ondata(b: Buffer) { + buffers.push(b); + buffersLength += b.length; + + const buffered = Buffer.concat(buffers, buffersLength); + const endOfHeaders = buffered.indexOf('\r\n\r\n'); + + if (endOfHeaders === -1) { + // keep buffering + debug('have not received end of HTTP headers yet...'); + read(); + return; + } + + const headerParts = buffered.slice(0, endOfHeaders).toString('ascii').split('\r\n'); + const firstLine = headerParts.shift(); + if (!firstLine) { + socket.destroy(); + return reject(new Error('No header received from proxy CONNECT response')); + } + const firstLineParts = firstLine.split(' '); + const statusCode = +firstLineParts[1]; + const statusText = firstLineParts.slice(2).join(' '); + const headers: IncomingHttpHeaders = {}; + for (const header of headerParts) { + if (!header) continue; + const firstColon = header.indexOf(':'); + if (firstColon === -1) { + socket.destroy(); + return reject(new Error(`Invalid header from proxy CONNECT response: "${header}"`)); + } + const key = header.slice(0, firstColon).toLowerCase(); + const value = header.slice(firstColon + 1).trimStart(); + const current = headers[key]; + if (typeof current === 'string') { + headers[key] = [current, value]; + } else if (Array.isArray(current)) { + current.push(value); + } else { + headers[key] = value; + } + } + debug('got proxy server response: %o %o', firstLine, headers); + cleanup(); + resolve({ + connect: { + statusCode, + statusText, + headers, + }, + buffered, + }); + } + + socket.on('error', onerror); + socket.on('end', onend); + + read(); + }); +} diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index 4aa88d121414..6037df31c849 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -1,19 +1,27 @@ -import { NodeClient, SDK_VERSION } from '@sentry/node'; - +import * as os from 'os'; import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; -import { applySdkMetadata } from '@sentry/core'; +import type { ServerRuntimeClientOptions } from '@sentry/core'; +import { SDK_VERSION, ServerRuntimeClient, applySdkMetadata } from '@sentry/core'; +import type { NodeClientOptions } from '../types'; /** A client for using Sentry with Node & OpenTelemetry. */ -export class NodeExperimentalClient extends NodeClient { +export class NodeClient extends ServerRuntimeClient { public traceProvider: BasicTracerProvider | undefined; private _tracer: Tracer | undefined; - public constructor(options: ConstructorParameters[0]) { - applySdkMetadata(options, 'node-experimental'); + public constructor(options: NodeClientOptions) { + const clientOptions: ServerRuntimeClientOptions = { + ...options, + platform: 'node', + runtime: { name: 'node', version: global.process.version }, + serverName: options.serverName || global.process.env.SENTRY_NAME || os.hostname(), + }; + + applySdkMetadata(clientOptions, 'node-experimental'); - super(options); + super(clientOptions); } /** Get the OTEL tracer. */ diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index c190c934776b..78d9fa7ef572 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -10,7 +10,6 @@ import { defaultStackParser, getDefaultIntegrations as getDefaultNodeIntegrations, getSentryRelease, - makeNodeTransport, spotlightIntegration, } from '@sentry/node'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; @@ -27,8 +26,9 @@ import { DEBUG_BUILD } from '../debug-build'; import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations'; import { httpIntegration } from '../integrations/http'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; -import type { NodeExperimentalClientOptions, NodeExperimentalOptions } from '../types'; -import { NodeExperimentalClient } from './client'; +import { makeNodeTransport } from '../transports'; +import type { NodeClientOptions, NodeOptions } from '../types'; +import { NodeClient } from './client'; import { initOtel } from './initOtel'; const ignoredDefaultIntegrations = ['Http', 'Undici']; @@ -46,7 +46,7 @@ export function getDefaultIntegrations(options: Options): Integration[] { /** * Initialize Sentry for Node. */ -export function init(options: NodeExperimentalOptions | undefined = {}): void { +export function init(options: NodeOptions | undefined = {}): void { const clientOptions = getClientOptions(options); if (clientOptions.debug === true) { @@ -66,7 +66,7 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { const scope = getCurrentScope(); scope.update(options.initialScope); - const client = new NodeExperimentalClient(clientOptions); + const client = new NodeClient(clientOptions); // The client is on the current scope, from where it generally is inherited getCurrentScope().setClient(client); @@ -98,7 +98,7 @@ export function init(options: NodeExperimentalOptions | undefined = {}): void { initOtel(); } -function getClientOptions(options: NodeExperimentalOptions): NodeExperimentalClientOptions { +function getClientOptions(options: NodeOptions): NodeClientOptions { if (options.defaultIntegrations === undefined) { options.defaultIntegrations = getDefaultIntegrations(options); } @@ -126,7 +126,7 @@ function getClientOptions(options: NodeExperimentalOptions): NodeExperimentalCli tracesSampleRate, }); - const clientOptions: NodeExperimentalClientOptions = { + const clientOptions: NodeClientOptions = { ...baseOptions, ...options, ...overwriteOptions, @@ -141,7 +141,7 @@ function getClientOptions(options: NodeExperimentalOptions): NodeExperimentalCli return clientOptions; } -function getRelease(release: NodeExperimentalOptions['release']): string | undefined { +function getRelease(release: NodeOptions['release']): string | undefined { if (release !== undefined) { return release; } @@ -154,7 +154,7 @@ function getRelease(release: NodeExperimentalOptions['release']): string | undef return undefined; } -function getTracesSampleRate(tracesSampleRate: NodeExperimentalOptions['tracesSampleRate']): number | undefined { +function getTracesSampleRate(tracesSampleRate: NodeOptions['tracesSampleRate']): number | undefined { if (tracesSampleRate !== undefined) { return tracesSampleRate; } diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts index 1303b59483ff..af6de43f5e9a 100644 --- a/packages/node-experimental/src/sdk/initOtel.ts +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -8,14 +8,14 @@ import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { SentryContextManager } from '../otel/contextManager'; -import type { NodeExperimentalClient } from '../types'; import { getClient } from './api'; +import type { NodeClient } from './client'; /** * Initialize OpenTelemetry for Node. */ export function initOtel(): void { - const client = getClient(); + const client = getClient(); if (!client) { DEBUG_BUILD && @@ -43,7 +43,7 @@ export function initOtel(): void { } /** Just exported for tests. */ -export function setupOtel(client: NodeExperimentalClient): BasicTracerProvider { +export function setupOtel(client: NodeClient): BasicTracerProvider { // Create and configure NodeTracerProvider const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), diff --git a/packages/node-experimental/src/sdk/types.ts b/packages/node-experimental/src/sdk/types.ts deleted file mode 100644 index 686e67787916..000000000000 --- a/packages/node-experimental/src/sdk/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { - Attachment, - Breadcrumb, - Contexts, - EventProcessor, - Extras, - Primitive, - PropagationContext, - Scope, - SeverityLevel, - User, -} from '@sentry/types'; - -export interface ScopeData { - eventProcessors: EventProcessor[]; - breadcrumbs: Breadcrumb[]; - user: User; - tags: { [key: string]: Primitive }; - extra: Extras; - contexts: Contexts; - attachments: Attachment[]; - propagationContext: PropagationContext; - sdkProcessingMetadata: { [key: string]: unknown }; - fingerprint: string[]; - level?: SeverityLevel; -} - -export interface CurrentScopes { - scope: Scope; - isolationScope: Scope; -} diff --git a/packages/node-experimental/src/transports/http-module.ts b/packages/node-experimental/src/transports/http-module.ts new file mode 100644 index 000000000000..b4dd0492f4fd --- /dev/null +++ b/packages/node-experimental/src/transports/http-module.ts @@ -0,0 +1,29 @@ +import type { ClientRequest, IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http'; +import type { RequestOptions as HTTPSRequestOptions } from 'https'; +import type { URL } from 'url'; + +export type HTTPModuleRequestOptions = HTTPRequestOptions | HTTPSRequestOptions | string | URL; + +/** + * Cut version of http.IncomingMessage. + * Some transports work in a special Javascript environment where http.IncomingMessage is not available. + */ +export interface HTTPModuleRequestIncomingMessage { + headers: IncomingHttpHeaders; + statusCode?: number; + on(event: 'data' | 'end', listener: () => void): void; + setEncoding(encoding: string): void; +} + +/** + * Internal used interface for typescript. + * @hidden + */ +export interface HTTPModule { + /** + * Request wrapper + * @param options These are {@see TransportOptions} + * @param callback Callback when request is finished + */ + request(options: HTTPModuleRequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void): ClientRequest; +} diff --git a/packages/node-experimental/src/transports/http.ts b/packages/node-experimental/src/transports/http.ts new file mode 100644 index 000000000000..83d8bab5141a --- /dev/null +++ b/packages/node-experimental/src/transports/http.ts @@ -0,0 +1,174 @@ +import * as http from 'http'; +import * as https from 'https'; +import { Readable } from 'stream'; +import { URL } from 'url'; +import { createGzip } from 'zlib'; +import { createTransport } from '@sentry/core'; +import type { + BaseTransportOptions, + Transport, + TransportMakeRequestResponse, + TransportRequest, + TransportRequestExecutor, +} from '@sentry/types'; +import { consoleSandbox } from '@sentry/utils'; +import { HttpsProxyAgent } from '../proxy'; + +import type { HTTPModule } from './http-module'; + +export interface NodeTransportOptions extends BaseTransportOptions { + /** Define custom headers */ + headers?: Record; + /** Set a proxy that should be used for outbound requests. */ + proxy?: string; + /** HTTPS proxy CA certificates */ + caCerts?: string | Buffer | Array; + /** Custom HTTP module. Defaults to the native 'http' and 'https' modules. */ + httpModule?: HTTPModule; + /** Allow overriding connection keepAlive, defaults to false */ + keepAlive?: boolean; +} + +// Estimated maximum size for reasonable standalone event +const GZIP_THRESHOLD = 1024 * 32; + +/** + * Gets a stream from a Uint8Array or string + * Readable.from is ideal but was added in node.js v12.3.0 and v10.17.0 + */ +function streamFromBody(body: Uint8Array | string): Readable { + return new Readable({ + read() { + this.push(body); + this.push(null); + }, + }); +} + +/** + * Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry. + */ +export function makeNodeTransport(options: NodeTransportOptions): Transport { + let urlSegments: URL; + + try { + urlSegments = new URL(options.url); + } catch (e) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.', + ); + }); + return createTransport(options, () => Promise.resolve({})); + } + + const isHttps = urlSegments.protocol === 'https:'; + + // Proxy prioritization: http => `options.proxy` | `process.env.http_proxy` + // Proxy prioritization: https => `options.proxy` | `process.env.https_proxy` | `process.env.http_proxy` + const proxy = applyNoProxyOption( + urlSegments, + options.proxy || (isHttps ? process.env.https_proxy : undefined) || process.env.http_proxy, + ); + + const nativeHttpModule = isHttps ? https : http; + const keepAlive = options.keepAlive === undefined ? false : options.keepAlive; + + // TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node + // versions(>= 8) as they had memory leaks when using it: #2555 + const agent = proxy + ? (new HttpsProxyAgent(proxy) as http.Agent) + : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 }); + + const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent); + return createTransport(options, requestExecutor); +} + +/** + * Honors the `no_proxy` env variable with the highest priority to allow for hosts exclusion. + * + * @param transportUrl The URL the transport intends to send events to. + * @param proxy The client configured proxy. + * @returns A proxy the transport should use. + */ +function applyNoProxyOption(transportUrlSegments: URL, proxy: string | undefined): string | undefined { + const { no_proxy } = process.env; + + const urlIsExemptFromProxy = + no_proxy && + no_proxy + .split(',') + .some( + exemption => transportUrlSegments.host.endsWith(exemption) || transportUrlSegments.hostname.endsWith(exemption), + ); + + if (urlIsExemptFromProxy) { + return undefined; + } else { + return proxy; + } +} + +/** + * Creates a RequestExecutor to be used with `createTransport`. + */ +function createRequestExecutor( + options: NodeTransportOptions, + httpModule: HTTPModule, + agent: http.Agent, +): TransportRequestExecutor { + const { hostname, pathname, port, protocol, search } = new URL(options.url); + return function makeRequest(request: TransportRequest): Promise { + return new Promise((resolve, reject) => { + let body = streamFromBody(request.body); + + const headers: Record = { ...options.headers }; + + if (request.body.length > GZIP_THRESHOLD) { + headers['content-encoding'] = 'gzip'; + body = body.pipe(createGzip()); + } + + const req = httpModule.request( + { + method: 'POST', + agent, + headers, + hostname, + path: `${pathname}${search}`, + port, + protocol, + ca: options.caCerts, + }, + res => { + res.on('data', () => { + // Drain socket + }); + + res.on('end', () => { + // Drain socket + }); + + res.setEncoding('utf8'); + + // "Key-value pairs of header names and values. Header names are lower-cased." + // https://nodejs.org/api/http.html#http_message_headers + const retryAfterHeader = res.headers['retry-after'] ?? null; + const rateLimitsHeader = res.headers['x-sentry-rate-limits'] ?? null; + + resolve({ + statusCode: res.statusCode, + headers: { + 'retry-after': retryAfterHeader, + 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] : rateLimitsHeader, + }, + }); + }, + ); + + req.on('error', reject); + body.pipe(req); + }); + }; +} diff --git a/packages/node-experimental/src/transports/index.ts b/packages/node-experimental/src/transports/index.ts new file mode 100644 index 000000000000..ba59ba8878a4 --- /dev/null +++ b/packages/node-experimental/src/transports/index.ts @@ -0,0 +1,3 @@ +export type { NodeTransportOptions } from './http'; + +export { makeNodeTransport } from './http'; diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts index 70384ddbd3f8..9ac45c54b6ae 100644 --- a/packages/node-experimental/src/types.ts +++ b/packages/node-experimental/src/types.ts @@ -1,13 +1,88 @@ import type { Span as WriteableSpan } from '@opentelemetry/api'; import type { ReadableSpan, Span } from '@opentelemetry/sdk-trace-base'; -import type { NodeClient, NodeOptions } from '@sentry/node'; -import type { OpenTelemetryClient } from '@sentry/opentelemetry'; +import type { ClientOptions, Options, SamplingContext, Scope, TracePropagationTargets } from '@sentry/types'; -export type NodeExperimentalOptions = NodeOptions; -export type NodeExperimentalClientOptions = ConstructorParameters[0]; +import type { NodeTransportOptions } from './transports'; -export interface NodeExperimentalClient extends NodeClient, OpenTelemetryClient { - getOptions(): NodeExperimentalClientOptions; +export interface BaseNodeOptions { + /** + * List of strings/regex controlling to which outgoing requests + * the SDK will attach tracing headers. + * + * By default the SDK will attach those headers to all outgoing + * requests. If this option is provided, the SDK will match the + * request URL of outgoing requests against the items in this + * array, and only attach tracing headers if a match was found. + * + * @example + * ```js + * Sentry.init({ + * tracePropagationTargets: ['api.site.com'], + * }); + * ``` + */ + tracePropagationTargets?: TracePropagationTargets; + + /** + * Sets profiling sample rate when @sentry/profiling-node is installed + */ + profilesSampleRate?: number; + + /** + * Function to compute profiling sample rate dynamically and filter unwanted profiles. + * + * Profiling is enabled if either this or `profilesSampleRate` is defined. If both are defined, `profilesSampleRate` is + * ignored. + * + * Will automatically be passed a context object of default and optional custom data. See + * {@link Transaction.samplingContext} and {@link Hub.startTransaction}. + * + * @returns A sample rate between 0 and 1 (0 drops the profile, 1 guarantees it will be sent). Returning `true` is + * equivalent to returning 1 and returning `false` is equivalent to returning 0. + */ + profilesSampler?: (samplingContext: SamplingContext) => number | boolean; + + /** Sets an optional server name (device name) */ + serverName?: string; + + /** + * Include local variables with stack traces. + * + * Requires the `LocalVariables` integration. + */ + includeLocalVariables?: boolean; + + /** + * If you use Spotlight by Sentry during development, use + * this option to forward captured Sentry events to Spotlight. + * + * Either set it to true, or provide a specific Spotlight Sidecar URL. + * + * More details: https://spotlightjs.com/ + * + * IMPORTANT: Only set this option to `true` while developing, not in production! + */ + spotlight?: boolean | string; + + /** Callback that is executed when a fatal global error occurs. */ + onFatalError?(this: void, error: Error): void; +} + +/** + * Configuration options for the Sentry Node SDK + * @see @sentry/types Options for more information. + */ +export interface NodeOptions extends Options, BaseNodeOptions {} + +/** + * Configuration options for the Sentry Node SDK Client class + * @see NodeClient for more information. + */ +export interface NodeClientOptions extends ClientOptions, BaseNodeOptions {} + +export interface CurrentScopes { + scope: Scope; + isolationScope: Scope; } /** diff --git a/packages/node-experimental/test/helpers/createSpan.ts b/packages/node-experimental/test/helpers/createSpan.ts deleted file mode 100644 index af96dde1e994..000000000000 --- a/packages/node-experimental/test/helpers/createSpan.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Context, SpanContext } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import type { Tracer } from '@opentelemetry/sdk-trace-base'; -import { SentrySpan } from '@opentelemetry/sdk-trace-base'; -import { uuid4 } from '@sentry/utils'; - -export function createSpan( - name?: string, - { spanId, parentSpanId }: { spanId?: string; parentSpanId?: string } = {}, -): SentrySpan { - const spanProcessor = { - onStart: () => {}, - onEnd: () => {}, - }; - const tracer = { - resource: 'test-resource', - instrumentationLibrary: 'test-instrumentation-library', - getSpanLimits: () => ({}), - getActiveSpanProcessor: () => spanProcessor, - } as unknown as Tracer; - - const spanContext: SpanContext = { - spanId: spanId || uuid4(), - traceId: uuid4(), - traceFlags: 0, - }; - - // eslint-disable-next-line deprecation/deprecation - return new SentrySpan(tracer, {} as Context, name || 'test', spanContext, SpanKind.INTERNAL, parentSpanId); -} diff --git a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts index 00778e78582a..ec42177a1335 100644 --- a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts +++ b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts @@ -1,12 +1,11 @@ import { createTransport } from '@sentry/core'; import { resolvedSyncPromise } from '@sentry/utils'; -import type { NodeExperimentalClientOptions } from '../../src/types'; +import type { NodeClientOptions } from '../../src/types'; -export function getDefaultNodeExperimentalClientOptions( - options: Partial = {}, -): NodeExperimentalClientOptions { +export function getDefaultNodeClientOptions(options: Partial = {}): NodeClientOptions { return { + dsn: 'https://username@domain/123', tracesSampleRate: 1, integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), diff --git a/packages/node-experimental/test/helpers/mockSdkInit.ts b/packages/node-experimental/test/helpers/mockSdkInit.ts index 33e452a0a6c9..53b8aa308a9c 100644 --- a/packages/node-experimental/test/helpers/mockSdkInit.ts +++ b/packages/node-experimental/test/helpers/mockSdkInit.ts @@ -3,7 +3,7 @@ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { init } from '../../src/sdk/init'; -import type { NodeExperimentalClientOptions } from '../../src/types'; +import type { NodeClientOptions } from '../../src/types'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -14,7 +14,7 @@ export function resetGlobals(): void { getGlobalScope().clear(); } -export function mockSdkInit(options?: Partial) { +export function mockSdkInit(options?: Partial) { resetGlobals(); init({ dsn: PUBLIC_DSN, defaultIntegrations: false, ...options }); } diff --git a/packages/node-experimental/test/integration/breadcrumbs.test.ts b/packages/node-experimental/test/integration/breadcrumbs.test.ts index db3eef9e52fb..d6741b017764 100644 --- a/packages/node-experimental/test/integration/breadcrumbs.test.ts +++ b/packages/node-experimental/test/integration/breadcrumbs.test.ts @@ -1,8 +1,8 @@ import { addBreadcrumb, captureException, withIsolationScope, withScope } from '@sentry/core'; import { startSpan } from '@sentry/opentelemetry'; import { getClient } from '../../src/sdk/api'; +import type { NodeClient } from '../../src/sdk/client'; -import type { NodeExperimentalClient } from '../../src/types'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; describe('Integration | breadcrumbs', () => { @@ -19,7 +19,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; addBreadcrumb({ timestamp: 123456, message: 'test1' }); addBreadcrumb({ timestamp: 123457, message: 'test2', data: { nested: 'yes' } }); @@ -101,7 +101,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const error = new Error('test'); @@ -146,7 +146,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const error = new Error('test'); @@ -198,7 +198,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const error = new Error('test'); @@ -239,7 +239,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const error = new Error('test'); @@ -297,7 +297,7 @@ describe('Integration | breadcrumbs', () => { mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const error = new Error('test'); diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index 3f6c8a441216..6552037c548d 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -2,7 +2,7 @@ import { getCurrentScope, setGlobalScope } from '@sentry/core'; import { getClient, getSpanScopes } from '@sentry/opentelemetry'; import * as Sentry from '../../src/'; -import type { NodeExperimentalClient } from '../../src/types'; +import type { NodeClient } from '../../src/sdk/client'; import { cleanupOtel, mockSdkInit, resetGlobals } from '../helpers/mockSdkInit'; describe('Integration | Scope', () => { @@ -20,7 +20,7 @@ describe('Integration | Scope', () => { mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const rootScope = getCurrentScope(); @@ -121,7 +121,7 @@ describe('Integration | Scope', () => { mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const rootScope = getCurrentScope(); const error1 = new Error('test error 1'); diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index f1b89e922641..74af33ab3642 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -6,7 +6,7 @@ import type { PropagationContext, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; import * as Sentry from '../../src'; -import type { NodeExperimentalClient } from '../../src/types'; +import type { NodeClient } from '../../src/sdk/client'; import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; describe('Integration | Transactions', () => { @@ -20,7 +20,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ enableTracing: true, beforeSendTransaction }); - const client = Sentry.getClient(); + const client = Sentry.getClient(); Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); Sentry.setTag('outer.tag', 'test value'); @@ -343,7 +343,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ enableTracing: true, beforeSendTransaction }); - const client = Sentry.getClient(); + const client = Sentry.getClient(); Sentry.addBreadcrumb({ message: 'test breadcrumb 1', timestamp: 123456 }); @@ -516,7 +516,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ enableTracing: true, beforeSendTransaction }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; // We simulate the correct context we'd normally get from the SentryPropagator context.with( @@ -634,7 +634,7 @@ describe('Integration | Transactions', () => { mockSdkInit({ enableTracing: true, beforeSendTransaction }); - const client = getClient() as NodeExperimentalClient; + const client = getClient() as NodeClient; const provider = getProvider(); const multiSpanProcessor = provider?.activeSpanProcessor as | (SpanProcessor & { _spanProcessors?: SpanProcessor[] }) diff --git a/packages/node-experimental/test/sdk/client.test.ts b/packages/node-experimental/test/sdk/client.test.ts index b7db215a4cd8..f9e69b0b7233 100644 --- a/packages/node-experimental/test/sdk/client.test.ts +++ b/packages/node-experimental/test/sdk/client.test.ts @@ -1,15 +1,43 @@ +import * as os from 'os'; import { ProxyTracer } from '@opentelemetry/api'; -import { SDK_VERSION } from '@sentry/core'; +import { + SDK_VERSION, + SessionFlusher, + getCurrentScope, + getGlobalScope, + getIsolationScope, + setCurrentClient, + withIsolationScope, +} from '@sentry/core'; +import type { Event, EventHint } from '@sentry/types'; +import type { Scope } from '@sentry/types'; -import { NodeExperimentalClient } from '../../src/sdk/client'; -import { getDefaultNodeExperimentalClientOptions } from '../helpers/getDefaultNodePreviewClientOptions'; +import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; +import { NodeClient } from '../../src/sdk/client'; +import { initOtel } from '../../src/sdk/initOtel'; +import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodePreviewClientOptions'; +import { cleanupOtel } from '../helpers/mockSdkInit'; + +describe('NodeClient', () => { + beforeEach(() => { + getIsolationScope().clear(); + getGlobalScope().clear(); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + setOpenTelemetryContextAsyncContextStrategy(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + cleanupOtel(); + }); -describe('NodeExperimentalClient', () => { it('sets correct metadata', () => { - const options = getDefaultNodeExperimentalClientOptions(); - const client = new NodeExperimentalClient(options); + const options = getDefaultNodeClientOptions(); + const client = new NodeClient(options); expect(client.getOptions()).toEqual({ + dsn: expect.any(String), integrations: [], transport: options.transport, stackParser: options.stackParser, @@ -25,7 +53,6 @@ describe('NodeExperimentalClient', () => { version: SDK_VERSION, }, }, - transportOptions: { textEncoder: expect.any(Object) }, platform: 'node', runtime: { name: 'node', version: expect.any(String) }, serverName: expect.any(String), @@ -34,7 +61,7 @@ describe('NodeExperimentalClient', () => { }); it('exposes a tracer', () => { - const client = new NodeExperimentalClient(getDefaultNodeExperimentalClientOptions()); + const client = new NodeClient(getDefaultNodeClientOptions()); const tracer = client.tracer; expect(tracer).toBeDefined(); @@ -45,4 +72,455 @@ describe('NodeExperimentalClient', () => { expect(tracer2).toBe(tracer); }); + + describe('captureException', () => { + test('when autoSessionTracking is enabled, and requestHandler is not used -> requestStatus should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureException(new Error('test exception')); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + + test('when autoSessionTracking is disabled -> requestStatus should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureException(new Error('test exception')); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + + test('when autoSessionTracking is enabled + requestSession status is Crashed -> requestStatus should not be overridden', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'crashed' }); + + client.captureException(new Error('test exception')); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('crashed'); + }); + }); + + test('when autoSessionTracking is enabled + error occurs within request bounds -> requestStatus should be set to Errored', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureException(new Error('test exception')); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('errored'); + }); + }); + + test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + let isolationScope: Scope; + withIsolationScope(_isolationScope => { + _isolationScope.setRequestSession({ status: 'ok' }); + isolationScope = _isolationScope; + }); + + client.captureException(new Error('test exception')); + + setImmediate(() => { + const requestSession = isolationScope.getRequestSession(); + expect(requestSession).toEqual({ status: 'ok' }); + done(); + }); + }); + }); + + describe('captureEvent()', () => { + test('If autoSessionTracking is disabled, requestSession status should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + + test('When captureEvent is called with an exception, requestSession status should be set to Errored', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); + const client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('errored'); + }); + }); + + test('When captureEvent is called without an exception, requestSession status should not be set to Errored', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message' }); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + + test('When captureEvent is called with an exception but outside of a request, then requestStatus should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.clear(); + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); + + expect(isolationScope.getRequestSession()).toEqual(undefined); + }); + }); + + test('When captureEvent is called with a transaction, then requestSession status should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.3' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message', type: 'transaction' }); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + + test('When captureEvent is called with an exception but requestHandler is not used, then requestSession status should not be set', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.3' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOtel(); + + withIsolationScope(isolationScope => { + isolationScope.setRequestSession({ status: 'ok' }); + + client.captureEvent({ message: 'message', exception: { values: [{ type: 'exception type 1' }] } }); + + const requestSession = isolationScope.getRequestSession(); + expect(requestSession!.status).toEqual('ok'); + }); + }); + }); + + describe('_prepareEvent', () => { + test('adds platform to event', () => { + const options = getDefaultNodeClientOptions({}); + const client = new NodeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.platform).toEqual('node'); + }); + + test('adds runtime context to event', () => { + const options = getDefaultNodeClientOptions({}); + const client = new NodeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.contexts?.runtime).toEqual({ + name: 'node', + version: process.version, + }); + }); + + test('adds server name to event when value passed in options', () => { + const options = getDefaultNodeClientOptions({ serverName: 'foo' }); + const client = new NodeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.server_name).toEqual('foo'); + }); + + test('adds server name to event when value given in env', () => { + const options = getDefaultNodeClientOptions({}); + process.env.SENTRY_NAME = 'foo'; + const client = new NodeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.server_name).toEqual('foo'); + + delete process.env.SENTRY_NAME; + }); + + test('adds hostname as event server name when no value given', () => { + const options = getDefaultNodeClientOptions({}); + const client = new NodeClient(options); + + const event: Event = {}; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.server_name).toEqual(os.hostname()); + }); + + test("doesn't clobber existing runtime data", () => { + const options = getDefaultNodeClientOptions({ serverName: 'bar' }); + const client = new NodeClient(options); + + const event: Event = { contexts: { runtime: { name: 'foo', version: '1.2.3' } } }; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.contexts?.runtime).toEqual({ name: 'foo', version: '1.2.3' }); + expect(event.contexts?.runtime).not.toEqual({ name: 'node', version: process.version }); + }); + + test("doesn't clobber existing server name", () => { + const options = getDefaultNodeClientOptions({ serverName: 'bar' }); + const client = new NodeClient(options); + + const event: Event = { server_name: 'foo' }; + const hint: EventHint = {}; + client['_prepareEvent'](event, hint); + + expect(event.server_name).toEqual('foo'); + expect(event.server_name).not.toEqual('bar'); + }); + }); + + describe('captureCheckIn', () => { + it('sends a checkIn envelope', () => { + const options = getDefaultNodeClientOptions({ + serverName: 'bar', + release: '1.0.0', + environment: 'dev', + }); + const client = new NodeClient(options); + + // @ts-expect-error accessing private method + const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + + const id = client.captureCheckIn( + { monitorSlug: 'foo', status: 'in_progress' }, + { + schedule: { + type: 'crontab', + value: '0 * * * *', + }, + checkinMargin: 2, + maxRuntime: 12333, + timezone: 'Canada/Eastern', + }, + ); + + expect(sendEnvelopeSpy).toHaveBeenCalledTimes(1); + expect(sendEnvelopeSpy).toHaveBeenCalledWith([ + expect.any(Object), + [ + [ + expect.any(Object), + { + check_in_id: id, + monitor_slug: 'foo', + status: 'in_progress', + release: '1.0.0', + environment: 'dev', + monitor_config: { + schedule: { + type: 'crontab', + value: '0 * * * *', + }, + checkin_margin: 2, + max_runtime: 12333, + timezone: 'Canada/Eastern', + }, + }, + ], + ], + ]); + + client.captureCheckIn({ monitorSlug: 'foo', status: 'ok', duration: 1222, checkInId: id }); + + expect(sendEnvelopeSpy).toHaveBeenCalledTimes(2); + expect(sendEnvelopeSpy).toHaveBeenCalledWith([ + expect.any(Object), + [ + [ + expect.any(Object), + { + check_in_id: id, + monitor_slug: 'foo', + duration: 1222, + status: 'ok', + release: '1.0.0', + environment: 'dev', + }, + ], + ], + ]); + }); + + it('sends a checkIn envelope for heartbeat checkIns', () => { + const options = getDefaultNodeClientOptions({ + serverName: 'server', + release: '1.0.0', + environment: 'dev', + }); + const client = new NodeClient(options); + + // @ts-expect-error accessing private method + const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + + const id = client.captureCheckIn({ monitorSlug: 'heartbeat-monitor', status: 'ok' }); + + expect(sendEnvelopeSpy).toHaveBeenCalledTimes(1); + expect(sendEnvelopeSpy).toHaveBeenCalledWith([ + expect.any(Object), + [ + [ + expect.any(Object), + { + check_in_id: id, + monitor_slug: 'heartbeat-monitor', + status: 'ok', + release: '1.0.0', + environment: 'dev', + }, + ], + ], + ]); + }); + + it('does not send a checkIn envelope if disabled', () => { + const options = getDefaultNodeClientOptions({ serverName: 'bar', enabled: false }); + const client = new NodeClient(options); + + // @ts-expect-error accessing private method + const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + + client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' }); + + expect(sendEnvelopeSpy).toHaveBeenCalledTimes(0); + }); + }); +}); + +describe('flush/close', () => { + test('client close function disables _sessionFlusher', async () => { + jest.useRealTimers(); + + const options = getDefaultNodeClientOptions({ + autoSessionTracking: true, + release: '1.1', + }); + const client = new NodeClient(options); + client.initSessionFlusher(); + // Clearing interval is important here to ensure that the flush function later on is called by the `client.close()` + // not due to the interval running every 60s + clearInterval(client['_sessionFlusher']!['_intervalId']); + + const sessionFlusherFlushFunc = jest.spyOn(SessionFlusher.prototype, 'flush'); + + const delay = 1; + await client.close(delay); + + expect(client['_sessionFlusher']!['_isEnabled']).toBeFalsy(); + expect(sessionFlusherFlushFunc).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/node-experimental/test/transports/http.test.ts b/packages/node-experimental/test/transports/http.test.ts new file mode 100644 index 000000000000..c8c82d62faba --- /dev/null +++ b/packages/node-experimental/test/transports/http.test.ts @@ -0,0 +1,416 @@ +import * as http from 'http'; +import { TextEncoder } from 'util'; +import { createGunzip } from 'zlib'; +import { createTransport } from '@sentry/core'; +import type { EventEnvelope, EventItem } from '@sentry/types'; +import { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, serializeEnvelope } from '@sentry/utils'; + +import { makeNodeTransport } from '../../src/transports'; + +const textEncoder = new TextEncoder(); + +jest.mock('@sentry/core', () => { + const actualCore = jest.requireActual('@sentry/core'); + return { + ...actualCore, + createTransport: jest.fn().mockImplementation(actualCore.createTransport), + }; +}); + +import * as httpProxyAgent from '../../src/proxy'; + +const SUCCESS = 200; +const RATE_LIMIT = 429; +const INVALID = 400; +const FAILED = 500; + +interface TestServerOptions { + statusCode: number; + responseHeaders?: Record; +} + +let testServer: http.Server | undefined; + +function setupTestServer( + options: TestServerOptions, + requestInspector?: (req: http.IncomingMessage, body: string, raw: Uint8Array) => void, +) { + testServer = http.createServer((req, res) => { + const chunks: Buffer[] = []; + + const stream = req.headers['content-encoding'] === 'gzip' ? req.pipe(createGunzip({})) : req; + + stream.on('data', data => { + chunks.push(data); + }); + + stream.on('end', () => { + requestInspector?.(req, chunks.join(), Buffer.concat(chunks)); + }); + + res.writeHead(options.statusCode, options.responseHeaders); + res.end(); + + // also terminate socket because keepalive hangs connection a bit + // eslint-disable-next-line deprecation/deprecation + res.connection?.end(); + }); + + testServer.listen(18099); + + return new Promise(resolve => { + testServer?.on('listening', resolve); + }); +} + +const TEST_SERVER_URL = 'http://localhost:18099'; + +const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, +]); + +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); + +const ATTACHMENT_ITEM = createAttachmentEnvelopeItem( + { filename: 'empty-file.bin', data: new Uint8Array(50_000) }, + textEncoder, +); +const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope(EVENT_ENVELOPE, ATTACHMENT_ITEM); +const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE, textEncoder) as Uint8Array; + +const defaultOptions = { + url: TEST_SERVER_URL, + recordDroppedEvent: () => undefined, + textEncoder, +}; + +// empty function to keep test output clean +const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + +describe('makeNewHttpTransport()', () => { + afterEach(() => { + jest.clearAllMocks(); + + if (testServer) { + testServer.close(); + } + }); + + describe('.send()', () => { + it('should correctly send envelope to server', async () => { + await setupTestServer({ statusCode: SUCCESS }, (req, body) => { + expect(req.method).toBe('POST'); + expect(body).toBe(SERIALIZED_EVENT_ENVELOPE); + }); + + const transport = makeNodeTransport(defaultOptions); + await transport.send(EVENT_ENVELOPE); + }); + + it('allows overriding keepAlive', async () => { + await setupTestServer({ statusCode: SUCCESS }, req => { + expect(req.headers).toEqual( + expect.objectContaining({ + // node http module lower-cases incoming headers + connection: 'keep-alive', + }), + ); + }); + + const transport = makeNodeTransport({ keepAlive: true, ...defaultOptions }); + await transport.send(EVENT_ENVELOPE); + }); + + it('should correctly send user-provided headers to server', async () => { + await setupTestServer({ statusCode: SUCCESS }, req => { + expect(req.headers).toEqual( + expect.objectContaining({ + // node http module lower-cases incoming headers + 'x-some-custom-header-1': 'value1', + 'x-some-custom-header-2': 'value2', + }), + ); + }); + + const transport = makeNodeTransport({ + ...defaultOptions, + headers: { + 'X-Some-Custom-Header-1': 'value1', + 'X-Some-Custom-Header-2': 'value2', + }, + }); + + await transport.send(EVENT_ENVELOPE); + }); + + it.each([RATE_LIMIT, INVALID, FAILED])( + 'should resolve on bad server response (status %i)', + async serverStatusCode => { + await setupTestServer({ statusCode: serverStatusCode }); + + const transport = makeNodeTransport(defaultOptions); + + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual( + expect.objectContaining({ statusCode: serverStatusCode }), + ); + }, + ); + + it('should resolve when server responds with rate limit header and status code 200', async () => { + await setupTestServer({ + statusCode: SUCCESS, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + const transport = makeNodeTransport(defaultOptions); + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({ + statusCode: SUCCESS, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }); + }); + }); + + describe('compression', () => { + it('small envelopes should not be compressed', async () => { + await setupTestServer( + { + statusCode: SUCCESS, + responseHeaders: {}, + }, + (req, body) => { + expect(req.headers['content-encoding']).toBeUndefined(); + expect(body).toBe(SERIALIZED_EVENT_ENVELOPE); + }, + ); + + const transport = makeNodeTransport(defaultOptions); + await transport.send(EVENT_ENVELOPE); + }); + + it('large envelopes should be compressed', async () => { + await setupTestServer( + { + statusCode: SUCCESS, + responseHeaders: {}, + }, + (req, _, raw) => { + expect(req.headers['content-encoding']).toEqual('gzip'); + expect(raw.buffer).toStrictEqual(SERIALIZED_EVENT_ATTACHMENT_ENVELOPE.buffer); + }, + ); + + const transport = makeNodeTransport(defaultOptions); + await transport.send(EVENT_ATTACHMENT_ENVELOPE); + }); + }); + + describe('proxy', () => { + const proxyAgentSpy = jest + .spyOn(httpProxyAgent, 'HttpsProxyAgent') + // @ts-expect-error using http agent as https proxy agent + .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); + + it('can be configured through option', () => { + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'http://example.com', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://example.com'); + }); + + it('can be configured through env variables option', () => { + process.env.http_proxy = 'http://example.com'; + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://example.com'); + delete process.env.http_proxy; + }); + + it('client options have priority over env variables', () => { + process.env.http_proxy = 'http://foo.com'; + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'http://bar.com', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('http://bar.com'); + delete process.env.http_proxy; + }); + + it('no_proxy allows for skipping specific hosts', () => { + process.env.no_proxy = 'sentry.io'; + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'http://example.com', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + }); + + it('no_proxy works with a port', () => { + process.env.http_proxy = 'http://example.com:8080'; + process.env.no_proxy = 'sentry.io:8989'; + + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + delete process.env.http_proxy; + }); + + it('no_proxy works with multiple comma-separated hosts', () => { + process.env.http_proxy = 'http://example.com:8080'; + process.env.no_proxy = 'example.com,sentry.io,wat.com:1337'; + + makeNodeTransport({ + ...defaultOptions, + url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + delete process.env.http_proxy; + }); + }); + + describe('should register TransportRequestExecutor that returns the correct object from server response', () => { + it('rate limit', async () => { + await setupTestServer({ + statusCode: RATE_LIMIT, + responseHeaders: {}, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: RATE_LIMIT, + }), + ); + }); + + it('OK', async () => { + await setupTestServer({ + statusCode: SUCCESS, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: SUCCESS, + headers: { + 'retry-after': null, + 'x-sentry-rate-limits': null, + }, + }), + ); + }); + + it('OK with rate-limit headers', async () => { + await setupTestServer({ + statusCode: SUCCESS, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: SUCCESS, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }), + ); + }); + + it('NOK with rate-limit headers', async () => { + await setupTestServer({ + statusCode: RATE_LIMIT, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: RATE_LIMIT, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }), + ); + }); + }); + + it('should create a noop transport if an invalid url is passed', async () => { + const requestSpy = jest.spyOn(http, 'request'); + const transport = makeNodeTransport({ ...defaultOptions, url: 'foo' }); + await transport.send(EVENT_ENVELOPE); + expect(requestSpy).not.toHaveBeenCalled(); + }); + + it('should warn if an invalid url is passed', async () => { + const transport = makeNodeTransport({ ...defaultOptions, url: 'invalid url' }); + await transport.send(EVENT_ENVELOPE); + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.', + ); + }); +}); diff --git a/packages/node-experimental/test/transports/https.test.ts b/packages/node-experimental/test/transports/https.test.ts new file mode 100644 index 000000000000..40bed042eca5 --- /dev/null +++ b/packages/node-experimental/test/transports/https.test.ts @@ -0,0 +1,389 @@ +import * as http from 'http'; +import * as https from 'https'; +import { TextEncoder } from 'util'; +import { createTransport } from '@sentry/core'; +import type { EventEnvelope, EventItem } from '@sentry/types'; +import { createEnvelope, serializeEnvelope } from '@sentry/utils'; + +import { makeNodeTransport } from '../../src/transports'; +import type { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; +import testServerCerts from './test-server-certs'; + +const textEncoder = new TextEncoder(); + +jest.mock('@sentry/core', () => { + const actualCore = jest.requireActual('@sentry/core'); + return { + ...actualCore, + createTransport: jest.fn().mockImplementation(actualCore.createTransport), + }; +}); + +import * as httpProxyAgent from '../../src/proxy'; + +const SUCCESS = 200; +const RATE_LIMIT = 429; +const INVALID = 400; +const FAILED = 500; + +interface TestServerOptions { + statusCode: number; + responseHeaders?: Record; +} + +let testServer: http.Server | undefined; + +function setupTestServer( + options: TestServerOptions, + requestInspector?: (req: http.IncomingMessage, body: string) => void, +) { + testServer = https.createServer(testServerCerts, (req, res) => { + let body = ''; + + req.on('data', data => { + body += data; + }); + + req.on('end', () => { + requestInspector?.(req, body); + }); + + res.writeHead(options.statusCode, options.responseHeaders); + res.end(); + + // also terminate socket because keepalive hangs connection a bit + // eslint-disable-next-line deprecation/deprecation + res.connection?.end(); + }); + + testServer.listen(8099); + + return new Promise(resolve => { + testServer?.on('listening', resolve); + }); +} + +const TEST_SERVER_URL = 'https://localhost:8099'; + +const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, +]); + +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); + +const unsafeHttpsModule: HTTPModule = { + request: jest + .fn() + .mockImplementation((options: https.RequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void) => { + return https.request({ ...options, rejectUnauthorized: false }, callback); + }), +}; + +const defaultOptions = { + httpModule: unsafeHttpsModule, + url: TEST_SERVER_URL, + recordDroppedEvent: () => undefined, // noop + textEncoder, +}; + +describe('makeNewHttpsTransport()', () => { + afterEach(() => { + jest.clearAllMocks(); + + if (testServer) { + testServer.close(); + } + }); + + describe('.send()', () => { + it('should correctly send envelope to server', async () => { + await setupTestServer({ statusCode: SUCCESS }, (req, body) => { + expect(req.method).toBe('POST'); + expect(body).toBe(SERIALIZED_EVENT_ENVELOPE); + }); + + const transport = makeNodeTransport(defaultOptions); + await transport.send(EVENT_ENVELOPE); + }); + + it('should correctly send user-provided headers to server', async () => { + await setupTestServer({ statusCode: SUCCESS }, req => { + expect(req.headers).toEqual( + expect.objectContaining({ + // node http module lower-cases incoming headers + 'x-some-custom-header-1': 'value1', + 'x-some-custom-header-2': 'value2', + }), + ); + }); + + const transport = makeNodeTransport({ + ...defaultOptions, + headers: { + 'X-Some-Custom-Header-1': 'value1', + 'X-Some-Custom-Header-2': 'value2', + }, + }); + + await transport.send(EVENT_ENVELOPE); + }); + + it.each([RATE_LIMIT, INVALID, FAILED])( + 'should resolve on bad server response (status %i)', + async serverStatusCode => { + await setupTestServer({ statusCode: serverStatusCode }); + + const transport = makeNodeTransport(defaultOptions); + expect(() => { + expect(transport.send(EVENT_ENVELOPE)); + }).not.toThrow(); + }, + ); + + it('should resolve when server responds with rate limit header and status code 200', async () => { + await setupTestServer({ + statusCode: SUCCESS, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + const transport = makeNodeTransport(defaultOptions); + await expect(transport.send(EVENT_ENVELOPE)).resolves.toEqual({ + statusCode: SUCCESS, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }); + }); + + it('should use `caCerts` option', async () => { + await setupTestServer({ statusCode: SUCCESS }); + + const transport = makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: TEST_SERVER_URL, + caCerts: 'some cert', + }); + + await transport.send(EVENT_ENVELOPE); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(unsafeHttpsModule.request).toHaveBeenCalledWith( + expect.objectContaining({ + ca: 'some cert', + }), + expect.anything(), + ); + }); + }); + + describe('proxy', () => { + const proxyAgentSpy = jest + .spyOn(httpProxyAgent, 'HttpsProxyAgent') + // @ts-expect-error using http agent as https proxy agent + .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); + + it('can be configured through option', () => { + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'https://example.com', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); + }); + + it('can be configured through env variables option (http)', () => { + process.env.http_proxy = 'https://example.com'; + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); + delete process.env.http_proxy; + }); + + it('can be configured through env variables option (https)', () => { + process.env.https_proxy = 'https://example.com'; + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); + delete process.env.https_proxy; + }); + + it('client options have priority over env variables', () => { + process.env.https_proxy = 'https://foo.com'; + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'https://bar.com', + }); + + expect(proxyAgentSpy).toHaveBeenCalledTimes(1); + expect(proxyAgentSpy).toHaveBeenCalledWith('https://bar.com'); + delete process.env.https_proxy; + }); + + it('no_proxy allows for skipping specific hosts', () => { + process.env.no_proxy = 'sentry.io'; + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + proxy: 'https://example.com', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + }); + + it('no_proxy works with a port', () => { + process.env.http_proxy = 'https://example.com:8080'; + process.env.no_proxy = 'sentry.io:8989'; + + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + delete process.env.http_proxy; + }); + + it('no_proxy works with multiple comma-separated hosts', () => { + process.env.http_proxy = 'https://example.com:8080'; + process.env.no_proxy = 'example.com,sentry.io,wat.com:1337'; + + makeNodeTransport({ + ...defaultOptions, + httpModule: unsafeHttpsModule, + url: 'https://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', + }); + + expect(proxyAgentSpy).not.toHaveBeenCalled(); + + delete process.env.no_proxy; + delete process.env.http_proxy; + }); + }); + + it('should register TransportRequestExecutor that returns the correct object from server response (rate limit)', async () => { + await setupTestServer({ + statusCode: RATE_LIMIT, + responseHeaders: {}, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: RATE_LIMIT, + }), + ); + }); + + it('should register TransportRequestExecutor that returns the correct object from server response (OK)', async () => { + await setupTestServer({ + statusCode: SUCCESS, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: SUCCESS, + headers: { + 'retry-after': null, + 'x-sentry-rate-limits': null, + }, + }), + ); + }); + + it('should register TransportRequestExecutor that returns the correct object from server response (OK with rate-limit headers)', async () => { + await setupTestServer({ + statusCode: SUCCESS, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: SUCCESS, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }), + ); + }); + + it('should register TransportRequestExecutor that returns the correct object from server response (NOK with rate-limit headers)', async () => { + await setupTestServer({ + statusCode: RATE_LIMIT, + responseHeaders: { + 'Retry-After': '2700', + 'X-Sentry-Rate-Limits': '60::organization, 2700::organization', + }, + }); + + makeNodeTransport(defaultOptions); + const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + + const executorResult = registeredRequestExecutor({ + body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + category: 'error', + }); + + await expect(executorResult).resolves.toEqual( + expect.objectContaining({ + statusCode: RATE_LIMIT, + headers: { + 'retry-after': '2700', + 'x-sentry-rate-limits': '60::organization, 2700::organization', + }, + }), + ); + }); +}); diff --git a/packages/node-experimental/test/transports/test-server-certs.ts b/packages/node-experimental/test/transports/test-server-certs.ts new file mode 100644 index 000000000000..a5ce436c4234 --- /dev/null +++ b/packages/node-experimental/test/transports/test-server-certs.ts @@ -0,0 +1,48 @@ +export default { + key: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuMunjXC2tu2d4x8vKuPQbHwPjYG6pVvAUs7wzpDnMEGo3o2A +bZpL7vUAkQWZ86M84rX9b65cVvT35uqM9uxnJKQhSdGARxEcrz9yxjc9RaIO9xM4 +6WdFd6pcVHW9MF6njnc19jyIoSGXRADJjreNZHyMobAHyL2ZbFiptknUWFW3YT4t +q9bQD5yfhZ94fRt1IbdBAn5Bmz6x61BYudWU2KA3G1akPUmzj0OwZwaIrnGbfLUH +M5F50dNUYfCdmxtE8YRBPyWwcg+KOWa/P8C84p1UQ+/0GHNqUTa4wXBgKeUXNjth +AhV/4JgDDdec+/W0Z1UdEqxZvKfAYnjveFpxEwIDAQABAoIBADLsjEPB59gJKxVH +pqvfE7SRi4enVFP1MM6hEGMcM1ls/qg1vkp11q8G/Rz5ui8VsNWY6To5hmDAKQCN +akMxaksCn9nDzeHHqWvxxCMzXcMuoYkc1vYa613KqJ7twzDtJKdx2oD8tXoR06l9 +vg2CL4idefOkmsCK3xioZjxBpC6jF6ybvlY241MGhaAGRHmP6ik1uFJ+6Y8smh6R +AQKO0u0oQPy6bka9F6DTP6BMUeZ+OA/oOrrb5FxTHu8AHcyCSk2wHnCkB9EF/Ou2 +xSWrnu0O0/0Px6OO9oEsNSq2/fKNV9iuEU8LeAoDVm4ysyMrPce2c4ZsB4U244bj +yQpQZ6ECgYEA9KwA7Lmyf+eeZHxEM4MNSqyeXBtSKu4Zyk0RRY1j69ConjHKet3Q +ylVedXQ0/FJAHHKEm4zFGZtnaaxrzCIcQSKJBCoaA+cN44MM3D1nKmHjgPy8R/yE +BNgIVwJB1MmVSGa+NYnQgUomcCIEr/guNMIxV7p2iybqoxaEHKLfGFUCgYEAwVn1 +8LARsZihLUdxxbAc9+v/pBeMTrkTw1eN1ki9VWYoRam2MLozehEzabt677cU4h7+ +bjdKCKo1x2liY9zmbIiVHssv9Jf3E9XhcajsXB42m1+kjUYVPh8o9lDXcatV9EKt +DZK8wfRY9boyDKB2zRyo6bvIEK3qWbas31W3a8cCgYA6w0TFliPkzEAiaiYHKSZ8 +FNFD1dv6K41OJQxM5BRngom81MCImdWXgsFY/DvtjeOP8YEfysNbzxMbMioBsP+Q +NTcrJOFypn+TcNoZ2zV33GLDi++8ak1azHfUTdp5vKB57xMn0J2fL6vjqoftq3GN +gkZPh50I9qPL35CDQCrMsQKBgC6tFfc1uf/Cld5FagzMOCINodguKxvyB/hXUZFS +XAqar8wpbScUPEsSjfPPY50s+GiiDM/0nvW6iWMLaMos0J+Q1VbqvDfy2525O0Ri +ADU4wfv+Oc41BfnKMexMlcYGE6j006v8KX81Cqi/e0ebETLw4UITp/eG1JU1yUPd +AHuPAoGBAL25v4/onoH0FBLdEwb2BAENxc+0g4In1T+83jfHbfD0gOF3XTbgH4FF +MduIG8qBoZC5whiZ3qH7YJK7sydaM1bDwiesqIik+gEUE65T7S2ZF84y5GC5JjTf +z6v6i+DMCIJXDY5/gjzOED6UllV2Jrn2pDoV++zVyR6KAwXpCmK6 +-----END RSA PRIVATE KEY-----`, + cert: `-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFCMI53aBdS2kWTrw39Kkv93ErG3iMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjIwMzI4MDgzODQwWhcNNDkwODEyMDgz +ODQwWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAuMunjXC2tu2d4x8vKuPQbHwPjYG6pVvAUs7wzpDnMEGo3o2A +bZpL7vUAkQWZ86M84rX9b65cVvT35uqM9uxnJKQhSdGARxEcrz9yxjc9RaIO9xM4 +6WdFd6pcVHW9MF6njnc19jyIoSGXRADJjreNZHyMobAHyL2ZbFiptknUWFW3YT4t +q9bQD5yfhZ94fRt1IbdBAn5Bmz6x61BYudWU2KA3G1akPUmzj0OwZwaIrnGbfLUH +M5F50dNUYfCdmxtE8YRBPyWwcg+KOWa/P8C84p1UQ+/0GHNqUTa4wXBgKeUXNjth +AhV/4JgDDdec+/W0Z1UdEqxZvKfAYnjveFpxEwIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQBh4BKiByhyvAc5uHj5bkSqspY2xZWW8xiEGaCaQWDMlyjP9mVVWFHfE3XL +lzsJdZVnHDZUliuA5L+qTEpLJ5GmgDWqnKp3HdhtkL16mPbPyJLPY0X+m7wvoZRt +RwLfFCx1E13m0ktYWWgmSCnBl+rI7pyagDhZ2feyxsMrecCazyG/llFBuyWSOnIi +OHxjdHV7be5c8uOOp1iNB9j++LW1pRVrSCWOKRLcsUBal73FW+UvhM5+1If/F9pF +GNQrMhVRA8aHD0JAu3tpjYRKRuOpAbbqtiAUSbDPsJBQy/K9no2K83G7+AV+aGai +HXfQqFFJS6xGKU79azH51wLVEGXq +-----END CERTIFICATE-----`, +}; diff --git a/yarn.lock b/yarn.lock index e85098e415ac..d6265ed91967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6801,6 +6801,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g== +"@types/node@14.18.63": + version "14.18.63" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" + integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== + "@types/node@16.18.70": version "16.18.70" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.70.tgz#d4c819be1e9f8b69a794d6f2fd929d9ff76f6d4b" From e4dc5a7facaeca9c479909683dc02b1796e11e94 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 19 Feb 2024 17:13:19 +0100 Subject: [PATCH 096/173] fx(node): Fix anr worker check (#10719) I've removed the `hub` global on the carrier in a recent PR, and noticed this leftover. Guess this is not used/covered by tests...? It should work with using `acs` as that should now always be set too (also for the default case), unless sentry is not initialized. But for type safety, the acs _may_ be undefined. --- packages/node/src/integrations/anr/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 2e23f823891c..a8b984b48379 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -173,7 +173,7 @@ if (options.captureStackTrace) { { // Grab the trace context from the current scope expression: - 'const ctx = __SENTRY__.hub.getScope().getPropagationContext(); ctx.traceId + "-" + ctx.spanId + "-" + ctx.parentSpanId', + 'const ctx = __SENTRY__.acs?.getCurrentScope().getPropagationContext() || {}; ctx.traceId + "-" + ctx.spanId + "-" + ctx.parentSpanId', // Don't re-trigger the debugger if this causes an error silent: true, }, From 837bbd1b7db16a7fda09d7f284c75a8caeee60a5 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 09:01:50 +0100 Subject: [PATCH 097/173] ref: Make `setupOnce` optional in integrations (#10729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is now properly optional 🎉 --- .../browser/src/integrations/breadcrumbs.ts | 2 -- packages/browser/src/integrations/dedupe.ts | 2 -- .../browser/src/integrations/httpcontext.ts | 2 -- .../browser/src/integrations/linkederrors.ts | 2 -- packages/browser/src/profiling/integration.ts | 1 - packages/core/src/integration.ts | 2 +- .../core/src/integrations/inboundfilters.ts | 2 -- .../core/src/integrations/linkederrors.ts | 2 -- packages/core/src/integrations/metadata.ts | 2 -- packages/core/src/integrations/requestdata.ts | 2 -- packages/deno/src/integrations/context.ts | 2 -- .../deno/src/integrations/contextlines.ts | 2 -- .../deno/src/integrations/globalhandlers.ts | 2 -- .../deno/src/integrations/normalizepaths.ts | 2 -- packages/integrations/src/captureconsole.ts | 2 -- packages/integrations/src/contextlines.ts | 2 -- packages/integrations/src/debug.ts | 2 -- packages/integrations/src/dedupe.ts | 2 -- packages/integrations/src/extraerrordata.ts | 2 -- packages/integrations/src/httpclient.ts | 2 -- packages/integrations/src/rewriteframes.ts | 2 -- packages/integrations/src/sessiontiming.ts | 2 -- .../test/reportingobserver.test.ts | 34 +++++++++---------- packages/node/src/integrations/anr/index.ts | 2 -- packages/node/src/integrations/console.ts | 2 -- packages/node/src/integrations/context.ts | 2 -- .../node/src/integrations/contextlines.ts | 2 -- .../local-variables/local-variables-async.ts | 2 -- packages/node/src/integrations/modules.ts | 2 -- .../src/integrations/onuncaughtexception.ts | 2 -- .../src/integrations/onunhandledrejection.ts | 2 -- packages/node/src/integrations/spotlight.ts | 2 -- packages/replay-canvas/src/canvas.ts | 2 -- packages/types/src/integration.ts | 8 ++--- .../src/integrations/wintercg-fetch.ts | 2 -- .../vercel-edge/test/wintercg-fetch.test.ts | 12 +++---- packages/vue/src/integration.ts | 1 - 37 files changed, 26 insertions(+), 94 deletions(-) diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 0fb305767414..efef3f8057cd 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -70,8 +70,6 @@ const _breadcrumbsIntegration = ((options: Partial = {}) => return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { if (_options.console) { addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client)); diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts index f4ace6011b19..1e12b5c5a92c 100644 --- a/packages/browser/src/integrations/dedupe.ts +++ b/packages/browser/src/integrations/dedupe.ts @@ -11,8 +11,6 @@ const _dedupeIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(currentEvent) { // We want to ignore any non-error type events, e.g. transactions or replays // These should never be deduped, and also not be compared against as _previousEvent. diff --git a/packages/browser/src/integrations/httpcontext.ts b/packages/browser/src/integrations/httpcontext.ts index 1a5e95f2eee3..5fa59dd3585d 100644 --- a/packages/browser/src/integrations/httpcontext.ts +++ b/packages/browser/src/integrations/httpcontext.ts @@ -8,8 +8,6 @@ const INTEGRATION_NAME = 'HttpContext'; const _httpContextIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function preprocessEvent(event) { // if none of the information we want exists, don't bother if (!WINDOW.navigator && !WINDOW.location && !WINDOW.document) { diff --git a/packages/browser/src/integrations/linkederrors.ts b/packages/browser/src/integrations/linkederrors.ts index b03855303c58..8d15b2055048 100644 --- a/packages/browser/src/integrations/linkederrors.ts +++ b/packages/browser/src/integrations/linkederrors.ts @@ -19,8 +19,6 @@ const _linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function preprocessEvent(event, hint, client) { const options = client.getOptions(); diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index 1c076e09b17d..f9b924d9102c 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -22,7 +22,6 @@ const _browserProfilingIntegration = (() => { return { name: INTEGRATION_NAME, // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { const scope = getCurrentScope(); diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 1e0273d72f2b..ccf1f86cff18 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -129,7 +129,7 @@ export function setupIntegration(client: Client, integration: Integration, integ integrationIndex[integration.name] = integration; // `setupOnce` is only called the first time - if (installedIntegrations.indexOf(integration.name) === -1) { + if (installedIntegrations.indexOf(integration.name) === -1 && typeof integration.setupOnce === 'function') { // eslint-disable-next-line deprecation/deprecation integration.setupOnce(addGlobalEventProcessor, getCurrentHub); installedIntegrations.push(integration.name); diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 6ed891c253fa..dff1130cb708 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -33,8 +33,6 @@ const INTEGRATION_NAME = 'InboundFilters'; const _inboundFiltersIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event, _hint, client) { const clientOptions = client.getOptions(); const mergedOptions = _mergeOptions(options, clientOptions); diff --git a/packages/core/src/integrations/linkederrors.ts b/packages/core/src/integrations/linkederrors.ts index e753ca2ccd75..b23eaf4272b0 100644 --- a/packages/core/src/integrations/linkederrors.ts +++ b/packages/core/src/integrations/linkederrors.ts @@ -18,8 +18,6 @@ const _linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function preprocessEvent(event, hint, client) { const options = client.getOptions(); diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/metadata.ts index 3d5b89e07d2e..99fc4834b060 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -9,8 +9,6 @@ const INTEGRATION_NAME = 'ModuleMetadata'; const _moduleMetadataIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. client.on('beforeEnvelope', envelope => { diff --git a/packages/core/src/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index 1835fda4ec4a..002673cdc13f 100644 --- a/packages/core/src/integrations/requestdata.ts +++ b/packages/core/src/integrations/requestdata.ts @@ -75,8 +75,6 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) = return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event, _hint, client) { // Note: In the long run, most of the logic here should probably move into the request data utility functions. For // the moment it lives here, though, until https://github.com/getsentry/sentry-javascript/issues/5718 is addressed. diff --git a/packages/deno/src/integrations/context.ts b/packages/deno/src/integrations/context.ts index f844b80be6c8..ca0735c4e0ab 100644 --- a/packages/deno/src/integrations/context.ts +++ b/packages/deno/src/integrations/context.ts @@ -55,8 +55,6 @@ async function addDenoRuntimeContext(event: Event): Promise { const _denoContextIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { return addDenoRuntimeContext(event); }, diff --git a/packages/deno/src/integrations/contextlines.ts b/packages/deno/src/integrations/contextlines.ts index fc51e4ad2d57..4b43c6bc34c2 100644 --- a/packages/deno/src/integrations/contextlines.ts +++ b/packages/deno/src/integrations/contextlines.ts @@ -52,8 +52,6 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { return addSourceContext(event, contextLines); }, diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts index 2c562a7aa0f9..6498f2e2ffb5 100644 --- a/packages/deno/src/integrations/globalhandlers.ts +++ b/packages/deno/src/integrations/globalhandlers.ts @@ -31,8 +31,6 @@ const _globalHandlersIntegration = ((options?: GlobalHandlersIntegrations) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { if (_options.error) { installGlobalErrorHandler(client); diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index a9b8f3dbb0e3..d5304e9e62dd 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -70,8 +70,6 @@ const _normalizePathsIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { // This error.stack hopefully contains paths that traverse the app cwd const error = new Error(); diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index 43419dcd32bd..2593ec8de7f2 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -20,8 +20,6 @@ const _captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { if (!('console' in GLOBAL_OBJ)) { return; diff --git a/packages/integrations/src/contextlines.ts b/packages/integrations/src/contextlines.ts index 62c1b2728f1a..8e77ea6f8a08 100644 --- a/packages/integrations/src/contextlines.ts +++ b/packages/integrations/src/contextlines.ts @@ -23,8 +23,6 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { return addSourceContext(event, contextLines); }, diff --git a/packages/integrations/src/debug.ts b/packages/integrations/src/debug.ts index 1c1911cd03d8..559041b40d36 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/integrations/src/debug.ts @@ -24,8 +24,6 @@ const _debugIntegration = ((options: DebugOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { client.on('beforeSendEvent', (event: Event, hint?: EventHint) => { if (_options.debugger) { diff --git a/packages/integrations/src/dedupe.ts b/packages/integrations/src/dedupe.ts index 63db131127c9..5aeafd42e97e 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/integrations/src/dedupe.ts @@ -11,8 +11,6 @@ const _dedupeIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(currentEvent) { // We want to ignore any non-error type events, e.g. transactions or replays // These should never be deduped, and also not be compared against as _previousEvent. diff --git a/packages/integrations/src/extraerrordata.ts b/packages/integrations/src/extraerrordata.ts index 21cc91008b7f..fc0c9c05fe58 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/integrations/src/extraerrordata.ts @@ -27,8 +27,6 @@ const _extraErrorDataIntegration = ((options: Partial = { const { depth = 3, captureErrorCause = true } = options; return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event, hint) { return _enhanceEventWithErrorData(event, hint, depth, captureErrorCause); }, diff --git a/packages/integrations/src/httpclient.ts b/packages/integrations/src/httpclient.ts index 276aadaf9baa..1c93a232104b 100644 --- a/packages/integrations/src/httpclient.ts +++ b/packages/integrations/src/httpclient.ts @@ -47,8 +47,6 @@ const _httpClientIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client): void { _wrapFetch(client, _options); _wrapXHR(client, _options); diff --git a/packages/integrations/src/rewriteframes.ts b/packages/integrations/src/rewriteframes.ts index 12183a3192ae..d32252ef15d8 100644 --- a/packages/integrations/src/rewriteframes.ts +++ b/packages/integrations/src/rewriteframes.ts @@ -71,8 +71,6 @@ const _rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(originalEvent) { let processedEvent = originalEvent; diff --git a/packages/integrations/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts index 2c79dd5cd69a..41af19bed15c 100644 --- a/packages/integrations/src/sessiontiming.ts +++ b/packages/integrations/src/sessiontiming.ts @@ -8,8 +8,6 @@ const _sessionTimingIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { const now = Date.now(); diff --git a/packages/integrations/test/reportingobserver.test.ts b/packages/integrations/test/reportingobserver.test.ts index 8c8088770973..a8766e7cd58c 100644 --- a/packages/integrations/test/reportingobserver.test.ts +++ b/packages/integrations/test/reportingobserver.test.ts @@ -50,7 +50,7 @@ describe('ReportingObserver', () => { const reportingObserver = reportingObserverIntegration(); expect(() => { - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); }).not.toThrow(); expect(mockReportingObserverConstructor).not.toHaveBeenCalled(); @@ -59,7 +59,7 @@ describe('ReportingObserver', () => { it('should use default report types', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); @@ -71,7 +71,7 @@ describe('ReportingObserver', () => { it('should use user-provided report types', () => { const reportingObserver = reportingObserverIntegration({ types: ['crash'] }); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); @@ -83,7 +83,7 @@ describe('ReportingObserver', () => { it('should use `buffered` option', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); @@ -95,7 +95,7 @@ describe('ReportingObserver', () => { it('should call `observe` function', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); expect(mockObserve).toHaveBeenCalledTimes(1); @@ -105,7 +105,7 @@ describe('ReportingObserver', () => { describe('handler', () => { it('should abort gracefully and not do anything when integration is not installed', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); // without calling setup, the integration is not registered const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -119,7 +119,7 @@ describe('ReportingObserver', () => { it('should capture messages', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -133,7 +133,7 @@ describe('ReportingObserver', () => { it('should set extra including the url of a report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -148,7 +148,7 @@ describe('ReportingObserver', () => { it('should set extra including the report body if available', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -163,7 +163,7 @@ describe('ReportingObserver', () => { it('should not set extra report body extra when no body is set', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -174,7 +174,7 @@ describe('ReportingObserver', () => { it('should capture report details from body on crash report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -192,7 +192,7 @@ describe('ReportingObserver', () => { it('should capture report message from body on deprecation report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -209,7 +209,7 @@ describe('ReportingObserver', () => { it('should capture report message from body on intervention report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -226,7 +226,7 @@ describe('ReportingObserver', () => { it('should use fallback message when no body is available', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -242,7 +242,7 @@ describe('ReportingObserver', () => { it('should use fallback message when no body details are available for crash report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -255,7 +255,7 @@ describe('ReportingObserver', () => { it('should use fallback message when no body message is available for deprecation report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -272,7 +272,7 @@ describe('ReportingObserver', () => { it('should use fallback message when no body message is available for intervention report', () => { const reportingObserver = reportingObserverIntegration(); - reportingObserver.setupOnce(); + reportingObserver.setupOnce!(); reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 7da63fdc50bd..bfb81557fd75 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -55,8 +55,6 @@ const INTEGRATION_NAME = 'Anr'; const _anrIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client: NodeClient) { if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) { throw new Error('ANR detection requires Node 16.17.0 or later'); diff --git a/packages/node/src/integrations/console.ts b/packages/node/src/integrations/console.ts index aacf3447ea2a..5a185b8fcee1 100644 --- a/packages/node/src/integrations/console.ts +++ b/packages/node/src/integrations/console.ts @@ -8,8 +8,6 @@ const INTEGRATION_NAME = 'Console'; const _consoleIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { addConsoleInstrumentationHandler(({ args, level }) => { if (getClient() !== client) { diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index db712f4ea95d..fa5184204bf2 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -102,8 +102,6 @@ const _nodeContextIntegration = ((options: ContextOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { return addContext(event); }, diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index 5e98c7cbb813..2f292a5606dc 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -40,8 +40,6 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { return addSourceContext(event, contextLines); }, diff --git a/packages/node/src/integrations/local-variables/local-variables-async.ts b/packages/node/src/integrations/local-variables/local-variables-async.ts index b5f015b2b7db..0d04f1f0b6e1 100644 --- a/packages/node/src/integrations/local-variables/local-variables-async.ts +++ b/packages/node/src/integrations/local-variables/local-variables-async.ts @@ -220,8 +220,6 @@ const _localVariablesAsyncIntegration = ((options: LocalVariablesIntegrationOpti return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client: NodeClient) { const clientOptions = client.getOptions(); diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts index 008376670724..1f9aff7303e3 100644 --- a/packages/node/src/integrations/modules.ts +++ b/packages/node/src/integrations/modules.ts @@ -79,8 +79,6 @@ function _getModules(): { [key: string]: string } { const _modulesIntegration = (() => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function processEvent(event) { event.modules = { ...event.modules, diff --git a/packages/node/src/integrations/onuncaughtexception.ts b/packages/node/src/integrations/onuncaughtexception.ts index 0eb79833ddbf..68be68a6d6cc 100644 --- a/packages/node/src/integrations/onuncaughtexception.ts +++ b/packages/node/src/integrations/onuncaughtexception.ts @@ -48,8 +48,6 @@ const _onUncaughtExceptionIntegration = ((options: Partial = { return { name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { if (typeof process === 'object' && process.env && process.env.NODE_ENV !== 'development') { logger.warn("[Spotlight] It seems you're not in dev mode. Do you really want to have Spotlight enabled?"); diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 04f91af7d578..5e3f5ab1b7bd 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -66,8 +66,6 @@ export const _replayCanvasIntegration = ((options: Partial return { name: INTEGRATION_NAME, - // eslint-disable-next-line @typescript-eslint/no-empty-function - setupOnce() {}, getOptions(): ReplayCanvasIntegrationOptions { const { quality, enableManualSnapshot } = _canvasOptions; diff --git a/packages/types/src/integration.ts b/packages/types/src/integration.ts index 3c3b44eb0ed8..543238ccc07b 100644 --- a/packages/types/src/integration.ts +++ b/packages/types/src/integration.ts @@ -24,10 +24,8 @@ export interface IntegrationFnResult { /** * This hook is only called once, even if multiple clients are created. * It does not receives any arguments, and should only use for e.g. global monkey patching and similar things. - * - * NOTE: In v8, this will become optional. */ - setupOnce(): void; + setupOnce?(): void; /** * Set up an integration for the given client. @@ -74,10 +72,8 @@ export interface Integration { /** * This hook is only called once, even if multiple clients are created. * It does not receives any arguments, and should only use for e.g. global monkey patching and similar things. - * - * NOTE: In v8, this will become optional, and not receive any arguments anymore. */ - setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void; + setupOnce?(addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void; /** * Set up an integration for the given client. diff --git a/packages/vercel-edge/src/integrations/wintercg-fetch.ts b/packages/vercel-edge/src/integrations/wintercg-fetch.ts index 507a34aedab4..436e7f6f9b2d 100644 --- a/packages/vercel-edge/src/integrations/wintercg-fetch.ts +++ b/packages/vercel-edge/src/integrations/wintercg-fetch.ts @@ -87,8 +87,6 @@ const _winterCGFetch = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, - // TODO v8: Remove this again - // eslint-disable-next-line @typescript-eslint/no-empty-function setupOnce() { addFetchInstrumentationHandler(handlerData => { const client = getClient(); diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index 2121f037e479..f9e1c3ca8aed 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -43,7 +43,7 @@ describe('WinterCGFetch instrumentation', () => { addFetchInstrumentationHandlerSpy.mockImplementationOnce(() => undefined); const integration = winterCGFetchIntegration(); - integration.setupOnce(); + integration.setupOnce!(); integration.setup!(client); const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; @@ -77,7 +77,7 @@ describe('WinterCGFetch instrumentation', () => { addFetchInstrumentationHandlerSpy.mockImplementationOnce(() => undefined); const integration = winterCGFetchIntegration(); - integration.setupOnce(); + integration.setupOnce!(); // integration.setup!(client) is not called! const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; @@ -97,7 +97,7 @@ describe('WinterCGFetch instrumentation', () => { addFetchInstrumentationHandlerSpy.mockImplementationOnce(() => undefined); const integration = winterCGFetchIntegration(); - integration.setupOnce(); + integration.setupOnce!(); integration.setup!(client); const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; @@ -121,7 +121,7 @@ describe('WinterCGFetch instrumentation', () => { return url === 'http://only-acceptable-url.com/'; }, }); - integration.setupOnce(); + integration.setupOnce!(); integration.setup!(client); const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; @@ -145,7 +145,7 @@ describe('WinterCGFetch instrumentation', () => { addFetchInstrumentationHandlerSpy.mockImplementationOnce(() => undefined); const integration = winterCGFetchIntegration(); - integration.setupOnce(); + integration.setupOnce!(); integration.setup!(client); const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; @@ -182,7 +182,7 @@ describe('WinterCGFetch instrumentation', () => { addFetchInstrumentationHandlerSpy.mockImplementationOnce(() => undefined); const integration = winterCGFetchIntegration({ breadcrumbs: false }); - integration.setupOnce(); + integration.setupOnce!(); integration.setup!(client); const [fetchInstrumentationHandlerCallback] = addFetchInstrumentationHandlerSpy.mock.calls[0]; diff --git a/packages/vue/src/integration.ts b/packages/vue/src/integration.ts index 4bd99e3d6c8c..bd528f276ab3 100644 --- a/packages/vue/src/integration.ts +++ b/packages/vue/src/integration.ts @@ -25,7 +25,6 @@ const _vueIntegration = ((integrationOptions: Partial = {}) => { return { name: INTEGRATION_NAME, // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function setup(client) { _setupIntegration(client, integrationOptions); }, From 1f75cde1ea42f64c8413c443ab8360d6ae876569 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 09:02:07 +0100 Subject: [PATCH 098/173] feat(node-experimental): Move `defaultStackParser` & `getSentryRelease` (#10722) These were imported from `@sentry/node` before. Also moves the `createGetModuleFromFilename` utility over, which is needed for `defaultStackParser`. --- packages/node-experimental/src/index.ts | 6 +- packages/node-experimental/src/sdk/api.ts | 41 ++++++++++++- packages/node-experimental/src/sdk/init.ts | 8 +-- .../node-experimental/src/utils/module.ts | 57 +++++++++++++++++++ 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 packages/node-experimental/src/utils/module.ts diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 22e29a146df2..b988822a5a38 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -17,7 +17,8 @@ export * as Handlers from './sdk/handlers'; export type { Span } from './types'; export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; -export { getClient } from './sdk/api'; +export { getClient, getSentryRelease, defaultStackParser } from './sdk/api'; +export { createGetModuleFromFilename } from './utils/module'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; @@ -25,15 +26,12 @@ export { addBreadcrumb, isInitialized, makeNodeTransport, - defaultStackParser, - getSentryRelease, getGlobalScope, addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData, // eslint-disable-next-line deprecation/deprecation getModuleFromFilename, - createGetModuleFromFilename, close, createTransport, flush, diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index 0f0a4de321aa..2608795a8264 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -1,7 +1,9 @@ // PUBLIC APIS import { getCurrentScope } from '@sentry/core'; -import type { Client } from '@sentry/types'; +import type { Client, StackParser } from '@sentry/types'; +import { GLOBAL_OBJ, createStackParser, nodeStackLineParser } from '@sentry/utils'; +import { createGetModuleFromFilename } from '../utils/module'; /** Get the currently active client. */ export function getClient(): C { @@ -15,3 +17,40 @@ export function getClient(): C { // TODO otherwise ensure we use a noop client return {} as C; } + +/** + * Returns a release dynamically from environment variables. + */ +export function getSentryRelease(fallback?: string): string | undefined { + // Always read first as Sentry takes this as precedence + if (process.env.SENTRY_RELEASE) { + return process.env.SENTRY_RELEASE; + } + + // This supports the variable that sentry-webpack-plugin injects + if (GLOBAL_OBJ.SENTRY_RELEASE && GLOBAL_OBJ.SENTRY_RELEASE.id) { + return GLOBAL_OBJ.SENTRY_RELEASE.id; + } + + return ( + // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables + process.env.GITHUB_SHA || + // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata + process.env.COMMIT_REF || + // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables + process.env.VERCEL_GIT_COMMIT_SHA || + process.env.VERCEL_GITHUB_COMMIT_SHA || + process.env.VERCEL_GITLAB_COMMIT_SHA || + process.env.VERCEL_BITBUCKET_COMMIT_SHA || + // Zeit (now known as Vercel) + process.env.ZEIT_GITHUB_COMMIT_SHA || + process.env.ZEIT_GITLAB_COMMIT_SHA || + process.env.ZEIT_BITBUCKET_COMMIT_SHA || + // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables + process.env.CF_PAGES_COMMIT_SHA || + fallback + ); +} + +/** Node.js stack parser */ +export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename())); diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 78d9fa7ef572..3c2d09fb3847 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -6,12 +6,7 @@ import { hasTracingEnabled, startSession, } from '@sentry/core'; -import { - defaultStackParser, - getDefaultIntegrations as getDefaultNodeIntegrations, - getSentryRelease, - spotlightIntegration, -} from '@sentry/node'; +import { getDefaultIntegrations as getDefaultNodeIntegrations, spotlightIntegration } from '@sentry/node'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import type { Client, Integration, Options } from '@sentry/types'; import { @@ -28,6 +23,7 @@ import { httpIntegration } from '../integrations/http'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; +import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initOtel } from './initOtel'; diff --git a/packages/node-experimental/src/utils/module.ts b/packages/node-experimental/src/utils/module.ts new file mode 100644 index 000000000000..d873bf9b2f2e --- /dev/null +++ b/packages/node-experimental/src/utils/module.ts @@ -0,0 +1,57 @@ +import { posix, sep } from 'path'; +import { dirname } from '@sentry/utils'; + +/** normalizes Windows paths */ +function normalizeWindowsPath(path: string): string { + return path + .replace(/^[A-Z]:/, '') // remove Windows-style prefix + .replace(/\\/g, '/'); // replace all `\` instances with `/` +} + +/** Creates a function that gets the module name from a filename */ +export function createGetModuleFromFilename( + basePath: string = process.argv[1] ? dirname(process.argv[1]) : process.cwd(), + isWindows: boolean = sep === '\\', +): (filename: string | undefined) => string | undefined { + const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath; + + return (filename: string | undefined) => { + if (!filename) { + return; + } + + const normalizedFilename = isWindows ? normalizeWindowsPath(filename) : filename; + + // eslint-disable-next-line prefer-const + let { dir, base: file, ext } = posix.parse(normalizedFilename); + + if (ext === '.js' || ext === '.mjs' || ext === '.cjs') { + file = file.slice(0, ext.length * -1); + } + + if (!dir) { + // No dirname whatsoever + dir = '.'; + } + + const n = dir.lastIndexOf('/node_modules'); + if (n > -1) { + return `${dir.slice(n + 14).replace(/\//g, '.')}:${file}`; + } + + // Let's see if it's a part of the main module + // To be a part of main module, it has to share the same base + if (dir.startsWith(normalizedBase)) { + let moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, '.'); + + if (moduleName) { + moduleName += ':'; + } + moduleName += file; + + return moduleName; + } + + return file; + }; +} From 97cc1908ea29f63f6e5bffe48dc497d6f6e42993 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 10:20:14 +0100 Subject: [PATCH 099/173] fix(core): Fix scope capturing via `captureContext` function (#10735) In https://github.com/getsentry/sentry-javascript/pull/9801, we introduced a regression that if you pass a function as `captureContext` to capture methods, the returned scope is not used. The cause for this was a confusion on my end, based on the slightly weird way this works in `scope.update(fn)` - we don't actually merge this or update the scope based on the return value of `fn`, but `fn` receives the `scope` as argument, does nothing with the return type of `fn` and just returns it - which we didn't use, because I assumed that `scope.update` would actually return the scope (also, the return type of it is `this` which is not correct there). This PR changes this so that the returned scope of `fn` is actually merged with the scope, same as if you'd pass a `scope` directly - so this is fundamentally the same now: ```js const otherScope = new Scope(); scope.update(otherScope); scope.update(() => otherScope); ``` (which before would have had vastly different outcomes!) I added a bunch of tests to verify how this works/should work. Fixes https://github.com/getsentry/sentry-javascript/issues/10686 --- packages/core/src/scope.ts | 66 +++++----- packages/core/test/lib/prepareEvent.test.ts | 126 ++++++++++++++++++++ 2 files changed, 158 insertions(+), 34 deletions(-) diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 8f73f68060f3..6f2cc0aeafcd 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -366,50 +366,48 @@ export class Scope implements ScopeInterface { return this; } - if (typeof captureContext === 'function') { - const updatedScope = (captureContext as (scope: T) => T)(this); - return updatedScope instanceof Scope ? updatedScope : this; - } + const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext; + + if (scopeToMerge instanceof Scope) { + const scopeData = scopeToMerge.getScopeData(); - if (captureContext instanceof Scope) { - this._tags = { ...this._tags, ...captureContext._tags }; - this._extra = { ...this._extra, ...captureContext._extra }; - this._contexts = { ...this._contexts, ...captureContext._contexts }; - if (captureContext._user && Object.keys(captureContext._user).length) { - this._user = captureContext._user; + this._tags = { ...this._tags, ...scopeData.tags }; + this._extra = { ...this._extra, ...scopeData.extra }; + this._contexts = { ...this._contexts, ...scopeData.contexts }; + if (scopeData.user && Object.keys(scopeData.user).length) { + this._user = scopeData.user; } - if (captureContext._level) { - this._level = captureContext._level; + if (scopeData.level) { + this._level = scopeData.level; } - if (captureContext._fingerprint) { - this._fingerprint = captureContext._fingerprint; + if (scopeData.fingerprint) { + this._fingerprint = scopeData.fingerprint; } - if (captureContext._requestSession) { - this._requestSession = captureContext._requestSession; + if (scopeToMerge.getRequestSession()) { + this._requestSession = scopeToMerge.getRequestSession(); } - if (captureContext._propagationContext) { - this._propagationContext = captureContext._propagationContext; + if (scopeData.propagationContext) { + this._propagationContext = scopeData.propagationContext; } - } else if (isPlainObject(captureContext)) { - // eslint-disable-next-line no-param-reassign - captureContext = captureContext as ScopeContext; - this._tags = { ...this._tags, ...captureContext.tags }; - this._extra = { ...this._extra, ...captureContext.extra }; - this._contexts = { ...this._contexts, ...captureContext.contexts }; - if (captureContext.user) { - this._user = captureContext.user; + } else if (isPlainObject(scopeToMerge)) { + const scopeContext = captureContext as ScopeContext; + this._tags = { ...this._tags, ...scopeContext.tags }; + this._extra = { ...this._extra, ...scopeContext.extra }; + this._contexts = { ...this._contexts, ...scopeContext.contexts }; + if (scopeContext.user) { + this._user = scopeContext.user; } - if (captureContext.level) { - this._level = captureContext.level; + if (scopeContext.level) { + this._level = scopeContext.level; } - if (captureContext.fingerprint) { - this._fingerprint = captureContext.fingerprint; + if (scopeContext.fingerprint) { + this._fingerprint = scopeContext.fingerprint; } - if (captureContext.requestSession) { - this._requestSession = captureContext.requestSession; + if (scopeContext.requestSession) { + this._requestSession = scopeContext.requestSession; } - if (captureContext.propagationContext) { - this._propagationContext = captureContext.propagationContext; + if (scopeContext.propagationContext) { + this._propagationContext = scopeContext.propagationContext; } } diff --git a/packages/core/test/lib/prepareEvent.test.ts b/packages/core/test/lib/prepareEvent.test.ts index 07abcd7e4c33..1ad80fb5ce68 100644 --- a/packages/core/test/lib/prepareEvent.test.ts +++ b/packages/core/test/lib/prepareEvent.test.ts @@ -379,4 +379,130 @@ describe('prepareEvent', () => { }, }); }); + + describe('captureContext', () => { + it('works with scope & captureContext=POJO', async () => { + const scope = new Scope(); + scope.setTags({ + initial: 'aa', + foo: 'foo', + }); + + const event = { message: 'foo' }; + + const options = {} as ClientOptions; + const client = { + getEventProcessors() { + return [] as EventProcessor[]; + }, + } as Client; + + const processedEvent = await prepareEvent( + options, + event, + { + captureContext: { tags: { foo: 'bar' } }, + integrations: [], + }, + scope, + client, + ); + + expect(processedEvent).toEqual({ + timestamp: expect.any(Number), + event_id: expect.any(String), + environment: 'production', + message: 'foo', + sdkProcessingMetadata: {}, + tags: { initial: 'aa', foo: 'bar' }, + }); + }); + + it('works with scope & captureContext=scope instance', async () => { + const scope = new Scope(); + scope.setTags({ + initial: 'aa', + foo: 'foo', + }); + + const event = { message: 'foo' }; + + const options = {} as ClientOptions; + const client = { + getEventProcessors() { + return [] as EventProcessor[]; + }, + } as Client; + + const captureContext = new Scope(); + captureContext.setTags({ foo: 'bar' }); + + const processedEvent = await prepareEvent( + options, + event, + { + captureContext, + integrations: [], + }, + scope, + client, + ); + + expect(processedEvent).toEqual({ + timestamp: expect.any(Number), + event_id: expect.any(String), + environment: 'production', + message: 'foo', + sdkProcessingMetadata: {}, + tags: { initial: 'aa', foo: 'bar' }, + }); + }); + + it('works with scope & captureContext=function', async () => { + const scope = new Scope(); + scope.setTags({ + initial: 'aa', + foo: 'foo', + }); + + const event = { message: 'foo' }; + + const options = {} as ClientOptions; + const client = { + getEventProcessors() { + return [] as EventProcessor[]; + }, + } as Client; + + const captureContextScope = new Scope(); + captureContextScope.setTags({ foo: 'bar' }); + + const captureContext = jest.fn(passedScope => { + expect(passedScope).toEqual(scope); + return captureContextScope; + }); + + const processedEvent = await prepareEvent( + options, + event, + { + captureContext, + integrations: [], + }, + scope, + client, + ); + + expect(captureContext).toHaveBeenCalledTimes(1); + + expect(processedEvent).toEqual({ + timestamp: expect.any(Number), + event_id: expect.any(String), + environment: 'production', + message: 'foo', + sdkProcessingMetadata: {}, + tags: { initial: 'aa', foo: 'bar' }, + }); + }); + }); }); From c002d8fa06e4f459116fe4873c618c1008dd8fdc Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 10:31:42 +0100 Subject: [PATCH 100/173] ref(node-experimental): Cleanup tracing intergations (#10730) This moves the tracing integration into a folder (to prepare to add more integrations from node), and also removes the deprecated legacy class integrations. --- packages/node-experimental/src/index.ts | 22 +++---- .../NodePerformanceIntegration.ts | 57 ---------------- .../src/integrations/express.ts | 62 ------------------ .../src/integrations/fastify.ts | 63 ------------------ .../src/integrations/graphql.ts | 65 ------------------- .../src/integrations/hapi.ts | 51 --------------- .../src/integrations/mongo.ts | 63 ------------------ .../src/integrations/mongoose.ts | 63 ------------------ .../src/integrations/mysql.ts | 51 --------------- .../src/integrations/mysql2.ts | 63 ------------------ .../src/integrations/nest.ts | 51 --------------- .../src/integrations/postgres.ts | 65 ------------------- .../src/integrations/tracing/express.ts | 30 +++++++++ .../src/integrations/tracing/fastify.ts | 30 +++++++++ .../src/integrations/tracing/graphql.ts | 31 +++++++++ .../src/integrations/tracing/hapi.ts | 22 +++++++ .../index.ts} | 0 .../src/integrations/{ => tracing}/koa.ts | 0 .../src/integrations/tracing/mongo.ts | 30 +++++++++ .../src/integrations/tracing/mongoose.ts | 30 +++++++++ .../src/integrations/tracing/mysql.ts | 22 +++++++ .../src/integrations/tracing/mysql2.ts | 30 +++++++++ .../src/integrations/tracing/nest.ts | 22 +++++++ .../src/integrations/tracing/postgres.ts | 31 +++++++++ .../src/integrations/{ => tracing}/prisma.ts | 33 +--------- packages/node-experimental/src/sdk/init.ts | 2 +- .../node-experimental/test/sdk/init.test.ts | 2 +- 27 files changed, 293 insertions(+), 698 deletions(-) delete mode 100644 packages/node-experimental/src/integrations/NodePerformanceIntegration.ts delete mode 100644 packages/node-experimental/src/integrations/express.ts delete mode 100644 packages/node-experimental/src/integrations/fastify.ts delete mode 100644 packages/node-experimental/src/integrations/graphql.ts delete mode 100644 packages/node-experimental/src/integrations/hapi.ts delete mode 100644 packages/node-experimental/src/integrations/mongo.ts delete mode 100644 packages/node-experimental/src/integrations/mongoose.ts delete mode 100644 packages/node-experimental/src/integrations/mysql.ts delete mode 100644 packages/node-experimental/src/integrations/mysql2.ts delete mode 100644 packages/node-experimental/src/integrations/nest.ts delete mode 100644 packages/node-experimental/src/integrations/postgres.ts create mode 100644 packages/node-experimental/src/integrations/tracing/express.ts create mode 100644 packages/node-experimental/src/integrations/tracing/fastify.ts create mode 100644 packages/node-experimental/src/integrations/tracing/graphql.ts create mode 100644 packages/node-experimental/src/integrations/tracing/hapi.ts rename packages/node-experimental/src/integrations/{getAutoPerformanceIntegrations.ts => tracing/index.ts} (100%) rename packages/node-experimental/src/integrations/{ => tracing}/koa.ts (100%) create mode 100644 packages/node-experimental/src/integrations/tracing/mongo.ts create mode 100644 packages/node-experimental/src/integrations/tracing/mongoose.ts create mode 100644 packages/node-experimental/src/integrations/tracing/mysql.ts create mode 100644 packages/node-experimental/src/integrations/tracing/mysql2.ts create mode 100644 packages/node-experimental/src/integrations/tracing/nest.ts create mode 100644 packages/node-experimental/src/integrations/tracing/postgres.ts rename packages/node-experimental/src/integrations/{ => tracing}/prisma.ts (51%) diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index b988822a5a38..9ff7b3eb28a6 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -1,18 +1,18 @@ -export { expressIntegration } from './integrations/express'; -export { fastifyIntegration } from './integrations/fastify'; -export { graphqlIntegration } from './integrations/graphql'; +export { expressIntegration } from './integrations/tracing/express'; +export { fastifyIntegration } from './integrations/tracing/fastify'; +export { graphqlIntegration } from './integrations/tracing/graphql'; export { httpIntegration } from './integrations/http'; -export { mongoIntegration } from './integrations/mongo'; -export { mongooseIntegration } from './integrations/mongoose'; -export { mysqlIntegration } from './integrations/mysql'; -export { mysql2Integration } from './integrations/mysql2'; -export { nestIntegration } from './integrations/nest'; +export { mongoIntegration } from './integrations/tracing/mongo'; +export { mongooseIntegration } from './integrations/tracing/mongoose'; +export { mysqlIntegration } from './integrations/tracing/mysql'; +export { mysql2Integration } from './integrations/tracing/mysql2'; +export { nestIntegration } from './integrations/tracing/nest'; export { nativeNodeFetchIntegration } from './integrations/node-fetch'; -export { postgresIntegration } from './integrations/postgres'; -export { prismaIntegration } from './integrations/prisma'; +export { postgresIntegration } from './integrations/tracing/postgres'; +export { prismaIntegration } from './integrations/tracing/prisma'; export { init, getDefaultIntegrations } from './sdk/init'; -export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations'; +export { getAutoPerformanceIntegrations } from './integrations/tracing'; export * as Handlers from './sdk/handlers'; export type { Span } from './types'; diff --git a/packages/node-experimental/src/integrations/NodePerformanceIntegration.ts b/packages/node-experimental/src/integrations/NodePerformanceIntegration.ts deleted file mode 100644 index 536b9ed55daa..000000000000 --- a/packages/node-experimental/src/integrations/NodePerformanceIntegration.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; - -/** - * The base node performance integration. - */ -export abstract class NodePerformanceIntegration { - protected _options: IntegrationOptions; - protected _unload?: () => void; - protected _instrumentations?: Instrumentation[] | undefined; - - public abstract name: string; - - public constructor(options: IntegrationOptions) { - this._options = options; - } - - /** - * Load the instrumentation(s) for this integration. - * Returns `true` if the instrumentations were loaded, else false. - */ - public loadInstrumentations(): boolean { - try { - this._instrumentations = this.setupInstrumentation(this._options) || undefined; - } catch (error) { - return false; - } - - return true; - } - - /** - * @inheritDoc - */ - public setupOnce(): void { - const instrumentations = this._instrumentations || this.setupInstrumentation(this._options); - - if (!instrumentations) { - return; - } - - // Register instrumentations we care about - this._unload = registerInstrumentations({ - instrumentations, - }); - } - - /** - * Unregister this integration. - */ - public unregister(): void { - this._unload?.(); - } - - // Return the instrumentation(s) needed for this integration. - public abstract setupInstrumentation(options: IntegrationOptions): Instrumentation[] | void; -} diff --git a/packages/node-experimental/src/integrations/express.ts b/packages/node-experimental/src/integrations/express.ts deleted file mode 100644 index 1931038da714..000000000000 --- a/packages/node-experimental/src/integrations/express.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _expressIntegration = (() => { - return { - name: 'Express', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new ExpressInstrumentation({ - requestHook(span) { - addOriginToSpan(span, 'auto.http.otel.express'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const expressIntegration = defineIntegration(_expressIntegration); - -/** - * Express integration - * - * Capture tracing data for express. - * @deprecated Use `expressIntegration()` instead. - */ -export class Express extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Express'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Express.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new ExpressInstrumentation({ - requestHook(span) { - addOriginToSpan(span, 'auto.http.otel.express'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/fastify.ts b/packages/node-experimental/src/integrations/fastify.ts deleted file mode 100644 index b34d267934aa..000000000000 --- a/packages/node-experimental/src/integrations/fastify.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _fastifyIntegration = (() => { - return { - name: 'Fastify', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new FastifyInstrumentation({ - requestHook(span) { - addOriginToSpan(span, 'auto.http.otel.fastify'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const fastifyIntegration = defineIntegration(_fastifyIntegration); - -/** - * Express integration - * - * Capture tracing data for fastify. - * - * @deprecated Use `fastifyIntegration()` instead. - */ -export class Fastify extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Fastify'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Fastify.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new FastifyInstrumentation({ - requestHook(span) { - addOriginToSpan(span, 'auto.http.otel.fastify'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/graphql.ts b/packages/node-experimental/src/integrations/graphql.ts deleted file mode 100644 index 576d049c44b9..000000000000 --- a/packages/node-experimental/src/integrations/graphql.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _graphqlIntegration = (() => { - return { - name: 'Graphql', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new GraphQLInstrumentation({ - ignoreTrivialResolveSpans: true, - responseHook(span) { - addOriginToSpan(span, 'auto.graphql.otel.graphql'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const graphqlIntegration = defineIntegration(_graphqlIntegration); - -/** - * GraphQL integration - * - * Capture tracing data for GraphQL. - * - * @deprecated Use `graphqlIntegration()` instead. - */ -export class GraphQL extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'GraphQL'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = GraphQL.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new GraphQLInstrumentation({ - ignoreTrivialResolveSpans: true, - responseHook(span) { - addOriginToSpan(span, 'auto.graphql.otel.graphql'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/hapi.ts b/packages/node-experimental/src/integrations/hapi.ts deleted file mode 100644 index 1376bcb49ccf..000000000000 --- a/packages/node-experimental/src/integrations/hapi.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _hapiIntegration = (() => { - return { - name: 'Hapi', - setupOnce() { - registerInstrumentations({ - instrumentations: [new HapiInstrumentation()], - }); - }, - }; -}) satisfies IntegrationFn; - -export const hapiIntegration = defineIntegration(_hapiIntegration); - -/** - * Hapi integration - * - * Capture tracing data for Hapi. - * - * @deprecated Use `hapiIntegration()` instead. - */ -export class Hapi extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Hapi'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Hapi.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - // Has no hook to adjust spans and add origin - return [new HapiInstrumentation()]; - } -} diff --git a/packages/node-experimental/src/integrations/mongo.ts b/packages/node-experimental/src/integrations/mongo.ts deleted file mode 100644 index bcfaaaf1bc62..000000000000 --- a/packages/node-experimental/src/integrations/mongo.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _mongoIntegration = (() => { - return { - name: 'Mongo', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new MongoDBInstrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mongo'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const mongoIntegration = defineIntegration(_mongoIntegration); - -/** - * MongoDB integration - * - * Capture tracing data for MongoDB. - * - * @deprecated Use `mongoIntegration()` instead. - */ -export class Mongo extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Mongo'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Mongo.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new MongoDBInstrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mongo'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/mongoose.ts b/packages/node-experimental/src/integrations/mongoose.ts deleted file mode 100644 index a14c7d54a266..000000000000 --- a/packages/node-experimental/src/integrations/mongoose.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _mongooseIntegration = (() => { - return { - name: 'Mongoose', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new MongooseInstrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mongoose'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const mongooseIntegration = defineIntegration(_mongooseIntegration); - -/** - * Mongoose integration - * - * Capture tracing data for Mongoose. - * - * @deprecated Use `mongooseIntegration()` instead. - */ -export class Mongoose extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Mongoose'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Mongoose.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new MongooseInstrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mongoose'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/mysql.ts b/packages/node-experimental/src/integrations/mysql.ts deleted file mode 100644 index 3cf0f4e42c87..000000000000 --- a/packages/node-experimental/src/integrations/mysql.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _mysqlIntegration = (() => { - return { - name: 'Mysql', - setupOnce() { - registerInstrumentations({ - instrumentations: [new MySQLInstrumentation({})], - }); - }, - }; -}) satisfies IntegrationFn; - -export const mysqlIntegration = defineIntegration(_mysqlIntegration); - -/** - * MySQL integration - * - * Capture tracing data for mysql. - * - * @deprecated Use `mysqlIntegration()` instead. - */ -export class Mysql extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Mysql'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Mysql.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - // Has no hook to adjust spans and add origin - return [new MySQLInstrumentation({})]; - } -} diff --git a/packages/node-experimental/src/integrations/mysql2.ts b/packages/node-experimental/src/integrations/mysql2.ts deleted file mode 100644 index bb89d0aa01cb..000000000000 --- a/packages/node-experimental/src/integrations/mysql2.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _mysql2Integration = (() => { - return { - name: 'Mysql2', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new MySQL2Instrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mysql2'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const mysql2Integration = defineIntegration(_mysql2Integration); - -/** - * MySQL2 integration - * - * Capture tracing data for mysql2 - * - * @deprecated Use `mysql2Integration()` instead. - */ -export class Mysql2 extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Mysql2'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Mysql2.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new MySQL2Instrumentation({ - responseHook(span) { - addOriginToSpan(span, 'auto.db.otel.mysql2'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/nest.ts b/packages/node-experimental/src/integrations/nest.ts deleted file mode 100644 index c03955f71193..000000000000 --- a/packages/node-experimental/src/integrations/nest.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _nestIntegration = (() => { - return { - name: 'Nest', - setupOnce() { - registerInstrumentations({ - instrumentations: [new NestInstrumentation({})], - }); - }, - }; -}) satisfies IntegrationFn; - -export const nestIntegration = defineIntegration(_nestIntegration); - -/** - * Nest framework integration - * - * Capture tracing data for nest. - * - * @deprecated Use `nestIntegration()` instead. - */ -export class Nest extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Nest'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Nest.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - // Does not have a hook to adjust spans and add origin - return [new NestInstrumentation({})]; - } -} diff --git a/packages/node-experimental/src/integrations/postgres.ts b/packages/node-experimental/src/integrations/postgres.ts deleted file mode 100644 index 91a6a710ffdd..000000000000 --- a/packages/node-experimental/src/integrations/postgres.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'; -import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { addOriginToSpan } from '../utils/addOriginToSpan'; -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; - -const _postgresIntegration = (() => { - return { - name: 'Postgres', - setupOnce() { - registerInstrumentations({ - instrumentations: [ - new PgInstrumentation({ - requireParentSpan: true, - requestHook(span) { - addOriginToSpan(span, 'auto.db.otel.postgres'); - }, - }), - ], - }); - }, - }; -}) satisfies IntegrationFn; - -export const postgresIntegration = defineIntegration(_postgresIntegration); - -/** - * Postgres integration - * - * Capture tracing data for pg. - * - * @deprecated Use `postgresIntegration()` instead. - */ -export class Postgres extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Postgres'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Postgres.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - return [ - new PgInstrumentation({ - requireParentSpan: true, - requestHook(span) { - addOriginToSpan(span, 'auto.db.otel.postgres'); - }, - }), - ]; - } -} diff --git a/packages/node-experimental/src/integrations/tracing/express.ts b/packages/node-experimental/src/integrations/tracing/express.ts new file mode 100644 index 000000000000..0606702d8220 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/express.ts @@ -0,0 +1,30 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _expressIntegration = (() => { + return { + name: 'Express', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new ExpressInstrumentation({ + requestHook(span) { + addOriginToSpan(span, 'auto.http.otel.express'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Express integration + * + * Capture tracing data for express. + */ +export const expressIntegration = defineIntegration(_expressIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/fastify.ts b/packages/node-experimental/src/integrations/tracing/fastify.ts new file mode 100644 index 000000000000..11742d960dff --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/fastify.ts @@ -0,0 +1,30 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _fastifyIntegration = (() => { + return { + name: 'Fastify', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new FastifyInstrumentation({ + requestHook(span) { + addOriginToSpan(span, 'auto.http.otel.fastify'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Express integration + * + * Capture tracing data for fastify. + */ +export const fastifyIntegration = defineIntegration(_fastifyIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/graphql.ts b/packages/node-experimental/src/integrations/tracing/graphql.ts new file mode 100644 index 000000000000..a91524ece6bb --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/graphql.ts @@ -0,0 +1,31 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _graphqlIntegration = (() => { + return { + name: 'Graphql', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new GraphQLInstrumentation({ + ignoreTrivialResolveSpans: true, + responseHook(span) { + addOriginToSpan(span, 'auto.graphql.otel.graphql'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * GraphQL integration + * + * Capture tracing data for GraphQL. + */ +export const graphqlIntegration = defineIntegration(_graphqlIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/hapi.ts b/packages/node-experimental/src/integrations/tracing/hapi.ts new file mode 100644 index 000000000000..949726af698b --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/hapi.ts @@ -0,0 +1,22 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +const _hapiIntegration = (() => { + return { + name: 'Hapi', + setupOnce() { + registerInstrumentations({ + instrumentations: [new HapiInstrumentation()], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Hapi integration + * + * Capture tracing data for Hapi. + */ +export const hapiIntegration = defineIntegration(_hapiIntegration); diff --git a/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts b/packages/node-experimental/src/integrations/tracing/index.ts similarity index 100% rename from packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts rename to packages/node-experimental/src/integrations/tracing/index.ts diff --git a/packages/node-experimental/src/integrations/koa.ts b/packages/node-experimental/src/integrations/tracing/koa.ts similarity index 100% rename from packages/node-experimental/src/integrations/koa.ts rename to packages/node-experimental/src/integrations/tracing/koa.ts diff --git a/packages/node-experimental/src/integrations/tracing/mongo.ts b/packages/node-experimental/src/integrations/tracing/mongo.ts new file mode 100644 index 000000000000..9bbfd16a9581 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/mongo.ts @@ -0,0 +1,30 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _mongoIntegration = (() => { + return { + name: 'Mongo', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new MongoDBInstrumentation({ + responseHook(span) { + addOriginToSpan(span, 'auto.db.otel.mongo'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * MongoDB integration + * + * Capture tracing data for MongoDB. + */ +export const mongoIntegration = defineIntegration(_mongoIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/mongoose.ts b/packages/node-experimental/src/integrations/tracing/mongoose.ts new file mode 100644 index 000000000000..ba15fc647907 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/mongoose.ts @@ -0,0 +1,30 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _mongooseIntegration = (() => { + return { + name: 'Mongoose', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new MongooseInstrumentation({ + responseHook(span) { + addOriginToSpan(span, 'auto.db.otel.mongoose'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Mongoose integration + * + * Capture tracing data for Mongoose. + */ +export const mongooseIntegration = defineIntegration(_mongooseIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/mysql.ts b/packages/node-experimental/src/integrations/tracing/mysql.ts new file mode 100644 index 000000000000..4f6123c7eed9 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/mysql.ts @@ -0,0 +1,22 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +const _mysqlIntegration = (() => { + return { + name: 'Mysql', + setupOnce() { + registerInstrumentations({ + instrumentations: [new MySQLInstrumentation({})], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * MySQL integration + * + * Capture tracing data for mysql. + */ +export const mysqlIntegration = defineIntegration(_mysqlIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/mysql2.ts b/packages/node-experimental/src/integrations/tracing/mysql2.ts new file mode 100644 index 000000000000..14a9e826c332 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/mysql2.ts @@ -0,0 +1,30 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _mysql2Integration = (() => { + return { + name: 'Mysql2', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new MySQL2Instrumentation({ + responseHook(span) { + addOriginToSpan(span, 'auto.db.otel.mysql2'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * MySQL2 integration + * + * Capture tracing data for mysql2 + */ +export const mysql2Integration = defineIntegration(_mysql2Integration); diff --git a/packages/node-experimental/src/integrations/tracing/nest.ts b/packages/node-experimental/src/integrations/tracing/nest.ts new file mode 100644 index 000000000000..1f2c75e3807e --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/nest.ts @@ -0,0 +1,22 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +const _nestIntegration = (() => { + return { + name: 'Nest', + setupOnce() { + registerInstrumentations({ + instrumentations: [new NestInstrumentation({})], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Nest framework integration + * + * Capture tracing data for nest. + */ +export const nestIntegration = defineIntegration(_nestIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/postgres.ts b/packages/node-experimental/src/integrations/tracing/postgres.ts new file mode 100644 index 000000000000..e57df5b52d21 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/postgres.ts @@ -0,0 +1,31 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const _postgresIntegration = (() => { + return { + name: 'Postgres', + setupOnce() { + registerInstrumentations({ + instrumentations: [ + new PgInstrumentation({ + requireParentSpan: true, + requestHook(span) { + addOriginToSpan(span, 'auto.db.otel.postgres'); + }, + }), + ], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Postgres integration + * + * Capture tracing data for pg. + */ +export const postgresIntegration = defineIntegration(_postgresIntegration); diff --git a/packages/node-experimental/src/integrations/prisma.ts b/packages/node-experimental/src/integrations/tracing/prisma.ts similarity index 51% rename from packages/node-experimental/src/integrations/prisma.ts rename to packages/node-experimental/src/integrations/tracing/prisma.ts index 9edd6ce9d02d..1f78262b9a3c 100644 --- a/packages/node-experimental/src/integrations/prisma.ts +++ b/packages/node-experimental/src/integrations/tracing/prisma.ts @@ -1,10 +1,7 @@ -import type { Instrumentation } from '@opentelemetry/instrumentation'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { PrismaInstrumentation } from '@prisma/instrumentation'; import { defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationFn } from '@sentry/types'; - -import { NodePerformanceIntegration } from './NodePerformanceIntegration'; +import type { IntegrationFn } from '@sentry/types'; const _prismaIntegration = (() => { return { @@ -20,8 +17,6 @@ const _prismaIntegration = (() => { }; }) satisfies IntegrationFn; -export const prismaIntegration = defineIntegration(_prismaIntegration); - /** * Prisma integration * @@ -30,29 +25,5 @@ export const prismaIntegration = defineIntegration(_prismaIntegration); * previewFeatures = ["tracing"] * For the prisma client. * See https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing for more details. - * - * @deprecated Use `prismaIntegration()` instead. */ -export class Prisma extends NodePerformanceIntegration implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'Prisma'; - - /** - * @inheritDoc - */ - public name: string; - - public constructor() { - super(); - // eslint-disable-next-line deprecation/deprecation - this.name = Prisma.id; - } - - /** @inheritDoc */ - public setupInstrumentation(): void | Instrumentation[] { - // does not have a hook to adjust spans & add origin - return [new PrismaInstrumentation({})]; - } -} +export const prismaIntegration = defineIntegration(_prismaIntegration); diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 3c2d09fb3847..f5b6f511360c 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -18,9 +18,9 @@ import { } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations'; import { httpIntegration } from '../integrations/http'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; +import { getAutoPerformanceIntegrations } from '../integrations/tracing'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; import { defaultStackParser, getSentryRelease } from './api'; diff --git a/packages/node-experimental/test/sdk/init.test.ts b/packages/node-experimental/test/sdk/init.test.ts index 5474f3c1ca07..7f0c3a8d71ec 100644 --- a/packages/node-experimental/test/sdk/init.test.ts +++ b/packages/node-experimental/test/sdk/init.test.ts @@ -1,6 +1,6 @@ import type { Integration } from '@sentry/types'; -import * as auto from '../../src/integrations/getAutoPerformanceIntegrations'; +import * as auto from '../../src/integrations/tracing'; import { getClient } from '../../src/sdk/api'; import { init } from '../../src/sdk/init'; import { cleanupOtel } from '../helpers/mockSdkInit'; From 6cc1e5111ddb76f94f2e11406064b012114f37f6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 10:46:52 +0100 Subject: [PATCH 101/173] test(node): Handle some jest & axios issues (#10736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes I ran into issues when running node-integration tests locally, something along the lines of: ``` Test suite failed to run Jest encountered an unexpected token Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax. Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. By default "node_modules" folder is ignored by transformers. Here's what you can do: • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it. • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config. • If you need a custom transformation specify a "transform" option in your config. • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option. You'll find more details and examples of these config options in the docs: https://jestjs.io/docs/configuration For information about custom transformations, see: https://jestjs.io/docs/code-transformation Details: /sentry-javascript/node_modules/axios/index.js:1 ({"Object.":function(module,exports,require,__dirname,__filename,jest){import axios from './lib/axios.js'; ``` After some investigation, it seems adding a custom module mapping for axios in jest config fixes this... not sure why this happened sometimes and other times not. While at it, I also bumped axios to latest, and ensured we clean watchman on `yarn clean` (that also cluttered logs sometimes). --- .../browser-integration-tests/package.json | 2 +- .../node-integration-tests/package.json | 6 +-- .../node-integration-tests/scripts/clean.js | 9 ++++ dev-packages/overhead-metrics/package.json | 2 +- jest/jest.config.js | 3 ++ yarn.lock | 43 +++++-------------- 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 7f12a2c7d954..08320f47628c 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -49,7 +49,7 @@ "@sentry-internal/rrweb": "2.11.0", "@sentry/browser": "7.100.0", "@sentry/tracing": "7.100.0", - "axios": "1.6.0", + "axios": "1.6.7", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", "pako": "^2.1.0", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index d9679c29dc27..3826c0024f88 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -14,8 +14,8 @@ "build:dev": "yarn build", "build:transpile": "rollup -c rollup.npm.config.mjs", "build:types": "tsc -p tsconfig.types.json", - "clean": "rimraf -g **/node_modules && run-p clean:docker", - "clean:docker": "node scripts/clean.js", + "clean": "rimraf -g **/node_modules && run-p clean:script", + "clean:script": "node scripts/clean.js", "prisma:init": "(cd suites/tracing/prisma-orm && ts-node ./setup.ts)", "prisma:init:new": "(cd suites/tracing-new/prisma-orm && ts-node ./setup.ts)", "lint": "eslint . --format stylish", @@ -36,7 +36,7 @@ "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", "apollo-server": "^3.11.1", - "axios": "^0.27.2", + "axios": "^1.6.7", "cors": "^2.8.5", "cron": "^3.1.6", "express": "^4.17.3", diff --git a/dev-packages/node-integration-tests/scripts/clean.js b/dev-packages/node-integration-tests/scripts/clean.js index 0610e39f92d4..b7ae8505e916 100644 --- a/dev-packages/node-integration-tests/scripts/clean.js +++ b/dev-packages/node-integration-tests/scripts/clean.js @@ -17,3 +17,12 @@ for (const path of paths) { // } } + +// eslint-disable-next-line no-console +console.log('Cleaning up watchman...'); + +try { + execSync('watchman watch-del "./../.."; watchman watch-project "./../.."', { stdio: 'inherit' }); +} catch (_) { + // +} diff --git a/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json index 4e99daa10705..f9fa2967c498 100644 --- a/dev-packages/overhead-metrics/package.json +++ b/dev-packages/overhead-metrics/package.json @@ -19,7 +19,7 @@ "dependencies": { "@octokit/rest": "^19.0.5", "@types/node": "^18.11.17", - "axios": "^1.2.2", + "axios": "^1.6.7", "extract-zip": "^2.0.1", "filesize": "^10.0.6", "fs-extra": "^11.1.0", diff --git a/jest/jest.config.js b/jest/jest.config.js index 3a92f7c81eec..37a2ee48658b 100644 --- a/jest/jest.config.js +++ b/jest/jest.config.js @@ -9,6 +9,9 @@ module.exports = { coverageDirectory: '/coverage', moduleFileExtensions: ['js', 'ts', 'tsx'], testMatch: ['/**/*.test.ts', '/**/*.test.tsx'], + moduleNameMapper: { + '^axios$': require.resolve('axios'), + }, globals: { 'ts-jest': { tsconfig: '/tsconfig.test.json', diff --git a/yarn.lock b/yarn.lock index d6265ed91967..9d103a596e57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8992,38 +8992,12 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" - integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== +axios@1.6.7, axios@^1.0.0, axios@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== - dependencies: - follow-redirects "^1.14.9" - form-data "^4.0.0" - -axios@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.0.tgz#1cb65bd75162c70e9f8d118a905126c4a201d383" - integrity sha512-zT7wZyNYu3N5Bu0wuZ6QccIf93Qk1eV8LOewxgjOZFd2DenOs98cJ7+Y6703d0wkaXGY6/nZd4EweJaHz9uzQw== - dependencies: - follow-redirects "^1.15.0" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - -axios@^1.2.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.4.tgz#f5760cefd9cfb51fd2481acf88c05f67c4523024" - integrity sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ== - dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.4" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -16256,11 +16230,16 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.0.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: +follow-redirects@^1.0.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" From 28402cb1f943cf52255a4b592106c52d275fa71c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 13:06:27 +0100 Subject: [PATCH 102/173] fix(core): Add lost scope tests & fix update case (#10738) Noticed in the backport PR https://github.com/getsentry/sentry-javascript/actions/runs/7971156054/job/21760458423?pr=10737, where this failed, that we apparently lost some scope tests that used to only be in the hub package. This forwards-ports them to v8, and also fixes a case that was failing previously. --- packages/core/src/scope.ts | 2 +- packages/core/test/lib/scope.test.ts | 394 ++++++++++++++++++++++++++- 2 files changed, 394 insertions(+), 2 deletions(-) diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 6f2cc0aeafcd..cb00f0bd1bbf 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -380,7 +380,7 @@ export class Scope implements ScopeInterface { if (scopeData.level) { this._level = scopeData.level; } - if (scopeData.fingerprint) { + if (scopeData.fingerprint.length) { this._fingerprint = scopeData.fingerprint; } if (scopeToMerge.getRequestSession()) { diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 360cd34938e5..30697275db2f 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -1,4 +1,4 @@ -import type { Attachment, Breadcrumb, Client, Event } from '@sentry/types'; +import type { Attachment, Breadcrumb, Client, Event, RequestSessionStatus } from '@sentry/types'; import { addTracingExtensions, applyScopeDataToEvent, @@ -104,6 +104,398 @@ describe('Scope', () => { }); }); + describe('init', () => { + test('it creates a propagation context', () => { + const scope = new Scope(); + + expect(scope.getScopeData().propagationContext).toEqual({ + traceId: expect.any(String), + spanId: expect.any(String), + sampled: undefined, + dsc: undefined, + parentSpanId: undefined, + }); + }); + }); + + describe('attributes modification', () => { + test('setFingerprint', () => { + const scope = new Scope(); + scope.setFingerprint(['abcd']); + expect(scope['_fingerprint']).toEqual(['abcd']); + }); + + test('setExtra', () => { + const scope = new Scope(); + scope.setExtra('a', 1); + expect(scope['_extra']).toEqual({ a: 1 }); + }); + + test('setExtras', () => { + const scope = new Scope(); + scope.setExtras({ a: 1 }); + expect(scope['_extra']).toEqual({ a: 1 }); + }); + + test('setExtras with undefined overrides the value', () => { + const scope = new Scope(); + scope.setExtra('a', 1); + scope.setExtras({ a: undefined }); + expect(scope['_extra']).toEqual({ a: undefined }); + }); + + test('setTag', () => { + const scope = new Scope(); + scope.setTag('a', 'b'); + expect(scope['_tags']).toEqual({ a: 'b' }); + }); + + test('setTags', () => { + const scope = new Scope(); + scope.setTags({ a: 'b' }); + expect(scope['_tags']).toEqual({ a: 'b' }); + }); + + test('setUser', () => { + const scope = new Scope(); + scope.setUser({ id: '1' }); + expect(scope['_user']).toEqual({ id: '1' }); + }); + + test('setUser with null unsets the user', () => { + const scope = new Scope(); + scope.setUser({ id: '1' }); + scope.setUser(null); + expect(scope['_user']).toEqual({}); + }); + + test('addBreadcrumb', () => { + const scope = new Scope(); + scope.addBreadcrumb({ message: 'test' }); + expect(scope['_breadcrumbs'][0]).toHaveProperty('message', 'test'); + }); + + test('addBreadcrumb can be limited to hold up to N breadcrumbs', () => { + const scope = new Scope(); + for (let i = 0; i < 10; i++) { + scope.addBreadcrumb({ message: 'test' }, 5); + } + expect(scope['_breadcrumbs']).toHaveLength(5); + }); + + test('addBreadcrumb can go over DEFAULT_MAX_BREADCRUMBS value', () => { + const scope = new Scope(); + for (let i = 0; i < 120; i++) { + scope.addBreadcrumb({ message: 'test' }, 111); + } + expect(scope['_breadcrumbs']).toHaveLength(111); + }); + + test('setLevel', () => { + const scope = new Scope(); + scope.setLevel('fatal'); + expect(scope['_level']).toEqual('fatal'); + }); + + test('setContext', () => { + const scope = new Scope(); + scope.setContext('os', { id: '1' }); + expect(scope['_contexts'].os).toEqual({ id: '1' }); + }); + + test('setContext with null unsets it', () => { + const scope = new Scope(); + scope.setContext('os', { id: '1' }); + scope.setContext('os', null); + expect(scope['_user']).toEqual({}); + }); + + test('setSpan', () => { + const scope = new Scope(); + const span = { fake: 'span' } as any; + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(span); + expect(scope['_span']).toEqual(span); + }); + + test('setSpan with no value unsets it', () => { + const scope = new Scope(); + // eslint-disable-next-line deprecation/deprecation + scope.setSpan({ fake: 'span' } as any); + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(); + expect(scope['_span']).toEqual(undefined); + }); + + test('setProcessingMetadata', () => { + const scope = new Scope(); + scope.setSDKProcessingMetadata({ dogs: 'are great!' }); + expect(scope['_sdkProcessingMetadata'].dogs).toEqual('are great!'); + }); + + test('set and get propagation context', () => { + const scope = new Scope(); + const oldPropagationContext = scope.getPropagationContext(); + scope.setPropagationContext({ + traceId: '86f39e84263a4de99c326acab3bfe3bd', + spanId: '6e0c63257de34c92', + sampled: true, + }); + expect(scope.getPropagationContext()).not.toEqual(oldPropagationContext); + expect(scope.getPropagationContext()).toEqual({ + traceId: '86f39e84263a4de99c326acab3bfe3bd', + spanId: '6e0c63257de34c92', + sampled: true, + }); + }); + + test('chaining', () => { + const scope = new Scope(); + scope.setLevel('fatal').setUser({ id: '1' }); + expect(scope['_level']).toEqual('fatal'); + expect(scope['_user']).toEqual({ id: '1' }); + }); + }); + + describe('clone', () => { + test('basic inheritance', () => { + const parentScope = new Scope(); + parentScope.setExtra('a', 1); + const scope = parentScope.clone(); + expect(parentScope['_extra']).toEqual(scope['_extra']); + }); + + test('_requestSession clone', () => { + const parentScope = new Scope(); + parentScope.setRequestSession({ status: 'errored' }); + const scope = parentScope.clone(); + expect(parentScope.getRequestSession()).toEqual(scope.getRequestSession()); + }); + + test('parent changed inheritance', () => { + const parentScope = new Scope(); + const scope = parentScope.clone(); + parentScope.setExtra('a', 2); + expect(scope['_extra']).toEqual({}); + expect(parentScope['_extra']).toEqual({ a: 2 }); + }); + + test('child override inheritance', () => { + const parentScope = new Scope(); + parentScope.setExtra('a', 1); + + const scope = parentScope.clone(); + scope.setExtra('a', 2); + expect(parentScope['_extra']).toEqual({ a: 1 }); + expect(scope['_extra']).toEqual({ a: 2 }); + }); + + test('child override should set the value of parent _requestSession', () => { + // Test that ensures if the status value of `status` of `_requestSession` is changed in a child scope + // that it should also change in parent scope because we are copying the reference to the object + const parentScope = new Scope(); + parentScope.setRequestSession({ status: 'errored' }); + + const scope = parentScope.clone(); + const requestSession = scope.getRequestSession(); + if (requestSession) { + requestSession.status = 'ok'; + } + + expect(parentScope.getRequestSession()).toEqual({ status: 'ok' }); + expect(scope.getRequestSession()).toEqual({ status: 'ok' }); + }); + + test('should clone propagation context', () => { + const parentScope = new Scope(); + const scope = parentScope.clone(); + + expect(scope.getScopeData().propagationContext).toEqual(parentScope.getScopeData().propagationContext); + }); + }); + + test('clear', () => { + const scope = new Scope(); + const oldPropagationContext = scope.getScopeData().propagationContext; + scope.setExtra('a', 2); + scope.setTag('a', 'b'); + scope.setUser({ id: '1' }); + scope.setFingerprint(['abcd']); + scope.addBreadcrumb({ message: 'test' }); + scope.setRequestSession({ status: 'ok' }); + expect(scope['_extra']).toEqual({ a: 2 }); + scope.clear(); + expect(scope['_extra']).toEqual({}); + expect(scope['_requestSession']).toEqual(undefined); + expect(scope['_propagationContext']).toEqual({ + traceId: expect.any(String), + spanId: expect.any(String), + sampled: undefined, + }); + expect(scope['_propagationContext']).not.toEqual(oldPropagationContext); + }); + + test('clearBreadcrumbs', () => { + const scope = new Scope(); + scope.addBreadcrumb({ message: 'test' }); + expect(scope['_breadcrumbs']).toHaveLength(1); + scope.clearBreadcrumbs(); + expect(scope['_breadcrumbs']).toHaveLength(0); + }); + + describe('update', () => { + let scope: Scope; + + beforeEach(() => { + scope = new Scope(); + scope.setTags({ foo: '1', bar: '2' }); + scope.setExtras({ foo: '1', bar: '2' }); + scope.setContext('foo', { id: '1' }); + scope.setContext('bar', { id: '2' }); + scope.setUser({ id: '1337' }); + scope.setLevel('info'); + scope.setFingerprint(['foo']); + scope.setRequestSession({ status: 'ok' }); + }); + + test('given no data, returns the original scope', () => { + const updatedScope = scope.update(); + expect(updatedScope).toEqual(scope); + }); + + test('given neither function, Scope or plain object, returns original scope', () => { + // @ts-expect-error we want to be able to update scope with string + const updatedScope = scope.update('wat'); + expect(updatedScope).toEqual(scope); + }); + + test('given callback function, pass it the scope and returns original or modified scope', () => { + const cb = jest + .fn() + .mockImplementationOnce(v => v) + .mockImplementationOnce(v => { + v.setTag('foo', 'bar'); + return v; + }); + + let updatedScope = scope.update(cb); + expect(cb).toHaveBeenNthCalledWith(1, scope); + expect(updatedScope).toEqual(scope); + + updatedScope = scope.update(cb); + expect(cb).toHaveBeenNthCalledWith(2, scope); + expect(updatedScope).toEqual(scope); + }); + + test('given callback function, when it doesnt return instanceof Scope, ignore it and return original scope', () => { + const cb = jest.fn().mockImplementationOnce(_v => 'wat'); + const updatedScope = scope.update(cb); + expect(cb).toHaveBeenCalledWith(scope); + expect(updatedScope).toEqual(scope); + }); + + test('given another instance of Scope, it should merge two together, with the passed scope having priority', () => { + const localScope = new Scope(); + localScope.setTags({ bar: '3', baz: '4' }); + localScope.setExtras({ bar: '3', baz: '4' }); + localScope.setContext('bar', { id: '3' }); + localScope.setContext('baz', { id: '4' }); + localScope.setUser({ id: '42' }); + localScope.setLevel('warning'); + localScope.setFingerprint(['bar']); + (localScope as any)._requestSession = { status: 'ok' }; + + const updatedScope = scope.update(localScope) as any; + + expect(updatedScope._tags).toEqual({ + bar: '3', + baz: '4', + foo: '1', + }); + expect(updatedScope._extra).toEqual({ + bar: '3', + baz: '4', + foo: '1', + }); + expect(updatedScope._contexts).toEqual({ + bar: { id: '3' }, + baz: { id: '4' }, + foo: { id: '1' }, + }); + expect(updatedScope._user).toEqual({ id: '42' }); + expect(updatedScope._level).toEqual('warning'); + expect(updatedScope._fingerprint).toEqual(['bar']); + expect(updatedScope._requestSession.status).toEqual('ok'); + // @ts-expect-error accessing private property for test + expect(updatedScope._propagationContext).toEqual(localScope._propagationContext); + }); + + test('given an empty instance of Scope, it should preserve all the original scope data', () => { + const updatedScope = scope.update(new Scope()) as any; + + expect(updatedScope._tags).toEqual({ + bar: '2', + foo: '1', + }); + expect(updatedScope._extra).toEqual({ + bar: '2', + foo: '1', + }); + expect(updatedScope._contexts).toEqual({ + bar: { id: '2' }, + foo: { id: '1' }, + }); + expect(updatedScope._user).toEqual({ id: '1337' }); + expect(updatedScope._level).toEqual('info'); + expect(updatedScope._fingerprint).toEqual(['foo']); + expect(updatedScope._requestSession.status).toEqual('ok'); + }); + + test('given a plain object, it should merge two together, with the passed object having priority', () => { + const localAttributes = { + contexts: { bar: { id: '3' }, baz: { id: '4' } }, + extra: { bar: '3', baz: '4' }, + fingerprint: ['bar'], + level: 'warning' as const, + tags: { bar: '3', baz: '4' }, + user: { id: '42' }, + requestSession: { status: 'errored' as RequestSessionStatus }, + propagationContext: { + traceId: '8949daf83f4a4a70bee4c1eb9ab242ed', + spanId: 'a024ad8fea82680e', + sampled: true, + }, + }; + + const updatedScope = scope.update(localAttributes) as any; + + expect(updatedScope._tags).toEqual({ + bar: '3', + baz: '4', + foo: '1', + }); + expect(updatedScope._extra).toEqual({ + bar: '3', + baz: '4', + foo: '1', + }); + expect(updatedScope._contexts).toEqual({ + bar: { id: '3' }, + baz: { id: '4' }, + foo: { id: '1' }, + }); + expect(updatedScope._user).toEqual({ id: '42' }); + expect(updatedScope._level).toEqual('warning'); + expect(updatedScope._fingerprint).toEqual(['bar']); + expect(updatedScope._requestSession).toEqual({ status: 'errored' }); + expect(updatedScope._propagationContext).toEqual({ + traceId: '8949daf83f4a4a70bee4c1eb9ab242ed', + spanId: 'a024ad8fea82680e', + sampled: true, + }); + }); + }); + describe('global scope', () => { beforeEach(() => { setGlobalScope(undefined); From a02aa00626eb37fce85399533973c62678100734 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 13:06:47 +0100 Subject: [PATCH 103/173] feat(node-experimental): Move `errorHandler` (#10728) Also stop exporting `requestHandler`, as that should be handled bu the otel http instrumentation. I also fixed the otel stuff to properly take care of sessions. --- .../express/tracing-experimental/server.js | 2 +- packages/node-experimental/src/index.ts | 7 +- .../src/integrations/http.ts | 14 +- .../node-experimental/src/sdk/handlers.ts | 5 - .../src/sdk/handlers/errorHandler.ts | 76 ++++++++++ packages/node-experimental/src/sdk/init.ts | 6 + .../test/sdk/handlers/errorHandler.test.ts | 137 ++++++++++++++++++ 7 files changed, 237 insertions(+), 10 deletions(-) delete mode 100644 packages/node-experimental/src/sdk/handlers.ts create mode 100644 packages/node-experimental/src/sdk/handlers/errorHandler.ts create mode 100644 packages/node-experimental/test/sdk/handlers/errorHandler.test.ts diff --git a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js index 843c8f47b3f2..0f9136789b12 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js +++ b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js @@ -34,6 +34,6 @@ app.get(['/test/arr/:id', /\/test\/arr[0-9]*\/required(path)?(\/optionalPath)?\/ res.send({ response: 'response 4' }); }); -app.use(Sentry.Handlers.errorHandler()); +app.use(Sentry.errorHandler()); startExpressServerAndSendPortToRunner(app); diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 9ff7b3eb28a6..872901f09957 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -1,19 +1,20 @@ +export { errorHandler } from './sdk/handlers/errorHandler'; + +export { httpIntegration } from './integrations/http'; +export { nativeNodeFetchIntegration } from './integrations/node-fetch'; export { expressIntegration } from './integrations/tracing/express'; export { fastifyIntegration } from './integrations/tracing/fastify'; export { graphqlIntegration } from './integrations/tracing/graphql'; -export { httpIntegration } from './integrations/http'; export { mongoIntegration } from './integrations/tracing/mongo'; export { mongooseIntegration } from './integrations/tracing/mongoose'; export { mysqlIntegration } from './integrations/tracing/mysql'; export { mysql2Integration } from './integrations/tracing/mysql2'; export { nestIntegration } from './integrations/tracing/nest'; -export { nativeNodeFetchIntegration } from './integrations/node-fetch'; export { postgresIntegration } from './integrations/tracing/postgres'; export { prismaIntegration } from './integrations/tracing/prisma'; export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/tracing'; -export * as Handlers from './sdk/handlers'; export type { Span } from './types'; export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 14b163e5a309..0c53a0c7e889 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -8,6 +8,7 @@ import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl import { _INTERNAL, getClient, getSpanKind, setSpanMetadata } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; +import type { NodeClient } from '../sdk/client'; import { setIsolationScope } from '../sdk/scope'; import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module'; import { addOriginToSpan } from '../utils/addOriginToSpan'; @@ -86,7 +87,11 @@ const _httpIntegration = ((options: HttpOptions = {}) => { if (getSpanKind(span) === SpanKind.SERVER) { const isolationScope = getIsolationScope().clone(); isolationScope.setSDKProcessingMetadata({ request: req }); - isolationScope.setRequestSession({ status: 'ok' }); + + const client = getClient(); + if (client && client.getOptions().autoSessionTracking) { + isolationScope.setRequestSession({ status: 'ok' }); + } setIsolationScope(isolationScope); } }, @@ -94,6 +99,13 @@ const _httpIntegration = ((options: HttpOptions = {}) => { if (_breadcrumbs) { _addRequestBreadcrumb(span, res); } + + const client = getClient(); + if (client && client.getOptions().autoSessionTracking) { + setImmediate(() => { + client['_captureRequestSession'](); + }); + } }, }), ]; diff --git a/packages/node-experimental/src/sdk/handlers.ts b/packages/node-experimental/src/sdk/handlers.ts deleted file mode 100644 index 2c9b9b1ad832..000000000000 --- a/packages/node-experimental/src/sdk/handlers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Handlers } from '@sentry/node'; - -const { requestHandler, errorHandler } = Handlers; - -export { requestHandler, errorHandler }; diff --git a/packages/node-experimental/src/sdk/handlers/errorHandler.ts b/packages/node-experimental/src/sdk/handlers/errorHandler.ts new file mode 100644 index 000000000000..2460ea11a0e4 --- /dev/null +++ b/packages/node-experimental/src/sdk/handlers/errorHandler.ts @@ -0,0 +1,76 @@ +import type * as http from 'http'; +import { captureException, getClient, getIsolationScope } from '@sentry/core'; +import type { NodeClient } from '../client'; + +interface MiddlewareError extends Error { + status?: number | string; + statusCode?: number | string; + status_code?: number | string; + output?: { + statusCode?: number | string; + }; +} + +/** + * An Express-compatible error handler. + */ +export function errorHandler(options?: { + /** + * Callback method deciding whether error should be captured and sent to Sentry + * @param error Captured middleware error + */ + shouldHandleError?(this: void, error: MiddlewareError): boolean; +}): ( + error: MiddlewareError, + req: http.IncomingMessage, + res: http.ServerResponse, + next: (error: MiddlewareError) => void, +) => void { + return function sentryErrorMiddleware( + error: MiddlewareError, + _req: http.IncomingMessage, + res: http.ServerResponse, + next: (error: MiddlewareError) => void, + ): void { + const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; + + if (shouldHandleError(error)) { + const client = getClient(); + if (client && client.getOptions().autoSessionTracking) { + // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the + // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only + // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be + // running in SessionAggregates mode + const isSessionAggregatesMode = client['_sessionFlusher'] !== undefined; + if (isSessionAggregatesMode) { + const requestSession = getIsolationScope().getRequestSession(); + // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a + // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within + // the bounds of a request, and if so the status is updated + if (requestSession && requestSession.status !== undefined) { + requestSession.status = 'crashed'; + } + } + } + + const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } }); + (res as { sentry?: string }).sentry = eventId; + next(error); + + return; + } + + next(error); + }; +} + +function getStatusCodeFromResponse(error: MiddlewareError): number { + const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode); + return statusCode ? parseInt(statusCode as string, 10) : 500; +} + +/** Returns true if response code is internal server error */ +function defaultShouldHandleError(error: MiddlewareError): boolean { + const status = getStatusCodeFromResponse(error); + return status >= 500; +} diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index f5b6f511360c..c61a72155cda 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -1,5 +1,6 @@ import { endSession, + getClient, getCurrentScope, getIntegrationsToSetup, getIsolationScope, @@ -184,6 +185,11 @@ function updateScopeFromEnvVariables(): void { * Enable automatic Session Tracking for the node process. */ function startSessionTracking(): void { + const client = getClient(); + if (client && client.getOptions().autoSessionTracking) { + client.initSessionFlusher(); + } + startSession(); // Emitted in the case of healthy sessions, error of `mechanism.handled: true` and unhandledrejections because diff --git a/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts b/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts new file mode 100644 index 000000000000..05d3f73858d7 --- /dev/null +++ b/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts @@ -0,0 +1,137 @@ +import * as http from 'http'; +import { getCurrentScope, getIsolationScope, setAsyncContextStrategy, setCurrentClient, withScope } from '@sentry/core'; +import type { Scope } from '@sentry/types'; +import { NodeClient } from '../../../src/sdk/client'; +import { errorHandler } from '../../../src/sdk/handlers/errorHandler'; +import { getDefaultNodeClientOptions } from '../../helpers/getDefaultNodePreviewClientOptions'; + +describe('errorHandler()', () => { + beforeEach(() => { + getCurrentScope().clear(); + getIsolationScope().clear(); + + setAsyncContextStrategy(undefined); + }); + + const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' }; + const method = 'wagging'; + const protocol = 'mutualsniffing'; + const hostname = 'the.dog.park'; + const path = '/by/the/trees/'; + const queryString = 'chase=me&please=thankyou'; + + const sentryErrorMiddleware = errorHandler(); + + let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; + let client: NodeClient; + + function createNoOpSpy() { + const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it + return jest.spyOn(noop, 'noop') as any; + } + + beforeEach(() => { + req = { + headers, + method, + protocol, + hostname, + originalUrl: `${path}?${queryString}`, + } as unknown as http.IncomingMessage; + res = new http.ServerResponse(req); + next = createNoOpSpy(); + }); + + afterEach(() => { + if (client['_sessionFlusher']) { + clearInterval(client['_sessionFlusher']['_intervalId']); + } + jest.restoreAllMocks(); + }); + it('when autoSessionTracking is disabled, does not set requestSession status on Crash', done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); + client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + setCurrentClient(client); + + jest.spyOn(client, '_captureRequestSession'); + + getIsolationScope().setRequestSession({ status: 'ok' }); + + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); + }); + + it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); + client = new NodeClient(options); + setCurrentClient(client); + + jest.spyOn(client, '_captureRequestSession'); + + getIsolationScope().setRequestSession({ status: 'ok' }); + + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); + }); + + it('when autoSessionTracking is enabled, should set requestSession status to Crashed when an unhandled error occurs within the bounds of a request', () => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.1' }); + client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + + setCurrentClient(client); + + jest.spyOn(client, '_captureRequestSession'); + + withScope(() => { + getIsolationScope().setRequestSession({ status: 'ok' }); + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + expect(getIsolationScope().getRequestSession()).toEqual({ status: 'crashed' }); + }); + }); + }); + + it('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); + client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + setCurrentClient(client); + + jest.spyOn(client, '_captureRequestSession'); + + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); + + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual(undefined); + done(); + }); + }); +}); From 54b94dab3307b6aef5b854e8e4a3281bc91629cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:25:13 +0100 Subject: [PATCH 104/173] feat(deps): bump @sentry/cli from 2.28.5 to 2.28.6 (#10727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@sentry/cli](https://github.com/getsentry/sentry-cli) from 2.28.5 to 2.28.6.
    Release notes

    Sourced from @​sentry/cli's releases.

    2.28.6

    Various fixes & improvements

    Changelog

    Sourced from @​sentry/cli's changelog.

    2.28.6

    Various fixes & improvements

    Commits

    [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/cli&package-manager=npm_and_yarn&previous-version=2.28.5&new-version=2.28.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
    Dependabot commands and options
    You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/remix/package.json | 2 +- yarn.lock | 90 ++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index 9a3b6ce588f4..54c5c980db55 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -34,7 +34,7 @@ "access": "public" }, "dependencies": { - "@sentry/cli": "^2.28.5", + "@sentry/cli": "^2.28.6", "@sentry/core": "7.100.0", "@sentry/node": "7.100.0", "@sentry/react": "7.100.0", diff --git a/yarn.lock b/yarn.lock index 9d103a596e57..df4a75089671 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5612,40 +5612,40 @@ magic-string "0.27.0" unplugin "1.0.1" -"@sentry/cli-darwin@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.5.tgz#d9ab340acaa8179fbfc0bafbf784e66b52ad5f89" - integrity sha512-+18cWhVcAGB8rGEDQxMn+eZIwXdm7nMp0JJFhZ+QJLA9hYr6m2rLKkQqh9g7TgDPZo+NAsE1KSe/LcFy9gW9gw== - -"@sentry/cli-linux-arm64@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.5.tgz#7c3619288dc052560edad90bf27320e660181d8d" - integrity sha512-a0A06O4lc4vmeVLfbKuIavvqOy1Woe0uGEO4tPt2aELtHS280g0pLn2JZ48wQ7XgfC60UK1h/iW1B+XKcT0aKg== - -"@sentry/cli-linux-arm@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.5.tgz#06f14a6cad7c7a2ae0bc051ce858da65b9d2f95e" - integrity sha512-05MR8BEU+wmHWw9ejD73IQlK737SfEKgRd1WOO+ZYan512yA0CpCTbPMBds3ciZWzyeIcEgLaMhh8syGL8PWeA== - -"@sentry/cli-linux-i686@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.5.tgz#802aca898894bcc76145fd4d3836098d51449ec7" - integrity sha512-mPJVU+H+eyXoq0KoqjptGrnX8utJTAL+iWtiB4MmKxUddTQhuFHzTLKjLQslzY1k0AwDoE2zKK7IwzFDHkPuXw== - -"@sentry/cli-linux-x64@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.5.tgz#a06b84ba3308e0cdeaf068b999bd026404ca24bf" - integrity sha512-XZj35T0WfS9erKelUXZRibCcB7n1tTz/f/xuQRYdHtyIp0gF0RoOXM7rJfqPbMW6r/0mI8lB5f9OHXvttEW4qg== - -"@sentry/cli-win32-i686@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.5.tgz#977d055315f91b3b8ed9e268877d7de8cfe5b473" - integrity sha512-VugwqUqMc8ZNj5J/FWyahVuraoYEID9SIjPolIRcodAySGI47BiR5qx7KA6UF0Dh5UnDLGvb2Tu/u21N/mKc1A== - -"@sentry/cli-win32-x64@2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.5.tgz#e152376d0b08226558231c2876fc0515f02ab04b" - integrity sha512-OaOfXxP8Kr0GO/JmdK6waKCtMDECzc5Mo2O+WjjV148GffEMlCfbnbcNdqgug/AyXwT/rhrZC6bIwErIKN3KaQ== +"@sentry/cli-darwin@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.6.tgz#83f9127de77e2a2d25eb143d90720b3e9042adc1" + integrity sha512-KRf0VvTltHQ5gA7CdbUkaIp222LAk/f1+KqpDzO6nB/jC/tL4sfiy6YyM4uiH6IbVEudB8WpHCECiatmyAqMBA== + +"@sentry/cli-linux-arm64@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.6.tgz#6bb660e5d8145270e287a9a21201d2f9576b0634" + integrity sha512-caMDt37FI752n4/3pVltDjlrRlPFCOxK4PHvoZGQ3KFMsai0ZhE/0CLBUMQqfZf0M0r8KB2x7wqLm7xSELjefQ== + +"@sentry/cli-linux-arm@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.6.tgz#73d466004ac445d9258e83a7b3d4e0ee6604e0bd" + integrity sha512-ANG7U47yEHD1g3JrfhpT4/MclEvmDZhctWgSP5gVw5X4AlcI87E6dTqccnLgvZjiIAQTaJJAZuSHVVF3Jk403w== + +"@sentry/cli-linux-i686@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.6.tgz#f7175ca639ee05cf12d808f7fc31d59d6e2ee3b9" + integrity sha512-Tj1+GMc6lFsDRquOqaGKXFpW9QbmNK4TSfynkWKiJxdTEn5jSMlXXfr0r9OQrxu3dCCqEHkhEyU63NYVpgxIPw== + +"@sentry/cli-linux-x64@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.6.tgz#df0af8d6c8c8c880eb7345c715a4dfa509544a40" + integrity sha512-Dt/Xz784w/z3tEObfyJEMmRIzn0D5qoK53H9kZ6e0yNvJOSKNCSOq5cQk4n1/qeG0K/6SU9dirmvHwFUiVNyYg== + +"@sentry/cli-win32-i686@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.6.tgz#0df19912d1823b6ec034b4c4c714c7601211c926" + integrity sha512-zkpWtvY3kt+ogVaAbfFr2MEkgMMHJNJUnNMO8Ixce9gh38sybIkDkZNFnVPBXMClJV0APa4QH0EwumYBFZUMuQ== + +"@sentry/cli-win32-x64@2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.6.tgz#2344a206be3b555ec6540740f93a181199962804" + integrity sha512-TG2YzZ9JMeNFzbicdr5fbtsusVGACbrEfHmPgzWGDeLUP90mZxiMTjkXsE1X/5jQEQjB2+fyfXloba/Ugo51hA== "@sentry/cli@^1.74.4", "@sentry/cli@^1.77.1": version "1.77.1" @@ -5659,10 +5659,10 @@ proxy-from-env "^1.1.0" which "^2.0.2" -"@sentry/cli@^2.17.0", "@sentry/cli@^2.21.2", "@sentry/cli@^2.28.5": - version "2.28.5" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.5.tgz#ecac5afbe4fe8476f1416dedf8237237665fce1f" - integrity sha512-MrIPqpjvPFnJYaQ1od02IwJCeuOxMfQw4A26A2oBAipRSrS0j4eRAPSO8oSAqt2yUx0i3MnFl1/vZiaZqgiRMQ== +"@sentry/cli@^2.17.0", "@sentry/cli@^2.21.2", "@sentry/cli@^2.28.6": + version "2.28.6" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.6.tgz#645f31b9e742e7bf7668c8f867149359e79b8123" + integrity sha512-o2Ngz7xXuhwHxMi+4BFgZ4qjkX0tdZeOSIZkFAGnTbRhQe5T8bxq6CcQRLdPhqMgqvDn7XuJ3YlFtD3ZjHvD7g== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -5670,13 +5670,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.28.5" - "@sentry/cli-linux-arm" "2.28.5" - "@sentry/cli-linux-arm64" "2.28.5" - "@sentry/cli-linux-i686" "2.28.5" - "@sentry/cli-linux-x64" "2.28.5" - "@sentry/cli-win32-i686" "2.28.5" - "@sentry/cli-win32-x64" "2.28.5" + "@sentry/cli-darwin" "2.28.6" + "@sentry/cli-linux-arm" "2.28.6" + "@sentry/cli-linux-arm64" "2.28.6" + "@sentry/cli-linux-i686" "2.28.6" + "@sentry/cli-linux-x64" "2.28.6" + "@sentry/cli-win32-i686" "2.28.6" + "@sentry/cli-win32-x64" "2.28.6" "@sentry/core@7.93.0": version "7.93.0" From 658d6d13927690536d6c3d083bba430cab073ce3 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 20 Feb 2024 13:42:24 +0100 Subject: [PATCH 105/173] ref: Collect child spans references via non-enumerable on Span object (#10715) --- packages/core/src/tracing/hubextensions.ts | 4 +- packages/core/src/tracing/sentrySpan.ts | 6 +++ packages/core/src/tracing/trace.ts | 49 +++++++++++++++++++ packages/core/src/tracing/transaction.ts | 9 ++-- packages/core/test/lib/tracing/trace.test.ts | 29 +++++++++++ .../opentelemetry/src/custom/transaction.ts | 5 +- packages/tracing/test/span.test.ts | 32 ------------ 7 files changed, 91 insertions(+), 43 deletions(-) diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts index eb82ecb613d8..9d789d2d2620 100644 --- a/packages/core/src/tracing/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -75,7 +75,7 @@ The transaction will not be sampled. Please use the ${configInstrumenter} instru ...customSamplingContext, }); if (transaction.isRecording()) { - transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); + transaction.initSpanRecorder(); } if (client) { client.emit('startTransaction', transaction); @@ -122,7 +122,7 @@ export function startIdleTransaction( ...customSamplingContext, }); if (transaction.isRecording()) { - transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); + transaction.initSpanRecorder(); } if (client) { client.emit('startTransaction', transaction); diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 425ded18713b..379eb1515cab 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -29,6 +29,7 @@ import { } from '../utils/spanUtils'; import type { SpanStatusType } from './spanstatus'; import { setHttpStatus } from './spanstatus'; +import { addChildSpanToSpan } from './trace'; /** * Keeps track of finished spans for a given transaction @@ -401,6 +402,11 @@ export class SentrySpan implements SpanInterface { childSpan.spanRecorder.add(childSpan); } + // To allow for interoperability we track the children of a span twice: Once with the span recorder (old) once with + // the `addChildSpanToSpan`. Eventually we will only use `addChildSpanToSpan` and drop the span recorder. + // To ensure interoperability with the `startSpan` API, `addChildSpanToSpan` is also called here. + addChildSpanToSpan(this, childSpan); + const rootSpan = getRootSpan(this); // TODO: still set span.transaction here until we have a more permanent solution // Probably similarly to the weakmap we hold in node-experimental diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 8e94e7acd891..dff11499285b 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -156,6 +156,10 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { }); } + if (parentSpan) { + addChildSpanToSpan(parentSpan, span); + } + setCapturedScopesOnSpan(span, scope, isolationScope); return span; @@ -307,6 +311,10 @@ function createChildSpanOrTransaction( }); } + if (parentSpan) { + addChildSpanToSpan(parentSpan, span); + } + setCapturedScopesOnSpan(span, scope, isolationScope); return span; @@ -330,6 +338,47 @@ function normalizeContext(context: StartSpanOptions): TransactionContext { return context; } +const CHILD_SPANS_FIELD = '_sentryChildSpans'; + +type SpanWithPotentialChildren = Span & { + [CHILD_SPANS_FIELD]?: Set; +}; + +/** + * Adds an opaque child span reference to a span. + */ +export function addChildSpanToSpan(span: SpanWithPotentialChildren, childSpan: Span): void { + if (span[CHILD_SPANS_FIELD] && span[CHILD_SPANS_FIELD].size < 1000) { + span[CHILD_SPANS_FIELD].add(childSpan); + } else { + span[CHILD_SPANS_FIELD] = new Set([childSpan]); + } +} + +/** + * Obtains the entire span tree, meaning a span + all of its descendants for a particular span. + */ +export function getSpanTree(span: SpanWithPotentialChildren): Span[] { + const resultSet = new Set(); + + function addSpanChildren(span: SpanWithPotentialChildren): void { + // This exit condition is required to not infinitely loop in case of a circular dependency. + if (resultSet.has(span)) { + return; + } else { + resultSet.add(span); + const childSpans = span[CHILD_SPANS_FIELD] ? Array.from(span[CHILD_SPANS_FIELD]) : []; + for (const childSpan of childSpans) { + addSpanChildren(childSpan); + } + } + } + + addSpanChildren(span); + + return Array.from(resultSet); +} + const SCOPE_ON_START_SPAN_FIELD = '_sentryScope'; const ISOLATION_SCOPE_ON_START_SPAN_FIELD = '_sentryIsolationScope'; diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 5c5b284f0d29..35acd8d6b3e0 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -21,7 +21,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; import { SentrySpan, SpanRecorder } from './sentrySpan'; -import { getCapturedScopesOnSpan } from './trace'; +import { getCapturedScopesOnSpan, getSpanTree } from './trace'; /** JSDoc */ export class Transaction extends SentrySpan implements TransactionInterface { @@ -293,11 +293,8 @@ export class Transaction extends SentrySpan implements TransactionInterface { return undefined; } - // eslint-disable-next-line deprecation/deprecation - const finishedSpans = this.spanRecorder - ? // eslint-disable-next-line deprecation/deprecation - this.spanRecorder.spans.filter(span => span !== this && spanToJSON(span).timestamp) - : []; + // We only want to include finished spans in the event + const finishedSpans = getSpanTree(this).filter(span => span !== this && spanToJSON(span).timestamp); if (this._trimEnd && finishedSpans.length > 0) { const endTimes = finishedSpans.map(span => spanToJSON(span).timestamp).filter(Boolean) as number[]; diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 1d06ea009937..cb443043fd36 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -436,6 +436,16 @@ describe('startSpan', () => { expect.anything(), ); }); + + it('sets a child span reference on the parent span', () => { + expect.assertions(1); + startSpan({ name: 'outer' }, (outerSpan: any) => { + startSpan({ name: 'inner' }, innerSpan => { + const childSpans = Array.from(outerSpan._sentryChildSpans); + expect(childSpans).toContain(innerSpan); + }); + }); + }); }); describe('startSpanManual', () => { @@ -545,6 +555,16 @@ describe('startSpanManual', () => { expect(span).toBeDefined(); }); }); + + it('sets a child span reference on the parent span', () => { + expect.assertions(1); + startSpan({ name: 'outer' }, (outerSpan: any) => { + startSpanManual({ name: 'inner' }, innerSpan => { + const childSpans = Array.from(outerSpan._sentryChildSpans); + expect(childSpans).toContain(innerSpan); + }); + }); + }); }); describe('startInactiveSpan', () => { @@ -674,6 +694,15 @@ describe('startInactiveSpan', () => { expect.anything(), ); }); + + it('sets a child span reference on the parent span', () => { + expect.assertions(1); + startSpan({ name: 'outer' }, (outerSpan: any) => { + const innerSpan = startInactiveSpan({ name: 'inner' }); + const childSpans = Array.from(outerSpan._sentryChildSpans); + expect(childSpans).toContain(innerSpan); + }); + }); }); describe('continueTrace', () => { diff --git a/packages/opentelemetry/src/custom/transaction.ts b/packages/opentelemetry/src/custom/transaction.ts index 31b2efe31d03..5cd84892932b 100644 --- a/packages/opentelemetry/src/custom/transaction.ts +++ b/packages/opentelemetry/src/custom/transaction.ts @@ -1,6 +1,6 @@ import type { Hub } from '@sentry/core'; import { Transaction } from '@sentry/core'; -import type { ClientOptions, Hub as HubInterface, TransactionContext } from '@sentry/types'; +import type { Hub as HubInterface, TransactionContext } from '@sentry/types'; /** * This is a fork of core's tracing/hubextensions.ts _startTransaction, @@ -9,13 +9,12 @@ import type { ClientOptions, Hub as HubInterface, TransactionContext } from '@se export function startTransaction(hub: HubInterface, transactionContext: TransactionContext): Transaction { // eslint-disable-next-line deprecation/deprecation const client = hub.getClient(); - const options: Partial = (client && client.getOptions()) || {}; // eslint-disable-next-line deprecation/deprecation const transaction = new Transaction(transactionContext, hub as Hub); // Since we do not do sampling here, we assume that this is _always_ sampled // Any sampling decision happens in OpenTelemetry's sampler - transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); + transaction.initSpanRecorder(); if (client) { client.emit('startTransaction', transaction); diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index f65ceb75eaf6..b671779117f3 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -272,22 +272,6 @@ describe('SentrySpan', () => { expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); }); - test('maxSpans correctly limits number of spans', () => { - const options = getDefaultBrowserClientOptions({ - _experiments: { maxSpans: 3 }, - tracesSampleRate: 1, - }); - const _hub = new Hub(new BrowserClient(options)); - const spy = jest.spyOn(_hub as any, 'captureEvent') as any; - const transaction = _hub.startTransaction({ name: 'test' }); - for (let i = 0; i < 10; i++) { - const child = transaction.startChild(); - child.end(); - } - transaction.end(); - expect(spy.mock.calls[0][0].spans).toHaveLength(3); - }); - test('no span recorder created if transaction.sampled is false', () => { const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1, @@ -421,22 +405,6 @@ describe('SentrySpan', () => { expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); }); - test('maxSpans correctly limits number of spans', () => { - const options = getDefaultBrowserClientOptions({ - _experiments: { maxSpans: 3 }, - tracesSampleRate: 1, - }); - const _hub = new Hub(new BrowserClient(options)); - const spy = jest.spyOn(_hub as any, 'captureEvent') as any; - const transaction = _hub.startTransaction({ name: 'test' }); - for (let i = 0; i < 10; i++) { - const child = transaction.startChild(); - child.end(); - } - transaction.end(); - expect(spy.mock.calls[0][0].spans).toHaveLength(3); - }); - test('no span recorder created if transaction.sampled is false', () => { const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1, From 673f76a6d2255829d18872aeeac21b0f53a31383 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 13:45:17 +0100 Subject: [PATCH 106/173] ref(node-experimental): Cleanup re-exports (#10741) So we can clearly see what is missing from node - which is cron & integrations. --- packages/node-experimental/src/index.ts | 50 +++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 872901f09957..ce353f6e90ce 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -15,43 +15,43 @@ export { prismaIntegration } from './integrations/tracing/prisma'; export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/tracing'; -export type { Span } from './types'; - -export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; export { getClient, getSentryRelease, defaultStackParser } from './sdk/api'; export { createGetModuleFromFilename } from './utils/module'; +export { makeNodeTransport } from './transports'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; +export type { Span, NodeOptions } from './types'; + +export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; + +export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils'; + +export { + hapiErrorPlugin, + consoleIntegration, + onUncaughtExceptionIntegration, + onUnhandledRejectionIntegration, + modulesIntegration, + contextLinesIntegration, + nodeContextIntegration, + localVariablesIntegration, + cron, +} from '@sentry/node'; + export { addBreadcrumb, isInitialized, - makeNodeTransport, getGlobalScope, - addRequestDataToEvent, - DEFAULT_USER_INCLUDES, - extractRequestData, - // eslint-disable-next-line deprecation/deprecation - getModuleFromFilename, close, createTransport, flush, Hub, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, SDK_VERSION, getSpanStatusFromHttpCode, setHttpStatus, captureCheckIn, withMonitor, - hapiErrorPlugin, - consoleIntegration, - onUncaughtExceptionIntegration, - onUnhandledRejectionIntegration, - modulesIntegration, - contextLinesIntegration, - nodeContextIntegration, - localVariablesIntegration, requestDataIntegration, functionToStringIntegration, inboundFiltersIntegration, @@ -71,10 +71,7 @@ export { Scope, setMeasurement, continueTrace, - cron, parameterize, - // eslint-disable-next-line deprecation/deprecation - makeMain, getCurrentScope, getIsolationScope, withScope, @@ -82,12 +79,9 @@ export { captureException, captureEvent, captureMessage, -} from '@sentry/node'; +} from '@sentry/core'; export type { - SpanStatusType, - TransactionNamingScheme, - AddRequestDataToEventOptions, Breadcrumb, BreadcrumbHint, PolymorphicRequest, @@ -101,6 +95,6 @@ export type { StackFrame, Stacktrace, Thread, + Transaction, User, - NodeOptions, -} from '@sentry/node'; +} from '@sentry/types'; From f40265294754313b475a10b7255176f20fa52a76 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 20 Feb 2024 09:13:17 -0500 Subject: [PATCH 107/173] feat(v8): Remove defaultIntegrations deprecated export (#10691) ref https://github.com/getsentry/sentry-javascript/issues/10100 Removes deprecated `defaultIntegrations` export everywhere. --- packages/astro/src/index.types.ts | 1 - packages/browser/src/exports.ts | 2 - packages/browser/src/sdk.ts | 23 ++++------- packages/bun/src/index.ts | 2 - packages/bun/src/sdk.ts | 42 +++++++++----------- packages/deno/src/index.ts | 2 - packages/deno/src/sdk.ts | 38 ++++++++---------- packages/nextjs/src/index.types.ts | 1 - packages/node/src/index.ts | 2 - packages/node/src/sdk.ts | 40 ++++++++----------- packages/remix/src/index.server.ts | 2 - packages/remix/src/index.types.ts | 1 - packages/serverless/src/awslambda.ts | 8 ---- packages/serverless/src/gcpfunction/index.ts | 15 +------ packages/serverless/src/index.ts | 2 - packages/sveltekit/src/index.types.ts | 1 - packages/sveltekit/src/server/index.ts | 2 - packages/vercel-edge/src/index.ts | 2 - packages/vercel-edge/src/sdk.ts | 14 ++----- 19 files changed, 64 insertions(+), 136 deletions(-) diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index b19c6af22637..25317086ac56 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -19,7 +19,6 @@ export declare const Integrations: typeof serverSdk.Integrations; export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; -export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; export declare const defaultStackParser: StackParser; diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 301c681dc14a..a7bf47cd5a0f 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -95,8 +95,6 @@ export { export { eventFromException, eventFromMessage, exceptionFromError } from './eventbuilder'; export { createUserFeedbackEnvelope } from './userfeedback'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, forceLoad, init, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 871258fd49b7..0cad32a06474 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -29,24 +29,17 @@ import { browserApiErrorsIntegration } from './integrations/trycatch'; import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport, makeXHRTransport } from './transports'; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - inboundFiltersIntegration(), - functionToStringIntegration(), - browserApiErrorsIntegration(), - breadcrumbsIntegration(), - globalHandlersIntegration(), - linkedErrorsIntegration(), - dedupeIntegration(), - httpContextIntegration(), -]; - /** Get the default integrations for the browser SDK. */ export function getDefaultIntegrations(_options: Options): Integration[] { - // We return a copy of the defaultIntegrations here to avoid mutating this return [ - // eslint-disable-next-line deprecation/deprecation - ...defaultIntegrations, + inboundFiltersIntegration(), + functionToStringIntegration(), + browserApiErrorsIntegration(), + breadcrumbsIntegration(), + globalHandlersIntegration(), + linkedErrorsIntegration(), + dedupeIntegration(), + httpContextIntegration(), ]; } diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 6b06bd604244..686583276f4b 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -112,8 +112,6 @@ export { export { BunClient } from './client'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, } from './sdk'; diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index 1db72246178f..dacc78120ae1 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -21,34 +21,28 @@ import { bunServerIntegration } from './integrations/bunserver'; import { makeFetchTransport } from './transports'; import type { BunOptions } from './types'; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - // Common - inboundFiltersIntegration(), - functionToStringIntegration(), - linkedErrorsIntegration(), - requestDataIntegration(), - // Native Wrappers - consoleIntegration(), - httpIntegration(), - nativeNodeFetchintegration(), - // Global Handlers # TODO (waiting for https://github.com/oven-sh/bun/issues/5091) - // new NodeIntegrations.OnUncaughtException(), - // new NodeIntegrations.OnUnhandledRejection(), - // Event Info - contextLinesIntegration(), - nodeContextIntegration(), - modulesIntegration(), - // Bun Specific - bunServerIntegration(), -]; - /** Get the default integrations for the Bun SDK. */ export function getDefaultIntegrations(_options: Options): Integration[] { // We return a copy of the defaultIntegrations here to avoid mutating this return [ - // eslint-disable-next-line deprecation/deprecation - ...defaultIntegrations, + // Common + inboundFiltersIntegration(), + functionToStringIntegration(), + linkedErrorsIntegration(), + requestDataIntegration(), + // Native Wrappers + consoleIntegration(), + httpIntegration(), + nativeNodeFetchintegration(), + // Global Handlers # TODO (waiting for https://github.com/oven-sh/bun/issues/5091) + // new NodeIntegrations.OnUncaughtException(), + // new NodeIntegrations.OnUnhandledRejection(), + // Event Info + contextLinesIntegration(), + nodeContextIntegration(), + modulesIntegration(), + // Bun Specific + bunServerIntegration(), ]; } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 873cb4ba2b38..d554c7aa1e69 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -87,8 +87,6 @@ export type { SpanStatusType } from '@sentry/core'; export { DenoClient } from './client'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, } from './sdk'; diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index b9e3cdab1ebb..b0452ca5302e 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -13,32 +13,26 @@ import { normalizePathsIntegration } from './integrations/normalizepaths'; import { makeFetchTransport } from './transports'; import type { DenoOptions } from './types'; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - // Common - inboundFiltersIntegration(), - functionToStringIntegration(), - linkedErrorsIntegration(), - // From Browser - dedupeIntegration(), - breadcrumbsIntegration({ - dom: false, - history: false, - xhr: false, - }), - // Deno Specific - denoContextIntegration(), - contextLinesIntegration(), - normalizePathsIntegration(), - globalHandlersIntegration(), -]; - /** Get the default integrations for the Deno SDK. */ export function getDefaultIntegrations(_options: Options): Integration[] { // We return a copy of the defaultIntegrations here to avoid mutating this return [ - // eslint-disable-next-line deprecation/deprecation - ...defaultIntegrations, + // Common + inboundFiltersIntegration(), + functionToStringIntegration(), + linkedErrorsIntegration(), + // From Browser + dedupeIntegration(), + breadcrumbsIntegration({ + dom: false, + history: false, + xhr: false, + }), + // Deno Specific + denoContextIntegration(), + contextLinesIntegration(), + normalizePathsIntegration(), + globalHandlersIntegration(), ]; } diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 9d28ac85c9f3..652f8ae7b5f6 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -23,7 +23,6 @@ export declare const Integrations: undefined; // TODO(v8): Remove this line. Can export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; -export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; export declare const defaultStackParser: StackParser; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 519e969af141..880ceed38ee2 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -94,8 +94,6 @@ export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; export { NodeClient } from './client'; export { makeNodeTransport } from './transports'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, defaultStackParser, diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index f6f83aef9352..d39d853c7bbc 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -37,27 +37,6 @@ import { createGetModuleFromFilename } from './module'; import { makeNodeTransport } from './transports'; import type { NodeClientOptions, NodeOptions } from './types'; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - // Common - inboundFiltersIntegration(), - functionToStringIntegration(), - linkedErrorsIntegration(), - requestDataIntegration(), - // Native Wrappers - consoleIntegration(), - httpIntegration(), - nativeNodeFetchintegration(), - // Global Handlers - onUncaughtExceptionIntegration(), - onUnhandledRejectionIntegration(), - // Event Info - contextLinesIntegration(), - localVariablesIntegration(), - nodeContextIntegration(), - modulesIntegration(), -]; - /** Get the default integrations for the Node SDK. */ export function getDefaultIntegrations(_options: Options): Integration[] { const carrier = getMainCarrier(); @@ -65,8 +44,23 @@ export function getDefaultIntegrations(_options: Options): Integration[] { const autoloadedIntegrations = carrier.__SENTRY__?.integrations || []; return [ - // eslint-disable-next-line deprecation/deprecation - ...defaultIntegrations, + // Common + inboundFiltersIntegration(), + functionToStringIntegration(), + linkedErrorsIntegration(), + requestDataIntegration(), + // Native Wrappers + consoleIntegration(), + httpIntegration(), + nativeNodeFetchintegration(), + // Global Handlers + onUncaughtExceptionIntegration(), + onUnhandledRejectionIntegration(), + // Event Info + contextLinesIntegration(), + localVariablesIntegration(), + nodeContextIntegration(), + modulesIntegration(), ...autoloadedIntegrations, ]; } diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 3f106fc8eecf..54afd27f8bfa 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -50,8 +50,6 @@ export { withIsolationScope, autoDiscoverNodePerformanceMonitoringIntegrations, makeNodeTransport, - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, defaultStackParser, flush, diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index 37256e1f2dbe..0bf2620f6212 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -18,7 +18,6 @@ export declare const Integrations: typeof clientSdk.Integrations & typeof server export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; -export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; export declare const defaultStackParser: StackParser; diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index b739acbb933c..668132dd92ff 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -8,7 +8,6 @@ import { captureException, captureMessage, continueTrace, - defaultIntegrations as nodeDefaultIntegrations, flush, getCurrentScope, getDefaultIntegrations as getNodeDefaultIntegrations, @@ -66,13 +65,6 @@ export interface WrapperOptions { startTrace: boolean; } -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations: Integration[] = [ - // eslint-disable-next-line deprecation/deprecation - ...nodeDefaultIntegrations, - awsServicesIntegration({ optional: true }), -]; - /** Get the default integrations for the AWSLambda SDK. */ export function getDefaultIntegrations(options: Options): Integration[] { return [...getNodeDefaultIntegrations(options), awsServicesIntegration({ optional: true })]; diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts index 50556634c5fc..194f9a3a92cd 100644 --- a/packages/serverless/src/gcpfunction/index.ts +++ b/packages/serverless/src/gcpfunction/index.ts @@ -1,10 +1,5 @@ import type { NodeOptions } from '@sentry/node'; -import { - SDK_VERSION, - defaultIntegrations as defaultNodeIntegrations, - getDefaultIntegrations as getDefaultNodeIntegrations, - init as initNode, -} from '@sentry/node'; +import { SDK_VERSION, getDefaultIntegrations as getDefaultNodeIntegrations, init as initNode } from '@sentry/node'; import type { Integration, Options, SdkMetadata } from '@sentry/types'; import { googleCloudGrpcIntegration } from '../google-cloud-grpc'; @@ -14,14 +9,6 @@ export * from './http'; export * from './events'; export * from './cloud_events'; -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations: Integration[] = [ - // eslint-disable-next-line deprecation/deprecation - ...defaultNodeIntegrations, - googleCloudHttpIntegration({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing. - googleCloudGrpcIntegration({ optional: true }), // We mark this integration optional since 'google-gax' module could be missing. -]; - /** Get the default integrations for the GCP SDK. */ export function getDefaultIntegrations(options: Options): Integration[] { return [ diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 03a8539bf8f4..7f4c52a53e06 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -53,8 +53,6 @@ export { NodeClient, makeNodeTransport, close, - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, defaultStackParser, flush, diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index b162ef4c521f..e05aa031e53e 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -42,7 +42,6 @@ export declare const Integrations: typeof clientSdk.Integrations & typeof server export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; -export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; export declare const defaultStackParser: StackParser; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 44e073da8ece..6d948636feee 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -45,8 +45,6 @@ export { withIsolationScope, autoDiscoverNodePerformanceMonitoringIntegrations, makeNodeTransport, - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, defaultStackParser, flush, diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 416be96797f1..4dbca6124461 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -83,8 +83,6 @@ export type { SpanStatusType } from '@sentry/core'; export { VercelEdgeClient } from './client'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, } from './sdk'; diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index fe806ccfc282..5a0c00208b73 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -22,19 +22,13 @@ declare const process: { const nodeStackParser = createStackParser(nodeStackLineParser()); -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - inboundFiltersIntegration(), - functionToStringIntegration(), - linkedErrorsIntegration(), - winterCGFetchIntegration(), -]; - /** Get the default integrations for the browser SDK. */ export function getDefaultIntegrations(options: Options): Integration[] { return [ - // eslint-disable-next-line deprecation/deprecation - ...defaultIntegrations, + inboundFiltersIntegration(), + functionToStringIntegration(), + linkedErrorsIntegration(), + winterCGFetchIntegration(), ...(options.sendDefaultPii ? [requestDataIntegration()] : []), ]; } From db8f26e6d927ae550d56fae3897011fde129538b Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 20 Feb 2024 10:13:37 -0400 Subject: [PATCH 108/173] fix(stacktrace): Always use `?` for anonymous function name (#10732) This was previously opened as #5664 but closed as it was a breaking change. Now that we're getting ready to ship, v8 this is viable! --- .../browser/src/integrations/globalhandlers.ts | 3 ++- packages/browser/src/stack-parsers.ts | 7 ++----- .../browser/test/unit/tracekit/misc.test.ts | 2 +- .../deno/test/__snapshots__/mod.test.ts.snap | 2 +- packages/node/test/stacktrace.test.ts | 6 +++--- packages/utils/src/anr.ts | 6 +++--- packages/utils/src/index.ts | 1 + packages/utils/src/node-stack-trace.ts | 15 +++++++++++++-- packages/utils/src/stacktrace.ts | 18 ++---------------- packages/utils/test/stacktrace.test.ts | 14 +++++++------- 10 files changed, 35 insertions(+), 39 deletions(-) diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index e2aa0b6116f0..b57fd8383155 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -1,6 +1,7 @@ import { captureEvent, defineIntegration, getClient } from '@sentry/core'; import type { Client, Event, IntegrationFn, Primitive, StackParser } from '@sentry/types'; import { + UNKNOWN_FUNCTION, addGlobalErrorInstrumentationHandler, addGlobalUnhandledRejectionInstrumentationHandler, getLocationHref, @@ -172,7 +173,7 @@ function _enhanceEventWithInitialFrame(event: Event, url: any, line: any, column ev0sf.push({ colno, filename, - function: '?', + function: UNKNOWN_FUNCTION, in_app: true, lineno, }); diff --git a/packages/browser/src/stack-parsers.ts b/packages/browser/src/stack-parsers.ts index 609a6dd1fd51..effe7538178b 100644 --- a/packages/browser/src/stack-parsers.ts +++ b/packages/browser/src/stack-parsers.ts @@ -24,10 +24,7 @@ // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import type { StackFrame, StackLineParser, StackLineParserFn } from '@sentry/types'; -import { createStackParser } from '@sentry/utils'; - -// global reference to slice -const UNKNOWN_FUNCTION = '?'; +import { UNKNOWN_FUNCTION, createStackParser } from '@sentry/utils'; const OPERA10_PRIORITY = 10; const OPERA11_PRIORITY = 20; @@ -38,7 +35,7 @@ const GECKO_PRIORITY = 50; function createFrame(filename: string, func: string, lineno?: number, colno?: number): StackFrame { const frame: StackFrame = { filename, - function: func, + function: func === '' ? UNKNOWN_FUNCTION : func, in_app: true, // All browser frames are considered in_app }; diff --git a/packages/browser/test/unit/tracekit/misc.test.ts b/packages/browser/test/unit/tracekit/misc.test.ts index b092c8d10723..8cb31f9a7868 100644 --- a/packages/browser/test/unit/tracekit/misc.test.ts +++ b/packages/browser/test/unit/tracekit/misc.test.ts @@ -92,7 +92,7 @@ describe('Tracekit - Misc Tests', () => { { filename: '', function: 'Array.forEach', in_app: true }, { filename: '../node_modules/@sentry-internal/rrweb/es/rrweb/ext/@xstate/fsm/es/index.js', - function: '', + function: '?', in_app: true, lineno: 15, colno: 2595, diff --git a/packages/deno/test/__snapshots__/mod.test.ts.snap b/packages/deno/test/__snapshots__/mod.test.ts.snap index 417acf1e7639..6cbbe0182902 100644 --- a/packages/deno/test/__snapshots__/mod.test.ts.snap +++ b/packages/deno/test/__snapshots__/mod.test.ts.snap @@ -45,7 +45,7 @@ snapshot[`captureException 1`] = ` colno: 27, context_line: " client.captureException(something());", filename: "app:///test/mod.test.ts", - function: "", + function: "?", in_app: true, lineno: 47, post_context: [ diff --git a/packages/node/test/stacktrace.test.ts b/packages/node/test/stacktrace.test.ts index edd62c81d8e5..968cdccca9a4 100644 --- a/packages/node/test/stacktrace.test.ts +++ b/packages/node/test/stacktrace.test.ts @@ -193,7 +193,7 @@ describe('Stack parsing', () => { { filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', module: 'test_case', - function: '', + function: '?', lineno: 80, colno: 10, in_app: true, @@ -213,7 +213,7 @@ describe('Stack parsing', () => { { filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', module: 'test_case', - function: '', + function: '?', lineno: 80, colno: 10, in_app: true, @@ -290,7 +290,7 @@ describe('Stack parsing', () => { { filename: '/code/node_modules/kafkajs/src/consumer/runner.js', module: 'kafkajs.src.consumer:runner', - function: '', + function: '?', lineno: 376, colno: 15, in_app: false, diff --git a/packages/utils/src/anr.ts b/packages/utils/src/anr.ts index d962107bcb0e..839903de062c 100644 --- a/packages/utils/src/anr.ts +++ b/packages/utils/src/anr.ts @@ -1,7 +1,7 @@ import type { StackFrame } from '@sentry/types'; - +import { filenameIsInApp } from './node-stack-trace'; import { dropUndefinedKeys } from './object'; -import { filenameIsInApp } from './stacktrace'; +import { UNKNOWN_FUNCTION } from './stacktrace'; type WatchdogReturn = { /** Resets the watchdog timer */ @@ -84,7 +84,7 @@ export function callFrameToStackFrame( return dropUndefinedKeys({ filename, module: getModuleFromFilename(filename), - function: frame.functionName || '?', + function: frame.functionName || UNKNOWN_FUNCTION, colno, lineno, in_app: filename ? filenameIsInApp(filename) : undefined, diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d19991b7d401..5c714e8fbd28 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -18,6 +18,7 @@ export * from './promisebuffer'; export * from './requestdata'; export * from './severity'; export * from './stacktrace'; +export * from './node-stack-trace'; export * from './string'; export * from './supports'; export * from './syncpromise'; diff --git a/packages/utils/src/node-stack-trace.ts b/packages/utils/src/node-stack-trace.ts index 3b486be9148c..be858433d43a 100644 --- a/packages/utils/src/node-stack-trace.ts +++ b/packages/utils/src/node-stack-trace.ts @@ -21,7 +21,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import type { StackLineParserFn } from '@sentry/types'; +import type { StackLineParser, StackLineParserFn } from '@sentry/types'; +import { UNKNOWN_FUNCTION } from './stacktrace'; export type GetModuleFn = (filename: string | undefined) => string | undefined; @@ -96,7 +97,7 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { } if (functionName === undefined) { - methodName = methodName || ''; + methodName = methodName || UNKNOWN_FUNCTION; functionName = typeName ? `${typeName}.${methodName}` : methodName; } @@ -131,3 +132,13 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { return undefined; }; } + +/** + * Node.js stack line parser + * + * This is in @sentry/utils so it can be used from the Electron SDK in the browser for when `nodeIntegration == true`. + * This allows it to be used without referencing or importing any node specific code which causes bundlers to complain + */ +export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser { + return [90, node(getModule)]; +} diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index 917b46daa5d1..3fbecec82ebf 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -1,11 +1,7 @@ import type { StackFrame, StackLineParser, StackParser } from '@sentry/types'; -import type { GetModuleFn } from './node-stack-trace'; -import { filenameIsInApp, node } from './node-stack-trace'; - -export { filenameIsInApp }; - const STACKTRACE_FRAME_LIMIT = 50; +export const UNKNOWN_FUNCTION = '?'; // Used to sanitize webpack (error: *) wrapped stack errors const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/; const STRIP_FRAME_REGEXP = /captureMessage|captureException/; @@ -116,7 +112,7 @@ export function stripSentryFramesAndReverse(stack: ReadonlyArray): S return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map(frame => ({ ...frame, filename: frame.filename || localStack[localStack.length - 1].filename, - function: frame.function || '?', + function: frame.function || UNKNOWN_FUNCTION, })); } @@ -137,13 +133,3 @@ export function getFunctionName(fn: unknown): string { return defaultFunctionName; } } - -/** - * Node.js stack line parser - * - * This is in @sentry/utils so it can be used from the Electron SDK in the browser for when `nodeIntegration == true`. - * This allows it to be used without referencing or importing any node specific code which causes bundlers to complain - */ -export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser { - return [90, node(getModule)]; -} diff --git a/packages/utils/test/stacktrace.test.ts b/packages/utils/test/stacktrace.test.ts index 4e87399b91db..7e5251d0dd9c 100644 --- a/packages/utils/test/stacktrace.test.ts +++ b/packages/utils/test/stacktrace.test.ts @@ -1,4 +1,4 @@ -import { nodeStackLineParser, stripSentryFramesAndReverse } from '../src/stacktrace'; +import { nodeStackLineParser, stripSentryFramesAndReverse } from '../src'; describe('Stacktrace', () => { describe('stripSentryFramesAndReverse()', () => { @@ -157,7 +157,7 @@ describe('node', () => { const expectedOutput = { filename: '/path/to/file.js', module: undefined, - function: '', + function: '?', lineno: 10, colno: 5, in_app: true, @@ -257,7 +257,7 @@ describe('node', () => { expect(result).toEqual({ filename: '/path/to/myFile.js', - function: 'Object.', + function: 'Object.?', lineno: 10, colno: 20, in_app: true, @@ -269,7 +269,7 @@ describe('node', () => { const result = node(line); expect(result).toEqual({ filename: '/path/to/myFile.js', - function: '', + function: '?', lineno: 10, colno: 20, in_app: true, @@ -281,7 +281,7 @@ describe('node', () => { const result = node(line); expect(result).toEqual({ filename: undefined, - function: 'Object.', + function: 'Object.?', lineno: undefined, colno: undefined, in_app: false, @@ -294,7 +294,7 @@ describe('node', () => { expect(result).toEqual({ filename: '/path/to/node_modules/myModule/index.js', - function: 'Object.', + function: 'Object.?', lineno: 10, colno: 20, in_app: false, @@ -306,7 +306,7 @@ describe('node', () => { const result = node(line); expect(result).toEqual({ filename: 'C:\\path\\to\\myFile.js', - function: 'Object.', + function: 'Object.?', lineno: 10, colno: 20, in_app: true, From 3a4683e66c7e4391ff94e203932d0759af81e207 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 20 Feb 2024 09:14:02 -0500 Subject: [PATCH 109/173] feat(v8): Remove deprecated addInstrumentationHandler (#10693) Removes `addInstrumentationHandler`, but does not move code into core just yet. ref https://github.com/getsentry/sentry-javascript/issues/10100 --- packages/tracing-internal/src/common/fetch.ts | 2 +- packages/utils/src/instrument/index.ts | 46 ------------------- 2 files changed, 1 insertion(+), 47 deletions(-) diff --git a/packages/tracing-internal/src/common/fetch.ts b/packages/tracing-internal/src/common/fetch.ts index e3650c13e788..6087a65a2593 100644 --- a/packages/tracing-internal/src/common/fetch.ts +++ b/packages/tracing-internal/src/common/fetch.ts @@ -30,7 +30,7 @@ type PolymorphicRequestHeaders = }; /** - * Create and track fetch request spans for usage in combination with `addInstrumentationHandler`. + * Create and track fetch request spans for usage in combination with `addFetchInstrumentationHandler`. * * @returns Span if a span was created, otherwise void. */ diff --git a/packages/utils/src/instrument/index.ts b/packages/utils/src/instrument/index.ts index d28f17d277dc..40f2f0fe917c 100644 --- a/packages/utils/src/instrument/index.ts +++ b/packages/utils/src/instrument/index.ts @@ -1,11 +1,5 @@ // TODO(v8): Consider moving this file (or at least parts of it) into the browser package. The registered handlers are mostly non-generic and we risk leaking runtime specific code into generic packages. -import { DEBUG_BUILD } from '../debug-build'; -import { logger } from './../logger'; -import type { - InstrumentHandlerCallback as _InstrumentHandlerCallback, - InstrumentHandlerType as _InstrumentHandlerType, -} from './_handlers'; import { resetInstrumentationHandlers } from './_handlers'; import { addConsoleInstrumentationHandler } from './console'; import { addClickKeypressInstrumentationHandler } from './dom'; @@ -15,46 +9,6 @@ import { addGlobalUnhandledRejectionInstrumentationHandler } from './globalUnhan import { addHistoryInstrumentationHandler } from './history'; import { SENTRY_XHR_DATA_KEY, addXhrInstrumentationHandler } from './xhr'; -/** - * Add handler that will be called when given type of instrumentation triggers. - * Use at your own risk, this might break without changelog notice, only used internally. - * @hidden - * @deprecated Use the proper function per instrumentation type instead! - */ -export function addInstrumentationHandler(type: _InstrumentHandlerType, callback: _InstrumentHandlerCallback): void { - switch (type) { - case 'console': - return addConsoleInstrumentationHandler(callback); - case 'dom': - return addClickKeypressInstrumentationHandler(callback); - case 'xhr': - return addXhrInstrumentationHandler(callback); - case 'fetch': - return addFetchInstrumentationHandler(callback); - case 'history': - return addHistoryInstrumentationHandler(callback); - case 'error': - return addGlobalErrorInstrumentationHandler(callback); - case 'unhandledrejection': - return addGlobalUnhandledRejectionInstrumentationHandler(callback); - default: - DEBUG_BUILD && logger.warn('unknown instrumentation type:', type); - } -} - -/** - * @deprecated Use the specific handler data types from @sentry/types instead, e.g. HandlerDataFetch, HandlerDataConsole, ... - */ -type InstrumentHandlerCallback = _InstrumentHandlerCallback; - -/** - * @deprecated Use the specific handler functions instead, e.g. addConsoleInstrumentationHandler, ... - */ -type InstrumentHandlerType = _InstrumentHandlerType; - -// eslint-disable-next-line deprecation/deprecation -export type { InstrumentHandlerCallback, InstrumentHandlerType }; - export { addConsoleInstrumentationHandler, addClickKeypressInstrumentationHandler, From 3b9c37efae622b2c7127dc7478301e35ae1f7bf4 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 20 Feb 2024 09:14:38 -0500 Subject: [PATCH 110/173] feat(v8): Remove deprecated `span.isSuccess` method (#10699) ref https://github.com/getsentry/sentry-javascript/issues/10677 deprecation PR: #10213 --- packages/core/src/tracing/sentrySpan.ts | 9 --------- packages/tracing/test/span.test.ts | 25 ------------------------- packages/types/src/span.ts | 7 ------- 3 files changed, 41 deletions(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 379eb1515cab..97f21aef1c43 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -503,15 +503,6 @@ export class SentrySpan implements SpanInterface { return this; } - /** - * @inheritDoc - * - * @deprecated Use `spanToJSON(span).status === 'ok'` instead. - */ - public isSuccess(): boolean { - return this._status === 'ok'; - } - /** * @inheritDoc * diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index b671779117f3..b8a96e13c6f8 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -123,31 +123,6 @@ describe('SentrySpan', () => { expect(span.tags['http.status_code']).toBe('404'); expect(span.data['http.response.status_code']).toBe(404); }); - - // TODO (v8): Remove - test('isSuccess', () => { - const span = new SentrySpan({}); - expect(span.isSuccess()).toBe(false); - expect(spanToJSON(span).status).not.toBe('ok'); - span.setHttpStatus(200); - expect(span.isSuccess()).toBe(true); - expect(spanToJSON(span).status).toBe('ok'); - span.setStatus('permission_denied'); - expect(span.isSuccess()).toBe(false); - expect(spanToJSON(span).status).not.toBe('ok'); - span.setHttpStatus(0); - expect(span.isSuccess()).toBe(false); - expect(spanToJSON(span).status).not.toBe('ok'); - span.setHttpStatus(-1); - expect(span.isSuccess()).toBe(false); - expect(spanToJSON(span).status).not.toBe('ok'); - span.setHttpStatus(99); - expect(span.isSuccess()).toBe(false); - expect(spanToJSON(span).status).not.toBe('ok'); - span.setHttpStatus(100); - expect(span.isSuccess()).toBe(true); - expect(spanToJSON(span).status).toBe('ok'); - }); }); describe('toTraceparent', () => { diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index f9e29a188327..008f668e2b7a 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -365,13 +365,6 @@ export interface Span extends Omit { */ startChild(spanContext?: Pick>): Span; - /** - * Determines whether span was successful (HTTP200) - * - * @deprecated Use `spanToJSON(span).status === 'ok'` instead. - */ - isSuccess(): boolean; - /** * Return a traceparent compatible header string. * @deprecated Use `spanToTraceHeader()` instead. From fe4183605a55f13819346a2f15df6a93b480eeca Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 20 Feb 2024 10:17:55 -0400 Subject: [PATCH 111/173] feat(core): Use global `TextEncoder` and `TextDecoder` (#10701) Closes #10694 Use global `TextEncoder` and `TextDecoder` now that they are supported by all platforms that will be supported by v8 of the SDKs. --- .../utils/helpers.ts | 2 +- .../angular-17/event-proxy-server.ts | 2 +- .../nextjs-14/event-proxy-server.ts | 2 +- .../nextjs-app-dir/event-proxy-server.ts | 2 +- .../event-proxy-server.ts | 2 +- .../node-express-app/event-proxy-server.ts | 2 +- .../node-hapi-app/event-proxy-server.ts | 2 +- .../sveltekit-2/event-proxy-server.ts | 2 +- .../sveltekit/event-proxy-server.ts | 2 +- .../vue-3/event-proxy-server.ts | 2 +- .../node-integration-tests/utils/server.ts | 3 +- packages/browser/src/transports/offline.ts | 14 +------ .../test/unit/transports/fetch.test.ts | 6 +-- .../test/unit/transports/offline.test.ts | 4 +- .../browser/test/unit/transports/xhr.test.ts | 4 +- packages/core/src/baseclient.ts | 8 +--- packages/core/src/transports/base.ts | 2 +- packages/core/test/lib/attachments.test.ts | 5 +-- .../test/lib/integrations/metadata.test.ts | 5 +-- .../core/test/lib/transports/base.test.ts | 11 ++--- .../test/lib/transports/multiplexed.test.ts | 4 +- .../core/test/lib/transports/offline.test.ts | 2 - packages/core/test/mocks/client.ts | 3 -- packages/core/test/mocks/transport.ts | 3 +- packages/deno/test/transport.ts | 2 +- .../test/transports/http.test.ts | 21 ++++------ .../test/transports/https.test.ts | 14 +++---- packages/node/src/client.ts | 7 ---- .../manual/express-scope-separation/start.js | 3 +- .../aggregates-disable-single-session.js | 3 +- .../caught-exception-errored-session.js | 3 +- .../errors-in-session-capped-to-one.js | 3 +- .../single-session/healthy-session.js | 3 +- .../terminal-state-sessions-sent-once.js | 3 +- .../uncaught-exception-crashed-session.js | 3 +- .../unhandled-rejection-crashed-session.js | 3 +- .../manual/webpack-async-context/index.js | 3 +- packages/node/test/transports/http.test.ts | 22 ++++------ packages/node/test/transports/https.test.ts | 14 +++---- .../coreHandlers/handleNetworkBreadcrumbs.ts | 16 ++----- .../src/coreHandlers/util/fetchUtils.ts | 23 +++------- .../src/coreHandlers/util/networkUtils.ts | 8 ++-- .../replay/src/coreHandlers/util/xhrUtils.ts | 10 ++--- .../handleNetworkBreadcrumbs.test.ts | 4 -- .../unit/coreHandlers/util/fetchUtils.test.ts | 7 ---- .../coreHandlers/util/networkUtils.test.ts | 22 ++++------ packages/types/src/index.ts | 1 - packages/types/src/textencoder.ts | 16 ------- packages/types/src/transport.ts | 2 - packages/utils/src/envelope.ts | 42 ++++++++----------- packages/utils/test/clientreport.test.ts | 6 +-- packages/utils/test/envelope.test.ts | 18 ++++---- .../vercel-edge/test/transports/index.test.ts | 6 +-- 53 files changed, 117 insertions(+), 265 deletions(-) delete mode 100644 packages/types/src/textencoder.ts diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 861f05b9f641..0e54eea0fb31 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -34,7 +34,7 @@ export const properEnvelopeParser = (request: Request | null): EnvelopeItem[] => // https://develop.sentry.dev/sdk/envelopes/ const envelope = request?.postData() || ''; - const [, items] = parseEnvelope(envelope, new TextEncoder(), new TextDecoder()); + const [, items] = parseEnvelope(envelope); return items; }; diff --git a/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts index 4c2df32399f0..2a4dc2b525d9 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/event-proxy-server.ts index 9dee679c71e4..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/event-proxy-server.ts index 9dee679c71e4..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts index 9dee679c71e4..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/node-express-app/event-proxy-server.ts index 9dee679c71e4..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts index 9dee679c71e4..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts index 4c2df32399f0..2a4dc2b525d9 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts index 1bc419bd0b4c..11f00b4a2426 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts index 4c2df32399f0..2a4dc2b525d9 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts @@ -79,7 +79,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); const data: SentryRequestCallbackData = { - envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawSentryResponseBody, sentryResponseStatusCode: sentryResponse.statusCode, diff --git a/dev-packages/node-integration-tests/utils/server.ts b/dev-packages/node-integration-tests/utils/server.ts index 933d07d8741f..01af9558f0ab 100644 --- a/dev-packages/node-integration-tests/utils/server.ts +++ b/dev-packages/node-integration-tests/utils/server.ts @@ -1,5 +1,4 @@ import type { AddressInfo } from 'net'; -import { TextDecoder, TextEncoder } from 'util'; import type { Envelope } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; import express from 'express'; @@ -15,7 +14,7 @@ export function createBasicSentryServer(onEnvelope: (env: Envelope) => void): Pr app.use(express.raw({ type: () => true, inflate: true, limit: '100mb' })); app.post('/api/:id/envelope/', (req, res) => { try { - const env = parseEnvelope(req.body as Buffer, new TextEncoder(), new TextDecoder()); + const env = parseEnvelope(req.body as Buffer); onEnvelope(env); } catch (e) { // eslint-disable-next-line no-console diff --git a/packages/browser/src/transports/offline.ts b/packages/browser/src/transports/offline.ts index 099e6ad462c1..dbccb8507c01 100644 --- a/packages/browser/src/transports/offline.ts +++ b/packages/browser/src/transports/offline.ts @@ -1,7 +1,6 @@ import type { OfflineStore, OfflineTransportOptions } from '@sentry/core'; import { makeOfflineTransport } from '@sentry/core'; import type { Envelope, InternalBaseTransportOptions, Transport } from '@sentry/types'; -import type { TextDecoderInternal } from '@sentry/utils'; import { parseEnvelope, serializeEnvelope } from '@sentry/utils'; // 'Store', 'promisifyRequest' and 'createStore' were originally copied from the 'idb-keyval' package before being @@ -95,11 +94,6 @@ export interface BrowserOfflineTransportOptions extends OfflineTransportOptions * Default: 30 */ maxQueueSize?: number; - /** - * Only required for testing on node.js - * @ignore - */ - textDecoder?: TextDecoderInternal; } function createIndexedDbStore(options: BrowserOfflineTransportOptions): OfflineStore { @@ -117,7 +111,7 @@ function createIndexedDbStore(options: BrowserOfflineTransportOptions): OfflineS return { insert: async (env: Envelope) => { try { - const serialized = await serializeEnvelope(env, options.textEncoder); + const serialized = await serializeEnvelope(env); await insert(getStore(), serialized, options.maxQueueSize || 30); } catch (_) { // @@ -127,11 +121,7 @@ function createIndexedDbStore(options: BrowserOfflineTransportOptions): OfflineS try { const deserialized = await pop(getStore()); if (deserialized) { - return parseEnvelope( - deserialized, - options.textEncoder || new TextEncoder(), - options.textDecoder || new TextDecoder(), - ); + return parseEnvelope(deserialized); } } catch (_) { // diff --git a/packages/browser/test/unit/transports/fetch.test.ts b/packages/browser/test/unit/transports/fetch.test.ts index 01a063d19e2d..c80688eb11c3 100644 --- a/packages/browser/test/unit/transports/fetch.test.ts +++ b/packages/browser/test/unit/transports/fetch.test.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -9,7 +8,6 @@ import type { FetchImpl } from '../../../src/transports/utils'; const DEFAULT_FETCH_TRANSPORT_OPTIONS: BrowserTransportOptions = { url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', recordDroppedEvent: () => undefined, - textEncoder: new TextEncoder(), }; const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ @@ -47,7 +45,7 @@ describe('NewFetchTransport', () => { expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_FETCH_TRANSPORT_OPTIONS.url, { - body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', keepalive: true, referrerPolicy: 'origin', @@ -98,7 +96,7 @@ describe('NewFetchTransport', () => { await transport.send(ERROR_ENVELOPE); expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_FETCH_TRANSPORT_OPTIONS.url, { - body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', ...REQUEST_OPTIONS, }); diff --git a/packages/browser/test/unit/transports/offline.test.ts b/packages/browser/test/unit/transports/offline.test.ts index a4f91d40efff..758c19d30798 100644 --- a/packages/browser/test/unit/transports/offline.test.ts +++ b/packages/browser/test/unit/transports/offline.test.ts @@ -27,8 +27,6 @@ const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b const transportOptions = { recordDroppedEvent: () => undefined, // noop - textEncoder: new TextEncoder(), - textDecoder: new TextDecoder(), }; type MockResult = T | Error; @@ -61,6 +59,8 @@ function delay(ms: number): Promise { describe('makeOfflineTransport', () => { beforeAll(async () => { await deleteDatabase('sentry'); + (global as any).TextEncoder = TextEncoder; + (global as any).TextDecoder = TextDecoder; }); it('indexedDb wrappers insert and pop', async () => { diff --git a/packages/browser/test/unit/transports/xhr.test.ts b/packages/browser/test/unit/transports/xhr.test.ts index 9e5290d224c2..5f969a4f9205 100644 --- a/packages/browser/test/unit/transports/xhr.test.ts +++ b/packages/browser/test/unit/transports/xhr.test.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -8,7 +7,6 @@ import { makeXHRTransport } from '../../../src/transports/xhr'; const DEFAULT_XHR_TRANSPORT_OPTIONS: BrowserTransportOptions = { url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', recordDroppedEvent: () => undefined, - textEncoder: new TextEncoder(), }; const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ @@ -66,7 +64,7 @@ describe('NewXHRTransport', () => { expect(xhrMock.open).toHaveBeenCalledTimes(1); expect(xhrMock.open).toHaveBeenCalledWith('POST', DEFAULT_XHR_TRANSPORT_OPTIONS.url); expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.send).toHaveBeenCalledWith(serializeEnvelope(ERROR_ENVELOPE, new TextEncoder())); + expect(xhrMock.send).toHaveBeenCalledWith(serializeEnvelope(ERROR_ENVELOPE)); }); it('sets rate limit response headers', async () => { diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 2d802c95d938..583e329b4beb 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -374,13 +374,7 @@ export abstract class BaseClient implements Client { let env = createEventEnvelope(event, this._dsn, this._options._metadata, this._options.tunnel); for (const attachment of hint.attachments || []) { - env = addItemToEnvelope( - env, - createAttachmentEnvelopeItem( - attachment, - this._options.transportOptions && this._options.transportOptions.textEncoder, - ), - ); + env = addItemToEnvelope(env, createAttachmentEnvelopeItem(attachment)); } const promise = this._sendEnvelope(env); diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index 68e4522267db..2121ea6751f7 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -75,7 +75,7 @@ export function createTransport( }; const requestTask = (): PromiseLike => - makeRequest({ body: serializeEnvelope(filteredEnvelope, options.textEncoder) }).then( + makeRequest({ body: serializeEnvelope(filteredEnvelope) }).then( response => { // We don't want to throw on NOK responses, but we want to at least log them if (response.statusCode !== undefined && (response.statusCode < 200 || response.statusCode >= 300)) { diff --git a/packages/core/test/lib/attachments.test.ts b/packages/core/test/lib/attachments.test.ts index 31ca5d0f6eaa..4f43f86a6451 100644 --- a/packages/core/test/lib/attachments.test.ts +++ b/packages/core/test/lib/attachments.test.ts @@ -1,4 +1,3 @@ -import { TextDecoder, TextEncoder } from 'util'; import { parseEnvelope } from '@sentry/utils'; import { createTransport } from '../../src/transports/base'; @@ -21,8 +20,8 @@ describe('Attachments', () => { dsn: 'https://username@domain/123', enableSend: true, transport: () => - createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, async req => { - const [, items] = parseEnvelope(req.body, new TextEncoder(), new TextDecoder()); + createTransport({ recordDroppedEvent: () => undefined }, async req => { + const [, items] = parseEnvelope(req.body); expect(items.length).toEqual(2); // Second envelope item should be the attachment expect(items[1][0]).toEqual({ type: 'attachment', length: 50000, filename: 'empty.bin' }); diff --git a/packages/core/test/lib/integrations/metadata.test.ts b/packages/core/test/lib/integrations/metadata.test.ts index d81948a8e495..be5d5a8b955d 100644 --- a/packages/core/test/lib/integrations/metadata.test.ts +++ b/packages/core/test/lib/integrations/metadata.test.ts @@ -1,4 +1,3 @@ -import { TextDecoder, TextEncoder } from 'util'; import type { Event } from '@sentry/types'; import { GLOBAL_OBJ, createStackParser, nodeStackLineParser, parseEnvelope } from '@sentry/utils'; @@ -37,8 +36,8 @@ describe('ModuleMetadata integration', () => { return event; }, transport: () => - createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, async req => { - const [, items] = parseEnvelope(req.body, new TextEncoder(), new TextDecoder()); + createTransport({ recordDroppedEvent: () => undefined }, async req => { + const [, items] = parseEnvelope(req.body); expect(items[0][1]).toBeDefined(); const event = items[0][1] as Event; diff --git a/packages/core/test/lib/transports/base.test.ts b/packages/core/test/lib/transports/base.test.ts index 5e6b504d9f6e..01cc8bf59c9b 100644 --- a/packages/core/test/lib/transports/base.test.ts +++ b/packages/core/test/lib/transports/base.test.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { AttachmentItem, EventEnvelope, EventItem, TransportMakeRequestResponse } from '@sentry/types'; import type { PromiseBuffer } from '@sentry/utils'; import { createEnvelope, resolvedSyncPromise, serializeEnvelope } from '@sentry/utils'; @@ -32,7 +31,6 @@ const ATTACHMENT_ENVELOPE = createEnvelope( const transportOptions = { recordDroppedEvent: () => undefined, // noop - textEncoder: new TextEncoder(), }; describe('createTransport', () => { @@ -55,7 +53,7 @@ describe('createTransport', () => { it('constructs a request to send to Sentry', async () => { expect.assertions(1); const transport = createTransport(transportOptions, req => { - expect(req.body).toEqual(serializeEnvelope(ERROR_ENVELOPE, new TextEncoder())); + expect(req.body).toEqual(serializeEnvelope(ERROR_ENVELOPE)); return resolvedSyncPromise({}); }); await transport.send(ERROR_ENVELOPE); @@ -65,7 +63,7 @@ describe('createTransport', () => { expect.assertions(2); const transport = createTransport(transportOptions, req => { - expect(req.body).toEqual(serializeEnvelope(ERROR_ENVELOPE, new TextEncoder())); + expect(req.body).toEqual(serializeEnvelope(ERROR_ENVELOPE)); throw new Error(); }); @@ -101,10 +99,7 @@ describe('createTransport', () => { const mockRecordDroppedEventCallback = jest.fn(); - const transport = createTransport( - { recordDroppedEvent: mockRecordDroppedEventCallback, textEncoder: new TextEncoder() }, - mockRequestExecutor, - ); + const transport = createTransport({ recordDroppedEvent: mockRecordDroppedEventCallback }, mockRequestExecutor); return [transport, setTransportResponse, mockRequestExecutor, mockRecordDroppedEventCallback] as const; } diff --git a/packages/core/test/lib/transports/multiplexed.test.ts b/packages/core/test/lib/transports/multiplexed.test.ts index 097b3428805b..c2d0d2f1d318 100644 --- a/packages/core/test/lib/transports/multiplexed.test.ts +++ b/packages/core/test/lib/transports/multiplexed.test.ts @@ -1,4 +1,3 @@ -import { TextDecoder, TextEncoder } from 'util'; import type { BaseTransportOptions, ClientReport, @@ -59,7 +58,7 @@ const createTestTransport = (...assertions: Assertion[]): ((options: BaseTranspo throw new Error('No assertion left'); } - const event = eventFromEnvelope(parseEnvelope(request.body, new TextEncoder(), new TextDecoder()), ['event']); + const event = eventFromEnvelope(parseEnvelope(request.body), ['event']); assertion(options.url, event?.release, request.body); resolve({ statusCode: 200 }); @@ -69,7 +68,6 @@ const createTestTransport = (...assertions: Assertion[]): ((options: BaseTranspo const transportOptions = { recordDroppedEvent: () => undefined, // noop - textEncoder: new TextEncoder(), }; describe('makeMultiplexedTransport', () => { diff --git a/packages/core/test/lib/transports/offline.test.ts b/packages/core/test/lib/transports/offline.test.ts index 172c890e8049..7e87115b9cdb 100644 --- a/packages/core/test/lib/transports/offline.test.ts +++ b/packages/core/test/lib/transports/offline.test.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { ClientReport, Envelope, @@ -76,7 +75,6 @@ const CLIENT_REPORT_ENVELOPE = createClientReportEnvelope( const transportOptions = { recordDroppedEvent: () => undefined, // noop - textEncoder: new TextEncoder(), }; type MockResult = T | Error; diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index ad47a50fceb8..59a792cf4723 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { ClientOptions, Event, @@ -19,12 +18,10 @@ export function getDefaultTestClientOptions(options: Partial return { integrations: [], sendClientReports: true, - transportOptions: { textEncoder: new TextEncoder() }, transport: () => createTransport( { recordDroppedEvent: () => undefined, - textEncoder: new TextEncoder(), }, // noop _ => resolvedSyncPromise({}), ), diff --git a/packages/core/test/mocks/transport.ts b/packages/core/test/mocks/transport.ts index e2338327be16..cfb15f0abb7a 100644 --- a/packages/core/test/mocks/transport.ts +++ b/packages/core/test/mocks/transport.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { Transport } from '@sentry/types'; import { SyncPromise } from '@sentry/utils'; @@ -17,7 +16,7 @@ export function makeFakeTransport(delay: number = 2000): { let sendCalled = 0; let sentCount = 0; const makeTransport = () => - createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, () => { + createTransport({ recordDroppedEvent: () => undefined }, () => { sendCalled++; return new SyncPromise(async res => { await sleep(delay); diff --git a/packages/deno/test/transport.ts b/packages/deno/test/transport.ts index 47cb86622cb7..d18bd610c7dc 100644 --- a/packages/deno/test/transport.ts +++ b/packages/deno/test/transport.ts @@ -13,7 +13,7 @@ export function makeTestTransport(callback: (envelope: sentryTypes.Envelope) => async function doCallback( request: sentryTypes.TransportRequest, ): Promise { - await callback(sentryUtils.parseEnvelope(request.body, new TextEncoder(), new TextDecoder())); + await callback(sentryUtils.parseEnvelope(request.body)); return Promise.resolve({ statusCode: 200, diff --git a/packages/node-experimental/test/transports/http.test.ts b/packages/node-experimental/test/transports/http.test.ts index c8c82d62faba..be33fe618aa1 100644 --- a/packages/node-experimental/test/transports/http.test.ts +++ b/packages/node-experimental/test/transports/http.test.ts @@ -1,5 +1,4 @@ import * as http from 'http'; -import { TextEncoder } from 'util'; import { createGunzip } from 'zlib'; import { createTransport } from '@sentry/core'; import type { EventEnvelope, EventItem } from '@sentry/types'; @@ -7,8 +6,6 @@ import { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, serial import { makeNodeTransport } from '../../src/transports'; -const textEncoder = new TextEncoder(); - jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -69,19 +66,15 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE); -const ATTACHMENT_ITEM = createAttachmentEnvelopeItem( - { filename: 'empty-file.bin', data: new Uint8Array(50_000) }, - textEncoder, -); +const ATTACHMENT_ITEM = createAttachmentEnvelopeItem({ filename: 'empty-file.bin', data: new Uint8Array(50_000) }); const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope(EVENT_ENVELOPE, ATTACHMENT_ITEM); -const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE, textEncoder) as Uint8Array; +const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE) as Uint8Array; const defaultOptions = { url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, - textEncoder, }; // empty function to keep test output clean @@ -307,7 +300,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -327,7 +320,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -355,7 +348,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -383,7 +376,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); diff --git a/packages/node-experimental/test/transports/https.test.ts b/packages/node-experimental/test/transports/https.test.ts index 40bed042eca5..82be28f84e9a 100644 --- a/packages/node-experimental/test/transports/https.test.ts +++ b/packages/node-experimental/test/transports/https.test.ts @@ -1,6 +1,5 @@ import * as http from 'http'; import * as https from 'https'; -import { TextEncoder } from 'util'; import { createTransport } from '@sentry/core'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -9,8 +8,6 @@ import { makeNodeTransport } from '../../src/transports'; import type { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; import testServerCerts from './test-server-certs'; -const textEncoder = new TextEncoder(); - jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -69,7 +66,7 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE); const unsafeHttpsModule: HTTPModule = { request: jest @@ -83,7 +80,6 @@ const defaultOptions = { httpModule: unsafeHttpsModule, url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, // noop - textEncoder, }; describe('makeNewHttpsTransport()', () => { @@ -296,7 +292,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -316,7 +312,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -344,7 +340,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -372,7 +368,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); diff --git a/packages/node/src/client.ts b/packages/node/src/client.ts index 8611f902ab0e..5fbb7b208724 100644 --- a/packages/node/src/client.ts +++ b/packages/node/src/client.ts @@ -1,5 +1,4 @@ import * as os from 'os'; -import { TextEncoder } from 'util'; import type { ServerRuntimeClientOptions } from '@sentry/core'; import { ServerRuntimeClient, applySdkMetadata } from '@sentry/core'; @@ -19,12 +18,6 @@ export class NodeClient extends ServerRuntimeClient { public constructor(options: NodeClientOptions) { applySdkMetadata(options, 'node'); - // Until node supports global TextEncoder in all versions we support, we are forced to pass it from util - options.transportOptions = { - textEncoder: new TextEncoder(), - ...options.transportOptions, - }; - const clientOptions: ServerRuntimeClientOptions = { ...options, platform: 'node', diff --git a/packages/node/test/manual/express-scope-separation/start.js b/packages/node/test/manual/express-scope-separation/start.js index cfe8df9d2427..c1981c0d0632 100644 --- a/packages/node/test/manual/express-scope-separation/start.js +++ b/packages/node/test/manual/express-scope-separation/start.js @@ -3,7 +3,6 @@ const express = require('express'); const app = express(); const Sentry = require('../../../build/cjs'); const { colorize } = require('../colorize'); -const { TextEncoder } = require('util'); // don't log the test errors we're going to throw, so at a quick glance it doesn't look like the test itself has failed global.console.error = () => null; @@ -20,7 +19,7 @@ function assertTags(actual, expected) { let remaining = 3; function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { --remaining; if (!remaining) { diff --git a/packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js b/packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js index 8fe93e9760fd..c9b5f934bcff 100644 --- a/packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js +++ b/packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js @@ -3,7 +3,6 @@ const express = require('express'); const app = express(); const Sentry = require('../../../../build/cjs'); const { assertSessions } = require('../test-utils'); -const { TextEncoder } = require('util'); function cleanUpAndExitSuccessfully() { server.close(); @@ -32,7 +31,7 @@ function assertSessionAggregates(session, expected) { } function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { const sessionEnv = req.body .split('\n') .filter(l => !!l) diff --git a/packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js b/packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js index 0cecc8ee75e4..b2c2b0c9d265 100644 --- a/packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js +++ b/packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js @@ -1,6 +1,5 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject, validateSessionCountFunction } = require('../test-utils'); -const { TextEncoder } = require('util'); const sessionCounts = { sessionCounter: 0, @@ -10,7 +9,7 @@ const sessionCounts = { validateSessionCountFunction(sessionCounts); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { const payload = req.body .split('\n') .filter(l => !!l) diff --git a/packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js b/packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js index 18ec6fefdc78..b2eef0d2df72 100644 --- a/packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js +++ b/packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js @@ -1,6 +1,5 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject, validateSessionCountFunction } = require('../test-utils'); -const { TextEncoder } = require('util'); const sessionCounts = { sessionCounter: 0, @@ -10,7 +9,7 @@ const sessionCounts = { validateSessionCountFunction(sessionCounts); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { const payload = req.body .split('\n') .filter(l => !!l) diff --git a/packages/node/test/manual/release-health/single-session/healthy-session.js b/packages/node/test/manual/release-health/single-session/healthy-session.js index 11c3092dfcad..49420b0f23b5 100644 --- a/packages/node/test/manual/release-health/single-session/healthy-session.js +++ b/packages/node/test/manual/release-health/single-session/healthy-session.js @@ -1,6 +1,5 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject, validateSessionCountFunction } = require('../test-utils'); -const { TextEncoder } = require('util'); const sessionCounts = { sessionCounter: 0, @@ -10,7 +9,7 @@ const sessionCounts = { validateSessionCountFunction(sessionCounts); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { sessionCounts.sessionCounter++; const sessionEnv = req.body diff --git a/packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js b/packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js index 571af19d0f94..40f95736d4d4 100644 --- a/packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js +++ b/packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js @@ -1,6 +1,5 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject, validateSessionCountFunction } = require('../test-utils'); -const { TextEncoder } = require('util'); const sessionCounts = { sessionCounter: 0, @@ -10,7 +9,7 @@ const sessionCounts = { validateSessionCountFunction(sessionCounts); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { const payload = req.body .split('\n') .filter(l => !!l) diff --git a/packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js b/packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js index 1759c1cc2d0f..1c111e0d4f31 100644 --- a/packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js +++ b/packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js @@ -1,9 +1,8 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject } = require('../test-utils'); -const { TextEncoder } = require('util'); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { if (req.category === 'session') { sessionCounts.sessionCounter++; const sessionEnv = req.body diff --git a/packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js b/packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js index cc8cf921c5d0..d2c5e6cf11d3 100644 --- a/packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js +++ b/packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js @@ -1,6 +1,5 @@ const Sentry = require('../../../../build/cjs'); const { assertSessions, constructStrippedSessionObject, validateSessionCountFunction } = require('../test-utils'); -const { TextEncoder } = require('util'); const sessionCounts = { sessionCounter: 0, @@ -10,7 +9,7 @@ const sessionCounts = { validateSessionCountFunction(sessionCounts); function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { const payload = req.body .split('\n') .filter(l => !!l) diff --git a/packages/node/test/manual/webpack-async-context/index.js b/packages/node/test/manual/webpack-async-context/index.js index 143ae07e56f8..445b277d79aa 100644 --- a/packages/node/test/manual/webpack-async-context/index.js +++ b/packages/node/test/manual/webpack-async-context/index.js @@ -1,11 +1,10 @@ const Sentry = require('../../../build/cjs'); const { colorize } = require('../colorize'); -const { TextEncoder } = require('util'); let remaining = 2; function makeDummyTransport() { - return Sentry.createTransport({ recordDroppedEvent: () => undefined, textEncoder: new TextEncoder() }, req => { + return Sentry.createTransport({ recordDroppedEvent: () => undefined }, req => { --remaining; if (!remaining) { diff --git a/packages/node/test/transports/http.test.ts b/packages/node/test/transports/http.test.ts index 9611e34102f4..3b3d969af5e7 100644 --- a/packages/node/test/transports/http.test.ts +++ b/packages/node/test/transports/http.test.ts @@ -1,5 +1,5 @@ import * as http from 'http'; -import { TextEncoder } from 'util'; + import { createGunzip } from 'zlib'; import { createTransport } from '@sentry/core'; import type { EventEnvelope, EventItem } from '@sentry/types'; @@ -7,8 +7,6 @@ import { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, serial import { makeNodeTransport } from '../../src/transports'; -const textEncoder = new TextEncoder(); - jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -68,19 +66,15 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE); -const ATTACHMENT_ITEM = createAttachmentEnvelopeItem( - { filename: 'empty-file.bin', data: new Uint8Array(50_000) }, - textEncoder, -); +const ATTACHMENT_ITEM = createAttachmentEnvelopeItem({ filename: 'empty-file.bin', data: new Uint8Array(50_000) }); const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope(EVENT_ENVELOPE, ATTACHMENT_ITEM); -const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE, textEncoder) as Uint8Array; +const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope(EVENT_ATTACHMENT_ENVELOPE) as Uint8Array; const defaultOptions = { url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, - textEncoder, }; // empty function to keep test output clean @@ -306,7 +300,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -326,7 +320,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -354,7 +348,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -382,7 +376,7 @@ describe('makeNewHttpTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); diff --git a/packages/node/test/transports/https.test.ts b/packages/node/test/transports/https.test.ts index d26a65ddb5c8..58e3cb6c7faa 100644 --- a/packages/node/test/transports/https.test.ts +++ b/packages/node/test/transports/https.test.ts @@ -1,6 +1,5 @@ import * as http from 'http'; import * as https from 'https'; -import { TextEncoder } from 'util'; import { createTransport } from '@sentry/core'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -9,8 +8,6 @@ import { makeNodeTransport } from '../../src/transports'; import type { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; import testServerCerts from './test-server-certs'; -const textEncoder = new TextEncoder(); - jest.mock('@sentry/core', () => { const actualCore = jest.requireActual('@sentry/core'); return { @@ -68,7 +65,7 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, ]); -const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE, textEncoder); +const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE); const unsafeHttpsModule: HTTPModule = { request: jest @@ -82,7 +79,6 @@ const defaultOptions = { httpModule: unsafeHttpsModule, url: TEST_SERVER_URL, recordDroppedEvent: () => undefined, // noop - textEncoder, }; describe('makeNewHttpsTransport()', () => { @@ -295,7 +291,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -315,7 +311,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -343,7 +339,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); @@ -371,7 +367,7 @@ describe('makeNewHttpsTransport()', () => { const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ - body: serializeEnvelope(EVENT_ENVELOPE, textEncoder), + body: serializeEnvelope(EVENT_ENVELOPE), category: 'error', }); diff --git a/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts b/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts index 4acafd5ca207..a31fc046b17a 100644 --- a/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts +++ b/packages/replay/src/coreHandlers/handleNetworkBreadcrumbs.ts @@ -1,11 +1,5 @@ import { getClient } from '@sentry/core'; -import type { - Breadcrumb, - BreadcrumbHint, - FetchBreadcrumbData, - TextEncoderInternal, - XhrBreadcrumbData, -} from '@sentry/types'; +import type { Breadcrumb, BreadcrumbHint, FetchBreadcrumbData, XhrBreadcrumbData } from '@sentry/types'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; @@ -15,7 +9,6 @@ import { captureXhrBreadcrumbToReplay, enrichXhrBreadcrumb } from './util/xhrUti interface ExtendedNetworkBreadcrumbsOptions extends ReplayNetworkOptions { replay: ReplayContainer; - textEncoder: TextEncoderInternal; } /** @@ -28,8 +21,6 @@ export function handleNetworkBreadcrumbs(replay: ReplayContainer): void { const client = getClient(); try { - const textEncoder = new TextEncoder(); - const { networkDetailAllowUrls, networkDetailDenyUrls, @@ -40,7 +31,6 @@ export function handleNetworkBreadcrumbs(replay: ReplayContainer): void { const options: ExtendedNetworkBreadcrumbsOptions = { replay, - textEncoder, networkDetailAllowUrls, networkDetailDenyUrls, networkCaptureBodies, @@ -71,7 +61,7 @@ export function beforeAddNetworkBreadcrumb( // This has to be sync, as we need to ensure the breadcrumb is enriched in the same tick // Because the hook runs synchronously, and the breadcrumb is afterwards passed on // So any async mutations to it will not be reflected in the final breadcrumb - enrichXhrBreadcrumb(breadcrumb, hint, options); + enrichXhrBreadcrumb(breadcrumb, hint); // This call should not reject // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -82,7 +72,7 @@ export function beforeAddNetworkBreadcrumb( // This has to be sync, as we need to ensure the breadcrumb is enriched in the same tick // Because the hook runs synchronously, and the breadcrumb is afterwards passed on // So any async mutations to it will not be reflected in the final breadcrumb - enrichFetchBreadcrumb(breadcrumb, hint, options); + enrichFetchBreadcrumb(breadcrumb, hint); // This call should not reject // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/packages/replay/src/coreHandlers/util/fetchUtils.ts b/packages/replay/src/coreHandlers/util/fetchUtils.ts index 5b569b659f9d..399206ad659a 100644 --- a/packages/replay/src/coreHandlers/util/fetchUtils.ts +++ b/packages/replay/src/coreHandlers/util/fetchUtils.ts @@ -1,4 +1,4 @@ -import type { Breadcrumb, FetchBreadcrumbData, TextEncoderInternal } from '@sentry/types'; +import type { Breadcrumb, FetchBreadcrumbData } from '@sentry/types'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; @@ -31,7 +31,6 @@ export async function captureFetchBreadcrumbToReplay( breadcrumb: Breadcrumb & { data: FetchBreadcrumbData }, hint: Partial, options: ReplayNetworkOptions & { - textEncoder: TextEncoderInternal; replay: ReplayContainer; }, ): Promise { @@ -54,12 +53,11 @@ export async function captureFetchBreadcrumbToReplay( export function enrichFetchBreadcrumb( breadcrumb: Breadcrumb & { data: FetchBreadcrumbData }, hint: Partial, - options: { textEncoder: TextEncoderInternal }, ): void { const { input, response } = hint; const body = input ? _getFetchRequestArgBody(input) : undefined; - const reqSize = getBodySize(body, options.textEncoder); + const reqSize = getBodySize(body); const resSize = response ? parseContentLengthHeader(response.headers.get('content-length')) : undefined; @@ -74,9 +72,7 @@ export function enrichFetchBreadcrumb( async function _prepareFetchData( breadcrumb: Breadcrumb & { data: FetchBreadcrumbData }, hint: Partial, - options: ReplayNetworkOptions & { - textEncoder: TextEncoderInternal; - }, + options: ReplayNetworkOptions, ): Promise { const now = Date.now(); const { startTimestamp = now, endTimestamp = now } = hint; @@ -136,11 +132,8 @@ export async function _getResponseInfo( captureDetails: boolean, { networkCaptureBodies, - textEncoder, networkResponseHeaders, - }: Pick & { - textEncoder: TextEncoderInternal; - }, + }: Pick, response: Response | undefined, responseBodySize?: number, ): Promise { @@ -157,7 +150,7 @@ export async function _getResponseInfo( const [bodyText, warning] = await _parseFetchResponseBody(response); const result = getResponseData(bodyText, { networkCaptureBodies, - textEncoder, + responseBodySize, captureDetails, headers, @@ -174,7 +167,6 @@ function getResponseData( bodyText: string | undefined, { networkCaptureBodies, - textEncoder, responseBodySize, captureDetails, headers, @@ -183,14 +175,11 @@ function getResponseData( networkCaptureBodies: boolean; responseBodySize: number | undefined; headers: Record; - textEncoder: TextEncoderInternal; }, ): ReplayNetworkRequestOrResponse | undefined { try { const size = - bodyText && bodyText.length && responseBodySize === undefined - ? getBodySize(bodyText, textEncoder) - : responseBodySize; + bodyText && bodyText.length && responseBodySize === undefined ? getBodySize(bodyText) : responseBodySize; if (!captureDetails) { return buildSkippedNetworkRequestOrResponse(size); diff --git a/packages/replay/src/coreHandlers/util/networkUtils.ts b/packages/replay/src/coreHandlers/util/networkUtils.ts index 29d9684456ca..b6ae6ff5b13e 100644 --- a/packages/replay/src/coreHandlers/util/networkUtils.ts +++ b/packages/replay/src/coreHandlers/util/networkUtils.ts @@ -1,4 +1,3 @@ -import type { TextEncoderInternal } from '@sentry/types'; import { dropUndefinedKeys, logger, stringMatchesSomePattern } from '@sentry/utils'; import { NETWORK_BODY_MAX_SIZE, WINDOW } from '../../constants'; @@ -13,14 +12,13 @@ import type { } from '../../types'; /** Get the size of a body. */ -export function getBodySize( - body: RequestInit['body'], - textEncoder: TextEncoder | TextEncoderInternal, -): number | undefined { +export function getBodySize(body: RequestInit['body']): number | undefined { if (!body) { return undefined; } + const textEncoder = new TextEncoder(); + try { if (typeof body === 'string') { return textEncoder.encode(body).length; diff --git a/packages/replay/src/coreHandlers/util/xhrUtils.ts b/packages/replay/src/coreHandlers/util/xhrUtils.ts index b422d3bd1e81..4ce349f1bb2e 100644 --- a/packages/replay/src/coreHandlers/util/xhrUtils.ts +++ b/packages/replay/src/coreHandlers/util/xhrUtils.ts @@ -1,4 +1,4 @@ -import type { Breadcrumb, TextEncoderInternal, XhrBreadcrumbData } from '@sentry/types'; +import type { Breadcrumb, XhrBreadcrumbData } from '@sentry/types'; import { SENTRY_XHR_DATA_KEY, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../debug-build'; @@ -50,7 +50,6 @@ export async function captureXhrBreadcrumbToReplay( export function enrichXhrBreadcrumb( breadcrumb: Breadcrumb & { data: XhrBreadcrumbData }, hint: Partial, - options: { textEncoder: TextEncoderInternal }, ): void { const { xhr, input } = hint; @@ -58,10 +57,10 @@ export function enrichXhrBreadcrumb( return; } - const reqSize = getBodySize(input, options.textEncoder); + const reqSize = getBodySize(input); const resSize = xhr.getResponseHeader('content-length') ? parseContentLengthHeader(xhr.getResponseHeader('content-length')) - : _getBodySize(xhr.response, xhr.responseType, options.textEncoder); + : _getBodySize(xhr.response, xhr.responseType); if (reqSize !== undefined) { breadcrumb.data.request_body_size = reqSize; @@ -208,11 +207,10 @@ export function _parseXhrResponse( function _getBodySize( body: XMLHttpRequest['response'], responseType: XMLHttpRequest['responseType'], - textEncoder: TextEncoder | TextEncoderInternal, ): number | undefined { try { const bodyStr = responseType === 'json' && body && typeof body === 'object' ? JSON.stringify(body) : body; - return getBodySize(bodyStr, textEncoder); + return getBodySize(bodyStr); } catch { return undefined; } diff --git a/packages/replay/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts b/packages/replay/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts index 00a8ffdf8829..de16549d081e 100644 --- a/packages/replay/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts +++ b/packages/replay/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts @@ -1,10 +1,8 @@ -import { TextEncoder } from 'util'; import type { Breadcrumb, BreadcrumbHint, FetchBreadcrumbHint, SentryWrappedXMLHttpRequest, - TextEncoderInternal, XhrBreadcrumbHint, } from '@sentry/types'; import { SENTRY_XHR_DATA_KEY } from '@sentry/utils'; @@ -55,14 +53,12 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => { describe('beforeAddNetworkBreadcrumb()', () => { let options: ReplayNetworkOptions & { replay: ReplayContainer; - textEncoder: TextEncoderInternal; }; beforeEach(() => { jest.setSystemTime(BASE_TIMESTAMP); options = { - textEncoder: new TextEncoder(), replay: setupReplayContainer(), networkDetailAllowUrls: ['https://example.com'], networkDetailDenyUrls: ['http://localhost:8080'], diff --git a/packages/replay/test/unit/coreHandlers/util/fetchUtils.test.ts b/packages/replay/test/unit/coreHandlers/util/fetchUtils.test.ts index 123ec85fe887..6d78a2583774 100644 --- a/packages/replay/test/unit/coreHandlers/util/fetchUtils.test.ts +++ b/packages/replay/test/unit/coreHandlers/util/fetchUtils.test.ts @@ -1,5 +1,3 @@ -import { TextEncoder } from 'util'; - import { _getResponseInfo } from '../../../../src/coreHandlers/util/fetchUtils'; describe('Unit | coreHandlers | util | fetchUtils', () => { @@ -9,7 +7,6 @@ describe('Unit | coreHandlers | util | fetchUtils', () => { false, { networkCaptureBodies: true, - textEncoder: new TextEncoder(), networkResponseHeaders: [], }, undefined, @@ -24,7 +21,6 @@ describe('Unit | coreHandlers | util | fetchUtils', () => { false, { networkCaptureBodies: true, - textEncoder: new TextEncoder(), networkResponseHeaders: [], }, undefined, @@ -58,7 +54,6 @@ describe('Unit | coreHandlers | util | fetchUtils', () => { true, { networkCaptureBodies: true, - textEncoder: new TextEncoder(), networkResponseHeaders: [], }, response, @@ -90,7 +85,6 @@ describe('Unit | coreHandlers | util | fetchUtils', () => { true, { networkCaptureBodies: true, - textEncoder: new TextEncoder(), networkResponseHeaders: [], }, response, @@ -122,7 +116,6 @@ describe('Unit | coreHandlers | util | fetchUtils', () => { true, { networkCaptureBodies: true, - textEncoder: new TextEncoder(), networkResponseHeaders: [], }, response, diff --git a/packages/replay/test/unit/coreHandlers/util/networkUtils.test.ts b/packages/replay/test/unit/coreHandlers/util/networkUtils.test.ts index fccbf24a0a23..8f240c8ea7a7 100644 --- a/packages/replay/test/unit/coreHandlers/util/networkUtils.test.ts +++ b/packages/replay/test/unit/coreHandlers/util/networkUtils.test.ts @@ -1,5 +1,3 @@ -import { TextEncoder } from 'util'; - import { NETWORK_BODY_MAX_SIZE } from '../../../../src/constants'; import { buildNetworkRequestOrResponse, @@ -25,18 +23,16 @@ describe('Unit | coreHandlers | util | networkUtils', () => { }); describe('getBodySize()', () => { - const textEncoder = new TextEncoder(); - it('works with empty body', () => { - expect(getBodySize(undefined, textEncoder)).toBe(undefined); - expect(getBodySize(null, textEncoder)).toBe(undefined); - expect(getBodySize('', textEncoder)).toBe(undefined); + expect(getBodySize(undefined)).toBe(undefined); + expect(getBodySize(null)).toBe(undefined); + expect(getBodySize('')).toBe(undefined); }); it('works with string body', () => { - expect(getBodySize('abcd', textEncoder)).toBe(4); + expect(getBodySize('abcd')).toBe(4); // Emojis are correctly counted as mutliple characters - expect(getBodySize('With emoji: 😈', textEncoder)).toBe(16); + expect(getBodySize('With emoji: 😈')).toBe(16); }); it('works with URLSearchParams', () => { @@ -45,7 +41,7 @@ describe('Unit | coreHandlers | util | networkUtils', () => { params.append('age', '42'); params.append('emoji', '😈'); - expect(getBodySize(params, textEncoder)).toBe(35); + expect(getBodySize(params)).toBe(35); }); it('works with FormData', () => { @@ -54,19 +50,19 @@ describe('Unit | coreHandlers | util | networkUtils', () => { formData.append('age', '42'); formData.append('emoji', '😈'); - expect(getBodySize(formData, textEncoder)).toBe(35); + expect(getBodySize(formData)).toBe(35); }); it('works with Blob', () => { const blob = new Blob(['Hello world: 😈'], { type: 'text/html' }); - expect(getBodySize(blob, textEncoder)).toBe(30); + expect(getBodySize(blob)).toBe(30); }); it('works with ArrayBuffer', () => { const arrayBuffer = new ArrayBuffer(8); - expect(getBodySize(arrayBuffer, textEncoder)).toBe(8); + expect(getBodySize(arrayBuffer)).toBe(8); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index abdf076cf033..6a5f14d7b03c 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -102,7 +102,6 @@ export type { } from './span'; export type { StackFrame } from './stackframe'; export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace'; -export type { TextEncoderInternal } from './textencoder'; export type { PropagationContext, TracePropagationTargets } from './tracing'; export type { StartSpanOptions } from './startSpanOptions'; export type { diff --git a/packages/types/src/textencoder.ts b/packages/types/src/textencoder.ts deleted file mode 100644 index 9071c14c007c..000000000000 --- a/packages/types/src/textencoder.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Vendored type from TS 3.8 `typescript/lib/lib.dom.d.ts`. - * - * Type is vendored in so that users don't have to opt-in to DOM types. - */ -export interface TextEncoderCommon { - /** - * Returns "utf-8". - */ - readonly encoding: string; -} - -// Combination of global TextEncoder and Node require('util').TextEncoder -export interface TextEncoderInternal extends TextEncoderCommon { - encode(input?: string): Uint8Array; -} diff --git a/packages/types/src/transport.ts b/packages/types/src/transport.ts index 05638b67228e..6866f8d19032 100644 --- a/packages/types/src/transport.ts +++ b/packages/types/src/transport.ts @@ -1,6 +1,5 @@ import type { Client } from './client'; import type { Envelope } from './envelope'; -import type { TextEncoderInternal } from './textencoder'; export type TransportRequest = { body: string | Uint8Array; @@ -18,7 +17,6 @@ export type TransportMakeRequestResponse = { export interface InternalBaseTransportOptions { bufferSize?: number; recordDroppedEvent: Client['recordDroppedEvent']; - textEncoder?: TextEncoderInternal; } export interface BaseTransportOptions extends InternalBaseTransportOptions { diff --git a/packages/utils/src/envelope.ts b/packages/utils/src/envelope.ts index c716ab282dfe..6d08ab3e32fa 100644 --- a/packages/utils/src/envelope.ts +++ b/packages/utils/src/envelope.ts @@ -11,7 +11,6 @@ import type { EventEnvelopeHeaders, SdkInfo, SdkMetadata, - TextEncoderInternal, } from '@sentry/types'; import { dsnToString } from './dsn'; @@ -69,17 +68,23 @@ export function envelopeContainsItemType(envelope: Envelope, types: EnvelopeItem } /** - * Encode a string to UTF8. + * Encode a string to UTF8 array. */ -function encodeUTF8(input: string, textEncoder?: TextEncoderInternal): Uint8Array { - const utf8 = textEncoder || new TextEncoder(); - return utf8.encode(input); +function encodeUTF8(input: string): Uint8Array { + return new TextEncoder().encode(input); +} + +/** + * Decode a UTF8 array to string. + */ +function decodeUTF8(input: Uint8Array): string { + return new TextDecoder().decode(input); } /** * Serializes an envelope. */ -export function serializeEnvelope(envelope: Envelope, textEncoder?: TextEncoderInternal): string | Uint8Array { +export function serializeEnvelope(envelope: Envelope): string | Uint8Array { const [envHeaders, items] = envelope; // Initially we construct our envelope as a string and only convert to binary chunks if we encounter binary data @@ -87,9 +92,9 @@ export function serializeEnvelope(envelope: Envelope, textEncoder?: TextEncoderI function append(next: string | Uint8Array): void { if (typeof parts === 'string') { - parts = typeof next === 'string' ? parts + next : [encodeUTF8(parts, textEncoder), next]; + parts = typeof next === 'string' ? parts + next : [encodeUTF8(parts), next]; } else { - parts.push(typeof next === 'string' ? encodeUTF8(next, textEncoder) : next); + parts.push(typeof next === 'string' ? encodeUTF8(next) : next); } } @@ -130,19 +135,11 @@ function concatBuffers(buffers: Uint8Array[]): Uint8Array { return merged; } -export interface TextDecoderInternal { - decode(input?: Uint8Array): string; -} - /** * Parses an envelope */ -export function parseEnvelope( - env: string | Uint8Array, - textEncoder: TextEncoderInternal, - textDecoder: TextDecoderInternal, -): Envelope { - let buffer = typeof env === 'string' ? textEncoder.encode(env) : env; +export function parseEnvelope(env: string | Uint8Array): Envelope { + let buffer = typeof env === 'string' ? encodeUTF8(env) : env; function readBinary(length: number): Uint8Array { const bin = buffer.subarray(0, length); @@ -158,7 +155,7 @@ export function parseEnvelope( i = buffer.length; } - return JSON.parse(textDecoder.decode(readBinary(i))) as T; + return JSON.parse(decodeUTF8(readBinary(i))) as T; } const envelopeHeader = readJson(); @@ -178,11 +175,8 @@ export function parseEnvelope( /** * Creates attachment envelope items */ -export function createAttachmentEnvelopeItem( - attachment: Attachment, - textEncoder?: TextEncoderInternal, -): AttachmentItem { - const buffer = typeof attachment.data === 'string' ? encodeUTF8(attachment.data, textEncoder) : attachment.data; +export function createAttachmentEnvelopeItem(attachment: Attachment): AttachmentItem { + const buffer = typeof attachment.data === 'string' ? encodeUTF8(attachment.data) : attachment.data; return [ dropUndefinedKeys({ diff --git a/packages/utils/test/clientreport.test.ts b/packages/utils/test/clientreport.test.ts index 05c30a9cf3ea..d54ec311d839 100644 --- a/packages/utils/test/clientreport.test.ts +++ b/packages/utils/test/clientreport.test.ts @@ -1,12 +1,8 @@ -import { TextDecoder, TextEncoder } from 'util'; import type { ClientReport } from '@sentry/types'; import { createClientReportEnvelope } from '../src/clientreport'; import { parseEnvelope, serializeEnvelope } from '../src/envelope'; -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - const DEFAULT_DISCARDED_EVENTS: ClientReport['discarded_events'] = [ { reason: 'before_send', @@ -46,7 +42,7 @@ describe('createClientReportEnvelope', () => { it('serializes an envelope', () => { const env = createClientReportEnvelope(DEFAULT_DISCARDED_EVENTS, MOCK_DSN, 123456); - const [headers, items] = parseEnvelope(serializeEnvelope(env, encoder), encoder, decoder); + const [headers, items] = parseEnvelope(serializeEnvelope(env)); expect(headers).toEqual({ dsn: 'https://public@example.com/1' }); expect(items).toEqual([ diff --git a/packages/utils/test/envelope.test.ts b/packages/utils/test/envelope.test.ts index 12cc501a96be..8c4d1eccc9ac 100644 --- a/packages/utils/test/envelope.test.ts +++ b/packages/utils/test/envelope.test.ts @@ -1,9 +1,5 @@ -import { TextDecoder, TextEncoder } from 'util'; import type { Event, EventEnvelope } from '@sentry/types'; -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - import { addItemToEnvelope, createEnvelope, @@ -29,10 +25,10 @@ describe('envelope', () => { describe('serializeEnvelope and parseEnvelope', () => { it('serializes an envelope', () => { const env = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, []); - const serializedEnvelope = serializeEnvelope(env, encoder); + const serializedEnvelope = serializeEnvelope(env); expect(typeof serializedEnvelope).toBe('string'); - const [headers] = parseEnvelope(serializedEnvelope, encoder, decoder); + const [headers] = parseEnvelope(serializedEnvelope); expect(headers).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); }); @@ -50,10 +46,10 @@ describe('envelope', () => { expect.assertions(6); - const serializedEnvelope = serializeEnvelope(env, encoder); + const serializedEnvelope = serializeEnvelope(env); expect(serializedEnvelope).toBeInstanceOf(Uint8Array); - const [parsedHeaders, parsedItems] = parseEnvelope(serializedEnvelope, encoder, decoder); + const [parsedHeaders, parsedItems] = parseEnvelope(serializedEnvelope); expect(parsedHeaders).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); expect(parsedItems).toHaveLength(3); expect(items[0]).toEqual([{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }]); @@ -76,7 +72,7 @@ describe('envelope', () => { [{ type: 'event' }, egg], ]); - const serializedEnvelope = serializeEnvelope(env, encoder); + const serializedEnvelope = serializeEnvelope(env); const [, , serializedBody] = serializedEnvelope.toString().split('\n'); expect(serializedBody).toBe('{"chicken":{"egg":"[Circular ~]"}}'); @@ -86,7 +82,7 @@ describe('envelope', () => { describe('addItemToEnvelope()', () => { it('adds an item to an envelope', () => { const env = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, []); - let [envHeaders, items] = parseEnvelope(serializeEnvelope(env, encoder), encoder, decoder); + let [envHeaders, items] = parseEnvelope(serializeEnvelope(env)); expect(items).toHaveLength(0); expect(envHeaders).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); @@ -95,7 +91,7 @@ describe('envelope', () => { { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }, ]); - [envHeaders, items] = parseEnvelope(serializeEnvelope(newEnv, encoder), encoder, decoder); + [envHeaders, items] = parseEnvelope(serializeEnvelope(newEnv)); expect(envHeaders).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); expect(items).toHaveLength(1); expect(items[0]).toEqual([{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }]); diff --git a/packages/vercel-edge/test/transports/index.test.ts b/packages/vercel-edge/test/transports/index.test.ts index f2304f5b1fc2..74f5d1794ff8 100644 --- a/packages/vercel-edge/test/transports/index.test.ts +++ b/packages/vercel-edge/test/transports/index.test.ts @@ -1,4 +1,3 @@ -import { TextEncoder } from 'util'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -8,7 +7,6 @@ import { IsolatedPromiseBuffer, makeEdgeTransport } from '../../src/transports'; const DEFAULT_EDGE_TRANSPORT_OPTIONS: VercelEdgeTransportOptions = { url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', recordDroppedEvent: () => undefined, - textEncoder: new TextEncoder(), }; const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ @@ -55,7 +53,7 @@ describe('Edge Transport', () => { expect(mockFetch).toHaveBeenCalledTimes(1); expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, { - body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', }); }); @@ -104,7 +102,7 @@ describe('Edge Transport', () => { await transport.send(ERROR_ENVELOPE); await transport.flush(); expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, { - body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()), + body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', ...REQUEST_OPTIONS, }); From 3e2c8f490a117ad2ffa08e71cce85c6c228346b8 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 20 Feb 2024 15:29:12 +0100 Subject: [PATCH 112/173] feat(node-experimental): Move cron code over (#10742) Pending on https://github.com/getsentry/sentry-javascript/pull/10741 --- packages/node-experimental/src/cron/common.ts | 51 +++++ packages/node-experimental/src/cron/cron.ts | 147 ++++++++++++ packages/node-experimental/src/cron/index.ts | 10 + .../node-experimental/src/cron/node-cron.ts | 61 +++++ .../src/cron/node-schedule.ts | 60 +++++ packages/node-experimental/src/index.ts | 2 +- packages/node-experimental/test/cron.test.ts | 213 ++++++++++++++++++ 7 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 packages/node-experimental/src/cron/common.ts create mode 100644 packages/node-experimental/src/cron/cron.ts create mode 100644 packages/node-experimental/src/cron/index.ts create mode 100644 packages/node-experimental/src/cron/node-cron.ts create mode 100644 packages/node-experimental/src/cron/node-schedule.ts create mode 100644 packages/node-experimental/test/cron.test.ts diff --git a/packages/node-experimental/src/cron/common.ts b/packages/node-experimental/src/cron/common.ts new file mode 100644 index 000000000000..0fa8c1c18d23 --- /dev/null +++ b/packages/node-experimental/src/cron/common.ts @@ -0,0 +1,51 @@ +const replacements: [string, string][] = [ + ['january', '1'], + ['february', '2'], + ['march', '3'], + ['april', '4'], + ['may', '5'], + ['june', '6'], + ['july', '7'], + ['august', '8'], + ['september', '9'], + ['october', '10'], + ['november', '11'], + ['december', '12'], + ['jan', '1'], + ['feb', '2'], + ['mar', '3'], + ['apr', '4'], + ['may', '5'], + ['jun', '6'], + ['jul', '7'], + ['aug', '8'], + ['sep', '9'], + ['oct', '10'], + ['nov', '11'], + ['dec', '12'], + ['sunday', '0'], + ['monday', '1'], + ['tuesday', '2'], + ['wednesday', '3'], + ['thursday', '4'], + ['friday', '5'], + ['saturday', '6'], + ['sun', '0'], + ['mon', '1'], + ['tue', '2'], + ['wed', '3'], + ['thu', '4'], + ['fri', '5'], + ['sat', '6'], +]; + +/** + * Replaces names in cron expressions + */ +export function replaceCronNames(cronExpression: string): string { + return replacements.reduce( + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor + (acc, [name, replacement]) => acc.replace(new RegExp(name, 'gi'), replacement), + cronExpression, + ); +} diff --git a/packages/node-experimental/src/cron/cron.ts b/packages/node-experimental/src/cron/cron.ts new file mode 100644 index 000000000000..2540d82e736b --- /dev/null +++ b/packages/node-experimental/src/cron/cron.ts @@ -0,0 +1,147 @@ +import { withMonitor } from '@sentry/core'; +import { replaceCronNames } from './common'; + +export type CronJobParams = { + cronTime: string | Date; + onTick: (context: unknown, onComplete?: unknown) => void | Promise; + onComplete?: () => void | Promise; + start?: boolean | null; + context?: unknown; + runOnInit?: boolean | null; + unrefTimeout?: boolean | null; +} & ( + | { + timeZone?: string | null; + utcOffset?: never; + } + | { + timeZone?: never; + utcOffset?: number | null; + } +); + +export type CronJob = { + // +}; + +export type CronJobConstructor = { + from: (param: CronJobParams) => CronJob; + + new ( + cronTime: CronJobParams['cronTime'], + onTick: CronJobParams['onTick'], + onComplete?: CronJobParams['onComplete'], + start?: CronJobParams['start'], + timeZone?: CronJobParams['timeZone'], + context?: CronJobParams['context'], + runOnInit?: CronJobParams['runOnInit'], + utcOffset?: null, + unrefTimeout?: CronJobParams['unrefTimeout'], + ): CronJob; + new ( + cronTime: CronJobParams['cronTime'], + onTick: CronJobParams['onTick'], + onComplete?: CronJobParams['onComplete'], + start?: CronJobParams['start'], + timeZone?: null, + context?: CronJobParams['context'], + runOnInit?: CronJobParams['runOnInit'], + utcOffset?: CronJobParams['utcOffset'], + unrefTimeout?: CronJobParams['unrefTimeout'], + ): CronJob; +}; + +const ERROR_TEXT = 'Automatic instrumentation of CronJob only supports crontab string'; + +/** + * Instruments the `cron` library to send a check-in event to Sentry for each job execution. + * + * ```ts + * import * as Sentry from '@sentry/node'; + * import { CronJob } from 'cron'; + * + * const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); + * + * // use the constructor + * const job = new CronJobWithCheckIn('* * * * *', () => { + * console.log('You will see this message every minute'); + * }); + * + * // or from + * const job = CronJobWithCheckIn.from({ cronTime: '* * * * *', onTick: () => { + * console.log('You will see this message every minute'); + * }); + * ``` + */ +export function instrumentCron(lib: T & CronJobConstructor, monitorSlug: string): T { + let jobScheduled = false; + + return new Proxy(lib, { + construct(target, args: ConstructorParameters) { + const [cronTime, onTick, onComplete, start, timeZone, ...rest] = args; + + if (typeof cronTime !== 'string') { + throw new Error(ERROR_TEXT); + } + + if (jobScheduled) { + throw new Error(`A job named '${monitorSlug}' has already been scheduled`); + } + + jobScheduled = true; + + const cronString = replaceCronNames(cronTime); + + function monitoredTick(context: unknown, onComplete?: unknown): void | Promise { + return withMonitor( + monitorSlug, + () => { + return onTick(context, onComplete); + }, + { + schedule: { type: 'crontab', value: cronString }, + ...(timeZone ? { timeZone } : {}), + }, + ); + } + + return new target(cronTime, monitoredTick, onComplete, start, timeZone, ...rest); + }, + get(target, prop: keyof CronJobConstructor) { + if (prop === 'from') { + return (param: CronJobParams) => { + const { cronTime, onTick, timeZone } = param; + + if (typeof cronTime !== 'string') { + throw new Error(ERROR_TEXT); + } + + if (jobScheduled) { + throw new Error(`A job named '${monitorSlug}' has already been scheduled`); + } + + jobScheduled = true; + + const cronString = replaceCronNames(cronTime); + + param.onTick = (context: unknown, onComplete?: unknown) => { + return withMonitor( + monitorSlug, + () => { + return onTick(context, onComplete); + }, + { + schedule: { type: 'crontab', value: cronString }, + ...(timeZone ? { timeZone } : {}), + }, + ); + }; + + return target.from(param); + }; + } else { + return target[prop]; + } + }, + }); +} diff --git a/packages/node-experimental/src/cron/index.ts b/packages/node-experimental/src/cron/index.ts new file mode 100644 index 000000000000..eb4b915cff66 --- /dev/null +++ b/packages/node-experimental/src/cron/index.ts @@ -0,0 +1,10 @@ +import { instrumentCron } from './cron'; +import { instrumentNodeCron } from './node-cron'; +import { instrumentNodeSchedule } from './node-schedule'; + +/** Methods to instrument cron libraries for Sentry check-ins */ +export const cron = { + instrumentCron, + instrumentNodeCron, + instrumentNodeSchedule, +}; diff --git a/packages/node-experimental/src/cron/node-cron.ts b/packages/node-experimental/src/cron/node-cron.ts new file mode 100644 index 000000000000..4495a0b54909 --- /dev/null +++ b/packages/node-experimental/src/cron/node-cron.ts @@ -0,0 +1,61 @@ +import { withMonitor } from '@sentry/core'; +import { replaceCronNames } from './common'; + +export interface NodeCronOptions { + name: string; + timezone?: string; +} + +export interface NodeCron { + schedule: (cronExpression: string, callback: () => void, options: NodeCronOptions) => unknown; +} + +/** + * Wraps the `node-cron` library with check-in monitoring. + * + * ```ts + * import * as Sentry from "@sentry/node"; + * import cron from "node-cron"; + * + * const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron); + * + * cronWithCheckIn.schedule( + * "* * * * *", + * () => { + * console.log("running a task every minute"); + * }, + * { name: "my-cron-job" }, + * ); + * ``` + */ +export function instrumentNodeCron(lib: Partial & T): T { + return new Proxy(lib, { + get(target, prop: keyof NodeCron) { + if (prop === 'schedule' && target.schedule) { + // When 'get' is called for schedule, return a proxied version of the schedule function + return new Proxy(target.schedule, { + apply(target, thisArg, argArray: Parameters) { + const [expression, , options] = argArray; + + if (!options?.name) { + throw new Error('Missing "name" for scheduled job. A name is required for Sentry check-in monitoring.'); + } + + return withMonitor( + options.name, + () => { + return target.apply(thisArg, argArray); + }, + { + schedule: { type: 'crontab', value: replaceCronNames(expression) }, + timezone: options?.timezone, + }, + ); + }, + }); + } else { + return target[prop]; + } + }, + }); +} diff --git a/packages/node-experimental/src/cron/node-schedule.ts b/packages/node-experimental/src/cron/node-schedule.ts new file mode 100644 index 000000000000..79ae44a06e52 --- /dev/null +++ b/packages/node-experimental/src/cron/node-schedule.ts @@ -0,0 +1,60 @@ +import { withMonitor } from '@sentry/core'; +import { replaceCronNames } from './common'; + +export interface NodeSchedule { + scheduleJob( + nameOrExpression: string | Date | object, + expressionOrCallback: string | Date | object | (() => void), + callback?: () => void, + ): unknown; +} + +/** + * Instruments the `node-schedule` library to send a check-in event to Sentry for each job execution. + * + * ```ts + * import * as Sentry from '@sentry/node'; + * import * as schedule from 'node-schedule'; + * + * const scheduleWithCheckIn = Sentry.cron.instrumentNodeSchedule(schedule); + * + * const job = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * *', () => { + * console.log('You will see this message every minute'); + * }); + * ``` + */ +export function instrumentNodeSchedule(lib: T & NodeSchedule): T { + return new Proxy(lib, { + get(target, prop: keyof NodeSchedule) { + if (prop === 'scheduleJob') { + // eslint-disable-next-line @typescript-eslint/unbound-method + return new Proxy(target.scheduleJob, { + apply(target, thisArg, argArray: Parameters) { + const [nameOrExpression, expressionOrCallback] = argArray; + + if (typeof nameOrExpression !== 'string' || typeof expressionOrCallback !== 'string') { + throw new Error( + "Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string", + ); + } + + const monitorSlug = nameOrExpression; + const expression = expressionOrCallback; + + return withMonitor( + monitorSlug, + () => { + return target.apply(thisArg, argArray); + }, + { + schedule: { type: 'crontab', value: replaceCronNames(expression) }, + }, + ); + }, + }); + } + + return target[prop]; + }, + }); +} diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index ce353f6e90ce..5d30e3280ef1 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -20,6 +20,7 @@ export { createGetModuleFromFilename } from './utils/module'; export { makeNodeTransport } from './transports'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; +export { cron } from './cron'; export type { Span, NodeOptions } from './types'; @@ -36,7 +37,6 @@ export { contextLinesIntegration, nodeContextIntegration, localVariablesIntegration, - cron, } from '@sentry/node'; export { diff --git a/packages/node-experimental/test/cron.test.ts b/packages/node-experimental/test/cron.test.ts new file mode 100644 index 000000000000..eee6d4a66711 --- /dev/null +++ b/packages/node-experimental/test/cron.test.ts @@ -0,0 +1,213 @@ +import * as SentryCore from '@sentry/core'; + +import { cron } from '../src'; +import type { CronJob, CronJobParams } from '../src/cron/cron'; +import type { NodeCron, NodeCronOptions } from '../src/cron/node-cron'; + +describe('cron check-ins', () => { + let withMonitorSpy: jest.SpyInstance; + + beforeEach(() => { + withMonitorSpy = jest.spyOn(SentryCore, 'withMonitor'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('cron', () => { + class CronJobMock { + constructor( + cronTime: CronJobParams['cronTime'], + onTick: CronJobParams['onTick'], + _onComplete?: CronJobParams['onComplete'], + _start?: CronJobParams['start'], + _timeZone?: CronJobParams['timeZone'], + _context?: CronJobParams['context'], + _runOnInit?: CronJobParams['runOnInit'], + _utcOffset?: CronJobParams['utcOffset'], + _unrefTimeout?: CronJobParams['unrefTimeout'], + ) { + expect(cronTime).toBe('* * * Jan,Sep Sun'); + expect(onTick).toBeInstanceOf(Function); + setImmediate(() => onTick(undefined, undefined)); + } + + static from(params: CronJobParams): CronJob { + return new CronJobMock( + params.cronTime, + params.onTick, + params.onComplete, + params.start, + params.timeZone, + params.context, + params.runOnInit, + params.utcOffset, + params.unrefTimeout, + ); + } + } + + test('new CronJob()', done => { + expect.assertions(4); + + const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); + + new CronJobWithCheckIn('* * * Jan,Sep Sun', () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }); + }); + + test('CronJob.from()', done => { + expect.assertions(4); + + const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); + + CronJobWithCheckIn.from({ + cronTime: '* * * Jan,Sep Sun', + onTick: () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }, + }); + }); + + test('throws with multiple jobs same name', () => { + const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); + + CronJobWithCheckIn.from({ + cronTime: '* * * Jan,Sep Sun', + onTick: () => { + // + }, + }); + + expect(() => { + CronJobWithCheckIn.from({ + cronTime: '* * * Jan,Sep Sun', + onTick: () => { + // + }, + }); + }).toThrowError("A job named 'my-cron-job' has already been scheduled"); + }); + }); + + describe('node-cron', () => { + test('calls withMonitor', done => { + expect.assertions(5); + + const nodeCron: NodeCron = { + schedule: (expression: string, callback: () => void, options?: NodeCronOptions): unknown => { + expect(expression).toBe('* * * Jan,Sep Sun'); + expect(callback).toBeInstanceOf(Function); + expect(options?.name).toBe('my-cron-job'); + return callback(); + }, + }; + + const cronWithCheckIn = cron.instrumentNodeCron(nodeCron); + + cronWithCheckIn.schedule( + '* * * Jan,Sep Sun', + () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }, + { name: 'my-cron-job' }, + ); + }); + + test('throws without supplied name', () => { + const nodeCron: NodeCron = { + schedule: (): unknown => { + return undefined; + }, + }; + + const cronWithCheckIn = cron.instrumentNodeCron(nodeCron); + + expect(() => { + // @ts-expect-error Initially missing name + cronWithCheckIn.schedule('* * * * *', () => { + // + }); + }).toThrowError('Missing "name" for scheduled job. A name is required for Sentry check-in monitoring.'); + }); + }); + + describe('node-schedule', () => { + test('calls withMonitor', done => { + expect.assertions(5); + + class NodeScheduleMock { + scheduleJob( + nameOrExpression: string | Date | object, + expressionOrCallback: string | Date | object | (() => void), + callback: () => void, + ): unknown { + expect(nameOrExpression).toBe('my-cron-job'); + expect(expressionOrCallback).toBe('* * * Jan,Sep Sun'); + expect(callback).toBeInstanceOf(Function); + return callback(); + } + } + + const scheduleWithCheckIn = cron.instrumentNodeSchedule(new NodeScheduleMock()); + + scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * Jan,Sep Sun', () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }); + }); + + test('throws without crontab string', () => { + class NodeScheduleMock { + scheduleJob(_: string, __: string | Date, ___: () => void): unknown { + return undefined; + } + } + + const scheduleWithCheckIn = cron.instrumentNodeSchedule(new NodeScheduleMock()); + + expect(() => { + scheduleWithCheckIn.scheduleJob('my-cron-job', new Date(), () => { + // + }); + }).toThrowError( + "Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string", + ); + }); + + test('throws without job name', () => { + class NodeScheduleMock { + scheduleJob(_: string, __: () => void): unknown { + return undefined; + } + } + + const scheduleWithCheckIn = cron.instrumentNodeSchedule(new NodeScheduleMock()); + + expect(() => { + scheduleWithCheckIn.scheduleJob('* * * * *', () => { + // + }); + }).toThrowError( + "Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string", + ); + }); + }); +}); From de681dcf7d6dac69da9374bbdbe2e2f7e00f0fdc Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 20 Feb 2024 11:10:32 -0500 Subject: [PATCH 113/173] feat(v8): Update eventFromUnknownInput to only use client (#10692) ref https://github.com/getsentry/sentry-javascript/issues/10100 --- packages/core/src/server-runtime-client.ts | 4 +- .../deno/src/integrations/globalhandlers.ts | 4 +- packages/node/test/eventbuilders.test.ts | 47 ------------------- packages/utils/src/eventbuilder.ts | 11 +---- packages/utils/test/eventbuilder.test.ts | 27 ++++------- 5 files changed, 14 insertions(+), 79 deletions(-) diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 81109efd2be9..ecf0fc8ae2b6 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -15,7 +15,7 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; -import { getClient, getIsolationScope } from './currentScopes'; +import { getIsolationScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; @@ -56,7 +56,7 @@ export class ServerRuntimeClient< * @inheritDoc */ public eventFromException(exception: unknown, hint?: EventHint): PromiseLike { - return resolvedSyncPromise(eventFromUnknownInput(getClient(), this._options.stackParser, exception, hint)); + return resolvedSyncPromise(eventFromUnknownInput(this, this._options.stackParser, exception, hint)); } /** diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts index 6498f2e2ffb5..a653d7196246 100644 --- a/packages/deno/src/integrations/globalhandlers.ts +++ b/packages/deno/src/integrations/globalhandlers.ts @@ -67,7 +67,7 @@ function installGlobalErrorHandler(client: Client): void { const { message, error } = data; - const event = eventFromUnknownInput(getClient(), stackParser, error || message); + const event = eventFromUnknownInput(client, stackParser, error || message); event.level = 'fatal'; @@ -116,7 +116,7 @@ function installGlobalUnhandledRejectionHandler(client: Client): void { const event = isPrimitive(error) ? eventFromRejectionWithPrimitive(error) - : eventFromUnknownInput(getClient(), stackParser, error, undefined); + : eventFromUnknownInput(client, stackParser, error, undefined); event.level = 'fatal'; diff --git a/packages/node/test/eventbuilders.test.ts b/packages/node/test/eventbuilders.test.ts index ead2d01e9b44..8afc1537c082 100644 --- a/packages/node/test/eventbuilders.test.ts +++ b/packages/node/test/eventbuilders.test.ts @@ -1,4 +1,3 @@ -import type { Hub } from '@sentry/types'; import { eventFromUnknownInput } from '@sentry/utils'; import { defaultStackParser } from '../src'; @@ -44,50 +43,4 @@ describe('eventFromUnknownInput', () => { }, }); }); - - test('uses normalizeDepth from init options (passing getCurrentHub)', () => { - const deepObject = { - a: { - b: { - c: { - d: { - e: { - f: { - g: 'foo', - }, - }, - }, - }, - }, - }, - }; - - const getCurrentHub = jest.fn(() => { - return { - getClient: () => ({ - getOptions(): any { - return { normalizeDepth: 6 }; - }, - }), - } as unknown as Hub; - }); - - const event = eventFromUnknownInput(getCurrentHub, defaultStackParser, deepObject); - - const serializedObject = event.extra?.__serialized__; - expect(serializedObject).toBeDefined(); - expect(serializedObject).toEqual({ - a: { - b: { - c: { - d: { - e: { - f: '[Object]', - }, - }, - }, - }, - }, - }); - }); }); diff --git a/packages/utils/src/eventbuilder.ts b/packages/utils/src/eventbuilder.ts index 5391723dfe57..af1bb60cd09c 100644 --- a/packages/utils/src/eventbuilder.ts +++ b/packages/utils/src/eventbuilder.ts @@ -4,7 +4,6 @@ import type { EventHint, Exception, Extras, - Hub, Mechanism, ParameterizedString, SeverityLevel, @@ -63,22 +62,14 @@ function getMessageForObject(exception: object): string { /** * Builds and Event from a Exception - * - * TODO(v8): Remove getHub fallback * @hidden */ export function eventFromUnknownInput( - getHubOrClient: (() => Hub) | Client | undefined, + client: Client, stackParser: StackParser, exception: unknown, hint?: EventHint, ): Event { - const client = - typeof getHubOrClient === 'function' - ? // eslint-disable-next-line deprecation/deprecation - getHubOrClient().getClient() - : getHubOrClient; - let ex: unknown = exception; const providedMechanism: Mechanism | undefined = hint && hint.data && (hint.data as { mechanism: Mechanism }).mechanism; diff --git a/packages/utils/test/eventbuilder.test.ts b/packages/utils/test/eventbuilder.test.ts index ec3fdf4bf6ee..3f3f5c479c98 100644 --- a/packages/utils/test/eventbuilder.test.ts +++ b/packages/utils/test/eventbuilder.test.ts @@ -1,44 +1,35 @@ -import type { Hub, Scope } from '@sentry/types'; +import type { Client } from '@sentry/types'; import { createStackParser, eventFromUnknownInput, nodeStackLineParser } from '../src'; -function getCurrentHub(): Hub { - // Some fake hub to get us through - return { - getClient: () => undefined, - getScope: () => { - return { - setExtra: () => {}, - } as unknown as Scope; - }, - } as unknown as Hub; -} - const stackParser = createStackParser(nodeStackLineParser()); describe('eventFromUnknownInput', () => { + const fakeClient = { + getOptions: () => ({}), + } as Client; test('object with useless props', () => { - const event = eventFromUnknownInput(getCurrentHub, stackParser, { foo: { bar: 'baz' }, prop: 1 }); + const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, prop: 1 }); expect(event.exception?.values?.[0].value).toBe('Object captured as exception with keys: foo, prop'); }); test('object with name prop', () => { - const event = eventFromUnknownInput(getCurrentHub, stackParser, { foo: { bar: 'baz' }, name: 'BadType' }); + const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, name: 'BadType' }); expect(event.exception?.values?.[0].value).toBe("'BadType' captured as exception"); }); test('object with name and message props', () => { - const event = eventFromUnknownInput(getCurrentHub, stackParser, { message: 'went wrong', name: 'BadType' }); + const event = eventFromUnknownInput(fakeClient, stackParser, { message: 'went wrong', name: 'BadType' }); expect(event.exception?.values?.[0].value).toBe("'BadType' captured as exception with message 'went wrong'"); }); test('object with message prop', () => { - const event = eventFromUnknownInput(getCurrentHub, stackParser, { foo: { bar: 'baz' }, message: 'Some message' }); + const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, message: 'Some message' }); expect(event.exception?.values?.[0].value).toBe('Some message'); }); test('passing client directly', () => { - const event = eventFromUnknownInput(undefined, stackParser, { foo: { bar: 'baz' }, prop: 1 }); + const event = eventFromUnknownInput(fakeClient, stackParser, { foo: { bar: 'baz' }, prop: 1 }); expect(event.exception?.values?.[0].value).toBe('Object captured as exception with keys: foo, prop'); }); }); From d2fce9bcc9146b1a0fc11fba4ba27e5047afe02b Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 20 Feb 2024 16:50:48 +0000 Subject: [PATCH 114/173] test(node): Add NestJS OpenTelemetry Tests (#10684) Part of #9907 --- .github/workflows/build.yml | 1 + .../node-integration-tests/package.json | 5 + .../tracing-experimental/nestjs/scenario.ts | 53 ++++++ .../tracing-experimental/nestjs/test.ts | 49 ++++++ .../tracing-experimental/nestjs/tsconfig.json | 9 + yarn.lock | 163 +++++++++++++++++- 6 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14d255bae1a1..ba48a87bb6f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -899,6 +899,7 @@ jobs: - name: Run integration tests env: NODE_VERSION: ${{ matrix.node }} + TS_VERSION: ${{ matrix.typescript }} run: | cd dev-packages/node-integration-tests yarn test diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 3826c0024f88..b0e4156bfb14 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -28,6 +28,9 @@ }, "dependencies": { "@hapi/hapi": "^20.3.0", + "@nestjs/core": "^10.3.3", + "@nestjs/common": "^10.3.3", + "@nestjs/platform-express": "^10.3.3", "@prisma/client": "3.15.2", "@sentry/node": "7.100.0", "@sentry/tracing": "7.100.0", @@ -50,6 +53,8 @@ "nock": "^13.1.0", "pg": "^8.7.3", "proxy": "^2.1.1", + "reflect-metadata": "0.2.1", + "rxjs": "^7.8.1", "yargs": "^16.2.0" }, "devDependencies": { diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts new file mode 100644 index 000000000000..06ed91626b3b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +import { loggingTransport, sendPortToRunner } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node-experimental'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +import { Controller, Get, Injectable, Module } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; + +const port = 3450; + +// Stop the process from exiting before the transaction is sent +// eslint-disable-next-line @typescript-eslint/no-empty-function +setInterval(() => {}, 1000); + +@Injectable() +class AppService { + getHello(): string { + return 'Hello World!'; + } +} + +@Controller() +class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getHello(): string { + return this.appService.getHello(); + } +} + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService], +}) +class AppModule {} + +async function init(): Promise { + const app = await NestFactory.create(AppModule); + await app.listen(port); + sendPortToRunner(port); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +init(); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/test.ts new file mode 100644 index 000000000000..abb9cf6f0bdd --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/test.ts @@ -0,0 +1,49 @@ +import { conditionalTest } from '../../../utils'; +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +jest.setTimeout(20000); + +const { TS_VERSION } = process.env; +const isOldTS = TS_VERSION && TS_VERSION.startsWith('3.'); + +// This is required to run the test with ts-node and decorators +process.env.TS_NODE_PROJECT = `${__dirname}/tsconfig.json`; + +conditionalTest({ min: 16 })('nestjs auto instrumentation', () => { + afterAll(async () => { + cleanupChildProcesses(); + }); + + const CREATION_TRANSACTION = { + transaction: 'Create Nest App', + }; + + const GET_TRANSACTION = { + transaction: 'GET /', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'GET /', + data: expect.objectContaining({ + 'nestjs.callback': 'getHello', + 'nestjs.controller': 'AppController', + 'nestjs.type': 'request_context', + 'otel.kind': 'INTERNAL', + 'sentry.op': 'http', + }), + }), + ]), + }; + + test('should auto-instrument `nestjs` package', done => { + if (isOldTS) { + // Skipping test on old TypeScript + return done(); + } + + createRunner(__dirname, 'scenario.ts') + .expect({ transaction: CREATION_TRANSACTION }) + .expect({ transaction: GET_TRANSACTION }) + .start(done) + .makeRequest('get', '/'); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/tsconfig.json b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/tsconfig.json new file mode 100644 index 000000000000..84b8f8d6c44e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": ["scenario.ts"], + "compilerOptions": { + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES2021", + } +} diff --git a/yarn.lock b/yarn.lock index df4a75089671..f618c7d7d485 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4350,6 +4350,43 @@ tslib "^2.3.1" upath "^2.0.1" +"@lukeed/csprng@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" + integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== + +"@nestjs/common@^10.3.3": + version "10.3.3" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.3.tgz#ba20f756dbed62f5fe29737c42384ad41156c9e9" + integrity sha512-LAkTe8/CF0uNWM0ecuDwUNTHCi1lVSITmmR4FQ6Ftz1E7ujQCnJ5pMRzd8JRN14vdBkxZZ8VbVF0BDUKoKNxMQ== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.6.2" + +"@nestjs/core@^10.3.3": + version "10.3.3" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.3.tgz#f957068ddda59252b7c36fcdb07a0fb323b52bcf" + integrity sha512-kxJWggQAPX3RuZx9JVec69eSLaYLNIox2emkZJpfBJ5Qq7cAq7edQIt1r4LGjTKq6kFubNTPsqhWf5y7yFRBPw== + dependencies: + uid "2.0.2" + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + path-to-regexp "3.2.0" + tslib "2.6.2" + +"@nestjs/platform-express@^10.3.3": + version "10.3.3" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.3.tgz#c1484d30d1e7666c4c8d0d7cde31cfc0b9d166d7" + integrity sha512-GGKSEU48Os7nYFIsUM0nutuFUGn5AbeP8gzFBiBCAtiuJWrXZXpZ58pMBYxAbMf7IrcOZFInHEukjHGAQU0OZw== + dependencies: + body-parser "1.20.2" + cors "2.8.5" + express "4.18.2" + multer "1.4.4-lts.1" + tslib "2.6.2" + "@next/env@10.1.3": version "10.1.3" resolved "https://registry.yarnpkg.com/@next/env/-/env-10.1.3.tgz#29e5d62919b4a7b1859f8d36169848dc3f5ddebe" @@ -4559,6 +4596,15 @@ dependencies: nx "16.4.1" +"@nuxtjs/opencollective@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" + integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + "@nx/devkit@16.4.1", "@nx/devkit@>=16.1.3 < 17": version "16.4.1" resolved "https://registry.yarnpkg.com/@nx/devkit/-/devkit-16.4.1.tgz#43b126704d5611b8f19418ade4a60a0fd1e695ff" @@ -8466,6 +8512,11 @@ app-module-path@^2.2.0: resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" integrity sha1-ZBqlXft9am8KgUHEucCqULbCTdU= +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + "aproba@^1.0.3 || ^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" @@ -10007,6 +10058,24 @@ body-parser@1.20.1, body-parser@^1.18.3, body-parser@^1.19.0: type-is "~1.6.18" unpipe "1.0.0" +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + body@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" @@ -10856,6 +10925,13 @@ bun-types@latest: resolved "https://registry.yarnpkg.com/bun-types/-/bun-types-1.0.1.tgz#8bcb10ae3a1548a39f0932fdb365f4b3a649efba" integrity sha512-7NrXqhMIaNKmWn2dSWEQ50znMZqrN/5Z0NBMXvQTRu/+Y1CvoXRznFy0pnqLe024CeZgVdXoEpARNO1JZLAPGw== +busboy@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + byte-size@8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae" @@ -11960,7 +12036,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -12007,6 +12083,11 @@ connect@^3.6.6, connect@^3.7.0: parseurl "~1.3.3" utils-merge "1.0.1" +consola@^2.15.0: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -12057,6 +12138,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + continuable-cache@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" @@ -12287,7 +12373,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@^2.8.5, cors@~2.8.5: +cors@2.8.5, cors@^2.8.5, cors@~2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -15599,7 +15685,7 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -express@^4.10.7, express@^4.16.4, express@^4.17.1, express@^4.17.3, express@^4.18.1: +express@4.18.2, express@^4.10.7, express@^4.16.4, express@^4.17.1, express@^4.17.3, express@^4.18.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -15807,6 +15893,11 @@ fast-printf@^1.6.9: dependencies: boolean "^3.1.4" +fast-safe-stringify@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fast-sourcemap-concat@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-sourcemap-concat/-/fast-sourcemap-concat-2.1.0.tgz#12dd36bfc38c804093e4bd1de61dd6216f574211" @@ -19268,6 +19359,11 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + jackspeak@^2.0.3: version "2.2.1" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.1.tgz#655e8cf025d872c9c03d3eb63e8f0c024fef16a6" @@ -22545,7 +22641,7 @@ mkdirp@0.5.4: dependencies: minimist "^1.2.5" -mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@^0.5.6, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -22802,6 +22898,19 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multer@1.4.4-lts.1: + version "1.4.4-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.4-lts.1.tgz#24100f701a4611211cfae94ae16ea39bb314e04d" + integrity sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -24849,6 +24958,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" + integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== + path-to-regexp@^1.5.3, path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -26803,6 +26917,16 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + raw-body@~1.1.0: version "1.1.7" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" @@ -27230,6 +27354,11 @@ redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +reflect-metadata@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74" + integrity sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw== + reflect-metadata@^0.1.2: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" @@ -28191,6 +28320,13 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + sade@^1.7.3, sade@^1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -29593,6 +29729,11 @@ streamroller@^3.0.2: debug "^4.1.1" fs-extra "^10.0.0" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + streamx@^2.15.0: version "2.15.1" resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.1.tgz#396ad286d8bc3eeef8f5cea3f029e81237c024c6" @@ -30941,6 +31082,11 @@ tslib@2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tslib@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -31053,7 +31199,7 @@ type-fest@^2.13.0, type-fest@^2.3.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -type-is@~1.6.18: +type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -31175,6 +31321,13 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.3.tgz#ce72a1ad154348ea2af61f50933c76cc8802276e" integrity sha512-otIc7O9LyxpUcQoXzj2hL4LPWKklO6LJWoJUzNa8A17Xgi4fOeDC8FBDOLHnC/Slo1CQgsZMcM6as0M76BZaig== +uid@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-2.0.2.tgz#4b5782abf0f2feeefc00fa88006b2b3b7af3e3b9" + integrity sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g== + dependencies: + "@lukeed/csprng" "^1.0.0" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From 18f7107daede27b312c273724c6756a5ff0c77b2 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 20 Feb 2024 19:41:45 +0000 Subject: [PATCH 115/173] fix(test): Fix flaky browser integration tests. (#10484) This is not a complete fix for all flaky integration tests, but it seems to resolve flakiness of a few tests that fail very frequently. --- .../suites/integrations/Breadcrumbs/dom/click/test.ts | 3 +-- .../suites/integrations/httpclient/fetch/simple/test.ts | 2 ++ .../suites/public-api/startSpan/basic/subject.js | 4 +++- .../suites/replay/errorResponse/test.ts | 3 +-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts index a9f4a335c4e4..0b877fb7c8d8 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts @@ -78,9 +78,8 @@ sentryTest( await page.goto(url); await page.locator('#annotated-button').click(); - await page.evaluate('Sentry.captureException("test exception")'); - const eventData = await promise; + const [eventData] = await Promise.all([promise, page.evaluate('Sentry.captureException("test exception")')]); expect(eventData.breadcrumbs).toEqual([ { diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts index 6885de912437..bc56277c0f71 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/simple/test.ts @@ -4,6 +4,8 @@ import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; +// This test rarely flakes with timeouts. The reason might be: +// https://github.com/microsoft/playwright/issues/10376 sentryTest( 'should assign request and response context from a failed 500 fetch request', async ({ getLocalTestPath, page }) => { diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js index 5f4dbea513a8..aaf2de868f6f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js @@ -6,4 +6,6 @@ async function run() { }); } -run(); +(async () => { + await run(); +})(); diff --git a/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts b/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts index 70887ef2b9f6..67f1d1d12d6d 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts @@ -23,9 +23,8 @@ sentryTest('should stop recording after receiving an error response', async ({ g }); const url = await getLocalTestPath({ testDir: __dirname }); - await page.goto(url); + await Promise.all([page.goto(url), waitForReplayRequest(page)]); - await waitForReplayRequest(page); await page.locator('button').click(); expect(called).toBe(1); From c2baeace14009ab343ce52cbde6c675d3251b6e3 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 20 Feb 2024 19:39:53 -0500 Subject: [PATCH 116/173] feat(v8): Remove usage of span.description and span.name (#10697) ref https://github.com/getsentry/sentry-javascript/issues/10677 Removes all usage of setting/getting `span.description` and `span.name`, and removes the `span.setName` method. --- .github/workflows/build.yml | 3 +- .../backgroundtab-custom/test.ts | 8 +-- .../pageloadWithHeartbeatTimeout/test.ts | 5 +- .../backgroundtab-custom/test.ts | 8 +-- .../pageloadWithHeartbeatTimeout/test.ts | 5 +- packages/angular/src/tracing.ts | 8 +-- packages/angular/test/tracing.test.ts | 12 ++--- packages/core/src/tracing/sentrySpan.ts | 49 ++--------------- packages/core/src/tracing/transaction.ts | 28 +--------- packages/core/test/lib/tracing/span.test.ts | 44 ++------------- packages/core/test/lib/tracing/trace.test.ts | 4 +- .../core/test/lib/tracing/transaction.test.ts | 32 +++-------- packages/ember/addon/index.ts | 4 +- .../test/integration/transactions.test.ts | 54 +++---------------- .../node-experimental/test/sdk/api.test.ts | 2 +- packages/node/src/integrations/hapi/index.ts | 3 +- packages/node/src/integrations/http.ts | 12 ++--- .../node/src/integrations/undici/index.ts | 2 +- packages/node/src/integrations/utils/http.ts | 16 +++--- packages/node/test/handlers.test.ts | 22 ++++---- .../node/test/integrations/undici.test.ts | 9 ++-- packages/node/test/performance.test.ts | 5 +- .../opentelemetry-node/src/spanprocessor.ts | 2 +- packages/opentelemetry/src/spanExporter.ts | 2 +- .../test/integration/transactions.test.ts | 36 ++----------- packages/remix/src/utils/instrumentServer.ts | 4 +- packages/svelte/src/performance.ts | 4 +- packages/svelte/test/performance.test.ts | 18 +++---- .../src/client/browserTracingIntegration.ts | 1 - packages/sveltekit/src/client/router.ts | 3 +- .../client/browserTracingIntegration.test.ts | 1 - packages/sveltekit/test/client/router.test.ts | 5 +- packages/sveltekit/test/server/handle.test.ts | 15 +++--- .../src/browser/metrics/index.ts | 18 +++---- .../src/node/integrations/apollo.ts | 2 +- .../src/node/integrations/express.ts | 6 +-- .../src/node/integrations/graphql.ts | 2 +- .../src/node/integrations/mongo.ts | 2 +- .../src/node/integrations/mysql.ts | 2 +- .../src/node/integrations/postgres.ts | 2 +- .../test/browser/browsertracing.test.ts | 10 ++-- .../test/browser/metrics/index.test.ts | 8 +-- .../test/browser/metrics/utils.test.ts | 6 +-- .../test/integrations/apollo-nestjs.test.ts | 4 +- .../tracing/test/integrations/apollo.test.ts | 4 +- .../tracing/test/integrations/graphql.test.ts | 2 +- .../test/integrations/node/mongo.test.ts | 8 +-- .../test/integrations/node/postgres.test.ts | 6 +-- packages/tracing/test/span.test.ts | 16 +++--- packages/tracing/test/transaction.test.ts | 39 ++++++-------- packages/types/src/span.ts | 24 +-------- packages/types/src/startSpanOptions.ts | 6 --- packages/types/src/transaction.ts | 15 +----- packages/utils/src/url.ts | 2 +- 54 files changed, 180 insertions(+), 430 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba48a87bb6f5..4f42cbff1743 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1056,7 +1056,8 @@ jobs: 'sveltekit-2', 'generic-ts3.8', 'node-experimental-fastify-app', - 'node-hapi-app', + # TODO(v8): Re-enable hapi tests + # 'node-hapi-app', 'node-exports-test-app', 'vue-3' ] diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts index de1cd552ccab..0338a6cd5c85 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts @@ -23,23 +23,23 @@ sentryTest('should finish a custom transaction when the page goes background', a const transactionHandle = await page.evaluateHandle('window.transaction'); const id_before = await getPropertyValue(transactionHandle, 'span_id'); - const name_before = await getPropertyValue(transactionHandle, 'name'); + const nameBefore = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; const status_before = await getPropertyValue(transactionHandle, 'status'); const tags_before = await getPropertyValue(transactionHandle, 'tags'); - expect(name_before).toBe('test-transaction'); + expect(nameBefore).toBe('test-transaction'); expect(status_before).toBeUndefined(); expect(tags_before).toStrictEqual({}); await page.locator('#go-background').click(); const id_after = await getPropertyValue(transactionHandle, 'span_id'); - const name_after = await getPropertyValue(transactionHandle, 'name'); + const nameAfter = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; const status_after = await getPropertyValue(transactionHandle, 'status'); const tags_after = await getPropertyValue(transactionHandle, 'tags'); expect(id_before).toBe(id_after); - expect(name_after).toBe(name_before); + expect(nameAfter).toBe('test-transaction'); expect(status_after).toBe('cancelled'); expect(tags_after).toStrictEqual({ visibilitychange: 'document.hidden' }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts index dbb284aecb3b..ead37d6f8662 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -16,11 +16,10 @@ sentryTest( const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.contexts?.trace?.op).toBe('pageload'); expect( - // eslint-disable-next-line deprecation/deprecation eventData.spans?.find(span => span.description === 'pageload-child-span' && span.status === 'cancelled'), ).toBeDefined(); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts index de1cd552ccab..0338a6cd5c85 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts @@ -23,23 +23,23 @@ sentryTest('should finish a custom transaction when the page goes background', a const transactionHandle = await page.evaluateHandle('window.transaction'); const id_before = await getPropertyValue(transactionHandle, 'span_id'); - const name_before = await getPropertyValue(transactionHandle, 'name'); + const nameBefore = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; const status_before = await getPropertyValue(transactionHandle, 'status'); const tags_before = await getPropertyValue(transactionHandle, 'tags'); - expect(name_before).toBe('test-transaction'); + expect(nameBefore).toBe('test-transaction'); expect(status_before).toBeUndefined(); expect(tags_before).toStrictEqual({}); await page.locator('#go-background').click(); const id_after = await getPropertyValue(transactionHandle, 'span_id'); - const name_after = await getPropertyValue(transactionHandle, 'name'); + const nameAfter = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; const status_after = await getPropertyValue(transactionHandle, 'status'); const tags_after = await getPropertyValue(transactionHandle, 'tags'); expect(id_before).toBe(id_after); - expect(name_after).toBe(name_before); + expect(nameAfter).toBe('test-transaction'); expect(status_after).toBe('cancelled'); expect(tags_after).toStrictEqual({ visibilitychange: 'document.hidden' }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts index dbb284aecb3b..ead37d6f8662 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -16,11 +16,10 @@ sentryTest( const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.contexts?.trace?.op).toBe('pageload'); expect( - // eslint-disable-next-line deprecation/deprecation eventData.spans?.find(span => span.description === 'pageload-child-span' && span.status === 'cancelled'), ).toBeDefined(); }, diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index bbfe3afd3b67..e07ee34f6516 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -173,7 +173,7 @@ export class TraceService implements OnDestroy { if (activeTransaction) { // eslint-disable-next-line deprecation/deprecation this._routingSpan = activeTransaction.startChild({ - description: `${navigationEvent.url}`, + name: `${navigationEvent.url}`, op: ANGULAR_ROUTING_OP, origin: 'auto.ui.angular', tags: { @@ -327,7 +327,7 @@ export class TraceDirective implements OnInit, AfterViewInit { if (activeTransaction) { // eslint-disable-next-line deprecation/deprecation this._tracingSpan = activeTransaction.startChild({ - description: `<${this.componentName}>`, + name: `<${this.componentName}>`, op: ANGULAR_INIT_OP, origin: 'auto.ui.angular.trace_directive', }); @@ -371,7 +371,7 @@ export function TraceClassDecorator(): ClassDecorator { if (activeTransaction) { // eslint-disable-next-line deprecation/deprecation tracingSpan = activeTransaction.startChild({ - description: `<${target.name}>`, + name: `<${target.name}>`, op: ANGULAR_INIT_OP, origin: 'auto.ui.angular.trace_class_decorator', }); @@ -410,7 +410,7 @@ export function TraceMethodDecorator(): MethodDecorator { if (activeTransaction) { // eslint-disable-next-line deprecation/deprecation activeTransaction.startChild({ - description: `<${target.constructor.name}>`, + name: `<${target.constructor.name}>`, endTimestamp: now, op: `${ANGULAR_OP}.${String(propertyKey)}`, origin: 'auto.ui.angular.trace_method_decorator', diff --git a/packages/angular/test/tracing.test.ts b/packages/angular/test/tracing.test.ts index 31bd13473253..5a935f178c71 100644 --- a/packages/angular/test/tracing.test.ts +++ b/packages/angular/test/tracing.test.ts @@ -373,13 +373,13 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledWith({ op: 'ui.angular.init', origin: 'auto.ui.angular.trace_directive', - description: '', + name: '', }); env.destroy(); }); - it('should use component name as span description', async () => { + it('should use component name as span name', async () => { const directive = new TraceDirective(); const finishMock = jest.fn(); const customStartTransaction = jest.fn(defaultStartTransaction); @@ -400,7 +400,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledWith({ op: 'ui.angular.init', origin: 'auto.ui.angular.trace_directive', - description: '', + name: '', }); env.destroy(); @@ -472,7 +472,7 @@ describe('Angular Tracing', () => { }); expect(transaction.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.angular.init', origin: 'auto.ui.angular.trace_class_decorator', }); @@ -526,7 +526,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledTimes(2); expect(transaction.startChild.mock.calls[0][0]).toEqual({ - description: '', + name: '', op: 'ui.angular.ngOnInit', origin: 'auto.ui.angular.trace_method_decorator', startTimestamp: expect.any(Number), @@ -534,7 +534,7 @@ describe('Angular Tracing', () => { }); expect(transaction.startChild.mock.calls[1][0]).toEqual({ - description: '', + name: '', op: 'ui.angular.ngAfterViewInit', origin: 'auto.ui.angular.trace_method_decorator', startTimestamp: expect.any(Number), diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 97f21aef1c43..3faba55d8eab 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -144,8 +144,7 @@ export class SentrySpan implements SpanInterface { ...spanContext.attributes, }); - // eslint-disable-next-line deprecation/deprecation - this._name = spanContext.name || spanContext.description; + this._name = spanContext.name; if (spanContext.parentSpanId) { this._parentSpanId = spanContext.parentSpanId; @@ -165,38 +164,6 @@ export class SentrySpan implements SpanInterface { // This rule conflicts with another eslint rule :( /* eslint-disable @typescript-eslint/member-ordering */ - /** - * An alias for `description` of the Span. - * @deprecated Use `spanToJSON(span).description` instead. - */ - public get name(): string { - return this._name || ''; - } - - /** - * Update the name of the span. - * @deprecated Use `spanToJSON(span).description` instead. - */ - public set name(name: string) { - this.updateName(name); - } - - /** - * Get the description of the Span. - * @deprecated Use `spanToJSON(span).description` instead. - */ - public get description(): string | undefined { - return this._name; - } - - /** - * Get the description of the Span. - * @deprecated Use `spanToJSON(span).description` instead. - */ - public set description(description: string | undefined) { - this._name = description; - } - /** * The ID of the trace. * @deprecated Use `spanContext().traceId` instead. @@ -486,15 +453,6 @@ export class SentrySpan implements SpanInterface { return this; } - /** - * @inheritdoc - * - * @deprecated Use `.updateName()` instead. - */ - public setName(name: string): void { - this.updateName(name); - } - /** * @inheritDoc */ @@ -551,7 +509,7 @@ export class SentrySpan implements SpanInterface { public toContext(): SpanContext { return dropUndefinedKeys({ data: this._getData(), - description: this._name, + name: this._name, endTimestamp: this._endTime, // eslint-disable-next-line deprecation/deprecation op: this.op, @@ -574,8 +532,7 @@ export class SentrySpan implements SpanInterface { public updateWithContext(spanContext: SpanContext): this { // eslint-disable-next-line deprecation/deprecation this.data = spanContext.data || {}; - // eslint-disable-next-line deprecation/deprecation - this._name = spanContext.name || spanContext.description; + this._name = spanContext.name; this._endTime = spanContext.endTimestamp; // eslint-disable-next-line deprecation/deprecation this.op = spanContext.op; diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 35acd8d6b3e0..8b3f5e62288d 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -91,23 +91,6 @@ export class Transaction extends SentrySpan implements TransactionInterface { // This sadly conflicts with the getter/setter ordering :( /* eslint-disable @typescript-eslint/member-ordering */ - /** - * Getter for `name` property. - * @deprecated Use `spanToJSON(span).description` instead. - */ - public get name(): string { - return this._name; - } - - /** - * Setter for `name` property, which also sets `source` as custom. - * @deprecated Use `updateName()` and `setMetadata()` instead. - */ - public set name(newName: string) { - // eslint-disable-next-line deprecation/deprecation - this.setName(newName); - } - /** * Get the metadata for this transaction. * @deprecated Use `spanGetMetadata(transaction)` instead. @@ -138,19 +121,10 @@ export class Transaction extends SentrySpan implements TransactionInterface { /* eslint-enable @typescript-eslint/member-ordering */ - /** - * Setter for `name` property, which also sets `source` on the metadata. - * - * @deprecated Use `.updateName()` and `.setAttribute()` instead. - */ - public setName(name: string, source: TransactionSource = 'custom'): void { - this._name = name; - this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); - } - /** @inheritdoc */ public updateName(name: string): this { this._name = name; + this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); return this; } diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 16b92a4a283f..2209b9b8285a 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -7,54 +7,16 @@ describe('span', () => { /* eslint-disable deprecation/deprecation */ it('works with name', () => { const span = new SentrySpan({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); - }); - - it('works with description', () => { - const span = new SentrySpan({ description: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); - }); - - it('works without name', () => { - const span = new SentrySpan({}); - expect(span.name).toEqual(''); - expect(span.description).toEqual(undefined); - }); - - it('allows to update the name via setter', () => { - const span = new SentrySpan({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); - - span.name = 'new name'; - - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); - }); - - it('allows to update the name via setName', () => { - const span = new SentrySpan({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); - - // eslint-disable-next-line deprecation/deprecation - span.setName('new name'); - - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); + expect(spanToJSON(span).description).toEqual('span name'); }); it('allows to update the name via updateName', () => { const span = new SentrySpan({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + expect(spanToJSON(span).description).toEqual('span name'); span.updateName('new name'); - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); + expect(spanToJSON(span).description).toEqual('new name'); }); }); /* eslint-enable deprecation/deprecation */ diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index cb443043fd36..a260b8854cd0 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -105,7 +105,7 @@ describe('startSpan', () => { } expect(ref).toBeDefined(); - expect(ref.name).toEqual('GET users/[id]'); + expect(spanToJSON(ref).description).toEqual('GET users/[id]'); expect(ref.status).toEqual(isError ? 'internal_error' : undefined); }); @@ -192,7 +192,7 @@ describe('startSpan', () => { } expect(ref.spanRecorder.spans).toHaveLength(2); - expect(ref.spanRecorder.spans[1].description).toEqual('SELECT * from users'); + expect(spanToJSON(ref.spanRecorder.spans[1]).description).toEqual('SELECT * from users'); expect(ref.spanRecorder.spans[1].parentSpanId).toEqual(ref.spanId); expect(ref.spanRecorder.spans[1].status).toEqual(isError ? 'internal_error' : undefined); }); diff --git a/packages/core/test/lib/tracing/transaction.test.ts b/packages/core/test/lib/tracing/transaction.test.ts index 9371bda548fd..781b9bdc1472 100644 --- a/packages/core/test/lib/tracing/transaction.test.ts +++ b/packages/core/test/lib/tracing/transaction.test.ts @@ -3,6 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Transaction, + spanToJSON, } from '../../../src'; describe('transaction', () => { @@ -10,40 +11,19 @@ describe('transaction', () => { /* eslint-disable deprecation/deprecation */ it('works with name', () => { const transaction = new Transaction({ name: 'span name' }); - expect(transaction.name).toEqual('span name'); - }); - - it('allows to update the name via setter', () => { - const transaction = new Transaction({ name: 'span name' }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - expect(transaction.name).toEqual('span name'); - - transaction.name = 'new name'; - - expect(transaction.name).toEqual('new name'); - expect(transaction.attributes['sentry.source']).toEqual('custom'); - }); - - it('allows to update the name via setName', () => { - const transaction = new Transaction({ name: 'span name' }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - expect(transaction.name).toEqual('span name'); - - transaction.setName('new name'); - - expect(transaction.name).toEqual('new name'); - expect(transaction.attributes['sentry.source']).toEqual('custom'); + expect(spanToJSON(transaction).description).toEqual('span name'); }); it('allows to update the name via updateName', () => { const transaction = new Transaction({ name: 'span name' }); transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - expect(transaction.name).toEqual('span name'); + expect(spanToJSON(transaction).description).toEqual('span name'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); transaction.updateName('new name'); - expect(transaction.name).toEqual('new name'); - expect(transaction.attributes['sentry.source']).toEqual('route'); + expect(spanToJSON(transaction).description).toEqual('new name'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); /* eslint-enable deprecation/deprecation */ }); diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index 5f5cbf054666..5ab053aa63df 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -66,7 +66,7 @@ export const instrumentRoutePerformance = (BaseRoute // eslint-disable-next-line @typescript-eslint/no-explicit-any const instrumentFunction = async any>( op: string, - description: string, + name: string, fn: X, args: Parameters, source: TransactionSource, @@ -77,7 +77,7 @@ export const instrumentRoutePerformance = (BaseRoute [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, op, - name: description, + name, }, () => { return fn(...args); diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index 74af33ab3642..f5753055bc1c 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -115,14 +115,7 @@ describe('Integration | Transactions', () => { server_name: expect.any(String), // spans are circular (they have a reference to the transaction), which leads to jest choking on this // instead we compare them in detail below - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'outer.tag': 'test value', @@ -270,14 +263,7 @@ describe('Integration | Transactions', () => { origin: 'auto.test', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value' }, timestamp: expect.any(Number), @@ -317,14 +303,7 @@ describe('Integration | Transactions', () => { origin: 'manual', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1b', - }), - expect.objectContaining({ - description: 'inner span 2b', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value b' }, timestamp: expect.any(Number), @@ -429,14 +408,7 @@ describe('Integration | Transactions', () => { origin: 'manual', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value' }, timestamp: expect.any(Number), @@ -473,14 +445,7 @@ describe('Integration | Transactions', () => { origin: 'manual', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1b', - }), - expect.objectContaining({ - description: 'inner span 2b', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value b' }, timestamp: expect.any(Number), @@ -565,14 +530,7 @@ describe('Integration | Transactions', () => { }), // spans are circular (they have a reference to the transaction), which leads to jest choking on this // instead we compare them in detail below - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), timestamp: expect.any(Number), transaction: 'test name', diff --git a/packages/node-experimental/test/sdk/api.test.ts b/packages/node-experimental/test/sdk/api.test.ts index 5465d288def9..6ad2d0700108 100644 --- a/packages/node-experimental/test/sdk/api.test.ts +++ b/packages/node-experimental/test/sdk/api.test.ts @@ -40,7 +40,7 @@ describe('withActiveSpan()', () => { expect(beforeSendTransaction).toHaveBeenCalledWith( expect.objectContaining({ transaction: 'inactive-span', - spans: expect.arrayContaining([expect.objectContaining({ description: 'child-span' })]), + spans: expect.arrayContaining([expect.any(Object)]), }), expect.anything(), ); diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 82f8737721a9..9ea2df9e696c 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -83,8 +83,7 @@ export const hapiTracingPlugin = { return startTransaction({ ...transactionContext, op: 'hapi.request', - name: request.route.path, - description: `${request.route.method} ${request.path}`, + name: `${request.route.method} ${request.path}`, }); }, ); diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 407f7da6a43d..8e9df91f6dc5 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -40,7 +40,7 @@ import { DEBUG_BUILD } from '../debug-build'; import { NODE_VERSION } from '../nodeVersion'; import type { NodeClientOptions } from '../types'; import type { RequestMethod, RequestMethodArgs, RequestOptions } from './utils/http'; -import { cleanSpanDescription, extractRawUrl, extractUrl, normalizeRequestArgs } from './utils/http'; +import { cleanSpanName, extractRawUrl, extractUrl, normalizeRequestArgs } from './utils/http'; interface TracingOptions { /** @@ -335,7 +335,7 @@ function _createWrappedRequestMethodFactory( parentSpan?.startChild({ op: 'http.client', origin: 'auto.http.node.http', - description: `${data['http.method']} ${data.url}`, + name: `${data['http.method']} ${data.url}`, data, }) : undefined; @@ -378,9 +378,7 @@ function _createWrappedRequestMethodFactory( if (res.statusCode) { setHttpStatus(requestSpan, res.statusCode); } - requestSpan.updateName( - cleanSpanDescription(spanToJSON(requestSpan).description || '', requestOptions, req) || '', - ); + requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || ''); requestSpan.end(); } }) @@ -393,9 +391,7 @@ function _createWrappedRequestMethodFactory( } if (requestSpan) { setHttpStatus(requestSpan, 500); - requestSpan.updateName( - cleanSpanDescription(spanToJSON(requestSpan).description || '', requestOptions, req) || '', - ); + requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || ''); requestSpan.end(); } }); diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index ed34a3fc0fb7..b76caf647fae 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -340,7 +340,7 @@ function createRequestSpan( return activeSpan?.startChild({ op: 'http.client', origin: 'auto.http.node.undici', - description: `${method} ${getSanitizedUrlString(url)}`, + name: `${method} ${getSanitizedUrlString(url)}`, data, }); } diff --git a/packages/node/src/integrations/utils/http.ts b/packages/node/src/integrations/utils/http.ts index 76a826801814..87982f5b3ba3 100644 --- a/packages/node/src/integrations/utils/http.ts +++ b/packages/node/src/integrations/utils/http.ts @@ -42,26 +42,26 @@ function redactAuthority(auth: string): string { } /** - * Handle various edge cases in the span description (for spans representing http(s) requests). + * Handle various edge cases in the span name (for spans representing http(s) requests). * - * @param description current `description` property of the span representing the request + * @param description current `name` property of the span representing the request * @param requestOptions Configuration data for the request * @param Request Request object * - * @returns The cleaned description + * @returns The cleaned name */ -export function cleanSpanDescription( - description: string | undefined, +export function cleanSpanName( + name: string | undefined, requestOptions: RequestOptions, request: http.ClientRequest, ): string | undefined { // nothing to clean - if (!description) { - return description; + if (!name) { + return name; } // eslint-disable-next-line prefer-const - let [method, requestUrl] = description.split(' '); + let [method, requestUrl] = name.split(' '); // superagent sticks the protocol in a weird place (we check for host because if both host *and* protocol are missing, // we're likely dealing with an internal route and this doesn't apply) diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index f64aa08a9129..0b24d182bdd2 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -2,6 +2,7 @@ import * as http from 'http'; import * as sentryCore from '@sentry/core'; import { Hub, + SEMANTIC_ATTRIBUTE_SENTRY_OP, Transaction, getClient, getCurrentScope, @@ -377,9 +378,9 @@ describe('tracingHandler', () => { const transaction = getCurrentScope().getTransaction(); expect(transaction).toBeDefined(); - expect(transaction).toEqual( - expect.objectContaining({ name: `${method.toUpperCase()} ${path}`, op: 'http.server' }), - ); + const transactionJson = spanToJSON(transaction as Transaction); + expect(transactionJson.description).toEqual(`${method.toUpperCase()} ${path}`); + expect(transactionJson.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]).toEqual('http.server'); }); it('puts its transaction on the response object', () => { @@ -388,9 +389,10 @@ describe('tracingHandler', () => { const transaction = (res as any).__sentry_transaction; expect(transaction).toBeDefined(); - expect(transaction).toEqual( - expect.objectContaining({ name: `${method.toUpperCase()} ${path}`, op: 'http.server' }), - ); + + const transactionJson = spanToJSON(transaction); + expect(transactionJson.description).toEqual(`${method.toUpperCase()} ${path}`); + expect(transactionJson.data?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]).toEqual('http.server'); }); it('pulls status code from the response', done => { @@ -422,7 +424,7 @@ describe('tracingHandler', () => { const transaction = (res as any).__sentry_transaction; - expect(transaction?.name).toBe(`${method.toUpperCase()} ${path}`); + expect(spanToJSON(transaction).description).toBe(`${method.toUpperCase()} ${path}`); }); it('strips fragment from request path', () => { @@ -432,7 +434,7 @@ describe('tracingHandler', () => { const transaction = (res as any).__sentry_transaction; - expect(transaction?.name).toBe(`${method.toUpperCase()} ${path}`); + expect(spanToJSON(transaction).description).toBe(`${method.toUpperCase()} ${path}`); }); it('strips query string and fragment from request path', () => { @@ -442,7 +444,7 @@ describe('tracingHandler', () => { const transaction = (res as any).__sentry_transaction; - expect(transaction?.name).toBe(`${method.toUpperCase()} ${path}`); + expect(spanToJSON(transaction).description).toBe(`${method.toUpperCase()} ${path}`); }); it('closes the transaction when request processing is done', done => { @@ -466,7 +468,7 @@ describe('tracingHandler', () => { transaction.initSpanRecorder(); // eslint-disable-next-line deprecation/deprecation const span = transaction.startChild({ - description: 'reallyCoolHandler', + name: 'reallyCoolHandler', op: 'middleware', }); jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as Transaction); diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 9bd28d33c713..3284e13436d8 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -7,6 +7,7 @@ import { getIsolationScope, getMainCarrier, setCurrentClient, + spanToJSON, startSpan, withIsolationScope, } from '@sentry/core'; @@ -129,7 +130,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { expect(spans.length).toBe(2); - const span = spans[1]; + const span = spanToJSON(spans[1]); expect(span).toEqual(expect.objectContaining(expected)); }); }); @@ -169,9 +170,9 @@ conditionalTest({ min: 16 })('Undici integration', () => { expect(spans.length).toBe(2); - const span = spans[1]; - expect(span).toEqual(expect.objectContaining({ description: 'GET http://a-url-that-no-exists.com//' })); - expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + const spanJson = spanToJSON(spans[1]); + expect(spanJson.description).toEqual('GET http://a-url-that-no-exists.com//'); + expect(spanJson.status).toEqual('internal_error'); }); }); diff --git a/packages/node/test/performance.test.ts b/packages/node/test/performance.test.ts index 513a3e95a7c0..3ae10bb1f4a3 100644 --- a/packages/node/test/performance.test.ts +++ b/packages/node/test/performance.test.ts @@ -1,6 +1,7 @@ import { setAsyncContextStrategy, setCurrentClient, + spanToJSON, startInactiveSpan, startSpan, startSpanManual, @@ -81,7 +82,7 @@ describe('startSpan()', () => { const transactionEvent = await transactionEventPromise; - expect(transactionEvent.spans).toContainEqual(expect.objectContaining({ description: 'second' })); + expect(spanToJSON(transactionEvent.spans?.[0] as any).description).toBe('second'); }); }); @@ -153,7 +154,7 @@ describe('startSpanManual()', () => { const transactionEvent = await transactionEventPromise; - expect(transactionEvent.spans).toContainEqual(expect.objectContaining({ description: 'second' })); + expect(spanToJSON(transactionEvent.spans?.[0] as any).description).toBe('second'); }); it('should use the scopes at time of creation instead of the scopes at time of termination', async () => { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index f79dc9f5b2e4..71a643a5b90b 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -66,7 +66,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { if (sentryParentSpan) { // eslint-disable-next-line deprecation/deprecation const sentryChildSpan = sentryParentSpan.startChild({ - description: otelSpan.name, + name: otelSpan.name, instrumenter: 'otel', startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), spanId: otelSpanId, diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index e8baeb17d2cc..2c3a4c677b39 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -209,7 +209,7 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, sentryParentSpan: Sentry // eslint-disable-next-line deprecation/deprecation const sentrySpan = sentryParentSpan.startChild({ - description, + name: description, op, data: allData, status: mapStatus(span), diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 8292152b420e..3ce365a6e886 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -114,14 +114,7 @@ describe('Integration | Transactions', () => { }), // spans are circular (they have a reference to the transaction), which leads to jest choking on this // instead we compare them in detail below - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'outer.tag': 'test value', @@ -261,14 +254,7 @@ describe('Integration | Transactions', () => { origin: 'auto.test', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value' }, timestamp: expect.any(Number), @@ -308,14 +294,7 @@ describe('Integration | Transactions', () => { origin: 'manual', }, }), - spans: [ - expect.objectContaining({ - description: 'inner span 1b', - }), - expect.objectContaining({ - description: 'inner span 2b', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), tags: { 'test.tag': 'test value b' }, timestamp: expect.any(Number), @@ -393,14 +372,7 @@ describe('Integration | Transactions', () => { }), // spans are circular (they have a reference to the transaction), which leads to jest choking on this // instead we compare them in detail below - spans: [ - expect.objectContaining({ - description: 'inner span 1', - }), - expect.objectContaining({ - description: 'inner span 2', - }), - ], + spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), timestamp: expect.any(Number), transaction: 'test name', diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 7eb30e46af12..76c181199541 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -196,7 +196,7 @@ function makeWrappedDocumentRequestFunction(remixVersion?: number) { const span = activeTransaction?.startChild({ op: 'function.remix.document_request', origin: 'auto.function.remix', - description: spanToJSON(activeTransaction).description, + name: spanToJSON(activeTransaction).description, tags: { method: request.method, url: request.url, @@ -253,7 +253,7 @@ function makeWrappedDataFunction( const span = activeTransaction?.startChild({ op: `function.remix.${name}`, origin: 'auto.ui.remix', - description: id, + name: id, tags: { name, }, diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index d2e23b53daba..8cc3e86017ed 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -51,7 +51,7 @@ function recordInitSpan(transaction: Transaction, componentName: string): Span { // eslint-disable-next-line deprecation/deprecation const initSpan = transaction.startChild({ op: UI_SVELTE_INIT, - description: componentName, + name: componentName, origin: 'auto.ui.svelte', }); @@ -80,7 +80,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void { // eslint-disable-next-line deprecation/deprecation updateSpan = parentSpan.startChild({ op: UI_SVELTE_UPDATE, - description: componentName, + name: componentName, origin: 'auto.ui.svelte', }); }); diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index bdede73bc092..1d90b0b9ab79 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -59,13 +59,13 @@ describe('Sentry.trackComponent()', () => { render(DummyComponent, { props: { options: {} } }); expect(testTransaction.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.init', origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', }); @@ -92,7 +92,7 @@ describe('Sentry.trackComponent()', () => { // once for init (unimportant here), once for starting the update span expect(testTransaction.startChild).toHaveBeenCalledTimes(2); expect(testTransaction.startChild).toHaveBeenLastCalledWith({ - description: '', + name: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', }); @@ -103,7 +103,7 @@ describe('Sentry.trackComponent()', () => { render(DummyComponent, { props: { options: { trackUpdates: false } } }); expect(testTransaction.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.init', origin: 'auto.ui.svelte', }); @@ -118,7 +118,7 @@ describe('Sentry.trackComponent()', () => { render(DummyComponent, { props: { options: { trackInit: false } } }); expect(testTransaction.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', }); @@ -137,19 +137,19 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.spans.length).toEqual(0); }); - it('sets a custom component name as a span description if `componentName` is provided', async () => { + it('sets a custom component name as a span name if `componentName` is provided', async () => { render(DummyComponent, { props: { options: { componentName: 'CustomComponentName' } }, }); expect(testTransaction.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.init', origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).toHaveBeenCalledWith({ - description: '', + name: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', }); @@ -188,7 +188,7 @@ describe('Sentry.trackComponent()', () => { // but not the second update expect(testTransaction.startChild).toHaveBeenCalledTimes(1); expect(testTransaction.startChild).toHaveBeenLastCalledWith({ - description: '', + name: '', op: 'ui.svelte.init', origin: 'auto.ui.svelte', }); diff --git a/packages/sveltekit/src/client/browserTracingIntegration.ts b/packages/sveltekit/src/client/browserTracingIntegration.ts index 07de01e86c11..ab727e9d76f8 100644 --- a/packages/sveltekit/src/client/browserTracingIntegration.ts +++ b/packages/sveltekit/src/client/browserTracingIntegration.ts @@ -67,7 +67,6 @@ function _instrumentPageload(client: Client): void { const pageloadSpan = startBrowserTracingPageLoadSpan(client, { name: initialPath, op: 'pageload', - description: initialPath, tags: { 'routing.instrumentation': '@sentry/sveltekit', }, diff --git a/packages/sveltekit/src/client/router.ts b/packages/sveltekit/src/client/router.ts index 593eeb97b1a2..cb38aad2ea3a 100644 --- a/packages/sveltekit/src/client/router.ts +++ b/packages/sveltekit/src/client/router.ts @@ -42,7 +42,6 @@ function instrumentPageload(startTransactionFn: (context: TransactionContext) => name: initialPath, op: 'pageload', origin: 'auto.pageload.sveltekit', - description: initialPath, tags: { ...DEFAULT_TAGS, }, @@ -124,7 +123,7 @@ function instrumentNavigations(startTransactionFn: (context: TransactionContext) // eslint-disable-next-line deprecation/deprecation routingSpan = activeTransaction.startChild({ op: 'ui.sveltekit.routing', - description: 'SvelteKit Route Change', + name: 'SvelteKit Route Change', origin: 'auto.ui.sveltekit', }); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/sveltekit/test/client/browserTracingIntegration.test.ts b/packages/sveltekit/test/client/browserTracingIntegration.test.ts index fba7136bd5eb..155f8f569f2d 100644 --- a/packages/sveltekit/test/client/browserTracingIntegration.test.ts +++ b/packages/sveltekit/test/client/browserTracingIntegration.test.ts @@ -115,7 +115,6 @@ describe('browserTracingIntegration', () => { expect(startBrowserTracingPageLoadSpanSpy).toHaveBeenCalledWith(fakeClient, { name: '/', op: 'pageload', - description: '/', tags: { 'routing.instrumentation': '@sentry/sveltekit', }, diff --git a/packages/sveltekit/test/client/router.test.ts b/packages/sveltekit/test/client/router.test.ts index a359a9dedbf0..ab8455c5c4e2 100644 --- a/packages/sveltekit/test/client/router.test.ts +++ b/packages/sveltekit/test/client/router.test.ts @@ -57,7 +57,6 @@ describe('sveltekitRoutingInstrumentation', () => { name: '/', op: 'pageload', origin: 'auto.pageload.sveltekit', - description: '/', tags: { 'routing.instrumentation': '@sentry/sveltekit', }, @@ -121,7 +120,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ op: 'ui.sveltekit.routing', origin: 'auto.ui.sveltekit', - description: 'SvelteKit Route Change', + name: 'SvelteKit Route Change', }); // eslint-disable-next-line deprecation/deprecation @@ -171,7 +170,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ op: 'ui.sveltekit.routing', origin: 'auto.ui.sveltekit', - description: 'SvelteKit Route Change', + name: 'SvelteKit Route Change', }); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 019eab5992d0..efd96107e651 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions, spanToJSON } from '@sentry/core'; import { NodeClient, setCurrentClient } from '@sentry/node'; import * as SentryNode from '@sentry/node'; import type { Transaction } from '@sentry/types'; @@ -130,8 +130,8 @@ describe('handleSentry', () => { expect(ref).toBeDefined(); - expect(ref.name).toEqual('GET /users/[id]'); - expect(ref.op).toEqual('http.server'); + expect(spanToJSON(ref).description).toEqual('GET /users/[id]'); + expect(spanToJSON(ref).op).toEqual('http.server'); expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); @@ -166,17 +166,18 @@ describe('handleSentry', () => { expect(txnCount).toEqual(1); expect(ref).toBeDefined(); - expect(ref.name).toEqual('GET /users/[id]'); - expect(ref.op).toEqual('http.server'); + expect(spanToJSON(ref).description).toEqual('GET /users/[id]'); + expect(spanToJSON(ref).op).toEqual('http.server'); expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); expect(ref.endTimestamp).toBeDefined(); expect(ref.spanRecorder.spans).toHaveLength(2); - expect(ref.spanRecorder.spans).toEqual( + const spans = ref.spanRecorder.spans.map(spanToJSON); + expect(spans).toEqual( expect.arrayContaining([ - expect.objectContaining({ op: 'http.server', name: 'GET /users/[id]' }), + expect.objectContaining({ op: 'http.server', description: 'GET /users/[id]' }), expect.objectContaining({ op: 'http.server', description: 'GET api/users/details/[id]' }), ]), ); diff --git a/packages/tracing-internal/src/browser/metrics/index.ts b/packages/tracing-internal/src/browser/metrics/index.ts index 4c9c25111e11..114ac74bd4e9 100644 --- a/packages/tracing-internal/src/browser/metrics/index.ts +++ b/packages/tracing-internal/src/browser/metrics/index.ts @@ -81,7 +81,7 @@ export function startTrackingLongTasks(): void { // eslint-disable-next-line deprecation/deprecation transaction.startChild({ - description: 'Main UI thread blocked', + name: 'Main UI thread blocked', op: 'ui.long-task', origin: 'auto.ui.browser.metrics', startTimestamp: startTime, @@ -108,7 +108,7 @@ export function startTrackingInteractions(): void { const duration = msToSec(entry.duration); const span: SpanContext = { - description: htmlTreeAsString(entry.target), + name: htmlTreeAsString(entry.target), op: `ui.interaction.${entry.name}`, origin: 'auto.ui.browser.metrics', startTimestamp: startTime, @@ -265,7 +265,7 @@ export function addPerformanceEntries(transaction: Transaction): void { if (fidMark && _measurements['fid']) { // create span for FID _startChild(transaction, { - description: 'first input delay', + name: 'first input delay', endTimestamp: fidMark.value + msToSec(_measurements['fid'].value), op: 'ui.action', origin: 'auto.ui.browser.metrics', @@ -307,7 +307,7 @@ export function _addMeasureSpans( const measureEndTimestamp = measureStartTimestamp + duration; _startChild(transaction, { - description: entry.name as string, + name: entry.name as string, endTimestamp: measureEndTimestamp, op: entry.entryType as string, origin: 'auto.resource.browser.metrics', @@ -336,7 +336,7 @@ function _addPerformanceNavigationTiming( entry: Record, event: string, timeOrigin: number, - description?: string, + name?: string, eventEnd?: string, ): void { const end = eventEnd ? (entry[eventEnd] as number | undefined) : (entry[`${event}End`] as number | undefined); @@ -347,7 +347,7 @@ function _addPerformanceNavigationTiming( _startChild(transaction, { op: 'browser', origin: 'auto.browser.browser.metrics', - description: description || event, + name: name || event, startTimestamp: timeOrigin + msToSec(start), endTimestamp: timeOrigin + msToSec(end), }); @@ -364,7 +364,7 @@ function _addRequest(transaction: Transaction, entry: Record, timeO _startChild(transaction, { op: 'browser', origin: 'auto.browser.browser.metrics', - description: 'request', + name: 'request', startTimestamp: timeOrigin + msToSec(entry.requestStart as number), endTimestamp: timeOrigin + msToSec(entry.responseEnd as number), }); @@ -372,7 +372,7 @@ function _addRequest(transaction: Transaction, entry: Record, timeO _startChild(transaction, { op: 'browser', origin: 'auto.browser.browser.metrics', - description: 'response', + name: 'response', startTimestamp: timeOrigin + msToSec(entry.responseStart as number), endTimestamp: timeOrigin + msToSec(entry.responseEnd as number), }); @@ -427,7 +427,7 @@ export function _addResourceSpans( const endTimestamp = startTimestamp + duration; _startChild(transaction, { - description: resourceUrl.replace(WINDOW.location.origin, ''), + name: resourceUrl.replace(WINDOW.location.origin, ''), endTimestamp, op: entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other', origin: 'auto.resource.browser.metrics', diff --git a/packages/tracing-internal/src/node/integrations/apollo.ts b/packages/tracing-internal/src/node/integrations/apollo.ts index 280d958126f7..85029943cbaf 100644 --- a/packages/tracing-internal/src/node/integrations/apollo.ts +++ b/packages/tracing-internal/src/node/integrations/apollo.ts @@ -194,7 +194,7 @@ function wrapResolver( const parentSpan = scope.getSpan(); // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ - description: `${resolverGroupName}.${resolverName}`, + name: `${resolverGroupName}.${resolverName}`, op: 'graphql.resolve', origin: 'auto.graphql.apollo', }); diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index 617a7d981cf4..04f3e55dec68 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -160,7 +160,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { if (transaction) { // eslint-disable-next-line deprecation/deprecation const span = transaction.startChild({ - description: fn.name, + name: fn.name, op: `middleware.express.${method}`, origin: 'auto.middleware.express', }); @@ -181,7 +181,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { const transaction = res.__sentry_transaction; // eslint-disable-next-line deprecation/deprecation const span = transaction?.startChild({ - description: fn.name, + name: fn.name, op: `middleware.express.${method}`, origin: 'auto.middleware.express', }); @@ -202,7 +202,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { const transaction = res.__sentry_transaction; // eslint-disable-next-line deprecation/deprecation const span = transaction?.startChild({ - description: fn.name, + name: fn.name, op: `middleware.express.${method}`, origin: 'auto.middleware.express', }); diff --git a/packages/tracing-internal/src/node/integrations/graphql.ts b/packages/tracing-internal/src/node/integrations/graphql.ts index 148329dbd86d..4ce7476a45ac 100644 --- a/packages/tracing-internal/src/node/integrations/graphql.ts +++ b/packages/tracing-internal/src/node/integrations/graphql.ts @@ -58,7 +58,7 @@ export class GraphQL implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ - description: 'execute', + name: 'execute', op: 'graphql.execute', origin: 'auto.graphql.graphql', }); diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index 27646e12b6bf..cd5779eca5c0 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -250,7 +250,7 @@ export class Mongo implements LazyLoadedIntegration { op: 'db', // TODO v8: Use `${collection.collectionName}.${operation}` origin: 'auto.db.mongo', - description: operation, + name: operation, data, }; diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index c7bf5451e5f5..09d3f45932eb 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -110,7 +110,7 @@ export class Mysql implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ - description: typeof options === 'string' ? options : (options as { sql: string }).sql, + name: typeof options === 'string' ? options : (options as { sql: string }).sql, op: 'db', origin: 'auto.db.mysql', data: { diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts index 086cebf84ee5..06b5eb7a76e9 100644 --- a/packages/tracing-internal/src/node/integrations/postgres.ts +++ b/packages/tracing-internal/src/node/integrations/postgres.ts @@ -132,7 +132,7 @@ export class Postgres implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ - description: typeof config === 'string' ? config : (config as { text: string }).text, + name: typeof config === 'string' ? config : (config as { text: string }).text, op: 'db', origin: 'auto.db.postgres', data, diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index fb93661607aa..f57c3c78f1ec 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -175,7 +175,7 @@ describe('BrowserTracing', () => { const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); - expect(transaction.name).toBe('a/path'); + expect(spanToJSON(transaction).description).toBe('a/path'); expect(transaction.op).toBe('pageload'); }); @@ -246,8 +246,8 @@ describe('BrowserTracing', () => { }); const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); - expect(transaction.name).toBe('newName'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); + expect(spanToJSON(transaction).description).toBe('newName'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); }); @@ -268,8 +268,8 @@ describe('BrowserTracing', () => { }); const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); - expect(transaction.name).toBe('a/path'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); + expect(spanToJSON(transaction).description).toBe('a/path'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); }); diff --git a/packages/tracing-internal/test/browser/metrics/index.test.ts b/packages/tracing-internal/test/browser/metrics/index.test.ts index e405760fbcc7..abeddad7c46c 100644 --- a/packages/tracing-internal/test/browser/metrics/index.test.ts +++ b/packages/tracing-internal/test/browser/metrics/index.test.ts @@ -50,7 +50,7 @@ describe('_addMeasureSpans', () => { expect(transaction.startChild).toHaveBeenCalledTimes(1); // eslint-disable-next-line @typescript-eslint/unbound-method, deprecation/deprecation expect(transaction.startChild).toHaveBeenLastCalledWith({ - description: 'measure-1', + name: 'measure-1', startTimestamp: timeOrigin + startTime, endTimestamp: timeOrigin + startTime + duration, op: 'measure', @@ -133,7 +133,7 @@ describe('_addResourceSpans', () => { ['server.address']: 'example.com', ['url.same_origin']: true, }, - description: '/assets/to/css', + name: '/assets/to/css', endTimestamp: timeOrigin + startTime + duration, op: 'resource.css', origin: 'auto.resource.browser.metrics', @@ -225,7 +225,7 @@ describe('_addResourceSpans', () => { expect(transaction.startChild).toHaveBeenLastCalledWith( expect.objectContaining({ data: { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https' }, - description: '/assets/to/css', + name: '/assets/to/css', endTimestamp: 468, op: 'resource.css', origin: 'auto.resource.browser.metrics', @@ -252,7 +252,7 @@ describe('_addResourceSpans', () => { expect(transaction.startChild).toHaveBeenLastCalledWith( expect.objectContaining({ data: { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https' }, - description: '/assets/to/css', + name: '/assets/to/css', endTimestamp: 468, op: 'resource.css', origin: 'auto.resource.browser.metrics', diff --git a/packages/tracing-internal/test/browser/metrics/utils.test.ts b/packages/tracing-internal/test/browser/metrics/utils.test.ts index 1d6a5621296f..a7de3e37acf8 100644 --- a/packages/tracing-internal/test/browser/metrics/utils.test.ts +++ b/packages/tracing-internal/test/browser/metrics/utils.test.ts @@ -6,7 +6,7 @@ describe('_startChild()', () => { // eslint-disable-next-line deprecation/deprecation const transaction = new Transaction({ name: 'test' }); const span = _startChild(transaction, { - description: 'evaluation', + name: 'evaluation', op: 'script', }); @@ -21,7 +21,7 @@ describe('_startChild()', () => { // eslint-disable-next-line deprecation/deprecation const transaction = new Transaction({ name: 'test', startTimestamp: 123 }); const span = _startChild(transaction, { - description: 'script.js', + name: 'script.js', op: 'resource', startTimestamp: 100, }); @@ -34,7 +34,7 @@ describe('_startChild()', () => { // eslint-disable-next-line deprecation/deprecation const transaction = new Transaction({ name: 'test', startTimestamp: 123 }); const span = _startChild(transaction, { - description: 'script.js', + name: 'script.js', op: 'resource', startTimestamp: 150, }); diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts index 7761b44f39c5..dbb9a4841c9c 100644 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ b/packages/tracing/test/integrations/apollo-nestjs.test.ts @@ -92,7 +92,7 @@ describe('setupOnce', () => { GraphQLFactoryInstance._resolvers[0]?.['Query']?.['res_1']?.(); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'Query.res_1', + name: 'Query.res_1', op: 'graphql.resolve', origin: 'auto.graphql.apollo', }); @@ -103,7 +103,7 @@ describe('setupOnce', () => { GraphQLFactoryInstance._resolvers[0]?.['Mutation']?.['res_2']?.(); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'Mutation.res_2', + name: 'Mutation.res_2', op: 'graphql.resolve', origin: 'auto.graphql.apollo', }); diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index 75b905a77a41..305acdcee186 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -92,7 +92,7 @@ describe('setupOnce', () => { ApolloServer.config.resolvers[0]?.['Query']?.['res_1']?.(); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'Query.res_1', + name: 'Query.res_1', op: 'graphql.resolve', origin: 'auto.graphql.apollo', }); @@ -103,7 +103,7 @@ describe('setupOnce', () => { ApolloServer.config.resolvers[0]?.['Mutation']?.['res_2']?.(); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'Mutation.res_2', + name: 'Mutation.res_2', op: 'graphql.resolve', origin: 'auto.graphql.apollo', }); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index e3ef5361e01b..2b3c3aa7e307 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -54,7 +54,7 @@ describe('setupOnce', () => { await GQLExecute.execute(); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'execute', + name: 'execute', op: 'graphql.execute', origin: 'auto.graphql.graphql', }); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index e1cee110e7b9..d6baafa71e7c 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -84,7 +84,7 @@ describe('patchOperation()', () => { }, op: 'db', origin: 'auto.db.mongo', - description: 'insertOne', + name: 'insertOne', }); expect(childSpan.end).toBeCalled(); done(); @@ -103,7 +103,7 @@ describe('patchOperation()', () => { }, op: 'db', origin: 'auto.db.mongo', - description: 'insertOne', + name: 'insertOne', }); expect(childSpan.end).toBeCalled(); }); @@ -122,7 +122,7 @@ describe('patchOperation()', () => { }, op: 'db', origin: 'auto.db.mongo', - description: 'insertOne', + name: 'insertOne', }); expect(childSpan.end).toBeCalled(); }); @@ -139,7 +139,7 @@ describe('patchOperation()', () => { }, op: 'db', origin: 'auto.db.mongo', - description: 'initializeOrderedBulkOp', + name: 'initializeOrderedBulkOp', }); expect(childSpan.end).toBeCalled(); }); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index c3f528d117b0..0608fbaf85ad 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -75,7 +75,7 @@ describe('setupOnce', () => { Client.query('SELECT NOW()', {}, function () { expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'SELECT NOW()', + name: 'SELECT NOW()', op: 'db', origin: 'auto.db.postgres', data: { @@ -91,7 +91,7 @@ describe('setupOnce', () => { Client.query('SELECT NOW()', function () { expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'SELECT NOW()', + name: 'SELECT NOW()', op: 'db', origin: 'auto.db.postgres', data: { @@ -107,7 +107,7 @@ describe('setupOnce', () => { await Client.query('SELECT NOW()', null); expect(scope.getSpan).toBeCalled(); expect(parentSpan.startChild).toBeCalledWith({ - description: 'SELECT NOW()', + name: 'SELECT NOW()', op: 'db', origin: 'auto.db.postgres', data: { diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index b8a96e13c6f8..03b286b23c12 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -102,9 +102,9 @@ describe('SentrySpan', () => { test('setName', () => { const span = new SentrySpan({}); - expect(span.description).toBeUndefined(); + expect(spanToJSON(span).description).toBeUndefined(); span.updateName('foo'); - expect(span.description).toBe('foo'); + expect(spanToJSON(span).description).toBe('foo'); }); }); @@ -464,7 +464,7 @@ describe('SentrySpan', () => { traceId: 'a', spanId: 'b', sampled: false, - description: 'test', + name: 'test', op: 'op', }; const span = new SentrySpan(originalContext); @@ -489,7 +489,7 @@ describe('SentrySpan', () => { traceId: 'a', spanId: 'b', sampled: false, - description: 'test', + name: 'test', op: 'op', tags: { tag0: 'hello', @@ -506,7 +506,7 @@ describe('SentrySpan', () => { expect(span.spanContext().traceId).toBe('c'); expect(span.spanContext().spanId).toBe('d'); expect(span.sampled).toBe(true); - expect(span.description).toBe(undefined); + expect(spanToJSON(span).description).toBe(undefined); expect(span.op).toBe(undefined); expect(span.tags).toStrictEqual({}); }); @@ -516,7 +516,7 @@ describe('SentrySpan', () => { traceId: 'a', spanId: 'b', sampled: false, - description: 'test', + name: 'test', op: 'op', tags: { tag0: 'hello' }, data: { data0: 'foo' }, @@ -525,7 +525,7 @@ describe('SentrySpan', () => { const newContext = { ...span.toContext(), - description: 'new', + name: 'new', endTimestamp: 1, op: 'new-op', sampled: true, @@ -543,7 +543,7 @@ describe('SentrySpan', () => { expect(span.spanContext().traceId).toBe('a'); expect(span.spanContext().spanId).toBe('b'); - expect(span.description).toBe('new'); + expect(spanToJSON(span).description).toBe('new'); expect(spanToJSON(span).timestamp).toBe(1); expect(span.op).toBe('new-op'); expect(span.sampled).toBe(true); diff --git a/packages/tracing/test/transaction.test.ts b/packages/tracing/test/transaction.test.ts index 55d162becc21..43f5a4d76544 100644 --- a/packages/tracing/test/transaction.test.ts +++ b/packages/tracing/test/transaction.test.ts @@ -4,6 +4,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + spanToJSON, } from '@sentry/core'; import { Transaction, addExtensionMethods } from '../src'; @@ -21,23 +22,23 @@ describe('`Transaction` class', () => { attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' }, }); - expect(transaction.name).toEqual('dogpark'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); + expect(spanToJSON(transaction).description).toEqual('dogpark'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); }); it("sets source to be `'custom'` in constructor if not provided", () => { const transaction = new Transaction({ name: 'dogpark' }); - expect(transaction.name).toEqual('dogpark'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); + expect(spanToJSON(transaction).description).toEqual('dogpark'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); }); it("sets source to `'custom'` when assigning to `name` property", () => { const transaction = new Transaction({ name: 'dogpark' }); - transaction.name = 'ballpit'; + transaction.updateName('ballpit'); - expect(transaction.name).toEqual('ballpit'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); + expect(spanToJSON(transaction).description).toEqual('ballpit'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); it('sets instrumenter to be `sentry` in constructor if not provided', () => { @@ -52,32 +53,22 @@ describe('`Transaction` class', () => { expect(transaction.instrumenter).toEqual('otel'); }); - describe('`setName` method', () => { + describe('`updateName` method', () => { it("sets source to `'custom'` if no source provided", () => { const transaction = new Transaction({ name: 'dogpark' }); - transaction.setName('ballpit'); + transaction.updateName('ballpit'); - expect(transaction.name).toEqual('ballpit'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); + expect(spanToJSON(transaction).description).toEqual('ballpit'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); it('uses given `source` value', () => { const transaction = new Transaction({ name: 'dogpark' }); - transaction.setName('ballpit', 'route'); - - expect(transaction.name).toEqual('ballpit'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - }); - }); - - describe('`updateName` method', () => { - it('does not change the source', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); transaction.updateName('ballpit'); + transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - expect(transaction.name).toEqual('ballpit'); - expect(transaction.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); + expect(spanToJSON(transaction).description).toEqual('ballpit'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); }); }); }); diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 008f668e2b7a..c6ab48e4ed7a 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -100,14 +100,7 @@ export interface SpanContextData { /** Interface holding all properties that can be set on a Span on creation. */ export interface SpanContext { /** - * Description of the Span. - * - * @deprecated Use `name` instead. - */ - description?: string | undefined; - - /** - * Human-readable identifier for the span. Alias for span.description. + * Human-readable identifier for the span. */ name?: string | undefined; @@ -181,13 +174,7 @@ export interface SpanContext { } /** Span holding trace_id, span_id */ -export interface Span extends Omit { - /** - * Human-readable identifier for the span. Identical to span.description. - * @deprecated Use `spanToJSON(span).description` instead. - */ - name: string; - +export interface Span extends Omit { /** * Operation of the Span. * @@ -345,13 +332,6 @@ export interface Span extends Omit { */ setHttpStatus(httpStatus: number): this; - /** - * Set the name of the span. - * - * @deprecated Use `updateName()` instead. - */ - setName(name: string): void; - /** * Update the name of the span. */ diff --git a/packages/types/src/startSpanOptions.ts b/packages/types/src/startSpanOptions.ts index 57ff96b3169f..31d57c39f50d 100644 --- a/packages/types/src/startSpanOptions.ts +++ b/packages/types/src/startSpanOptions.ts @@ -47,12 +47,6 @@ export interface StartSpanOptions extends TransactionContext { */ metadata?: Partial; - /** - * The name thingy. - * @deprecated Use `name` instead. - */ - description?: string; - /** * @deprecated Use `span.setStatus()` instead. */ diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index df22d28de7fc..f129cfebb3de 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -43,13 +43,7 @@ export type TraceparentData = Pick { - /** - * Human-readable identifier for the transaction. - * @deprecated Use `spanToJSON(span).description` instead. - */ - name: string; - +export interface Transaction extends Omit, Span { /** * The ID of the transaction. * @deprecated Use `spanContext().spanId` instead. @@ -104,13 +98,6 @@ export interface Transaction extends TransactionContext, Omit Date: Tue, 20 Feb 2024 20:12:15 -0500 Subject: [PATCH 117/173] feat(v8/browser): Remove XHR transport (#10703) resolves https://github.com/getsentry/sentry-javascript/issues/5927 Removes `makeXHRTransport` and makes the fetch transport the default one. I've also added a warning warning to browser client init that states that the sentry browser sdk requires the fetch API to function. --- MIGRATION.md | 5 + .../errors/errorModeCustomTransport/init.js | 2 +- packages/browser/src/exports.ts | 2 +- packages/browser/src/sdk.ts | 11 ++- packages/browser/src/transports/fetch.ts | 7 +- packages/browser/src/transports/index.ts | 2 - packages/browser/src/transports/utils.ts | 10 +- packages/browser/src/transports/xhr.ts | 52 ---------- .../browser/test/unit/transports/xhr.test.ts | 99 ------------------- .../test/integration/VueIntegration.test.ts | 29 +++++- 10 files changed, 57 insertions(+), 162 deletions(-) delete mode 100644 packages/browser/src/transports/index.ts delete mode 100644 packages/browser/src/transports/xhr.ts delete mode 100644 packages/browser/test/unit/transports/xhr.test.ts diff --git a/MIGRATION.md b/MIGRATION.md index 1e307d7d47fe..fd2e4cf6ccfd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -90,6 +90,11 @@ to access and mutate the current scope. `@sentry/hub` has been removed. All exports from `@sentry.hub` should be available in `@sentry/core`. +## Removal of `makeXHRTransport` transport (#10703) + +The `makeXHRTransport` transport has been removed. Only `makeFetchTransport` is available now. This means that the +Sentry SDK requires the fetch API to be available in the environment. + ## General API Changes - The minumum supported Node version for all the SDK packages is Node 14 (#10527) diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js b/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js index acf3b91c0dd3..934c6f5bca6a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js @@ -14,7 +14,7 @@ Sentry.init({ replaysOnErrorSampleRate: 1.0, integrations: [window.Replay], transport: options => { - const transport = new Sentry.makeXHRTransport(options); + const transport = new Sentry.makeFetchTransport(options); delete transport.send.__sentry__baseTransport__; diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index a7bf47cd5a0f..7083d71ce05a 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -82,7 +82,7 @@ export * from './metrics'; export { WINDOW } from './helpers'; export { BrowserClient } from './client'; -export { makeFetchTransport, makeXHRTransport } from './transports'; +export { makeFetchTransport } from './transports/fetch'; export { defaultStackParser, defaultStackLineParsers, diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 0cad32a06474..dd7c08f77e56 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -27,7 +27,7 @@ import { httpContextIntegration } from './integrations/httpcontext'; import { linkedErrorsIntegration } from './integrations/linkederrors'; import { browserApiErrorsIntegration } from './integrations/trycatch'; import { defaultStackParser } from './stack-parsers'; -import { makeFetchTransport, makeXHRTransport } from './transports'; +import { makeFetchTransport } from './transports/fetch'; /** Get the default integrations for the browser SDK. */ export function getDefaultIntegrations(_options: Options): Integration[] { @@ -116,11 +116,18 @@ export function init(options: BrowserOptions = {}): void { options.sendClientReports = true; } + if (DEBUG_BUILD) { + if (!supportsFetch()) { + logger.warn( + 'No Fetch API detected. The Sentry SDK requires a Fetch API compatible environment to send events. Please add a Fetch API polyfill.', + ); + } + } const clientOptions: BrowserClientOptions = { ...options, stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser), integrations: getIntegrationsToSetup(options), - transport: options.transport || (supportsFetch() ? makeFetchTransport : makeXHRTransport), + transport: options.transport || makeFetchTransport, }; initAndBind(BrowserClient, clientOptions); diff --git a/packages/browser/src/transports/fetch.ts b/packages/browser/src/transports/fetch.ts index e996b6f8277a..305afb9fc0ec 100644 --- a/packages/browser/src/transports/fetch.ts +++ b/packages/browser/src/transports/fetch.ts @@ -11,7 +11,7 @@ import { clearCachedFetchImplementation, getNativeFetchImplementation } from './ */ export function makeFetchTransport( options: BrowserTransportOptions, - nativeFetch: FetchImpl = getNativeFetchImplementation(), + nativeFetch: FetchImpl | undefined = getNativeFetchImplementation(), ): Transport { let pendingBodySize = 0; let pendingCount = 0; @@ -41,6 +41,11 @@ export function makeFetchTransport( ...options.fetchOptions, }; + if (!nativeFetch) { + clearCachedFetchImplementation(); + return rejectedSyncPromise('No fetch implementation available'); + } + try { return nativeFetch(options.url, requestOptions).then(response => { pendingBodySize -= requestSize; diff --git a/packages/browser/src/transports/index.ts b/packages/browser/src/transports/index.ts deleted file mode 100644 index c30287e3e616..000000000000 --- a/packages/browser/src/transports/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { makeFetchTransport } from './fetch'; -export { makeXHRTransport } from './xhr'; diff --git a/packages/browser/src/transports/utils.ts b/packages/browser/src/transports/utils.ts index 6b42ae77b480..053a0d0dd483 100644 --- a/packages/browser/src/transports/utils.ts +++ b/packages/browser/src/transports/utils.ts @@ -45,7 +45,7 @@ export type FetchImpl = typeof fetch; * Firefox: NetworkError when attempting to fetch resource * Safari: resource blocked by content blocker */ -export function getNativeFetchImplementation(): FetchImpl { +export function getNativeFetchImplementation(): FetchImpl | undefined { if (cachedFetchImpl) { return cachedFetchImpl; } @@ -75,7 +75,13 @@ export function getNativeFetchImplementation(): FetchImpl { } } - return (cachedFetchImpl = fetchImpl.bind(WINDOW)); + try { + return (cachedFetchImpl = fetchImpl.bind(WINDOW)); + } catch (e) { + // empty + } + + return undefined; /* eslint-enable @typescript-eslint/unbound-method */ } diff --git a/packages/browser/src/transports/xhr.ts b/packages/browser/src/transports/xhr.ts deleted file mode 100644 index 8fae5dc8067e..000000000000 --- a/packages/browser/src/transports/xhr.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { createTransport } from '@sentry/core'; -import type { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; -import { SyncPromise } from '@sentry/utils'; - -import type { BrowserTransportOptions } from './types'; - -/** - * The DONE ready state for XmlHttpRequest - * - * Defining it here as a constant b/c XMLHttpRequest.DONE is not always defined - * (e.g. during testing, it is `undefined`) - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState} - */ -const XHR_READYSTATE_DONE = 4; - -/** - * Creates a Transport that uses the XMLHttpRequest API to send events to Sentry. - */ -export function makeXHRTransport(options: BrowserTransportOptions): Transport { - function makeRequest(request: TransportRequest): PromiseLike { - return new SyncPromise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - - xhr.onerror = reject; - - xhr.onreadystatechange = (): void => { - if (xhr.readyState === XHR_READYSTATE_DONE) { - resolve({ - statusCode: xhr.status, - headers: { - 'x-sentry-rate-limits': xhr.getResponseHeader('X-Sentry-Rate-Limits'), - 'retry-after': xhr.getResponseHeader('Retry-After'), - }, - }); - } - }; - - xhr.open('POST', options.url); - - for (const header in options.headers) { - if (Object.prototype.hasOwnProperty.call(options.headers, header)) { - xhr.setRequestHeader(header, options.headers[header]); - } - } - - xhr.send(request.body); - }); - } - - return createTransport(options, makeRequest); -} diff --git a/packages/browser/test/unit/transports/xhr.test.ts b/packages/browser/test/unit/transports/xhr.test.ts deleted file mode 100644 index 5f969a4f9205..000000000000 --- a/packages/browser/test/unit/transports/xhr.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { EventEnvelope, EventItem } from '@sentry/types'; -import { createEnvelope, serializeEnvelope } from '@sentry/utils'; - -import type { BrowserTransportOptions } from '../../../src/transports/types'; -import { makeXHRTransport } from '../../../src/transports/xhr'; - -const DEFAULT_XHR_TRANSPORT_OPTIONS: BrowserTransportOptions = { - url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7', - recordDroppedEvent: () => undefined, -}; - -const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ - [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem, -]); - -function createXHRMock() { - const retryAfterSeconds = 10; - - const xhrMock: Partial = { - open: jest.fn(), - send: jest.fn(), - setRequestHeader: jest.fn(), - readyState: 4, - status: 200, - response: 'Hello World!', - onreadystatechange: () => {}, - getResponseHeader: jest.fn((header: string) => { - switch (header) { - case 'Retry-After': - return '10'; - case `${retryAfterSeconds}`: - return null; - default: - return `${retryAfterSeconds}:error:scope`; - } - }), - }; - - // casting `window` as `any` because XMLHttpRequest is missing in Window (TS-only) - jest.spyOn(window as any, 'XMLHttpRequest').mockImplementation(() => xhrMock as XMLHttpRequest); - - return xhrMock; -} - -describe('NewXHRTransport', () => { - const xhrMock: Partial = createXHRMock(); - - afterEach(() => { - jest.clearAllMocks(); - }); - - afterAll(() => { - jest.restoreAllMocks(); - }); - - it('makes an XHR request to the given URL', async () => { - const transport = makeXHRTransport(DEFAULT_XHR_TRANSPORT_OPTIONS); - expect(xhrMock.open).toHaveBeenCalledTimes(0); - expect(xhrMock.setRequestHeader).toHaveBeenCalledTimes(0); - expect(xhrMock.send).toHaveBeenCalledTimes(0); - - await Promise.all([transport.send(ERROR_ENVELOPE), (xhrMock as XMLHttpRequest).onreadystatechange!({} as Event)]); - - expect(xhrMock.open).toHaveBeenCalledTimes(1); - expect(xhrMock.open).toHaveBeenCalledWith('POST', DEFAULT_XHR_TRANSPORT_OPTIONS.url); - expect(xhrMock.send).toHaveBeenCalledTimes(1); - expect(xhrMock.send).toHaveBeenCalledWith(serializeEnvelope(ERROR_ENVELOPE)); - }); - - it('sets rate limit response headers', async () => { - const transport = makeXHRTransport(DEFAULT_XHR_TRANSPORT_OPTIONS); - - await Promise.all([transport.send(ERROR_ENVELOPE), (xhrMock as XMLHttpRequest).onreadystatechange!({} as Event)]); - - expect(xhrMock.getResponseHeader).toHaveBeenCalledTimes(2); - expect(xhrMock.getResponseHeader).toHaveBeenCalledWith('X-Sentry-Rate-Limits'); - expect(xhrMock.getResponseHeader).toHaveBeenCalledWith('Retry-After'); - }); - - it('sets custom request headers', async () => { - const headers = { - referrerPolicy: 'strict-origin', - keepalive: 'true', - referrer: 'http://example.org', - }; - const options: BrowserTransportOptions = { - ...DEFAULT_XHR_TRANSPORT_OPTIONS, - headers, - }; - - const transport = makeXHRTransport(options); - await Promise.all([transport.send(ERROR_ENVELOPE), (xhrMock as XMLHttpRequest).onreadystatechange!({} as Event)]); - - expect(xhrMock.setRequestHeader).toHaveBeenCalledTimes(3); - expect(xhrMock.setRequestHeader).toHaveBeenCalledWith('referrerPolicy', headers.referrerPolicy); - expect(xhrMock.setRequestHeader).toHaveBeenCalledWith('keepalive', headers.keepalive); - expect(xhrMock.setRequestHeader).toHaveBeenCalledWith('referrer', headers.referrer); - }); -}); diff --git a/packages/vue/test/integration/VueIntegration.test.ts b/packages/vue/test/integration/VueIntegration.test.ts index aeea0ebf1451..81e3b863a16e 100644 --- a/packages/vue/test/integration/VueIntegration.test.ts +++ b/packages/vue/test/integration/VueIntegration.test.ts @@ -10,6 +10,23 @@ describe('Sentry.VueIntegration', () => { let loggerWarnings: unknown[] = []; let warnings: unknown[] = []; + const globalFetch = globalThis.fetch; + const globalResponse = globalThis.Response; + const globalRequest = globalThis.Request; + + beforeAll(() => { + globalThis.fetch = jest.fn(); + // @ts-expect-error This is a mock + globalThis.Response = jest.fn(); + globalThis.Request = jest.fn(); + }); + + afterAll(() => { + globalThis.fetch = globalFetch; + globalThis.Response = globalResponse; + globalThis.Request = globalRequest; + }); + beforeEach(() => { warnings = []; loggerWarnings = []; @@ -28,7 +45,11 @@ describe('Sentry.VueIntegration', () => { }); it('allows to initialize integration later', () => { - Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false }); + Sentry.init({ + dsn: PUBLIC_DSN, + defaultIntegrations: false, + autoSessionTracking: false, + }); const el = document.createElement('div'); const app = createApp({ @@ -48,7 +69,11 @@ describe('Sentry.VueIntegration', () => { }); it('warns when mounting before SDK.VueIntegration', () => { - Sentry.init({ dsn: PUBLIC_DSN, defaultIntegrations: false, autoSessionTracking: false }); + Sentry.init({ + dsn: PUBLIC_DSN, + defaultIntegrations: false, + autoSessionTracking: false, + }); const el = document.createElement('div'); const app = createApp({ From f84d3b951963c286035702bd665f1824d5c25683 Mon Sep 17 00:00:00 2001 From: GingerAdonis <2751672+GingerAdonis@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:00:51 +0100 Subject: [PATCH 118/173] feat(integrations): Capture error arguments as exception regardless of level in `captureConsoleIntegration` (#10744) --- packages/integrations/src/captureconsole.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index 2593ec8de7f2..dd4bb34ac5c1 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -69,7 +69,7 @@ function consoleHandler(args: unknown[], level: string): void { } const error = args.find(arg => arg instanceof Error); - if (level === 'error' && error) { + if (error) { captureException(error, captureContext); return; } From 342b8eaa372198eb6b8177d308b58d802c860042 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 21 Feb 2024 09:19:08 +0100 Subject: [PATCH 119/173] feat(node-experimental): Move integrations from node (#10743) This moves the integrations from node over to node-experimental. This also removes the dependency on `@sentry/node`, making this completely stand-alone! --- .../node-hapi-app/src/app.js | 2 +- .../tracing-experimental/hapi/scenario.js | 1 + packages/node-experimental/package.json | 1 - packages/node-experimental/src/index.ts | 21 +- .../src/integrations/anr/common.ts | 44 + .../src/integrations/anr/index.ts | 160 + .../src/integrations/anr/worker-script.ts | 2 + .../src/integrations/anr/worker.ts | 248 ++ .../src/integrations/console.ts | 36 + .../src/integrations/context.ts | 452 +++ .../src/integrations/contextlines.ts | 146 + .../integrations/local-variables/common.ts | 119 + .../src/integrations/local-variables/index.ts | 3 + .../local-variables/inspector.d.ts | 3387 +++++++++++++++++ .../local-variables/local-variables-async.ts | 267 ++ .../local-variables/local-variables-sync.ts | 409 ++ .../src/integrations/modules.ts | 96 + .../src/integrations/node-fetch.ts | 6 +- .../src/integrations/onuncaughtexception.ts | 171 + .../src/integrations/onunhandledrejection.ts | 95 + .../src/integrations/spotlight.ts | 116 + .../src/integrations/tracing/hapi.ts | 22 - .../src/integrations/tracing/hapi/index.ts | 76 + .../src/integrations/tracing/hapi/types.ts | 279 ++ packages/node-experimental/src/nodeVersion.ts | 7 + packages/node-experimental/src/sdk/init.ts | 34 +- .../src/utils/errorhandling.ts | 40 + ...ions.ts => getDefaultNodeClientOptions.ts} | 0 .../test/integrations/context.test.ts | 22 + .../test/integrations/contextlines.test.ts | 150 + .../test/integrations/localvariables.test.ts | 140 + .../test/integrations/spotlight.test.ts | 181 + .../node-experimental/test/sdk/client.test.ts | 2 +- .../test/sdk/handlers/errorHandler.test.ts | 2 +- packages/node-experimental/tsconfig.json | 2 +- 35 files changed, 6693 insertions(+), 46 deletions(-) create mode 100644 packages/node-experimental/src/integrations/anr/common.ts create mode 100644 packages/node-experimental/src/integrations/anr/index.ts create mode 100644 packages/node-experimental/src/integrations/anr/worker-script.ts create mode 100644 packages/node-experimental/src/integrations/anr/worker.ts create mode 100644 packages/node-experimental/src/integrations/console.ts create mode 100644 packages/node-experimental/src/integrations/context.ts create mode 100644 packages/node-experimental/src/integrations/contextlines.ts create mode 100644 packages/node-experimental/src/integrations/local-variables/common.ts create mode 100644 packages/node-experimental/src/integrations/local-variables/index.ts create mode 100644 packages/node-experimental/src/integrations/local-variables/inspector.d.ts create mode 100644 packages/node-experimental/src/integrations/local-variables/local-variables-async.ts create mode 100644 packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts create mode 100644 packages/node-experimental/src/integrations/modules.ts create mode 100644 packages/node-experimental/src/integrations/onuncaughtexception.ts create mode 100644 packages/node-experimental/src/integrations/onunhandledrejection.ts create mode 100644 packages/node-experimental/src/integrations/spotlight.ts delete mode 100644 packages/node-experimental/src/integrations/tracing/hapi.ts create mode 100644 packages/node-experimental/src/integrations/tracing/hapi/index.ts create mode 100644 packages/node-experimental/src/integrations/tracing/hapi/types.ts create mode 100644 packages/node-experimental/src/nodeVersion.ts create mode 100644 packages/node-experimental/src/utils/errorhandling.ts rename packages/node-experimental/test/helpers/{getDefaultNodePreviewClientOptions.ts => getDefaultNodeClientOptions.ts} (100%) create mode 100644 packages/node-experimental/test/integrations/context.test.ts create mode 100644 packages/node-experimental/test/integrations/contextlines.test.ts create mode 100644 packages/node-experimental/test/integrations/localvariables.test.ts create mode 100644 packages/node-experimental/test/integrations/spotlight.test.ts diff --git a/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js b/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js index 4c71802c9be2..5a7712dd4495 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js +++ b/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js @@ -10,7 +10,7 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, includeLocalVariables: true, - integrations: [new Sentry.Integrations.Hapi({ server })], + integrations: [Sentry.hapiIntegration({ server })], debug: true, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js index 8ec0015d744e..2c86ac704618 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js @@ -26,6 +26,7 @@ const init = async () => { }, }); + await Sentry.setupHapiErrorHandler(server); await server.start(); sendPortToRunner(port); diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json index e3805a603faa..62c1072001cc 100644 --- a/packages/node-experimental/package.json +++ b/packages/node-experimental/package.json @@ -50,7 +50,6 @@ "@opentelemetry/semantic-conventions": "1.21.0", "@prisma/instrumentation": "5.9.0", "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", "@sentry/opentelemetry": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 5d30e3280ef1..ef9605afabe7 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -2,6 +2,15 @@ export { errorHandler } from './sdk/handlers/errorHandler'; export { httpIntegration } from './integrations/http'; export { nativeNodeFetchIntegration } from './integrations/node-fetch'; + +export { consoleIntegration } from './integrations/console'; +export { nodeContextIntegration } from './integrations/context'; +export { contextLinesIntegration } from './integrations/contextlines'; +export { localVariablesIntegration } from './integrations/local-variables'; +export { modulesIntegration } from './integrations/modules'; +export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception'; +export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection'; + export { expressIntegration } from './integrations/tracing/express'; export { fastifyIntegration } from './integrations/tracing/fastify'; export { graphqlIntegration } from './integrations/tracing/graphql'; @@ -12,6 +21,7 @@ export { mysql2Integration } from './integrations/tracing/mysql2'; export { nestIntegration } from './integrations/tracing/nest'; export { postgresIntegration } from './integrations/tracing/postgres'; export { prismaIntegration } from './integrations/tracing/prisma'; +export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi'; export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/tracing'; @@ -28,17 +38,6 @@ export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiv export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils'; -export { - hapiErrorPlugin, - consoleIntegration, - onUncaughtExceptionIntegration, - onUnhandledRejectionIntegration, - modulesIntegration, - contextLinesIntegration, - nodeContextIntegration, - localVariablesIntegration, -} from '@sentry/node'; - export { addBreadcrumb, isInitialized, diff --git a/packages/node-experimental/src/integrations/anr/common.ts b/packages/node-experimental/src/integrations/anr/common.ts new file mode 100644 index 000000000000..5617871ccb24 --- /dev/null +++ b/packages/node-experimental/src/integrations/anr/common.ts @@ -0,0 +1,44 @@ +import type { Contexts, DsnComponents, Primitive, SdkMetadata } from '@sentry/types'; + +export interface AnrIntegrationOptions { + /** + * Interval to send heartbeat messages to the ANR worker. + * + * Defaults to 50ms. + */ + pollInterval: number; + /** + * Threshold in milliseconds to trigger an ANR event. + * + * Defaults to 5000ms. + */ + anrThreshold: number; + /** + * Whether to capture a stack trace when the ANR event is triggered. + * + * Defaults to `false`. + * + * This uses the node debugger which enables the inspector API and opens the required ports. + */ + captureStackTrace: boolean; + /** + * Tags to include with ANR events. + */ + staticTags: { [key: string]: Primitive }; + /** + * @ignore Internal use only. + * + * If this is supplied, stack frame filenames will be rewritten to be relative to this path. + */ + appRootPath: string | undefined; +} + +export interface WorkerStartData extends AnrIntegrationOptions { + debug: boolean; + sdkMetadata: SdkMetadata; + dsn: DsnComponents; + release: string | undefined; + environment: string; + dist: string | undefined; + contexts: Contexts; +} diff --git a/packages/node-experimental/src/integrations/anr/index.ts b/packages/node-experimental/src/integrations/anr/index.ts new file mode 100644 index 000000000000..30ff52011d31 --- /dev/null +++ b/packages/node-experimental/src/integrations/anr/index.ts @@ -0,0 +1,160 @@ +import { URL } from 'url'; +import { defineIntegration, getCurrentScope } from '@sentry/core'; +import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; +import { dynamicRequire, logger } from '@sentry/utils'; +import type { Worker, WorkerOptions } from 'worker_threads'; +import { NODE_MAJOR, NODE_VERSION } from '../../nodeVersion'; +import type { NodeClient } from '../../sdk/client'; +import type { AnrIntegrationOptions, WorkerStartData } from './common'; +import { base64WorkerScript } from './worker-script'; + +const DEFAULT_INTERVAL = 50; +const DEFAULT_HANG_THRESHOLD = 5000; + +type WorkerNodeV14 = Worker & { new (filename: string | URL, options?: WorkerOptions): Worker }; + +type WorkerThreads = { + Worker: WorkerNodeV14; +}; + +function log(message: string, ...args: unknown[]): void { + logger.log(`[ANR] ${message}`, ...args); +} + +/** + * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when + * targeting those versions + */ +function getWorkerThreads(): WorkerThreads { + return dynamicRequire(module, 'worker_threads'); +} + +/** + * Gets contexts by calling all event processors. This relies on being called after all integrations are setup + */ +async function getContexts(client: NodeClient): Promise { + let event: Event | null = { message: 'ANR' }; + const eventHint: EventHint = {}; + + for (const processor of client.getEventProcessors()) { + if (event === null) break; + event = await processor(event, eventHint); + } + + return event?.contexts || {}; +} + +interface InspectorApi { + open: (port: number) => void; + url: () => string | undefined; +} + +const INTEGRATION_NAME = 'Anr'; + +const _anrIntegration = ((options: Partial = {}) => { + return { + name: INTEGRATION_NAME, + setup(client: NodeClient) { + if (NODE_MAJOR < 16 || (NODE_MAJOR === 16 && (NODE_VERSION.minor || 0) < 17)) { + throw new Error('ANR detection requires Node 16.17.0 or later'); + } + + // setImmediate is used to ensure that all other integrations have been setup + setImmediate(() => _startWorker(client, options)); + }, + }; +}) satisfies IntegrationFn; + +export const anrIntegration = defineIntegration(_anrIntegration); + +/** + * Starts the ANR worker thread + */ +async function _startWorker(client: NodeClient, _options: Partial): Promise { + const contexts = await getContexts(client); + const dsn = client.getDsn(); + + if (!dsn) { + return; + } + + // These will not be accurate if sent later from the worker thread + delete contexts.app?.app_memory; + delete contexts.device?.free_memory; + + const initOptions = client.getOptions(); + + const sdkMetadata = client.getSdkMetadata() || {}; + if (sdkMetadata.sdk) { + sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name); + } + + const options: WorkerStartData = { + debug: logger.isEnabled(), + dsn, + environment: initOptions.environment || 'production', + release: initOptions.release, + dist: initOptions.dist, + sdkMetadata, + appRootPath: _options.appRootPath, + pollInterval: _options.pollInterval || DEFAULT_INTERVAL, + anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD, + captureStackTrace: !!_options.captureStackTrace, + staticTags: _options.staticTags || {}, + contexts, + }; + + if (options.captureStackTrace) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const inspector: InspectorApi = require('inspector'); + if (!inspector.url()) { + inspector.open(0); + } + } + + const { Worker } = getWorkerThreads(); + + const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { + workerData: options, + }); + + process.on('exit', () => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + worker.terminate(); + }); + + const timer = setInterval(() => { + try { + const currentSession = getCurrentScope().getSession(); + // We need to copy the session object and remove the toJSON method so it can be sent to the worker + // serialized without making it a SerializedSession + const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; + // message the worker to tell it the main event loop is still running + worker.postMessage({ session }); + } catch (_) { + // + } + }, options.pollInterval); + // Timer should not block exit + timer.unref(); + + worker.on('message', (msg: string) => { + if (msg === 'session-ended') { + log('ANR event sent from ANR worker. Clearing session in this thread.'); + getCurrentScope().setSession(undefined); + } + }); + + worker.once('error', (err: Error) => { + clearInterval(timer); + log('ANR worker error', err); + }); + + worker.once('exit', (code: number) => { + clearInterval(timer); + log('ANR worker exit', code); + }); + + // Ensure this thread can't block app exit + worker.unref(); +} diff --git a/packages/node-experimental/src/integrations/anr/worker-script.ts b/packages/node-experimental/src/integrations/anr/worker-script.ts new file mode 100644 index 000000000000..16394eaacfe1 --- /dev/null +++ b/packages/node-experimental/src/integrations/anr/worker-script.ts @@ -0,0 +1,2 @@ +// This file is a placeholder that gets overwritten in the build directory. +export const base64WorkerScript = ''; diff --git a/packages/node-experimental/src/integrations/anr/worker.ts b/packages/node-experimental/src/integrations/anr/worker.ts new file mode 100644 index 000000000000..8bc6e1455cfb --- /dev/null +++ b/packages/node-experimental/src/integrations/anr/worker.ts @@ -0,0 +1,248 @@ +import { + createEventEnvelope, + createSessionEnvelope, + getEnvelopeEndpointWithUrlEncodedAuth, + makeSession, + updateSession, +} from '@sentry/core'; +import type { Event, Session, StackFrame, TraceContext } from '@sentry/types'; +import { + callFrameToStackFrame, + normalizeUrlToBase, + stripSentryFramesAndReverse, + uuid4, + watchdogTimer, +} from '@sentry/utils'; +import { Session as InspectorSession } from 'inspector'; +import { parentPort, workerData } from 'worker_threads'; + +import { makeNodeTransport } from '../../transports'; +import { createGetModuleFromFilename } from '../../utils/module'; +import type { WorkerStartData } from './common'; + +type VoidFunction = () => void; +type InspectorSessionNodeV12 = InspectorSession & { connectToMainThread: VoidFunction }; + +const options: WorkerStartData = workerData; +let session: Session | undefined; +let hasSentAnrEvent = false; + +function log(msg: string): void { + if (options.debug) { + // eslint-disable-next-line no-console + console.log(`[ANR Worker] ${msg}`); + } +} + +const url = getEnvelopeEndpointWithUrlEncodedAuth(options.dsn); +const transport = makeNodeTransport({ + url, + recordDroppedEvent: () => { + // + }, +}); + +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' }); + + const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata); + // Log the envelope so to aid in testing + log(JSON.stringify(envelope)); + + await transport.send(envelope); + + try { + // Notify the main process that the session has ended so the session can be cleared from the scope + parentPort?.postMessage('session-ended'); + } catch (_) { + // ignore + } + } +} + +log('Started'); + +function prepareStackFrames(stackFrames: StackFrame[] | undefined): StackFrame[] | undefined { + if (!stackFrames) { + return undefined; + } + + // Strip Sentry frames and reverse the stack frames so they are in the correct order + const strippedFrames = stripSentryFramesAndReverse(stackFrames); + + // If we have an app root path, rewrite the filenames to be relative to the app root + if (options.appRootPath) { + for (const frame of strippedFrames) { + if (!frame.filename) { + continue; + } + + frame.filename = normalizeUrlToBase(frame.filename, options.appRootPath); + } + } + + return strippedFrames; +} + +async function sendAnrEvent(frames?: StackFrame[], traceContext?: TraceContext): Promise { + if (hasSentAnrEvent) { + return; + } + + hasSentAnrEvent = true; + + await sendAbnormalSession(); + + log('Sending event'); + + const event: Event = { + event_id: uuid4(), + contexts: { ...options.contexts, trace: traceContext }, + release: options.release, + environment: options.environment, + dist: options.dist, + platform: 'node', + level: 'error', + exception: { + values: [ + { + type: 'ApplicationNotResponding', + value: `Application Not Responding for at least ${options.anrThreshold} ms`, + stacktrace: { frames: prepareStackFrames(frames) }, + // This ensures the UI doesn't say 'Crashed in' for the stack trace + mechanism: { type: 'ANR' }, + }, + ], + }, + tags: options.staticTags, + }; + + const envelope = createEventEnvelope(event, options.dsn, options.sdkMetadata); + // Log the envelope so to aid in testing + log(JSON.stringify(envelope)); + + await transport.send(envelope); + await transport.flush(2000); + + // Delay for 5 seconds so that stdio can flush in the main event loop ever restarts. + // This is mainly for the benefit of logging/debugging issues. + setTimeout(() => { + process.exit(0); + }, 5_000); +} + +let debuggerPause: VoidFunction | undefined; + +if (options.captureStackTrace) { + log('Connecting to debugger'); + + const session = new InspectorSession() as InspectorSessionNodeV12; + session.connectToMainThread(); + + log('Connected to debugger'); + + // Collect scriptId -> url map so we can look up the filenames later + const scripts = new Map(); + + session.on('Debugger.scriptParsed', event => { + scripts.set(event.params.scriptId, event.params.url); + }); + + session.on('Debugger.paused', event => { + if (event.params.reason !== 'other') { + return; + } + + try { + log('Debugger paused'); + + // copy the frames + const callFrames = [...event.params.callFrames]; + + const getModuleName = options.appRootPath ? createGetModuleFromFilename(options.appRootPath) : () => undefined; + const stackFrames = callFrames.map(frame => + callFrameToStackFrame(frame, scripts.get(frame.location.scriptId), getModuleName), + ); + + // Evaluate a script in the currently paused context + session.post( + 'Runtime.evaluate', + { + // Grab the trace context from the current scope + expression: + 'const ctx = __SENTRY__.acs?.getCurrentScope().getPropagationContext() || {}; ctx.traceId + "-" + ctx.spanId + "-" + ctx.parentSpanId', + // Don't re-trigger the debugger if this causes an error + silent: true, + }, + (_, param) => { + const traceId = param && param.result ? (param.result.value as string) : '--'; + const [trace_id, span_id, parent_span_id] = traceId.split('-') as (string | undefined)[]; + + session.post('Debugger.resume'); + session.post('Debugger.disable'); + + const context = trace_id?.length && span_id?.length ? { trace_id, span_id, parent_span_id } : undefined; + sendAnrEvent(stackFrames, context).then(null, () => { + log('Sending ANR event failed.'); + }); + }, + ); + } catch (e) { + session.post('Debugger.resume'); + session.post('Debugger.disable'); + throw e; + } + }); + + debuggerPause = () => { + try { + session.post('Debugger.enable', () => { + session.post('Debugger.pause'); + }); + } catch (_) { + // + } + }; +} + +function createHrTimer(): { getTimeMs: () => number; reset: VoidFunction } { + // TODO (v8): We can use process.hrtime.bigint() after we drop node v8 + let lastPoll = process.hrtime(); + + return { + getTimeMs: (): number => { + const [seconds, nanoSeconds] = process.hrtime(lastPoll); + return Math.floor(seconds * 1e3 + nanoSeconds / 1e6); + }, + reset: (): void => { + lastPoll = process.hrtime(); + }, + }; +} + +function watchdogTimeout(): void { + log('Watchdog timeout'); + + if (debuggerPause) { + log('Pausing debugger to capture stack trace'); + debuggerPause(); + } else { + log('Capturing event without a stack trace'); + sendAnrEvent().then(null, () => { + log('Sending ANR event failed on watchdog timeout.'); + }); + } +} + +const { poll } = watchdogTimer(createHrTimer, options.pollInterval, options.anrThreshold, watchdogTimeout); + +parentPort?.on('message', (msg: { session: Session | undefined }) => { + if (msg.session) { + session = makeSession(msg.session); + } + + poll(); +}); diff --git a/packages/node-experimental/src/integrations/console.ts b/packages/node-experimental/src/integrations/console.ts new file mode 100644 index 000000000000..0b3d27fe8510 --- /dev/null +++ b/packages/node-experimental/src/integrations/console.ts @@ -0,0 +1,36 @@ +import * as util from 'util'; +import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { addConsoleInstrumentationHandler, severityLevelFromString } from '@sentry/utils'; + +const INTEGRATION_NAME = 'Console'; + +const _consoleIntegration = (() => { + return { + name: INTEGRATION_NAME, + setup(client) { + addConsoleInstrumentationHandler(({ args, level }) => { + if (getClient() !== client) { + return; + } + + addBreadcrumb( + { + category: 'console', + level: severityLevelFromString(level), + message: util.format.apply(undefined, args), + }, + { + input: [...args], + level, + }, + ); + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Capture console logs as breadcrumbs. + */ +export const consoleIntegration = defineIntegration(_consoleIntegration); diff --git a/packages/node-experimental/src/integrations/context.ts b/packages/node-experimental/src/integrations/context.ts new file mode 100644 index 000000000000..c33d97e79044 --- /dev/null +++ b/packages/node-experimental/src/integrations/context.ts @@ -0,0 +1,452 @@ +import { execFile } from 'child_process'; +import { readFile, readdir } from 'fs'; +import * as os from 'os'; +import { join } from 'path'; +import { promisify } from 'util'; +import { defineIntegration } from '@sentry/core'; +import type { + AppContext, + CloudResourceContext, + Contexts, + CultureContext, + DeviceContext, + Event, + IntegrationFn, + OsContext, +} from '@sentry/types'; + +export const readFileAsync = promisify(readFile); +export const readDirAsync = promisify(readdir); + +const INTEGRATION_NAME = 'Context'; + +interface DeviceContextOptions { + cpu?: boolean; + memory?: boolean; +} + +interface ContextOptions { + app?: boolean; + os?: boolean; + device?: DeviceContextOptions | boolean; + culture?: boolean; + cloudResource?: boolean; +} + +const _nodeContextIntegration = ((options: ContextOptions = {}) => { + let cachedContext: Promise | undefined; + + const _options = { + app: true, + os: true, + device: true, + culture: true, + cloudResource: true, + ...options, + }; + + /** Add contexts to the event. Caches the context so we only look it up once. */ + async function addContext(event: Event): Promise { + if (cachedContext === undefined) { + cachedContext = _getContexts(); + } + + const updatedContext = _updateContext(await cachedContext); + + event.contexts = { + ...event.contexts, + app: { ...updatedContext.app, ...event.contexts?.app }, + os: { ...updatedContext.os, ...event.contexts?.os }, + device: { ...updatedContext.device, ...event.contexts?.device }, + culture: { ...updatedContext.culture, ...event.contexts?.culture }, + cloud_resource: { ...updatedContext.cloud_resource, ...event.contexts?.cloud_resource }, + }; + + return event; + } + + /** Get the contexts from node. */ + async function _getContexts(): Promise { + const contexts: Contexts = {}; + + if (_options.os) { + contexts.os = await getOsContext(); + } + + if (_options.app) { + contexts.app = getAppContext(); + } + + if (_options.device) { + contexts.device = getDeviceContext(_options.device); + } + + if (_options.culture) { + const culture = getCultureContext(); + + if (culture) { + contexts.culture = culture; + } + } + + if (_options.cloudResource) { + contexts.cloud_resource = getCloudResourceContext(); + } + + return contexts; + } + + return { + name: INTEGRATION_NAME, + processEvent(event) { + return addContext(event); + }, + }; +}) satisfies IntegrationFn; + +/** + * Capture context about the environment and the device that the client is running on, to events. + */ +export const nodeContextIntegration = defineIntegration(_nodeContextIntegration); + +/** + * Updates the context with dynamic values that can change + */ +function _updateContext(contexts: Contexts): Contexts { + // Only update properties if they exist + if (contexts?.app?.app_memory) { + contexts.app.app_memory = process.memoryUsage().rss; + } + + if (contexts?.device?.free_memory) { + contexts.device.free_memory = os.freemem(); + } + + return contexts; +} + +/** + * Returns the operating system context. + * + * Based on the current platform, this uses a different strategy to provide the + * most accurate OS information. Since this might involve spawning subprocesses + * or accessing the file system, this should only be executed lazily and cached. + * + * - On macOS (Darwin), this will execute the `sw_vers` utility. The context + * has a `name`, `version`, `build` and `kernel_version` set. + * - On Linux, this will try to load a distribution release from `/etc` and set + * the `name`, `version` and `kernel_version` fields. + * - On all other platforms, only a `name` and `version` will be returned. Note + * that `version` might actually be the kernel version. + */ +async function getOsContext(): Promise { + const platformId = os.platform(); + switch (platformId) { + case 'darwin': + return getDarwinInfo(); + case 'linux': + return getLinuxInfo(); + default: + return { + name: PLATFORM_NAMES[platformId] || platformId, + version: os.release(), + }; + } +} + +function getCultureContext(): CultureContext | undefined { + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + if (typeof (process.versions as unknown as any).icu !== 'string') { + // Node was built without ICU support + return; + } + + // Check that node was built with full Intl support. Its possible it was built without support for non-English + // locales which will make resolvedOptions inaccurate + // + // https://nodejs.org/api/intl.html#detecting-internationalization-support + const january = new Date(9e8); + const spanish = new Intl.DateTimeFormat('es', { month: 'long' }); + if (spanish.format(january) === 'enero') { + const options = Intl.DateTimeFormat().resolvedOptions(); + + return { + locale: options.locale, + timezone: options.timeZone, + }; + } + } catch (err) { + // + } + + return; +} + +function getAppContext(): AppContext { + const app_memory = process.memoryUsage().rss; + const app_start_time = new Date(Date.now() - process.uptime() * 1000).toISOString(); + + return { app_start_time, app_memory }; +} + +/** + * Gets device information from os + */ +export function getDeviceContext(deviceOpt: DeviceContextOptions | true): DeviceContext { + const device: DeviceContext = {}; + + // Sometimes os.uptime() throws due to lacking permissions: https://github.com/getsentry/sentry-javascript/issues/8202 + let uptime; + try { + uptime = os.uptime && os.uptime(); + } catch (e) { + // noop + } + + // os.uptime or its return value seem to be undefined in certain environments (e.g. Azure functions). + // Hence, we only set boot time, if we get a valid uptime value. + // @see https://github.com/getsentry/sentry-javascript/issues/5856 + if (typeof uptime === 'number') { + device.boot_time = new Date(Date.now() - uptime * 1000).toISOString(); + } + + device.arch = os.arch(); + + if (deviceOpt === true || deviceOpt.memory) { + device.memory_size = os.totalmem(); + device.free_memory = os.freemem(); + } + + if (deviceOpt === true || deviceOpt.cpu) { + const cpuInfo: os.CpuInfo[] | undefined = os.cpus(); + if (cpuInfo && cpuInfo.length) { + const firstCpu = cpuInfo[0]; + + device.processor_count = cpuInfo.length; + device.cpu_description = firstCpu.model; + device.processor_frequency = firstCpu.speed; + } + } + + return device; +} + +/** Mapping of Node's platform names to actual OS names. */ +const PLATFORM_NAMES: { [platform: string]: string } = { + aix: 'IBM AIX', + freebsd: 'FreeBSD', + openbsd: 'OpenBSD', + sunos: 'SunOS', + win32: 'Windows', +}; + +/** Linux version file to check for a distribution. */ +interface DistroFile { + /** The file name, located in `/etc`. */ + name: string; + /** Potential distributions to check. */ + distros: string[]; +} + +/** Mapping of linux release files located in /etc to distributions. */ +const LINUX_DISTROS: DistroFile[] = [ + { name: 'fedora-release', distros: ['Fedora'] }, + { name: 'redhat-release', distros: ['Red Hat Linux', 'Centos'] }, + { name: 'redhat_version', distros: ['Red Hat Linux'] }, + { name: 'SuSE-release', distros: ['SUSE Linux'] }, + { name: 'lsb-release', distros: ['Ubuntu Linux', 'Arch Linux'] }, + { name: 'debian_version', distros: ['Debian'] }, + { name: 'debian_release', distros: ['Debian'] }, + { name: 'arch-release', distros: ['Arch Linux'] }, + { name: 'gentoo-release', distros: ['Gentoo Linux'] }, + { name: 'novell-release', distros: ['SUSE Linux'] }, + { name: 'alpine-release', distros: ['Alpine Linux'] }, +]; + +/** Functions to extract the OS version from Linux release files. */ +const LINUX_VERSIONS: { + [identifier: string]: (content: string) => string | undefined; +} = { + alpine: content => content, + arch: content => matchFirst(/distrib_release=(.*)/, content), + centos: content => matchFirst(/release ([^ ]+)/, content), + debian: content => content, + fedora: content => matchFirst(/release (..)/, content), + mint: content => matchFirst(/distrib_release=(.*)/, content), + red: content => matchFirst(/release ([^ ]+)/, content), + suse: content => matchFirst(/VERSION = (.*)\n/, content), + ubuntu: content => matchFirst(/distrib_release=(.*)/, content), +}; + +/** + * Executes a regular expression with one capture group. + * + * @param regex A regular expression to execute. + * @param text Content to execute the RegEx on. + * @returns The captured string if matched; otherwise undefined. + */ +function matchFirst(regex: RegExp, text: string): string | undefined { + const match = regex.exec(text); + return match ? match[1] : undefined; +} + +/** Loads the macOS operating system context. */ +async function getDarwinInfo(): Promise { + // Default values that will be used in case no operating system information + // can be loaded. The default version is computed via heuristics from the + // kernel version, but the build ID is missing. + const darwinInfo: OsContext = { + kernel_version: os.release(), + name: 'Mac OS X', + version: `10.${Number(os.release().split('.')[0]) - 4}`, + }; + + try { + // We try to load the actual macOS version by executing the `sw_vers` tool. + // This tool should be available on every standard macOS installation. In + // case this fails, we stick with the values computed above. + + const output = await new Promise((resolve, reject) => { + execFile('/usr/bin/sw_vers', (error: Error | null, stdout: string) => { + if (error) { + reject(error); + return; + } + resolve(stdout); + }); + }); + + darwinInfo.name = matchFirst(/^ProductName:\s+(.*)$/m, output); + darwinInfo.version = matchFirst(/^ProductVersion:\s+(.*)$/m, output); + darwinInfo.build = matchFirst(/^BuildVersion:\s+(.*)$/m, output); + } catch (e) { + // ignore + } + + return darwinInfo; +} + +/** Returns a distribution identifier to look up version callbacks. */ +function getLinuxDistroId(name: string): string { + return name.split(' ')[0].toLowerCase(); +} + +/** Loads the Linux operating system context. */ +async function getLinuxInfo(): Promise { + // By default, we cannot assume anything about the distribution or Linux + // version. `os.release()` returns the kernel version and we assume a generic + // "Linux" name, which will be replaced down below. + const linuxInfo: OsContext = { + kernel_version: os.release(), + name: 'Linux', + }; + + try { + // We start guessing the distribution by listing files in the /etc + // directory. This is were most Linux distributions (except Knoppix) store + // release files with certain distribution-dependent meta data. We search + // for exactly one known file defined in `LINUX_DISTROS` and exit if none + // are found. In case there are more than one file, we just stick with the + // first one. + const etcFiles = await readDirAsync('/etc'); + const distroFile = LINUX_DISTROS.find(file => etcFiles.includes(file.name)); + if (!distroFile) { + return linuxInfo; + } + + // Once that file is known, load its contents. To make searching in those + // files easier, we lowercase the file contents. Since these files are + // usually quite small, this should not allocate too much memory and we only + // hold on to it for a very short amount of time. + const distroPath = join('/etc', distroFile.name); + const contents = ((await readFileAsync(distroPath, { encoding: 'utf-8' })) as string).toLowerCase(); + + // Some Linux distributions store their release information in the same file + // (e.g. RHEL and Centos). In those cases, we scan the file for an + // identifier, that basically consists of the first word of the linux + // distribution name (e.g. "red" for Red Hat). In case there is no match, we + // just assume the first distribution in our list. + const { distros } = distroFile; + linuxInfo.name = distros.find(d => contents.indexOf(getLinuxDistroId(d)) >= 0) || distros[0]; + + // Based on the found distribution, we can now compute the actual version + // number. This is different for every distribution, so several strategies + // are computed in `LINUX_VERSIONS`. + const id = getLinuxDistroId(linuxInfo.name); + linuxInfo.version = LINUX_VERSIONS[id](contents); + } catch (e) { + // ignore + } + + return linuxInfo; +} + +/** + * Grabs some information about hosting provider based on best effort. + */ +function getCloudResourceContext(): CloudResourceContext | undefined { + if (process.env.VERCEL) { + // https://vercel.com/docs/concepts/projects/environment-variables/system-environment-variables#system-environment-variables + return { + 'cloud.provider': 'vercel', + 'cloud.region': process.env.VERCEL_REGION, + }; + } else if (process.env.AWS_REGION) { + // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html + return { + 'cloud.provider': 'aws', + 'cloud.region': process.env.AWS_REGION, + 'cloud.platform': process.env.AWS_EXECUTION_ENV, + }; + } else if (process.env.GCP_PROJECT) { + // https://cloud.google.com/composer/docs/how-to/managing/environment-variables#reserved_variables + return { + 'cloud.provider': 'gcp', + }; + } else if (process.env.ALIYUN_REGION_ID) { + // TODO: find where I found these environment variables - at least gc.github.com returns something + return { + 'cloud.provider': 'alibaba_cloud', + 'cloud.region': process.env.ALIYUN_REGION_ID, + }; + } else if (process.env.WEBSITE_SITE_NAME && process.env.REGION_NAME) { + // https://learn.microsoft.com/en-us/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#app-environment + return { + 'cloud.provider': 'azure', + 'cloud.region': process.env.REGION_NAME, + }; + } else if (process.env.IBM_CLOUD_REGION) { + // TODO: find where I found these environment variables - at least gc.github.com returns something + return { + 'cloud.provider': 'ibm_cloud', + 'cloud.region': process.env.IBM_CLOUD_REGION, + }; + } else if (process.env.TENCENTCLOUD_REGION) { + // https://www.tencentcloud.com/document/product/583/32748 + return { + 'cloud.provider': 'tencent_cloud', + 'cloud.region': process.env.TENCENTCLOUD_REGION, + 'cloud.account.id': process.env.TENCENTCLOUD_APPID, + 'cloud.availability_zone': process.env.TENCENTCLOUD_ZONE, + }; + } else if (process.env.NETLIFY) { + // https://docs.netlify.com/configure-builds/environment-variables/#read-only-variables + return { + 'cloud.provider': 'netlify', + }; + } else if (process.env.FLY_REGION) { + // https://fly.io/docs/reference/runtime-environment/ + return { + 'cloud.provider': 'fly.io', + 'cloud.region': process.env.FLY_REGION, + }; + } else if (process.env.DYNO) { + // https://devcenter.heroku.com/articles/dynos#local-environment-variables + return { + 'cloud.provider': 'heroku', + }; + } else { + return undefined; + } +} diff --git a/packages/node-experimental/src/integrations/contextlines.ts b/packages/node-experimental/src/integrations/contextlines.ts new file mode 100644 index 000000000000..3755e164e5ea --- /dev/null +++ b/packages/node-experimental/src/integrations/contextlines.ts @@ -0,0 +1,146 @@ +import { promises } from 'fs'; +import { defineIntegration } from '@sentry/core'; +import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; +import { LRUMap, addContextToFrame } from '@sentry/utils'; + +const FILE_CONTENT_CACHE = new LRUMap(100); +const DEFAULT_LINES_OF_CONTEXT = 7; +const INTEGRATION_NAME = 'ContextLines'; + +const readFileAsync = promises.readFile; + +/** + * Resets the file cache. Exists for testing purposes. + * @hidden + */ +export function resetFileContentCache(): void { + FILE_CONTENT_CACHE.clear(); +} + +interface ContextLinesOptions { + /** + * Sets the number of context lines for each frame when loading a file. + * Defaults to 7. + * + * Set to 0 to disable loading and inclusion of source files. + **/ + frameContextLines?: number; +} + +/** Exported only for tests, as a type-safe variant. */ +export const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { + const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT; + + return { + name: INTEGRATION_NAME, + processEvent(event) { + return addSourceContext(event, contextLines); + }, + }; +}) satisfies IntegrationFn; + +/** + * Capture the lines before and after the frame's context. + */ +export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); + +async function addSourceContext(event: Event, contextLines: number): Promise { + // keep a lookup map of which files we've already enqueued to read, + // so we don't enqueue the same file multiple times which would cause multiple i/o reads + const enqueuedReadSourceFileTasks: Record = {}; + const readSourceFileTasks: Promise[] = []; + + if (contextLines > 0 && event.exception?.values) { + for (const exception of event.exception.values) { + if (!exception.stacktrace?.frames) { + continue; + } + + // We want to iterate in reverse order as calling cache.get will bump the file in our LRU cache. + // This ends up prioritizes source context for frames at the top of the stack instead of the bottom. + for (let i = exception.stacktrace.frames.length - 1; i >= 0; i--) { + const frame = exception.stacktrace.frames[i]; + // Call cache.get to bump the file to the top of the cache and ensure we have not already + // enqueued a read operation for this filename + if (frame.filename && !enqueuedReadSourceFileTasks[frame.filename] && !FILE_CONTENT_CACHE.get(frame.filename)) { + readSourceFileTasks.push(_readSourceFile(frame.filename)); + enqueuedReadSourceFileTasks[frame.filename] = 1; + } + } + } + } + + // check if files to read > 0, if so, await all of them to be read before adding source contexts. + // Normally, Promise.all here could be short circuited if one of the promises rejects, but we + // are guarding from that by wrapping the i/o read operation in a try/catch. + if (readSourceFileTasks.length > 0) { + await Promise.all(readSourceFileTasks); + } + + // Perform the same loop as above, but this time we can assume all files are in the cache + // and attempt to add source context to frames. + if (contextLines > 0 && event.exception?.values) { + for (const exception of event.exception.values) { + if (exception.stacktrace && exception.stacktrace.frames) { + await addSourceContextToFrames(exception.stacktrace.frames, contextLines); + } + } + } + + return event; +} + +/** Adds context lines to frames */ +function addSourceContextToFrames(frames: StackFrame[], contextLines: number): void { + for (const frame of frames) { + // Only add context if we have a filename and it hasn't already been added + if (frame.filename && frame.context_line === undefined) { + const sourceFileLines = FILE_CONTENT_CACHE.get(frame.filename); + + if (sourceFileLines) { + try { + addContextToFrame(sourceFileLines, frame, contextLines); + } catch (e) { + // anomaly, being defensive in case + // unlikely to ever happen in practice but can definitely happen in theory + } + } + } + } +} + +/** + * Reads file contents and caches them in a global LRU cache. + * If reading fails, mark the file as null in the cache so we don't try again. + * + * @param filename filepath to read content from. + */ +async function _readSourceFile(filename: string): Promise { + const cachedFile = FILE_CONTENT_CACHE.get(filename); + + // We have already attempted to read this file and failed, do not try again + if (cachedFile === null) { + return null; + } + + // We have a cache hit, return it + if (cachedFile !== undefined) { + return cachedFile; + } + + // Guard from throwing if readFile fails, this enables us to use Promise.all and + // not have it short circuiting if one of the promises rejects + since context lines are added + // on a best effort basis, we want to throw here anyways. + + // If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it + let content: string[] | null = null; + try { + const rawFileContents = await readFileAsync(filename, 'utf-8'); + content = rawFileContents.split('\n'); + } catch (_) { + // if we fail, we will mark the file as null in the cache and short circuit next time we try to read it + } + + FILE_CONTENT_CACHE.set(filename, content); + return content; +} diff --git a/packages/node-experimental/src/integrations/local-variables/common.ts b/packages/node-experimental/src/integrations/local-variables/common.ts new file mode 100644 index 000000000000..3ffee8c0a824 --- /dev/null +++ b/packages/node-experimental/src/integrations/local-variables/common.ts @@ -0,0 +1,119 @@ +import type { StackFrame, StackParser } from '@sentry/types'; +import type { Debugger } from 'inspector'; + +export type Variables = Record; + +export type RateLimitIncrement = () => void; + +/** + * Creates a rate limiter that will call the disable callback when the rate limit is reached and the enable callback + * when a timeout has occurred. + * @param maxPerSecond Maximum number of calls per second + * @param enable Callback to enable capture + * @param disable Callback to disable capture + * @returns A function to call to increment the rate limiter count + */ +export function createRateLimiter( + maxPerSecond: number, + enable: () => void, + disable: (seconds: number) => void, +): RateLimitIncrement { + let count = 0; + let retrySeconds = 5; + let disabledTimeout = 0; + + setInterval(() => { + if (disabledTimeout === 0) { + if (count > maxPerSecond) { + retrySeconds *= 2; + disable(retrySeconds); + + // Cap at one day + if (retrySeconds > 86400) { + retrySeconds = 86400; + } + disabledTimeout = retrySeconds; + } + } else { + disabledTimeout -= 1; + + if (disabledTimeout === 0) { + enable(); + } + } + + count = 0; + }, 1_000).unref(); + + return () => { + count += 1; + }; +} + +// Add types for the exception event data +export type PausedExceptionEvent = Debugger.PausedEventDataType & { + data: { + // This contains error.stack + description: string; + }; +}; + +/** Could this be an anonymous function? */ +export function isAnonymous(name: string | undefined): boolean { + return name !== undefined && (name.length === 0 || name === '?' || name === ''); +} + +/** Do the function names appear to match? */ +export function functionNamesMatch(a: string | undefined, b: string | undefined): boolean { + return a === b || (isAnonymous(a) && isAnonymous(b)); +} + +/** Creates a unique hash from stack frames */ +export function hashFrames(frames: StackFrame[] | undefined): string | undefined { + if (frames === undefined) { + return; + } + + // Only hash the 10 most recent frames (ie. the last 10) + return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, ''); +} + +/** + * We use the stack parser to create a unique hash from the exception stack trace + * This is used to lookup vars when the exception passes through the event processor + */ +export function hashFromStack(stackParser: StackParser, stack: string | undefined): string | undefined { + if (stack === undefined) { + return undefined; + } + + return hashFrames(stackParser(stack, 1)); +} + +export interface FrameVariables { + function: string; + vars?: Variables; +} + +export interface LocalVariablesIntegrationOptions { + /** + * Capture local variables for both caught and uncaught exceptions + * + * - When false, only uncaught exceptions will have local variables + * - When true, both caught and uncaught exceptions will have local variables. + * + * Defaults to `true`. + * + * Capturing local variables for all exceptions can be expensive since the debugger pauses for every throw to collect + * local variables. + * + * To reduce the likelihood of this feature impacting app performance or throughput, this feature is rate-limited. + * Once the rate limit is reached, local variables will only be captured for uncaught exceptions until a timeout has + * been reached. + */ + captureAllExceptions?: boolean; + /** + * Maximum number of exceptions to capture local variables for per second before rate limiting is triggered. + */ + maxExceptionsPerSecond?: number; +} diff --git a/packages/node-experimental/src/integrations/local-variables/index.ts b/packages/node-experimental/src/integrations/local-variables/index.ts new file mode 100644 index 000000000000..60649b03118f --- /dev/null +++ b/packages/node-experimental/src/integrations/local-variables/index.ts @@ -0,0 +1,3 @@ +import { localVariablesSyncIntegration } from './local-variables-sync'; + +export const localVariablesIntegration = localVariablesSyncIntegration; diff --git a/packages/node-experimental/src/integrations/local-variables/inspector.d.ts b/packages/node-experimental/src/integrations/local-variables/inspector.d.ts new file mode 100644 index 000000000000..fca628d8405d --- /dev/null +++ b/packages/node-experimental/src/integrations/local-variables/inspector.d.ts @@ -0,0 +1,3387 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/unified-signatures */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable max-lines */ +/* eslint-disable @typescript-eslint/ban-types */ +// Type definitions for inspector + +// These definitions were copied from: +// https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d37bf642ed2f3fe403e405892e2eb4240a191bb0/types/node/inspector.d.ts + +/** + * The `inspector` module provides an API for interacting with the V8 inspector. + * + * It can be accessed using: + * + * ```js + * const inspector = require('inspector'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/inspector.js) + */ +declare module 'inspector' { + import EventEmitter = require('node:events'); + interface InspectorNotification { + method: string; + params: T; + } + namespace Schema { + /** + * Description of the protocol domain. + */ + interface Domain { + /** + * Domain name. + */ + name: string; + /** + * Domain version. + */ + version: string; + } + interface GetDomainsReturnType { + /** + * List of supported domains. + */ + domains: Domain[]; + } + } + namespace Runtime { + /** + * Unique script identifier. + */ + type ScriptId = string; + /** + * Unique object identifier. + */ + type RemoteObjectId = string; + /** + * Primitive value which cannot be JSON-stringified. + */ + type UnserializableValue = string; + /** + * Mirror object referencing original JavaScript object. + */ + interface RemoteObject { + /** + * Object type. + */ + type: string; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + /** + * Object class (constructor) name. Specified for object type values only. + */ + className?: string | undefined; + /** + * Remote object value in case of primitive values or JSON values (if it was requested). + */ + value?: any; + /** + * Primitive value which can not be JSON-stringified does not have value, but gets this property. + */ + unserializableValue?: UnserializableValue | undefined; + /** + * String representation of the object. + */ + description?: string | undefined; + /** + * Unique object identifier (for non-primitive values). + */ + objectId?: RemoteObjectId | undefined; + /** + * Preview containing abbreviated property values. Specified for object type values only. + * @experimental + */ + preview?: ObjectPreview | undefined; + /** + * @experimental + */ + customPreview?: CustomPreview | undefined; + } + /** + * @experimental + */ + interface CustomPreview { + header: string; + hasBody: boolean; + formatterObjectId: RemoteObjectId; + bindRemoteObjectFunctionId: RemoteObjectId; + configObjectId?: RemoteObjectId | undefined; + } + /** + * Object containing abbreviated remote object value. + * @experimental + */ + interface ObjectPreview { + /** + * Object type. + */ + type: string; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + /** + * String representation of the object. + */ + description?: string | undefined; + /** + * True iff some of the properties or entries of the original object did not fit. + */ + overflow: boolean; + /** + * List of the properties. + */ + properties: PropertyPreview[]; + /** + * List of the entries. Specified for map and set subtype values only. + */ + entries?: EntryPreview[] | undefined; + } + /** + * @experimental + */ + interface PropertyPreview { + /** + * Property name. + */ + name: string; + /** + * Object type. Accessor means that the property itself is an accessor property. + */ + type: string; + /** + * User-friendly property value string. + */ + value?: string | undefined; + /** + * Nested value preview. + */ + valuePreview?: ObjectPreview | undefined; + /** + * Object subtype hint. Specified for object type values only. + */ + subtype?: string | undefined; + } + /** + * @experimental + */ + interface EntryPreview { + /** + * Preview of the key. Specified for map-like collection entries. + */ + key?: ObjectPreview | undefined; + /** + * Preview of the value. + */ + value: ObjectPreview; + } + /** + * Object property descriptor. + */ + interface PropertyDescriptor { + /** + * Property name or symbol description. + */ + name: string; + /** + * The value associated with the property. + */ + value?: RemoteObject | undefined; + /** + * True if the value associated with the property may be changed (data descriptors only). + */ + writable?: boolean | undefined; + /** + * A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only). + */ + get?: RemoteObject | undefined; + /** + * A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only). + */ + set?: RemoteObject | undefined; + /** + * True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. + */ + configurable: boolean; + /** + * True if this property shows up during enumeration of the properties on the corresponding object. + */ + enumerable: boolean; + /** + * True if the result was thrown during the evaluation. + */ + wasThrown?: boolean | undefined; + /** + * True if the property is owned for the object. + */ + isOwn?: boolean | undefined; + /** + * Property symbol object, if the property is of the symbol type. + */ + symbol?: RemoteObject | undefined; + } + /** + * Object internal property descriptor. This property isn't normally visible in JavaScript code. + */ + interface InternalPropertyDescriptor { + /** + * Conventional property name. + */ + name: string; + /** + * The value associated with the property. + */ + value?: RemoteObject | undefined; + } + /** + * Represents function call argument. Either remote object id objectId, primitive value, unserializable primitive value or neither of (for undefined) them should be specified. + */ + interface CallArgument { + /** + * Primitive value or serializable javascript object. + */ + value?: any; + /** + * Primitive value which can not be JSON-stringified. + */ + unserializableValue?: UnserializableValue | undefined; + /** + * Remote object handle. + */ + objectId?: RemoteObjectId | undefined; + } + /** + * Id of an execution context. + */ + type ExecutionContextId = number; + /** + * Description of an isolated world. + */ + interface ExecutionContextDescription { + /** + * Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed. + */ + id: ExecutionContextId; + /** + * Execution context origin. + */ + origin: string; + /** + * Human readable name describing given context. + */ + name: string; + /** + * Embedder-specific auxiliary data. + */ + auxData?: {} | undefined; + } + /** + * Detailed information about exception (or error) that was thrown during script compilation or execution. + */ + interface ExceptionDetails { + /** + * Exception id. + */ + exceptionId: number; + /** + * Exception text, which should be used together with exception object when available. + */ + text: string; + /** + * Line number of the exception location (0-based). + */ + lineNumber: number; + /** + * Column number of the exception location (0-based). + */ + columnNumber: number; + /** + * Script ID of the exception location. + */ + scriptId?: ScriptId | undefined; + /** + * URL of the exception location, to be used when the script was not reported. + */ + url?: string | undefined; + /** + * JavaScript stack trace if available. + */ + stackTrace?: StackTrace | undefined; + /** + * Exception object if available. + */ + exception?: RemoteObject | undefined; + /** + * Identifier of the context where exception happened. + */ + executionContextId?: ExecutionContextId | undefined; + } + /** + * Number of milliseconds since epoch. + */ + type Timestamp = number; + /** + * Stack entry for runtime errors and assertions. + */ + interface CallFrame { + /** + * JavaScript function name. + */ + functionName: string; + /** + * JavaScript script id. + */ + scriptId: ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * JavaScript script line number (0-based). + */ + lineNumber: number; + /** + * JavaScript script column number (0-based). + */ + columnNumber: number; + } + /** + * Call frames for assertions or error messages. + */ + interface StackTrace { + /** + * String label of this stack trace. For async traces this may be a name of the function that initiated the async call. + */ + description?: string | undefined; + /** + * JavaScript function name. + */ + callFrames: CallFrame[]; + /** + * Asynchronous JavaScript stack trace that preceded this stack, if available. + */ + parent?: StackTrace | undefined; + /** + * Asynchronous JavaScript stack trace that preceded this stack, if available. + * @experimental + */ + parentId?: StackTraceId | undefined; + } + /** + * Unique identifier of current debugger. + * @experimental + */ + type UniqueDebuggerId = string; + /** + * If debuggerId is set stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See Runtime.StackTrace and Debugger.paused for usages. + * @experimental + */ + interface StackTraceId { + id: string; + debuggerId?: UniqueDebuggerId | undefined; + } + interface EvaluateParameterType { + /** + * Expression to evaluate. + */ + expression: string; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + /** + * Determines whether Command Line API should be available during the evaluation. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Specifies in which execution context to perform evaluation. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + contextId?: ExecutionContextId | undefined; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should be treated as initiated by user in the UI. + */ + userGesture?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + } + interface AwaitPromiseParameterType { + /** + * Identifier of the promise. + */ + promiseObjectId: RemoteObjectId; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + */ + generatePreview?: boolean | undefined; + } + interface CallFunctionOnParameterType { + /** + * Declaration of the function to call. + */ + functionDeclaration: string; + /** + * Identifier of the object to call function on. Either objectId or executionContextId should be specified. + */ + objectId?: RemoteObjectId | undefined; + /** + * Call arguments. All call arguments must belong to the same JavaScript world as the target object. + */ + arguments?: CallArgument[] | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object which should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should be treated as initiated by user in the UI. + */ + userGesture?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + /** + * Specifies execution context which global object will be used to call function on. Either executionContextId or objectId should be specified. + */ + executionContextId?: ExecutionContextId | undefined; + /** + * Symbolic group name that can be used to release multiple objects. If objectGroup is not specified and objectId is, objectGroup will be inherited from object. + */ + objectGroup?: string | undefined; + } + interface GetPropertiesParameterType { + /** + * Identifier of the object to return properties for. + */ + objectId: RemoteObjectId; + /** + * If true, returns properties belonging only to the element itself, not to its prototype chain. + */ + ownProperties?: boolean | undefined; + /** + * If true, returns accessor properties (with getter/setter) only; internal properties are not returned either. + * @experimental + */ + accessorPropertiesOnly?: boolean | undefined; + /** + * Whether preview should be generated for the results. + * @experimental + */ + generatePreview?: boolean | undefined; + } + interface ReleaseObjectParameterType { + /** + * Identifier of the object to release. + */ + objectId: RemoteObjectId; + } + interface ReleaseObjectGroupParameterType { + /** + * Symbolic object group name. + */ + objectGroup: string; + } + interface SetCustomObjectFormatterEnabledParameterType { + enabled: boolean; + } + interface CompileScriptParameterType { + /** + * Expression to compile. + */ + expression: string; + /** + * Source url to be set for the script. + */ + sourceURL: string; + /** + * Specifies whether the compiled script should be persisted. + */ + persistScript: boolean; + /** + * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + executionContextId?: ExecutionContextId | undefined; + } + interface RunScriptParameterType { + /** + * Id of the script to run. + */ + scriptId: ScriptId; + /** + * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. + */ + executionContextId?: ExecutionContextId | undefined; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Determines whether Command Line API should be available during the evaluation. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object which should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + */ + generatePreview?: boolean | undefined; + /** + * Whether execution should await for resulting value and return once awaited promise is resolved. + */ + awaitPromise?: boolean | undefined; + } + interface QueryObjectsParameterType { + /** + * Identifier of the prototype to return objects for. + */ + prototypeObjectId: RemoteObjectId; + } + interface GlobalLexicalScopeNamesParameterType { + /** + * Specifies in which execution context to lookup global scope variables. + */ + executionContextId?: ExecutionContextId | undefined; + } + interface EvaluateReturnType { + /** + * Evaluation result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface AwaitPromiseReturnType { + /** + * Promise result. Will contain rejected value if promise was rejected. + */ + result: RemoteObject; + /** + * Exception details if stack strace is available. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface CallFunctionOnReturnType { + /** + * Call result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface GetPropertiesReturnType { + /** + * Object properties. + */ + result: PropertyDescriptor[]; + /** + * Internal object properties (only of the element itself). + */ + internalProperties?: InternalPropertyDescriptor[] | undefined; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface CompileScriptReturnType { + /** + * Id of the script. + */ + scriptId?: ScriptId | undefined; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface RunScriptReturnType { + /** + * Run result. + */ + result: RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: ExceptionDetails | undefined; + } + interface QueryObjectsReturnType { + /** + * Array with objects. + */ + objects: RemoteObject; + } + interface GlobalLexicalScopeNamesReturnType { + names: string[]; + } + interface ExecutionContextCreatedEventDataType { + /** + * A newly created execution context. + */ + context: ExecutionContextDescription; + } + interface ExecutionContextDestroyedEventDataType { + /** + * Id of the destroyed context + */ + executionContextId: ExecutionContextId; + } + interface ExceptionThrownEventDataType { + /** + * Timestamp of the exception. + */ + timestamp: Timestamp; + exceptionDetails: ExceptionDetails; + } + interface ExceptionRevokedEventDataType { + /** + * Reason describing why exception was revoked. + */ + reason: string; + /** + * The id of revoked exception, as reported in exceptionThrown. + */ + exceptionId: number; + } + interface ConsoleAPICalledEventDataType { + /** + * Type of the call. + */ + type: string; + /** + * Call arguments. + */ + args: RemoteObject[]; + /** + * Identifier of the context where the call was made. + */ + executionContextId: ExecutionContextId; + /** + * Call timestamp. + */ + timestamp: Timestamp; + /** + * Stack trace captured when the call was made. + */ + stackTrace?: StackTrace | undefined; + /** + * Console context descriptor for calls on non-default console context (not console.*): 'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call on named context. + * @experimental + */ + context?: string | undefined; + } + interface InspectRequestedEventDataType { + object: RemoteObject; + hints: {}; + } + } + namespace Debugger { + /** + * Breakpoint identifier. + */ + type BreakpointId = string; + /** + * Call frame identifier. + */ + type CallFrameId = string; + /** + * Location in the source code. + */ + interface Location { + /** + * Script identifier as reported in the Debugger.scriptParsed. + */ + scriptId: Runtime.ScriptId; + /** + * Line number in the script (0-based). + */ + lineNumber: number; + /** + * Column number in the script (0-based). + */ + columnNumber?: number | undefined; + } + /** + * Location in the source code. + * @experimental + */ + interface ScriptPosition { + lineNumber: number; + columnNumber: number; + } + /** + * JavaScript call frame. Array of call frames form the call stack. + */ + interface CallFrame { + /** + * Call frame identifier. This identifier is only valid while the virtual machine is paused. + */ + callFrameId: CallFrameId; + /** + * Name of the JavaScript function called on this call frame. + */ + functionName: string; + /** + * Location in the source code. + */ + functionLocation?: Location | undefined; + /** + * Location in the source code. + */ + location: Location; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Scope chain for this call frame. + */ + scopeChain: Scope[]; + /** + * this object for this call frame. + */ + this: Runtime.RemoteObject; + /** + * The value being returned, if the function is at return point. + */ + returnValue?: Runtime.RemoteObject | undefined; + } + /** + * Scope description. + */ + interface Scope { + /** + * Scope type. + */ + type: string; + /** + * Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties. + */ + object: Runtime.RemoteObject; + name?: string | undefined; + /** + * Location in the source code where scope starts + */ + startLocation?: Location | undefined; + /** + * Location in the source code where scope ends + */ + endLocation?: Location | undefined; + } + /** + * Search match for resource. + */ + interface SearchMatch { + /** + * Line number in resource content. + */ + lineNumber: number; + /** + * Line with match content. + */ + lineContent: string; + } + interface BreakLocation { + /** + * Script identifier as reported in the Debugger.scriptParsed. + */ + scriptId: Runtime.ScriptId; + /** + * Line number in the script (0-based). + */ + lineNumber: number; + /** + * Column number in the script (0-based). + */ + columnNumber?: number | undefined; + type?: string | undefined; + } + interface SetBreakpointsActiveParameterType { + /** + * New value for breakpoints active state. + */ + active: boolean; + } + interface SetSkipAllPausesParameterType { + /** + * New value for skip pauses state. + */ + skip: boolean; + } + interface SetBreakpointByUrlParameterType { + /** + * Line number to set breakpoint at. + */ + lineNumber: number; + /** + * URL of the resources to set breakpoint on. + */ + url?: string | undefined; + /** + * Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified. + */ + urlRegex?: string | undefined; + /** + * Script hash of the resources to set breakpoint on. + */ + scriptHash?: string | undefined; + /** + * Offset in the line to set breakpoint at. + */ + columnNumber?: number | undefined; + /** + * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + condition?: string | undefined; + } + interface SetBreakpointParameterType { + /** + * Location to set breakpoint in. + */ + location: Location; + /** + * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. + */ + condition?: string | undefined; + } + interface RemoveBreakpointParameterType { + breakpointId: BreakpointId; + } + interface GetPossibleBreakpointsParameterType { + /** + * Start of range to search possible breakpoint locations in. + */ + start: Location; + /** + * End of range to search possible breakpoint locations in (excluding). When not specified, end of scripts is used as end of range. + */ + end?: Location | undefined; + /** + * Only consider locations which are in the same (non-nested) function as start. + */ + restrictToFunction?: boolean | undefined; + } + interface ContinueToLocationParameterType { + /** + * Location to continue to. + */ + location: Location; + targetCallFrames?: string | undefined; + } + interface PauseOnAsyncCallParameterType { + /** + * Debugger will pause when async call with given stack trace is started. + */ + parentStackTraceId: Runtime.StackTraceId; + } + interface StepIntoParameterType { + /** + * Debugger will issue additional Debugger.paused notification if any async task is scheduled before next pause. + * @experimental + */ + breakOnAsyncCall?: boolean | undefined; + } + interface GetStackTraceParameterType { + stackTraceId: Runtime.StackTraceId; + } + interface SearchInContentParameterType { + /** + * Id of the script to search in. + */ + scriptId: Runtime.ScriptId; + /** + * String to search for. + */ + query: string; + /** + * If true, search is case sensitive. + */ + caseSensitive?: boolean | undefined; + /** + * If true, treats string parameter as regex. + */ + isRegex?: boolean | undefined; + } + interface SetScriptSourceParameterType { + /** + * Id of the script to edit. + */ + scriptId: Runtime.ScriptId; + /** + * New content of the script. + */ + scriptSource: string; + /** + * If true the change will not actually be applied. Dry run may be used to get result description without actually modifying the code. + */ + dryRun?: boolean | undefined; + } + interface RestartFrameParameterType { + /** + * Call frame identifier to evaluate on. + */ + callFrameId: CallFrameId; + } + interface GetScriptSourceParameterType { + /** + * Id of the script to get source for. + */ + scriptId: Runtime.ScriptId; + } + interface SetPauseOnExceptionsParameterType { + /** + * Pause on exceptions mode. + */ + state: string; + } + interface EvaluateOnCallFrameParameterType { + /** + * Call frame identifier to evaluate on. + */ + callFrameId: CallFrameId; + /** + * Expression to evaluate. + */ + expression: string; + /** + * String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup). + */ + objectGroup?: string | undefined; + /** + * Specifies whether command line API should be available to the evaluated expression, defaults to false. + */ + includeCommandLineAPI?: boolean | undefined; + /** + * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. + */ + silent?: boolean | undefined; + /** + * Whether the result is expected to be a JSON object that should be sent by value. + */ + returnByValue?: boolean | undefined; + /** + * Whether preview should be generated for the result. + * @experimental + */ + generatePreview?: boolean | undefined; + /** + * Whether to throw an exception if side effect cannot be ruled out during evaluation. + */ + throwOnSideEffect?: boolean | undefined; + } + interface SetVariableValueParameterType { + /** + * 0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually. + */ + scopeNumber: number; + /** + * Variable name. + */ + variableName: string; + /** + * New variable value. + */ + newValue: Runtime.CallArgument; + /** + * Id of callframe that holds variable. + */ + callFrameId: CallFrameId; + } + interface SetReturnValueParameterType { + /** + * New return value. + */ + newValue: Runtime.CallArgument; + } + interface SetAsyncCallStackDepthParameterType { + /** + * Maximum depth of async call stacks. Setting to 0 will effectively disable collecting async call stacks (default). + */ + maxDepth: number; + } + interface SetBlackboxPatternsParameterType { + /** + * Array of regexps that will be used to check script url for blackbox state. + */ + patterns: string[]; + } + interface SetBlackboxedRangesParameterType { + /** + * Id of the script. + */ + scriptId: Runtime.ScriptId; + positions: ScriptPosition[]; + } + interface EnableReturnType { + /** + * Unique identifier of the debugger. + * @experimental + */ + debuggerId: Runtime.UniqueDebuggerId; + } + interface SetBreakpointByUrlReturnType { + /** + * Id of the created breakpoint for further reference. + */ + breakpointId: BreakpointId; + /** + * List of the locations this breakpoint resolved into upon addition. + */ + locations: Location[]; + } + interface SetBreakpointReturnType { + /** + * Id of the created breakpoint for further reference. + */ + breakpointId: BreakpointId; + /** + * Location this breakpoint resolved into. + */ + actualLocation: Location; + } + interface GetPossibleBreakpointsReturnType { + /** + * List of the possible breakpoint locations. + */ + locations: BreakLocation[]; + } + interface GetStackTraceReturnType { + stackTrace: Runtime.StackTrace; + } + interface SearchInContentReturnType { + /** + * List of search matches. + */ + result: SearchMatch[]; + } + interface SetScriptSourceReturnType { + /** + * New stack trace in case editing has happened while VM was stopped. + */ + callFrames?: CallFrame[] | undefined; + /** + * Whether current call stack was modified after applying the changes. + */ + stackChanged?: boolean | undefined; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + /** + * Exception details if any. + */ + exceptionDetails?: Runtime.ExceptionDetails | undefined; + } + interface RestartFrameReturnType { + /** + * New stack trace. + */ + callFrames: CallFrame[]; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + } + interface GetScriptSourceReturnType { + /** + * Script source. + */ + scriptSource: string; + } + interface EvaluateOnCallFrameReturnType { + /** + * Object wrapper for the evaluation result. + */ + result: Runtime.RemoteObject; + /** + * Exception details. + */ + exceptionDetails?: Runtime.ExceptionDetails | undefined; + } + interface ScriptParsedEventDataType { + /** + * Identifier of the script parsed. + */ + scriptId: Runtime.ScriptId; + /** + * URL or name of the script parsed (if any). + */ + url: string; + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + startLine: number; + /** + * Column offset of the script within the resource with given URL. + */ + startColumn: number; + /** + * Last line of the script. + */ + endLine: number; + /** + * Length of the last line of the script. + */ + endColumn: number; + /** + * Specifies script creation context. + */ + executionContextId: Runtime.ExecutionContextId; + /** + * Content hash of the script. + */ + hash: string; + /** + * Embedder-specific auxiliary data. + */ + executionContextAuxData?: {} | undefined; + /** + * True, if this script is generated as a result of the live edit operation. + * @experimental + */ + isLiveEdit?: boolean | undefined; + /** + * URL of source map associated with script (if any). + */ + sourceMapURL?: string | undefined; + /** + * True, if this script has sourceURL. + */ + hasSourceURL?: boolean | undefined; + /** + * True, if this script is ES6 module. + */ + isModule?: boolean | undefined; + /** + * This script length. + */ + length?: number | undefined; + /** + * JavaScript top stack frame of where the script parsed event was triggered if available. + * @experimental + */ + stackTrace?: Runtime.StackTrace | undefined; + } + interface ScriptFailedToParseEventDataType { + /** + * Identifier of the script parsed. + */ + scriptId: Runtime.ScriptId; + /** + * URL or name of the script parsed (if any). + */ + url: string; + /** + * Line offset of the script within the resource with given URL (for script tags). + */ + startLine: number; + /** + * Column offset of the script within the resource with given URL. + */ + startColumn: number; + /** + * Last line of the script. + */ + endLine: number; + /** + * Length of the last line of the script. + */ + endColumn: number; + /** + * Specifies script creation context. + */ + executionContextId: Runtime.ExecutionContextId; + /** + * Content hash of the script. + */ + hash: string; + /** + * Embedder-specific auxiliary data. + */ + executionContextAuxData?: {} | undefined; + /** + * URL of source map associated with script (if any). + */ + sourceMapURL?: string | undefined; + /** + * True, if this script has sourceURL. + */ + hasSourceURL?: boolean | undefined; + /** + * True, if this script is ES6 module. + */ + isModule?: boolean | undefined; + /** + * This script length. + */ + length?: number | undefined; + /** + * JavaScript top stack frame of where the script parsed event was triggered if available. + * @experimental + */ + stackTrace?: Runtime.StackTrace | undefined; + } + interface BreakpointResolvedEventDataType { + /** + * Breakpoint unique identifier. + */ + breakpointId: BreakpointId; + /** + * Actual breakpoint location. + */ + location: Location; + } + interface PausedEventDataType { + /** + * Call stack the virtual machine stopped on. + */ + callFrames: CallFrame[]; + /** + * Pause reason. + */ + reason: string; + /** + * Object containing break-specific auxiliary properties. + */ + data?: {} | undefined; + /** + * Hit breakpoints IDs + */ + hitBreakpoints?: string[] | undefined; + /** + * Async stack trace, if any. + */ + asyncStackTrace?: Runtime.StackTrace | undefined; + /** + * Async stack trace, if any. + * @experimental + */ + asyncStackTraceId?: Runtime.StackTraceId | undefined; + /** + * Just scheduled async call will have this stack trace as parent stack during async execution. This field is available only after Debugger.stepInto call with breakOnAsynCall flag. + * @experimental + */ + asyncCallStackTraceId?: Runtime.StackTraceId | undefined; + } + } + namespace Console { + /** + * Console message. + */ + interface ConsoleMessage { + /** + * Message source. + */ + source: string; + /** + * Message severity. + */ + level: string; + /** + * Message text. + */ + text: string; + /** + * URL of the message origin. + */ + url?: string | undefined; + /** + * Line number in the resource that generated this message (1-based). + */ + line?: number | undefined; + /** + * Column number in the resource that generated this message (1-based). + */ + column?: number | undefined; + } + interface MessageAddedEventDataType { + /** + * Console message that has been added. + */ + message: ConsoleMessage; + } + } + namespace Profiler { + /** + * Profile node. Holds callsite information, execution statistics and child nodes. + */ + interface ProfileNode { + /** + * Unique id of the node. + */ + id: number; + /** + * Function location. + */ + callFrame: Runtime.CallFrame; + /** + * Number of samples where this node was on top of the call stack. + */ + hitCount?: number | undefined; + /** + * Child node ids. + */ + children?: number[] | undefined; + /** + * The reason of being not optimized. The function may be deoptimized or marked as don't optimize. + */ + deoptReason?: string | undefined; + /** + * An array of source position ticks. + */ + positionTicks?: PositionTickInfo[] | undefined; + } + /** + * Profile. + */ + interface Profile { + /** + * The list of profile nodes. First item is the root node. + */ + nodes: ProfileNode[]; + /** + * Profiling start timestamp in microseconds. + */ + startTime: number; + /** + * Profiling end timestamp in microseconds. + */ + endTime: number; + /** + * Ids of samples top nodes. + */ + samples?: number[] | undefined; + /** + * Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime. + */ + timeDeltas?: number[] | undefined; + } + /** + * Specifies a number of samples attributed to a certain source position. + */ + interface PositionTickInfo { + /** + * Source line number (1-based). + */ + line: number; + /** + * Number of samples attributed to the source line. + */ + ticks: number; + } + /** + * Coverage data for a source range. + */ + interface CoverageRange { + /** + * JavaScript script source offset for the range start. + */ + startOffset: number; + /** + * JavaScript script source offset for the range end. + */ + endOffset: number; + /** + * Collected execution count of the source range. + */ + count: number; + } + /** + * Coverage data for a JavaScript function. + */ + interface FunctionCoverage { + /** + * JavaScript function name. + */ + functionName: string; + /** + * Source ranges inside the function with coverage data. + */ + ranges: CoverageRange[]; + /** + * Whether coverage data for this function has block granularity. + */ + isBlockCoverage: boolean; + } + /** + * Coverage data for a JavaScript script. + */ + interface ScriptCoverage { + /** + * JavaScript script id. + */ + scriptId: Runtime.ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Functions contained in the script that has coverage data. + */ + functions: FunctionCoverage[]; + } + /** + * Describes a type collected during runtime. + * @experimental + */ + interface TypeObject { + /** + * Name of a type collected with type profiling. + */ + name: string; + } + /** + * Source offset and types for a parameter or return value. + * @experimental + */ + interface TypeProfileEntry { + /** + * Source offset of the parameter or end of function for return values. + */ + offset: number; + /** + * The types for this parameter or return value. + */ + types: TypeObject[]; + } + /** + * Type profile data collected during runtime for a JavaScript script. + * @experimental + */ + interface ScriptTypeProfile { + /** + * JavaScript script id. + */ + scriptId: Runtime.ScriptId; + /** + * JavaScript script name or url. + */ + url: string; + /** + * Type profile entries for parameters and return values of the functions in the script. + */ + entries: TypeProfileEntry[]; + } + interface SetSamplingIntervalParameterType { + /** + * New sampling interval in microseconds. + */ + interval: number; + } + interface StartPreciseCoverageParameterType { + /** + * Collect accurate call counts beyond simple 'covered' or 'not covered'. + */ + callCount?: boolean | undefined; + /** + * Collect block-based coverage. + */ + detailed?: boolean | undefined; + } + interface StopReturnType { + /** + * Recorded profile. + */ + profile: Profile; + } + interface TakePreciseCoverageReturnType { + /** + * Coverage data for the current isolate. + */ + result: ScriptCoverage[]; + } + interface GetBestEffortCoverageReturnType { + /** + * Coverage data for the current isolate. + */ + result: ScriptCoverage[]; + } + interface TakeTypeProfileReturnType { + /** + * Type profile for all scripts since startTypeProfile() was turned on. + */ + result: ScriptTypeProfile[]; + } + interface ConsoleProfileStartedEventDataType { + id: string; + /** + * Location of console.profile(). + */ + location: Debugger.Location; + /** + * Profile title passed as an argument to console.profile(). + */ + title?: string | undefined; + } + interface ConsoleProfileFinishedEventDataType { + id: string; + /** + * Location of console.profileEnd(). + */ + location: Debugger.Location; + profile: Profile; + /** + * Profile title passed as an argument to console.profile(). + */ + title?: string | undefined; + } + } + namespace HeapProfiler { + /** + * Heap snapshot object id. + */ + type HeapSnapshotObjectId = string; + /** + * Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes. + */ + interface SamplingHeapProfileNode { + /** + * Function location. + */ + callFrame: Runtime.CallFrame; + /** + * Allocations size in bytes for the node excluding children. + */ + selfSize: number; + /** + * Child nodes. + */ + children: SamplingHeapProfileNode[]; + } + /** + * Profile. + */ + interface SamplingHeapProfile { + head: SamplingHeapProfileNode; + } + interface StartTrackingHeapObjectsParameterType { + trackAllocations?: boolean | undefined; + } + interface StopTrackingHeapObjectsParameterType { + /** + * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped. + */ + reportProgress?: boolean | undefined; + } + interface TakeHeapSnapshotParameterType { + /** + * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken. + */ + reportProgress?: boolean | undefined; + } + interface GetObjectByHeapObjectIdParameterType { + objectId: HeapSnapshotObjectId; + /** + * Symbolic group name that can be used to release multiple objects. + */ + objectGroup?: string | undefined; + } + interface AddInspectedHeapObjectParameterType { + /** + * Heap snapshot object id to be accessible by means of $x command line API. + */ + heapObjectId: HeapSnapshotObjectId; + } + interface GetHeapObjectIdParameterType { + /** + * Identifier of the object to get heap object id for. + */ + objectId: Runtime.RemoteObjectId; + } + interface StartSamplingParameterType { + /** + * Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes. + */ + samplingInterval?: number | undefined; + } + interface GetObjectByHeapObjectIdReturnType { + /** + * Evaluation result. + */ + result: Runtime.RemoteObject; + } + interface GetHeapObjectIdReturnType { + /** + * Id of the heap snapshot object corresponding to the passed remote object id. + */ + heapSnapshotObjectId: HeapSnapshotObjectId; + } + interface StopSamplingReturnType { + /** + * Recorded sampling heap profile. + */ + profile: SamplingHeapProfile; + } + interface GetSamplingProfileReturnType { + /** + * Return the sampling profile being collected. + */ + profile: SamplingHeapProfile; + } + interface AddHeapSnapshotChunkEventDataType { + chunk: string; + } + interface ReportHeapSnapshotProgressEventDataType { + done: number; + total: number; + finished?: boolean | undefined; + } + interface LastSeenObjectIdEventDataType { + lastSeenObjectId: number; + timestamp: number; + } + interface HeapStatsUpdateEventDataType { + /** + * An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment. + */ + statsUpdate: number[]; + } + } + namespace NodeTracing { + interface TraceConfig { + /** + * Controls how the trace buffer stores data. + */ + recordMode?: string | undefined; + /** + * Included category filters. + */ + includedCategories: string[]; + } + interface StartParameterType { + traceConfig: TraceConfig; + } + interface GetCategoriesReturnType { + /** + * A list of supported tracing categories. + */ + categories: string[]; + } + interface DataCollectedEventDataType { + value: Array<{}>; + } + } + namespace NodeWorker { + type WorkerID = string; + /** + * Unique identifier of attached debugging session. + */ + type SessionID = string; + interface WorkerInfo { + workerId: WorkerID; + type: string; + title: string; + url: string; + } + interface SendMessageToWorkerParameterType { + message: string; + /** + * Identifier of the session. + */ + sessionId: SessionID; + } + interface EnableParameterType { + /** + * Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger` + * message to run them. + */ + waitForDebuggerOnStart: boolean; + } + interface DetachParameterType { + sessionId: SessionID; + } + interface AttachedToWorkerEventDataType { + /** + * Identifier assigned to the session used to send/receive messages. + */ + sessionId: SessionID; + workerInfo: WorkerInfo; + waitingForDebugger: boolean; + } + interface DetachedFromWorkerEventDataType { + /** + * Detached session identifier. + */ + sessionId: SessionID; + } + interface ReceivedMessageFromWorkerEventDataType { + /** + * Identifier of a session which sends a message. + */ + sessionId: SessionID; + message: string; + } + } + namespace NodeRuntime { + interface NotifyWhenWaitingForDisconnectParameterType { + enabled: boolean; + } + } + /** + * The `inspector.Session` is used for dispatching messages to the V8 inspector + * back-end and receiving message responses and notifications. + */ + class Session extends EventEmitter { + /** + * Create a new instance of the inspector.Session class. + * The inspector session needs to be connected through session.connect() before the messages can be dispatched to the inspector backend. + */ + constructor(); + /** + * Connects a session to the inspector back-end. + * @since v8.0.0 + */ + connect(): void; + /** + * Immediately close the session. All pending message callbacks will be called + * with an error. `session.connect()` will need to be called to be able to send + * messages again. Reconnected session will lose all inspector state, such as + * enabled agents or configured breakpoints. + * @since v8.0.0 + */ + disconnect(): void; + /** + * Posts a message to the inspector back-end. `callback` will be notified when + * a response is received. `callback` is a function that accepts two optional + * arguments: error and message-specific result. + * + * ```js + * session.post('Runtime.evaluate', { expression: '2 + 2' }, + * (error, { result }) => console.log(result)); + * // Output: { type: 'number', value: 4, description: '4' } + * ``` + * + * The latest version of the V8 inspector protocol is published on the [Chrome DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/v8/). + * + * Node.js inspector supports all the Chrome DevTools Protocol domains declared + * by V8\. Chrome DevTools Protocol domain provides an interface for interacting + * with one of the runtime agents used to inspect the application state and listen + * to the run-time events. + * + * ## Example usage + * + * Apart from the debugger, various V8 Profilers are available through the DevTools + * protocol. + * @since v8.0.0 + */ + post(method: string, params?: {}, callback?: (err: Error | null, params?: {}) => void): void; + post(method: string, callback?: (err: Error | null, params?: {}) => void): void; + /** + * Returns supported domains. + */ + post( + method: 'Schema.getDomains', + callback?: (err: Error | null, params: Schema.GetDomainsReturnType) => void, + ): void; + /** + * Evaluates expression on global object. + */ + post( + method: 'Runtime.evaluate', + params?: Runtime.EvaluateParameterType, + callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void, + ): void; + post(method: 'Runtime.evaluate', callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void): void; + /** + * Add handler to promise with given promise object id. + */ + post( + method: 'Runtime.awaitPromise', + params?: Runtime.AwaitPromiseParameterType, + callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, + ): void; + post( + method: 'Runtime.awaitPromise', + callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, + ): void; + /** + * Calls function with given declaration on the given object. Object group of the result is inherited from the target object. + */ + post( + method: 'Runtime.callFunctionOn', + params?: Runtime.CallFunctionOnParameterType, + callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, + ): void; + post( + method: 'Runtime.callFunctionOn', + callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, + ): void; + /** + * Returns properties of a given object. Object group of the result is inherited from the target object. + */ + post( + method: 'Runtime.getProperties', + params?: Runtime.GetPropertiesParameterType, + callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, + ): void; + post( + method: 'Runtime.getProperties', + callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, + ): void; + /** + * Releases remote object with given id. + */ + post( + method: 'Runtime.releaseObject', + params?: Runtime.ReleaseObjectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.releaseObject', callback?: (err: Error | null) => void): void; + /** + * Releases all remote objects that belong to a given group. + */ + post( + method: 'Runtime.releaseObjectGroup', + params?: Runtime.ReleaseObjectGroupParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.releaseObjectGroup', callback?: (err: Error | null) => void): void; + /** + * Tells inspected instance to run if it was waiting for debugger to attach. + */ + post(method: 'Runtime.runIfWaitingForDebugger', callback?: (err: Error | null) => void): void; + /** + * Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context. + */ + post(method: 'Runtime.enable', callback?: (err: Error | null) => void): void; + /** + * Disables reporting of execution contexts creation. + */ + post(method: 'Runtime.disable', callback?: (err: Error | null) => void): void; + /** + * Discards collected exceptions and console API calls. + */ + post(method: 'Runtime.discardConsoleEntries', callback?: (err: Error | null) => void): void; + /** + * @experimental + */ + post( + method: 'Runtime.setCustomObjectFormatterEnabled', + params?: Runtime.SetCustomObjectFormatterEnabledParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Runtime.setCustomObjectFormatterEnabled', callback?: (err: Error | null) => void): void; + /** + * Compiles expression. + */ + post( + method: 'Runtime.compileScript', + params?: Runtime.CompileScriptParameterType, + callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, + ): void; + post( + method: 'Runtime.compileScript', + callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, + ): void; + /** + * Runs script with given id in a given context. + */ + post( + method: 'Runtime.runScript', + params?: Runtime.RunScriptParameterType, + callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, + ): void; + post( + method: 'Runtime.runScript', + callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, + ): void; + post( + method: 'Runtime.queryObjects', + params?: Runtime.QueryObjectsParameterType, + callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, + ): void; + post( + method: 'Runtime.queryObjects', + callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, + ): void; + /** + * Returns all let, const and class variables from global scope. + */ + post( + method: 'Runtime.globalLexicalScopeNames', + params?: Runtime.GlobalLexicalScopeNamesParameterType, + callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, + ): void; + post( + method: 'Runtime.globalLexicalScopeNames', + callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, + ): void; + /** + * Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received. + */ + post(method: 'Debugger.enable', callback?: (err: Error | null, params: Debugger.EnableReturnType) => void): void; + /** + * Disables debugger for given page. + */ + post(method: 'Debugger.disable', callback?: (err: Error | null) => void): void; + /** + * Activates / deactivates all breakpoints on the page. + */ + post( + method: 'Debugger.setBreakpointsActive', + params?: Debugger.SetBreakpointsActiveParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBreakpointsActive', callback?: (err: Error | null) => void): void; + /** + * Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). + */ + post( + method: 'Debugger.setSkipAllPauses', + params?: Debugger.SetSkipAllPausesParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setSkipAllPauses', callback?: (err: Error | null) => void): void; + /** + * Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads. + */ + post( + method: 'Debugger.setBreakpointByUrl', + params?: Debugger.SetBreakpointByUrlParameterType, + callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, + ): void; + post( + method: 'Debugger.setBreakpointByUrl', + callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, + ): void; + /** + * Sets JavaScript breakpoint at a given location. + */ + post( + method: 'Debugger.setBreakpoint', + params?: Debugger.SetBreakpointParameterType, + callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, + ): void; + post( + method: 'Debugger.setBreakpoint', + callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, + ): void; + /** + * Removes JavaScript breakpoint. + */ + post( + method: 'Debugger.removeBreakpoint', + params?: Debugger.RemoveBreakpointParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.removeBreakpoint', callback?: (err: Error | null) => void): void; + /** + * Returns possible locations for breakpoint. scriptId in start and end range locations should be the same. + */ + post( + method: 'Debugger.getPossibleBreakpoints', + params?: Debugger.GetPossibleBreakpointsParameterType, + callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, + ): void; + post( + method: 'Debugger.getPossibleBreakpoints', + callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, + ): void; + /** + * Continues execution until specific location is reached. + */ + post( + method: 'Debugger.continueToLocation', + params?: Debugger.ContinueToLocationParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.continueToLocation', callback?: (err: Error | null) => void): void; + /** + * @experimental + */ + post( + method: 'Debugger.pauseOnAsyncCall', + params?: Debugger.PauseOnAsyncCallParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.pauseOnAsyncCall', callback?: (err: Error | null) => void): void; + /** + * Steps over the statement. + */ + post(method: 'Debugger.stepOver', callback?: (err: Error | null) => void): void; + /** + * Steps into the function call. + */ + post( + method: 'Debugger.stepInto', + params?: Debugger.StepIntoParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.stepInto', callback?: (err: Error | null) => void): void; + /** + * Steps out of the function call. + */ + post(method: 'Debugger.stepOut', callback?: (err: Error | null) => void): void; + /** + * Stops on the next JavaScript statement. + */ + post(method: 'Debugger.pause', callback?: (err: Error | null) => void): void; + /** + * This method is deprecated - use Debugger.stepInto with breakOnAsyncCall and Debugger.pauseOnAsyncTask instead. Steps into next scheduled async task if any is scheduled before next pause. Returns success when async task is actually scheduled, returns error if no task were scheduled or another scheduleStepIntoAsync was called. + * @experimental + */ + post(method: 'Debugger.scheduleStepIntoAsync', callback?: (err: Error | null) => void): void; + /** + * Resumes JavaScript execution. + */ + post(method: 'Debugger.resume', callback?: (err: Error | null) => void): void; + /** + * Returns stack trace with given stackTraceId. + * @experimental + */ + post( + method: 'Debugger.getStackTrace', + params?: Debugger.GetStackTraceParameterType, + callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, + ): void; + post( + method: 'Debugger.getStackTrace', + callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, + ): void; + /** + * Searches for given string in script content. + */ + post( + method: 'Debugger.searchInContent', + params?: Debugger.SearchInContentParameterType, + callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, + ): void; + post( + method: 'Debugger.searchInContent', + callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, + ): void; + /** + * Edits JavaScript source live. + */ + post( + method: 'Debugger.setScriptSource', + params?: Debugger.SetScriptSourceParameterType, + callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, + ): void; + post( + method: 'Debugger.setScriptSource', + callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, + ): void; + /** + * Restarts particular call frame from the beginning. + */ + post( + method: 'Debugger.restartFrame', + params?: Debugger.RestartFrameParameterType, + callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, + ): void; + post( + method: 'Debugger.restartFrame', + callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, + ): void; + /** + * Returns source for the script with given id. + */ + post( + method: 'Debugger.getScriptSource', + params?: Debugger.GetScriptSourceParameterType, + callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, + ): void; + post( + method: 'Debugger.getScriptSource', + callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, + ): void; + /** + * Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none. + */ + post( + method: 'Debugger.setPauseOnExceptions', + params?: Debugger.SetPauseOnExceptionsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setPauseOnExceptions', callback?: (err: Error | null) => void): void; + /** + * Evaluates expression on a given call frame. + */ + post( + method: 'Debugger.evaluateOnCallFrame', + params?: Debugger.EvaluateOnCallFrameParameterType, + callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, + ): void; + post( + method: 'Debugger.evaluateOnCallFrame', + callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, + ): void; + /** + * Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually. + */ + post( + method: 'Debugger.setVariableValue', + params?: Debugger.SetVariableValueParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setVariableValue', callback?: (err: Error | null) => void): void; + /** + * Changes return value in top frame. Available only at return break position. + * @experimental + */ + post( + method: 'Debugger.setReturnValue', + params?: Debugger.SetReturnValueParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setReturnValue', callback?: (err: Error | null) => void): void; + /** + * Enables or disables async call stacks tracking. + */ + post( + method: 'Debugger.setAsyncCallStackDepth', + params?: Debugger.SetAsyncCallStackDepthParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setAsyncCallStackDepth', callback?: (err: Error | null) => void): void; + /** + * Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in scripts with url matching one of the patterns. VM will try to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. + * @experimental + */ + post( + method: 'Debugger.setBlackboxPatterns', + params?: Debugger.SetBlackboxPatternsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBlackboxPatterns', callback?: (err: Error | null) => void): void; + /** + * Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted. + * @experimental + */ + post( + method: 'Debugger.setBlackboxedRanges', + params?: Debugger.SetBlackboxedRangesParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Debugger.setBlackboxedRanges', callback?: (err: Error | null) => void): void; + /** + * Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification. + */ + post(method: 'Console.enable', callback?: (err: Error | null) => void): void; + /** + * Disables console domain, prevents further console messages from being reported to the client. + */ + post(method: 'Console.disable', callback?: (err: Error | null) => void): void; + /** + * Does nothing. + */ + post(method: 'Console.clearMessages', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.enable', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.disable', callback?: (err: Error | null) => void): void; + /** + * Changes CPU profiler sampling interval. Must be called before CPU profiles recording started. + */ + post( + method: 'Profiler.setSamplingInterval', + params?: Profiler.SetSamplingIntervalParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Profiler.setSamplingInterval', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.start', callback?: (err: Error | null) => void): void; + post(method: 'Profiler.stop', callback?: (err: Error | null, params: Profiler.StopReturnType) => void): void; + /** + * Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters. + */ + post( + method: 'Profiler.startPreciseCoverage', + params?: Profiler.StartPreciseCoverageParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'Profiler.startPreciseCoverage', callback?: (err: Error | null) => void): void; + /** + * Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code. + */ + post(method: 'Profiler.stopPreciseCoverage', callback?: (err: Error | null) => void): void; + /** + * Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started. + */ + post( + method: 'Profiler.takePreciseCoverage', + callback?: (err: Error | null, params: Profiler.TakePreciseCoverageReturnType) => void, + ): void; + /** + * Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection. + */ + post( + method: 'Profiler.getBestEffortCoverage', + callback?: (err: Error | null, params: Profiler.GetBestEffortCoverageReturnType) => void, + ): void; + /** + * Enable type profile. + * @experimental + */ + post(method: 'Profiler.startTypeProfile', callback?: (err: Error | null) => void): void; + /** + * Disable type profile. Disabling releases type profile data collected so far. + * @experimental + */ + post(method: 'Profiler.stopTypeProfile', callback?: (err: Error | null) => void): void; + /** + * Collect type profile. + * @experimental + */ + post( + method: 'Profiler.takeTypeProfile', + callback?: (err: Error | null, params: Profiler.TakeTypeProfileReturnType) => void, + ): void; + post(method: 'HeapProfiler.enable', callback?: (err: Error | null) => void): void; + post(method: 'HeapProfiler.disable', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.startTrackingHeapObjects', + params?: HeapProfiler.StartTrackingHeapObjectsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.startTrackingHeapObjects', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.stopTrackingHeapObjects', + params?: HeapProfiler.StopTrackingHeapObjectsParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.stopTrackingHeapObjects', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.takeHeapSnapshot', + params?: HeapProfiler.TakeHeapSnapshotParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.takeHeapSnapshot', callback?: (err: Error | null) => void): void; + post(method: 'HeapProfiler.collectGarbage', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.getObjectByHeapObjectId', + params?: HeapProfiler.GetObjectByHeapObjectIdParameterType, + callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getObjectByHeapObjectId', + callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, + ): void; + /** + * Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). + */ + post( + method: 'HeapProfiler.addInspectedHeapObject', + params?: HeapProfiler.AddInspectedHeapObjectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.addInspectedHeapObject', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.getHeapObjectId', + params?: HeapProfiler.GetHeapObjectIdParameterType, + callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getHeapObjectId', + callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, + ): void; + post( + method: 'HeapProfiler.startSampling', + params?: HeapProfiler.StartSamplingParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'HeapProfiler.startSampling', callback?: (err: Error | null) => void): void; + post( + method: 'HeapProfiler.stopSampling', + callback?: (err: Error | null, params: HeapProfiler.StopSamplingReturnType) => void, + ): void; + post( + method: 'HeapProfiler.getSamplingProfile', + callback?: (err: Error | null, params: HeapProfiler.GetSamplingProfileReturnType) => void, + ): void; + /** + * Gets supported tracing categories. + */ + post( + method: 'NodeTracing.getCategories', + callback?: (err: Error | null, params: NodeTracing.GetCategoriesReturnType) => void, + ): void; + /** + * Start trace events collection. + */ + post( + method: 'NodeTracing.start', + params?: NodeTracing.StartParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeTracing.start', callback?: (err: Error | null) => void): void; + /** + * Stop trace events collection. Remaining collected events will be sent as a sequence of + * dataCollected events followed by tracingComplete event. + */ + post(method: 'NodeTracing.stop', callback?: (err: Error | null) => void): void; + /** + * Sends protocol message over session with given id. + */ + post( + method: 'NodeWorker.sendMessageToWorker', + params?: NodeWorker.SendMessageToWorkerParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.sendMessageToWorker', callback?: (err: Error | null) => void): void; + /** + * Instructs the inspector to attach to running workers. Will also attach to new workers + * as they start + */ + post( + method: 'NodeWorker.enable', + params?: NodeWorker.EnableParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.enable', callback?: (err: Error | null) => void): void; + /** + * Detaches from all running workers and disables attaching to new workers as they are started. + */ + post(method: 'NodeWorker.disable', callback?: (err: Error | null) => void): void; + /** + * Detached from the worker with given sessionId. + */ + post( + method: 'NodeWorker.detach', + params?: NodeWorker.DetachParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeWorker.detach', callback?: (err: Error | null) => void): void; + /** + * Enable the `NodeRuntime.waitingForDisconnect`. + */ + post( + method: 'NodeRuntime.notifyWhenWaitingForDisconnect', + params?: NodeRuntime.NotifyWhenWaitingForDisconnectParameterType, + callback?: (err: Error | null) => void, + ): void; + post(method: 'NodeRuntime.notifyWhenWaitingForDisconnect', callback?: (err: Error | null) => void): void; + // Events + addListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + addListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + addListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + addListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + addListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + addListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + addListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + addListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + addListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + addListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + addListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + addListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + addListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + addListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + addListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + addListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + addListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + addListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + addListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + addListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + addListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + addListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + addListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + addListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + addListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + addListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + addListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + addListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + emit(event: string | symbol, ...args: any[]): boolean; + emit(event: 'inspectorNotification', message: InspectorNotification<{}>): boolean; + emit( + event: 'Runtime.executionContextCreated', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.executionContextDestroyed', + message: InspectorNotification, + ): boolean; + emit(event: 'Runtime.executionContextsCleared'): boolean; + emit( + event: 'Runtime.exceptionThrown', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.exceptionRevoked', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.consoleAPICalled', + message: InspectorNotification, + ): boolean; + emit( + event: 'Runtime.inspectRequested', + message: InspectorNotification, + ): boolean; + emit(event: 'Debugger.scriptParsed', message: InspectorNotification): boolean; + emit( + event: 'Debugger.scriptFailedToParse', + message: InspectorNotification, + ): boolean; + emit( + event: 'Debugger.breakpointResolved', + message: InspectorNotification, + ): boolean; + emit(event: 'Debugger.paused', message: InspectorNotification): boolean; + emit(event: 'Debugger.resumed'): boolean; + emit(event: 'Console.messageAdded', message: InspectorNotification): boolean; + emit( + event: 'Profiler.consoleProfileStarted', + message: InspectorNotification, + ): boolean; + emit( + event: 'Profiler.consoleProfileFinished', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.addHeapSnapshotChunk', + message: InspectorNotification, + ): boolean; + emit(event: 'HeapProfiler.resetProfiles'): boolean; + emit( + event: 'HeapProfiler.reportHeapSnapshotProgress', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.lastSeenObjectId', + message: InspectorNotification, + ): boolean; + emit( + event: 'HeapProfiler.heapStatsUpdate', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeTracing.dataCollected', + message: InspectorNotification, + ): boolean; + emit(event: 'NodeTracing.tracingComplete'): boolean; + emit( + event: 'NodeWorker.attachedToWorker', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeWorker.detachedFromWorker', + message: InspectorNotification, + ): boolean; + emit( + event: 'NodeWorker.receivedMessageFromWorker', + message: InspectorNotification, + ): boolean; + emit(event: 'NodeRuntime.waitingForDisconnect'): boolean; + on(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + on(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + on( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + on( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + on(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + on( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + on( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + on( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + on( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + on( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + on( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + on( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + on( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + on(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + on( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + on( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + on( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + on( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + on(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + on( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + on( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + on( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + on( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + on(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + on( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + on( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + on( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + on(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + once(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + once(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + once( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + once( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + once(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + once( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + once( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + once( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + once( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + once( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + once( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + once( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + once( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + once(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + once( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + once( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + once( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + once( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + once(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + once( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + once( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + once( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + once( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + once(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + once( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + once( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + once( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + once(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + prependListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + prependListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + prependListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + prependListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + prependListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + prependListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + prependListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + prependListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + prependListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + prependListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + prependListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + prependListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + prependListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + prependListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + prependListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + prependListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + prependListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + prependListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + prependListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + prependListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + prependListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + prependListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + prependListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + prependListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + prependListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + /** + * Emitted when any notification from the V8 Inspector is received. + */ + prependOnceListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; + /** + * Issued when new execution context is created. + */ + prependOnceListener( + event: 'Runtime.executionContextCreated', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when execution context is destroyed. + */ + prependOnceListener( + event: 'Runtime.executionContextDestroyed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when all executionContexts were cleared in browser + */ + prependOnceListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; + /** + * Issued when exception was thrown and unhandled. + */ + prependOnceListener( + event: 'Runtime.exceptionThrown', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when unhandled exception was revoked. + */ + prependOnceListener( + event: 'Runtime.exceptionRevoked', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when console API was called. + */ + prependOnceListener( + event: 'Runtime.consoleAPICalled', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when object should be inspected (for example, as a result of inspect() command line API call). + */ + prependOnceListener( + event: 'Runtime.inspectRequested', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. + */ + prependOnceListener( + event: 'Debugger.scriptParsed', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when virtual machine fails to parse the script. + */ + prependOnceListener( + event: 'Debugger.scriptFailedToParse', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when breakpoint is resolved to an actual script and location. + */ + prependOnceListener( + event: 'Debugger.breakpointResolved', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. + */ + prependOnceListener( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Fired when the virtual machine resumed execution. + */ + prependOnceListener(event: 'Debugger.resumed', listener: () => void): this; + /** + * Issued when new console message is added. + */ + prependOnceListener( + event: 'Console.messageAdded', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Sent when new profile recording is started using console.profile() call. + */ + prependOnceListener( + event: 'Profiler.consoleProfileStarted', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener( + event: 'Profiler.consoleProfileFinished', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener( + event: 'HeapProfiler.addHeapSnapshotChunk', + listener: (message: InspectorNotification) => void, + ): this; + prependOnceListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; + prependOnceListener( + event: 'HeapProfiler.reportHeapSnapshotProgress', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. + */ + prependOnceListener( + event: 'HeapProfiler.lastSeenObjectId', + listener: (message: InspectorNotification) => void, + ): this; + /** + * If heap objects tracking has been started then backend may send update for one or more fragments + */ + prependOnceListener( + event: 'HeapProfiler.heapStatsUpdate', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Contains an bucket of collected trace events. + */ + prependOnceListener( + event: 'NodeTracing.dataCollected', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Signals that tracing is stopped and there is no trace buffers pending flush, all data were + * delivered via dataCollected events. + */ + prependOnceListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; + /** + * Issued when attached to a worker. + */ + prependOnceListener( + event: 'NodeWorker.attachedToWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Issued when detached from the worker. + */ + prependOnceListener( + event: 'NodeWorker.detachedFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * Notifies about a new protocol message received from the session + * (session ID is provided in attachedToWorker notification). + */ + prependOnceListener( + event: 'NodeWorker.receivedMessageFromWorker', + listener: (message: InspectorNotification) => void, + ): this; + /** + * This event is fired instead of `Runtime.executionContextDestroyed` when + * enabled. + * It is fired when the Node process finished all code execution and is + * waiting for all frontends to disconnect. + */ + prependOnceListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; + } + /** + * Activate inspector on host and port. Equivalent to`node --inspect=[[host:]port]`, but can be done programmatically after node has + * started. + * + * If wait is `true`, will block until a client has connected to the inspect port + * and flow control has been passed to the debugger client. + * + * See the `security warning` regarding the `host`parameter usage. + * @param [port='what was specified on the CLI'] Port to listen on for inspector connections. Optional. + * @param [host='what was specified on the CLI'] Host to listen on for inspector connections. Optional. + * @param [wait=false] Block until a client has connected. Optional. + */ + function open(port?: number, host?: string, wait?: boolean): void; + /** + * Deactivate the inspector. Blocks until there are no active connections. + */ + function close(): void; + /** + * Return the URL of the active inspector, or `undefined` if there is none. + * + * ```console + * $ node --inspect -p 'inspector.url()' + * Debugger listening on ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 + * For help, see: https://nodejs.org/en/docs/inspector + * ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 + * + * $ node --inspect=localhost:3000 -p 'inspector.url()' + * Debugger listening on ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a + * For help, see: https://nodejs.org/en/docs/inspector + * ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a + * + * $ node -p 'inspector.url()' + * undefined + * ``` + */ + function url(): string | undefined; + /** + * Blocks until a client (existing or connected later) has sent`Runtime.runIfWaitingForDebugger` command. + * + * An exception will be thrown if there is no active inspector. + * @since v12.7.0 + */ + function waitForDebugger(): void; +} +/** + * The inspector module provides an API for interacting with the V8 inspector. + */ +declare module 'node:inspector' { + import inspector = require('inspector'); + export = inspector; +} + +/** + * @types/node doesn't have a `node:inspector/promises` module, maybe because it's still experimental? + */ +declare module 'node:inspector/promises' { + /** + * Async Debugger session + */ + class Session { + constructor(); + + connect(): void; + + post(method: 'Debugger.pause' | 'Debugger.resume' | 'Debugger.enable' | 'Debugger.disable'): Promise; + post(method: 'Debugger.setPauseOnExceptions', params: Debugger.SetPauseOnExceptionsParameterType): Promise; + post( + method: 'Runtime.getProperties', + params: Runtime.GetPropertiesParameterType, + ): Promise; + + on( + event: 'Debugger.paused', + listener: (message: InspectorNotification) => void, + ): Session; + + on(event: 'Debugger.resumed', listener: () => void): Session; + } +} diff --git a/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts new file mode 100644 index 000000000000..db065466e82a --- /dev/null +++ b/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts @@ -0,0 +1,267 @@ +import type { Session } from 'node:inspector/promises'; +import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; +import { LRUMap, dynamicRequire, logger } from '@sentry/utils'; +import type { Debugger, InspectorNotification, Runtime } from 'inspector'; + +import type { NodeClient } from '../../sdk/client'; +import type { NodeClientOptions } from '../../types'; +import type { + FrameVariables, + LocalVariablesIntegrationOptions, + PausedExceptionEvent, + RateLimitIncrement, + Variables, +} from './common'; +import { createRateLimiter, functionNamesMatch, hashFrames, hashFromStack } from './common'; + +async function unrollArray(session: Session, objectId: string, name: string, vars: Variables): Promise { + const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { + objectId, + ownProperties: true, + }); + + vars[name] = properties.result + .filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10))) + .sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)) + .map(v => v.value?.value); +} + +async function unrollObject(session: Session, objectId: string, name: string, vars: Variables): Promise { + const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { + objectId, + ownProperties: true, + }); + + vars[name] = properties.result + .map<[string, unknown]>(v => [v.name, v.value?.value]) + .reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {} as Variables); +} + +function unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables): void { + if (prop?.value?.value) { + vars[prop.name] = prop.value.value; + } else if (prop?.value?.description && prop?.value?.type !== 'function') { + vars[prop.name] = `<${prop.value.description}>`; + } +} + +async function getLocalVariables(session: Session, objectId: string): Promise { + const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { + objectId, + ownProperties: true, + }); + const variables = {}; + + for (const prop of properties.result) { + if (prop?.value?.objectId && prop?.value.className === 'Array') { + const id = prop.value.objectId; + await unrollArray(session, id, prop.name, variables); + } else if (prop?.value?.objectId && prop?.value?.className === 'Object') { + const id = prop.value.objectId; + await unrollObject(session, id, prop.name, variables); + } else if (prop?.value?.value || prop?.value?.description) { + unrollOther(prop, variables); + } + } + + return variables; +} + +const INTEGRATION_NAME = 'LocalVariablesAsync'; + +/** + * Adds local variables to exception frames + */ +const _localVariablesAsyncIntegration = ((options: LocalVariablesIntegrationOptions = {}) => { + const cachedFrames: LRUMap = new LRUMap(20); + let rateLimiter: RateLimitIncrement | undefined; + let shouldProcessEvent = false; + + async function handlePaused( + session: Session, + stackParser: StackParser, + { reason, data, callFrames }: PausedExceptionEvent, + ): Promise { + if (reason !== 'exception' && reason !== 'promiseRejection') { + return; + } + + rateLimiter?.(); + + // data.description contains the original error.stack + const exceptionHash = hashFromStack(stackParser, data?.description); + + if (exceptionHash == undefined) { + return; + } + + const frames = []; + + for (let i = 0; i < callFrames.length; i++) { + const { scopeChain, functionName, this: obj } = callFrames[i]; + + const localScope = scopeChain.find(scope => scope.type === 'local'); + + // obj.className is undefined in ESM modules + const fn = obj.className === 'global' || !obj.className ? functionName : `${obj.className}.${functionName}`; + + if (localScope?.object.objectId === undefined) { + frames[i] = { function: fn }; + } else { + const vars = await getLocalVariables(session, localScope.object.objectId); + frames[i] = { function: fn, vars }; + } + } + + cachedFrames.set(exceptionHash, frames); + } + + async function startDebugger(session: Session, clientOptions: NodeClientOptions): Promise { + session.connect(); + + let isPaused = false; + + session.on('Debugger.resumed', () => { + isPaused = false; + }); + + session.on('Debugger.paused', (event: InspectorNotification) => { + isPaused = true; + + handlePaused(session, clientOptions.stackParser, event.params as PausedExceptionEvent).then( + () => { + // After the pause work is complete, resume execution! + return isPaused ? session.post('Debugger.resume') : Promise.resolve(); + }, + _ => { + // ignore + }, + ); + }); + + await session.post('Debugger.enable'); + + const captureAll = options.captureAllExceptions !== false; + await session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); + + if (captureAll) { + const max = options.maxExceptionsPerSecond || 50; + + rateLimiter = createRateLimiter( + max, + () => { + logger.log('Local variables rate-limit lifted.'); + return session.post('Debugger.setPauseOnExceptions', { state: 'all' }); + }, + seconds => { + logger.log( + `Local variables rate-limit exceeded. Disabling capturing of caught exceptions for ${seconds} seconds.`, + ); + return session.post('Debugger.setPauseOnExceptions', { state: 'uncaught' }); + }, + ); + } + + shouldProcessEvent = true; + } + + function addLocalVariablesToException(exception: Exception): void { + const hash = hashFrames(exception.stacktrace?.frames); + + if (hash === undefined) { + return; + } + + // Check if we have local variables for an exception that matches the hash + // remove is identical to get but also removes the entry from the cache + const cachedFrame = cachedFrames.remove(hash); + + if (cachedFrame === undefined) { + return; + } + + const frameCount = exception.stacktrace?.frames?.length || 0; + + for (let i = 0; i < frameCount; i++) { + // Sentry frames are in reverse order + const frameIndex = frameCount - i - 1; + + // Drop out if we run out of frames to match up + if (!exception.stacktrace?.frames?.[frameIndex] || !cachedFrame[i]) { + break; + } + + if ( + // We need to have vars to add + cachedFrame[i].vars === undefined || + // We're not interested in frames that are not in_app because the vars are not relevant + exception.stacktrace.frames[frameIndex].in_app === false || + // The function names need to match + !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrame[i].function) + ) { + continue; + } + + exception.stacktrace.frames[frameIndex].vars = cachedFrame[i].vars; + } + } + + function addLocalVariablesToEvent(event: Event): Event { + for (const exception of event.exception?.values || []) { + addLocalVariablesToException(exception); + } + + return event; + } + + return { + name: INTEGRATION_NAME, + setup(client: NodeClient) { + const clientOptions = client.getOptions(); + + if (!clientOptions.includeLocalVariables) { + return; + } + + try { + // TODO: Use import()... + // It would be nice to use import() here, but this built-in library is not in Node <19 so webpack will pick it + // up and report it as a missing dependency + const { Session } = dynamicRequire(module, 'node:inspector/promises'); + + startDebugger(new Session(), clientOptions).catch(e => { + logger.error('Failed to start inspector session', e); + }); + } catch (e) { + logger.error('Failed to load inspector API', e); + return; + } + }, + processEvent(event: Event): Event { + if (shouldProcessEvent) { + return addLocalVariablesToEvent(event); + } + + return event; + }, + }; +}) satisfies IntegrationFn; + +export const localVariablesAsyncIntegration = defineIntegration(_localVariablesAsyncIntegration); + +/** + * Adds local variables to exception frames. + * @deprecated Use `localVariablesAsyncIntegration()` instead. + */ +// eslint-disable-next-line deprecation/deprecation +export const LocalVariablesAsync = convertIntegrationFnToClass( + INTEGRATION_NAME, + localVariablesAsyncIntegration, +) as IntegrationClass Event; setup: (client: NodeClient) => void }>; + +// eslint-disable-next-line deprecation/deprecation +export type LocalVariablesAsync = typeof LocalVariablesAsync; diff --git a/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts new file mode 100644 index 000000000000..b014ed0adb77 --- /dev/null +++ b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts @@ -0,0 +1,409 @@ +import { convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core'; +import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; +import { LRUMap, logger } from '@sentry/utils'; +import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector'; + +import { NODE_MAJOR } from '../../nodeVersion'; +import type { NodeClient } from '../../sdk/client'; +import type { + FrameVariables, + LocalVariablesIntegrationOptions, + PausedExceptionEvent, + RateLimitIncrement, + Variables, +} from './common'; +import { createRateLimiter, functionNamesMatch, hashFrames, hashFromStack } from './common'; + +type OnPauseEvent = InspectorNotification; +export interface DebugSession { + /** Configures and connects to the debug session */ + configureAndConnect(onPause: (message: OnPauseEvent, complete: () => void) => void, captureAll: boolean): void; + /** Updates which kind of exceptions to capture */ + setPauseOnExceptions(captureAll: boolean): void; + /** Gets local variables for an objectId */ + getLocalVariables(objectId: string, callback: (vars: Variables) => void): void; +} + +type Next = (result: T) => void; +type Add = (fn: Next) => void; +type CallbackWrapper = { add: Add; next: Next }; + +/** Creates a container for callbacks to be called sequentially */ +export function createCallbackList(complete: Next): CallbackWrapper { + // A collection of callbacks to be executed last to first + let callbacks: Next[] = []; + + let completedCalled = false; + function checkedComplete(result: T): void { + callbacks = []; + if (completedCalled) { + return; + } + completedCalled = true; + complete(result); + } + + // complete should be called last + callbacks.push(checkedComplete); + + function add(fn: Next): void { + callbacks.push(fn); + } + + function next(result: T): void { + const popped = callbacks.pop() || checkedComplete; + + try { + popped(result); + } catch (_) { + // If there is an error, we still want to call the complete callback + checkedComplete(result); + } + } + + return { add, next }; +} + +/** + * Promise API is available as `Experimental` and in Node 19 only. + * + * Callback-based API is `Stable` since v14 and `Experimental` since v8. + * Because of that, we are creating our own `AsyncSession` class. + * + * https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api + * https://nodejs.org/docs/latest-v14.x/api/inspector.html + */ +class AsyncSession implements DebugSession { + private readonly _session: Session; + + /** Throws if inspector API is not available */ + public constructor() { + /* + TODO: We really should get rid of this require statement below for a couple of reasons: + 1. It makes the integration unusable in the SvelteKit SDK, as it's not possible to use `require` + in SvelteKit server code (at least not by default). + 2. Throwing in a constructor is bad practice + + More context for a future attempt to fix this: + We already tried replacing it with import but didn't get it to work because of async problems. + We still called import in the constructor but assigned to a promise which we "awaited" in + `configureAndConnect`. However, this broke the Node integration tests as no local variables + were reported any more. We probably missed a place where we need to await the promise, too. + */ + + // Node can be built without inspector support so this can throw + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { Session } = require('inspector'); + this._session = new Session(); + } + + /** @inheritdoc */ + public configureAndConnect(onPause: (event: OnPauseEvent, complete: () => void) => void, captureAll: boolean): void { + this._session.connect(); + + this._session.on('Debugger.paused', event => { + onPause(event, () => { + // After the pause work is complete, resume execution or the exception context memory is leaked + this._session.post('Debugger.resume'); + }); + }); + + this._session.post('Debugger.enable'); + this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); + } + + public setPauseOnExceptions(captureAll: boolean): void { + this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); + } + + /** @inheritdoc */ + public getLocalVariables(objectId: string, complete: (vars: Variables) => void): void { + this._getProperties(objectId, props => { + const { add, next } = createCallbackList(complete); + + for (const prop of props) { + if (prop?.value?.objectId && prop?.value.className === 'Array') { + const id = prop.value.objectId; + add(vars => this._unrollArray(id, prop.name, vars, next)); + } else if (prop?.value?.objectId && prop?.value?.className === 'Object') { + const id = prop.value.objectId; + add(vars => this._unrollObject(id, prop.name, vars, next)); + } else if (prop?.value?.value || prop?.value?.description) { + add(vars => this._unrollOther(prop, vars, next)); + } + } + + next({}); + }); + } + + /** + * Gets all the PropertyDescriptors of an object + */ + private _getProperties(objectId: string, next: (result: Runtime.PropertyDescriptor[]) => void): void { + this._session.post( + 'Runtime.getProperties', + { + objectId, + ownProperties: true, + }, + (err, params) => { + if (err) { + next([]); + } else { + next(params.result); + } + }, + ); + } + + /** + * Unrolls an array property + */ + private _unrollArray(objectId: string, name: string, vars: Variables, next: (vars: Variables) => void): void { + this._getProperties(objectId, props => { + vars[name] = props + .filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10))) + .sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)) + .map(v => v?.value?.value); + + next(vars); + }); + } + + /** + * Unrolls an object property + */ + private _unrollObject(objectId: string, name: string, vars: Variables, next: (obj: Variables) => void): void { + this._getProperties(objectId, props => { + vars[name] = props + .map<[string, unknown]>(v => [v.name, v?.value?.value]) + .reduce((obj, [key, val]) => { + obj[key] = val; + return obj; + }, {} as Variables); + + next(vars); + }); + } + + /** + * Unrolls other properties + */ + private _unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables, next: (vars: Variables) => void): void { + if (prop?.value?.value) { + vars[prop.name] = prop.value.value; + } else if (prop?.value?.description && prop?.value?.type !== 'function') { + vars[prop.name] = `<${prop.value.description}>`; + } + + next(vars); + } +} + +/** + * When using Vercel pkg, the inspector module is not available. + * https://github.com/getsentry/sentry-javascript/issues/6769 + */ +function tryNewAsyncSession(): AsyncSession | undefined { + try { + return new AsyncSession(); + } catch (e) { + return undefined; + } +} + +const INTEGRATION_NAME = 'LocalVariables'; + +/** + * Adds local variables to exception frames + */ +const _localVariablesSyncIntegration = (( + options: LocalVariablesIntegrationOptions = {}, + session: DebugSession | undefined = tryNewAsyncSession(), +) => { + const cachedFrames: LRUMap = new LRUMap(20); + let rateLimiter: RateLimitIncrement | undefined; + let shouldProcessEvent = false; + + function handlePaused( + stackParser: StackParser, + { params: { reason, data, callFrames } }: InspectorNotification, + complete: () => void, + ): void { + if (reason !== 'exception' && reason !== 'promiseRejection') { + complete(); + return; + } + + rateLimiter?.(); + + // data.description contains the original error.stack + const exceptionHash = hashFromStack(stackParser, data?.description); + + if (exceptionHash == undefined) { + complete(); + return; + } + + const { add, next } = createCallbackList(frames => { + cachedFrames.set(exceptionHash, frames); + complete(); + }); + + // Because we're queuing up and making all these calls synchronously, we can potentially overflow the stack + // For this reason we only attempt to get local variables for the first 5 frames + for (let i = 0; i < Math.min(callFrames.length, 5); i++) { + const { scopeChain, functionName, this: obj } = callFrames[i]; + + const localScope = scopeChain.find(scope => scope.type === 'local'); + + // obj.className is undefined in ESM modules + const fn = obj.className === 'global' || !obj.className ? functionName : `${obj.className}.${functionName}`; + + if (localScope?.object.objectId === undefined) { + add(frames => { + frames[i] = { function: fn }; + next(frames); + }); + } else { + const id = localScope.object.objectId; + add(frames => + session?.getLocalVariables(id, vars => { + frames[i] = { function: fn, vars }; + next(frames); + }), + ); + } + } + + next([]); + } + + function addLocalVariablesToException(exception: Exception): void { + const hash = hashFrames(exception?.stacktrace?.frames); + + if (hash === undefined) { + return; + } + + // Check if we have local variables for an exception that matches the hash + // remove is identical to get but also removes the entry from the cache + const cachedFrame = cachedFrames.remove(hash); + + if (cachedFrame === undefined) { + return; + } + + const frameCount = exception.stacktrace?.frames?.length || 0; + + for (let i = 0; i < frameCount; i++) { + // Sentry frames are in reverse order + const frameIndex = frameCount - i - 1; + + // Drop out if we run out of frames to match up + if (!exception?.stacktrace?.frames?.[frameIndex] || !cachedFrame[i]) { + break; + } + + if ( + // We need to have vars to add + cachedFrame[i].vars === undefined || + // We're not interested in frames that are not in_app because the vars are not relevant + exception.stacktrace.frames[frameIndex].in_app === false || + // The function names need to match + !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrame[i].function) + ) { + continue; + } + + exception.stacktrace.frames[frameIndex].vars = cachedFrame[i].vars; + } + } + + function addLocalVariablesToEvent(event: Event): Event { + for (const exception of event?.exception?.values || []) { + addLocalVariablesToException(exception); + } + + return event; + } + + return { + name: INTEGRATION_NAME, + setupOnce() { + const client = getClient(); + const clientOptions = client?.getOptions(); + + if (session && clientOptions?.includeLocalVariables) { + // Only setup this integration if the Node version is >= v18 + // https://github.com/getsentry/sentry-javascript/issues/7697 + const unsupportedNodeVersion = NODE_MAJOR < 18; + + if (unsupportedNodeVersion) { + logger.log('The `LocalVariables` integration is only supported on Node >= v18.'); + return; + } + + const captureAll = options.captureAllExceptions !== false; + + session.configureAndConnect( + (ev, complete) => + handlePaused(clientOptions.stackParser, ev as InspectorNotification, complete), + captureAll, + ); + + if (captureAll) { + const max = options.maxExceptionsPerSecond || 50; + + rateLimiter = createRateLimiter( + max, + () => { + logger.log('Local variables rate-limit lifted.'); + session?.setPauseOnExceptions(true); + }, + seconds => { + logger.log( + `Local variables rate-limit exceeded. Disabling capturing of caught exceptions for ${seconds} seconds.`, + ); + session?.setPauseOnExceptions(false); + }, + ); + } + + shouldProcessEvent = true; + } + }, + processEvent(event: Event): Event { + if (shouldProcessEvent) { + return addLocalVariablesToEvent(event); + } + + return event; + }, + // These are entirely for testing + _getCachedFramesCount(): number { + return cachedFrames.size; + }, + _getFirstCachedFrame(): FrameVariables[] | undefined { + return cachedFrames.values()[0]; + }, + }; +}) satisfies IntegrationFn; + +export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration); + +/** + * Adds local variables to exception frames. + * @deprecated Use `localVariablesSyncIntegration()` instead. + */ +// eslint-disable-next-line deprecation/deprecation +export const LocalVariablesSync = convertIntegrationFnToClass( + INTEGRATION_NAME, + localVariablesSyncIntegration, +) as IntegrationClass Event; setup: (client: NodeClient) => void }> & { + new (options?: LocalVariablesIntegrationOptions, session?: DebugSession): Integration; +}; + +// eslint-disable-next-line deprecation/deprecation +export type LocalVariablesSync = typeof LocalVariablesSync; diff --git a/packages/node-experimental/src/integrations/modules.ts b/packages/node-experimental/src/integrations/modules.ts new file mode 100644 index 000000000000..ad30bb4d7a3b --- /dev/null +++ b/packages/node-experimental/src/integrations/modules.ts @@ -0,0 +1,96 @@ +import { existsSync, readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +let moduleCache: { [key: string]: string }; + +const INTEGRATION_NAME = 'Modules'; + +const _modulesIntegration = (() => { + return { + name: INTEGRATION_NAME, + processEvent(event) { + event.modules = { + ...event.modules, + ..._getModules(), + }; + + return event; + }, + }; +}) satisfies IntegrationFn; + +/** + * Add node modules / packages to the event. + */ +export const modulesIntegration = defineIntegration(_modulesIntegration); + +/** Extract information about paths */ +function getPaths(): string[] { + try { + return require.cache ? Object.keys(require.cache as Record) : []; + } catch (e) { + return []; + } +} + +/** Extract information about package.json modules */ +function collectModules(): { + [name: string]: string; +} { + const mainPaths = (require.main && require.main.paths) || []; + const paths = getPaths(); + const infos: { + [name: string]: string; + } = {}; + const seen: { + [path: string]: boolean; + } = {}; + + paths.forEach(path => { + let dir = path; + + /** Traverse directories upward in the search of package.json file */ + const updir = (): void | (() => void) => { + const orig = dir; + dir = dirname(orig); + + if (!dir || orig === dir || seen[orig]) { + return undefined; + } + if (mainPaths.indexOf(dir) < 0) { + return updir(); + } + + const pkgfile = join(orig, 'package.json'); + seen[orig] = true; + + if (!existsSync(pkgfile)) { + return updir(); + } + + try { + const info = JSON.parse(readFileSync(pkgfile, 'utf8')) as { + name: string; + version: string; + }; + infos[info.name] = info.version; + } catch (_oO) { + // no-empty + } + }; + + updir(); + }); + + return infos; +} + +/** Fetches the list of modules and the versions loaded by the entry file for your node.js app. */ +function _getModules(): { [key: string]: string } { + if (!moduleCache) { + moduleCache = collectModules(); + } + return moduleCache; +} diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node-experimental/src/integrations/node-fetch.ts index 94eca67c29ba..254b3f93e930 100644 --- a/packages/node-experimental/src/integrations/node-fetch.ts +++ b/packages/node-experimental/src/integrations/node-fetch.ts @@ -5,12 +5,10 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { addBreadcrumb, defineIntegration } from '@sentry/core'; import { _INTERNAL, getSpanKind } from '@sentry/opentelemetry'; import type { IntegrationFn } from '@sentry/types'; -import { parseSemver } from '@sentry/utils'; +import { NODE_MAJOR } from '../nodeVersion'; import { addOriginToSpan } from '../utils/addOriginToSpan'; -const NODE_VERSION: ReturnType = parseSemver(process.versions.node); - interface NodeFetchOptions { /** * Whether breadcrumbs should be recorded for requests. @@ -31,7 +29,7 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => { function getInstrumentation(): [Instrumentation] | void { // Only add NodeFetch if Node >= 16, as previous versions do not support it - if (!NODE_VERSION.major || NODE_VERSION.major < 16) { + if (NODE_MAJOR < 16) { return; } diff --git a/packages/node-experimental/src/integrations/onuncaughtexception.ts b/packages/node-experimental/src/integrations/onuncaughtexception.ts new file mode 100644 index 000000000000..84cae854ab81 --- /dev/null +++ b/packages/node-experimental/src/integrations/onuncaughtexception.ts @@ -0,0 +1,171 @@ +import { captureException, defineIntegration } from '@sentry/core'; +import { getClient } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { logger } from '@sentry/utils'; + +import { DEBUG_BUILD } from '../debug-build'; +import type { NodeClient } from '../sdk/client'; +import { logAndExitProcess } from '../utils/errorhandling'; + +type OnFatalErrorHandler = (firstError: Error, secondError?: Error) => void; + +type TaggedListener = NodeJS.UncaughtExceptionListener & { + tag?: string; +}; + +// CAREFUL: Please think twice before updating the way _options looks because the Next.js SDK depends on it in `index.server.ts` +interface OnUncaughtExceptionOptions { + // TODO(v8): Evaluate whether we should switch the default behaviour here. + // Also, we can evaluate using https://nodejs.org/api/process.html#event-uncaughtexceptionmonitor per default, and + // falling back to current behaviour when that's not available. + /** + * Controls if the SDK should register a handler to exit the process on uncaught errors: + * - `true`: The SDK will exit the process on all uncaught errors. + * - `false`: The SDK will only exit the process when there are no other `uncaughtException` handlers attached. + * + * Default: `true` + */ + exitEvenIfOtherHandlersAreRegistered: boolean; + + /** + * This is called when an uncaught error would cause the process to exit. + * + * @param firstError Uncaught error causing the process to exit + * @param secondError Will be set if the handler was called multiple times. This can happen either because + * `onFatalError` itself threw, or because an independent error happened somewhere else while `onFatalError` + * was running. + */ + onFatalError?(this: void, firstError: Error, secondError?: Error): void; +} + +const INTEGRATION_NAME = 'OnUncaughtException'; + +const _onUncaughtExceptionIntegration = ((options: Partial = {}) => { + const _options = { + exitEvenIfOtherHandlersAreRegistered: true, + ...options, + }; + + return { + name: INTEGRATION_NAME, + setup(client: NodeClient) { + global.process.on('uncaughtException', makeErrorHandler(client, _options)); + }, + }; +}) satisfies IntegrationFn; + +/** + * Add a global exception handler. + */ +export const onUncaughtExceptionIntegration = defineIntegration(_onUncaughtExceptionIntegration); + +type ErrorHandler = { _errorHandler: boolean } & ((error: Error) => void); + +/** Exported only for tests */ +export function makeErrorHandler(client: NodeClient, options: OnUncaughtExceptionOptions): ErrorHandler { + const timeout = 2000; + let caughtFirstError: boolean = false; + let caughtSecondError: boolean = false; + let calledFatalError: boolean = false; + let firstError: Error; + + const clientOptions = client.getOptions(); + + return Object.assign( + (error: Error): void => { + let onFatalError: OnFatalErrorHandler = logAndExitProcess; + + if (options.onFatalError) { + onFatalError = options.onFatalError; + } else if (clientOptions.onFatalError) { + onFatalError = clientOptions.onFatalError as OnFatalErrorHandler; + } + + // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not + // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust + // exit behaviour of the SDK accordingly: + // - If other listeners are attached, do not exit. + // - If the only listener attached is ours, exit. + const userProvidedListenersCount = ( + global.process.listeners('uncaughtException') as TaggedListener[] + ).reduce((acc, listener) => { + if ( + // There are 3 listeners we ignore: + listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself + (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing + (listener as ErrorHandler)._errorHandler // the handler we register in this integration + ) { + return acc; + } else { + return acc + 1; + } + }, 0); + + const processWouldExit = userProvidedListenersCount === 0; + const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit; + + if (!caughtFirstError) { + // this is the first uncaught error and the ultimate reason for shutting down + // we want to do absolutely everything possible to ensure it gets captured + // also we want to make sure we don't go recursion crazy if more errors happen after this one + firstError = error; + caughtFirstError = true; + + if (getClient() === client) { + captureException(error, { + originalException: error, + captureContext: { + level: 'fatal', + }, + mechanism: { + handled: false, + type: 'onuncaughtexception', + }, + }); + } + + if (!calledFatalError && shouldApplyFatalHandlingLogic) { + calledFatalError = true; + onFatalError(error); + } + } else { + if (shouldApplyFatalHandlingLogic) { + if (calledFatalError) { + // we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down + DEBUG_BUILD && + logger.warn( + 'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown', + ); + logAndExitProcess(error); + } else if (!caughtSecondError) { + // two cases for how we can hit this branch: + // - capturing of first error blew up and we just caught the exception from that + // - quit trying to capture, proceed with shutdown + // - a second independent error happened while waiting for first error to capture + // - want to avoid causing premature shutdown before first error capture finishes + // it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff + // so let's instead just delay a bit before we proceed with our action here + // in case 1, we just wait a bit unnecessarily but ultimately do the same thing + // in case 2, the delay hopefully made us wait long enough for the capture to finish + // two potential nonideal outcomes: + // nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError + // nonideal case 2: case 2 happens, 1st error is captured but slowly, timeout completes before capture and we treat second error as the sendErr of (nonexistent) failure from trying to capture first error + // note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError) + // we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish + caughtSecondError = true; + setTimeout(() => { + if (!calledFatalError) { + // it was probably case 1, let's treat err as the sendErr and call onFatalError + calledFatalError = true; + onFatalError(firstError, error); + } else { + // it was probably case 2, our first error finished capturing while we waited, cool, do nothing + } + }, timeout); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc + } + } + } + }, + { _errorHandler: true }, + ); +} diff --git a/packages/node-experimental/src/integrations/onunhandledrejection.ts b/packages/node-experimental/src/integrations/onunhandledrejection.ts new file mode 100644 index 000000000000..e1bc0b4145cf --- /dev/null +++ b/packages/node-experimental/src/integrations/onunhandledrejection.ts @@ -0,0 +1,95 @@ +import { captureException, defineIntegration, getClient } from '@sentry/core'; +import type { Client, IntegrationFn } from '@sentry/types'; +import { consoleSandbox } from '@sentry/utils'; +import { logAndExitProcess } from '../utils/errorhandling'; + +type UnhandledRejectionMode = 'none' | 'warn' | 'strict'; + +interface OnUnhandledRejectionOptions { + /** + * Option deciding what to do after capturing unhandledRejection, + * that mimicks behavior of node's --unhandled-rejection flag. + */ + mode: UnhandledRejectionMode; +} + +const INTEGRATION_NAME = 'OnUnhandledRejection'; + +const _onUnhandledRejectionIntegration = ((options: Partial = {}) => { + const mode = options.mode || 'warn'; + + return { + name: INTEGRATION_NAME, + setup(client) { + global.process.on('unhandledRejection', makeUnhandledPromiseHandler(client, { mode })); + }, + }; +}) satisfies IntegrationFn; + +/** + * Add a global promise rejection handler. + */ +export const onUnhandledRejectionIntegration = defineIntegration(_onUnhandledRejectionIntegration); + +/** + * Send an exception with reason + * @param reason string + * @param promise promise + * + * Exported only for tests. + */ +export function makeUnhandledPromiseHandler( + client: Client, + options: OnUnhandledRejectionOptions, +): (reason: unknown, promise: unknown) => void { + return function sendUnhandledPromise(reason: unknown, promise: unknown): void { + if (getClient() !== client) { + return; + } + + captureException(reason, { + originalException: promise, + captureContext: { + extra: { unhandledPromiseRejection: true }, + }, + mechanism: { + handled: false, + type: 'onunhandledrejection', + }, + }); + + handleRejection(reason, options); + }; +} + +/** + * Handler for `mode` option + + */ +function handleRejection( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reason: any, + options: OnUnhandledRejectionOptions, +): void { + // https://github.com/nodejs/node/blob/7cf6f9e964aa00772965391c23acda6d71972a9a/lib/internal/process/promises.js#L234-L240 + const rejectionWarning = + 'This error originated either by ' + + 'throwing inside of an async function without a catch block, ' + + 'or by rejecting a promise which was not handled with .catch().' + + ' The promise rejected with the reason:'; + + /* eslint-disable no-console */ + if (options.mode === 'warn') { + consoleSandbox(() => { + console.warn(rejectionWarning); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + console.error(reason && reason.stack ? reason.stack : reason); + }); + } else if (options.mode === 'strict') { + consoleSandbox(() => { + console.warn(rejectionWarning); + }); + logAndExitProcess(reason); + } + /* eslint-enable no-console */ +} diff --git a/packages/node-experimental/src/integrations/spotlight.ts b/packages/node-experimental/src/integrations/spotlight.ts new file mode 100644 index 000000000000..ebd8573c5072 --- /dev/null +++ b/packages/node-experimental/src/integrations/spotlight.ts @@ -0,0 +1,116 @@ +import * as http from 'http'; +import { URL } from 'url'; +import { defineIntegration } from '@sentry/core'; +import type { Client, Envelope, IntegrationFn } from '@sentry/types'; +import { logger, serializeEnvelope } from '@sentry/utils'; + +type SpotlightConnectionOptions = { + /** + * Set this if the Spotlight Sidecar is not running on localhost:8969 + * By default, the Url is set to http://localhost:8969/stream + */ + sidecarUrl?: string; +}; + +const INTEGRATION_NAME = 'Spotlight'; + +const _spotlightIntegration = ((options: Partial = {}) => { + const _options = { + sidecarUrl: options.sidecarUrl || 'http://localhost:8969/stream', + }; + + return { + name: INTEGRATION_NAME, + setup(client) { + if (typeof process === 'object' && process.env && process.env.NODE_ENV !== 'development') { + logger.warn("[Spotlight] It seems you're not in dev mode. Do you really want to have Spotlight enabled?"); + } + connectToSpotlight(client, _options); + }, + }; +}) satisfies IntegrationFn; + +/** + * Use this integration to send errors and transactions to Spotlight. + * + * Learn more about spotlight at https://spotlightjs.com + * + * Important: This integration only works with Node 18 or newer. + */ +export const spotlightIntegration = defineIntegration(_spotlightIntegration); + +function connectToSpotlight(client: Client, options: Required): void { + const spotlightUrl = parseSidecarUrl(options.sidecarUrl); + if (!spotlightUrl) { + return; + } + + let failedRequests = 0; + + client.on('beforeEnvelope', (envelope: Envelope) => { + if (failedRequests > 3) { + logger.warn('[Spotlight] Disabled Sentry -> Spotlight integration due to too many failed requests'); + return; + } + + const serializedEnvelope = serializeEnvelope(envelope); + + const request = getNativeHttpRequest(); + const req = request( + { + method: 'POST', + path: spotlightUrl.pathname, + hostname: spotlightUrl.hostname, + port: spotlightUrl.port, + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + }, + res => { + res.on('data', () => { + // Drain socket + }); + + res.on('end', () => { + // Drain socket + }); + res.setEncoding('utf8'); + }, + ); + + req.on('error', () => { + failedRequests++; + logger.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar'); + }); + req.write(serializedEnvelope); + req.end(); + }); +} + +function parseSidecarUrl(url: string): URL | undefined { + try { + return new URL(`${url}`); + } catch { + logger.warn(`[Spotlight] Invalid sidecar URL: ${url}`); + return undefined; + } +} + +type HttpRequestImpl = typeof http.request; +type WrappedHttpRequest = HttpRequestImpl & { __sentry_original__: HttpRequestImpl }; + +/** + * We want to get an unpatched http request implementation to avoid capturing our own calls. + */ +export function getNativeHttpRequest(): HttpRequestImpl { + const { request } = http; + if (isWrapped(request)) { + return request.__sentry_original__; + } + + return request; +} + +function isWrapped(impl: HttpRequestImpl): impl is WrappedHttpRequest { + return '__sentry_original__' in impl; +} diff --git a/packages/node-experimental/src/integrations/tracing/hapi.ts b/packages/node-experimental/src/integrations/tracing/hapi.ts deleted file mode 100644 index 949726af698b..000000000000 --- a/packages/node-experimental/src/integrations/tracing/hapi.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { registerInstrumentations } from '@opentelemetry/instrumentation'; -import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; -import { defineIntegration } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; - -const _hapiIntegration = (() => { - return { - name: 'Hapi', - setupOnce() { - registerInstrumentations({ - instrumentations: [new HapiInstrumentation()], - }); - }, - }; -}) satisfies IntegrationFn; - -/** - * Hapi integration - * - * Capture tracing data for Hapi. - */ -export const hapiIntegration = defineIntegration(_hapiIntegration); diff --git a/packages/node-experimental/src/integrations/tracing/hapi/index.ts b/packages/node-experimental/src/integrations/tracing/hapi/index.ts new file mode 100644 index 000000000000..4d72af191f84 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/hapi/index.ts @@ -0,0 +1,76 @@ +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; +import { SDK_VERSION, captureException, defineIntegration, getActiveSpan, getRootSpan } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import type { Boom, RequestEvent, ResponseObject, Server } from './types'; + +const _hapiIntegration = (() => { + return { + name: 'Hapi', + setupOnce() { + registerInstrumentations({ + instrumentations: [new HapiInstrumentation()], + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Hapi integration + * + * Capture tracing data for Hapi. + * If you also want to capture errors, you need to call `setupHapiErrorHandler(server)` after you set up your server. + */ +export const hapiIntegration = defineIntegration(_hapiIntegration); + +function isBoomObject(response: ResponseObject | Boom): response is Boom { + return response && (response as Boom).isBoom !== undefined; +} + +function isErrorEvent(event: RequestEvent): event is RequestEvent { + return event && (event as RequestEvent).error !== undefined; +} + +function sendErrorToSentry(errorData: object): void { + captureException(errorData, { + mechanism: { + type: 'hapi', + handled: false, + data: { + function: 'hapiErrorPlugin', + }, + }, + }); +} + +export const hapiErrorPlugin = { + name: 'SentryHapiErrorPlugin', + version: SDK_VERSION, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + register: async function (serverArg: Record) { + const server = serverArg as unknown as Server; + + server.events.on('request', (request, event) => { + const activeSpan = getActiveSpan(); + const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; + + if (request.response && isBoomObject(request.response)) { + sendErrorToSentry(request.response); + } else if (isErrorEvent(event)) { + sendErrorToSentry(event.error); + } + + if (rootSpan) { + rootSpan.setStatus('internal_error'); + rootSpan.end(); + } + }); + }, +}; + +/** + * Add a Hapi plugin to capture errors to Sentry. + */ +export async function setupHapiErrorHandler(server: Server): Promise { + await server.register(hapiErrorPlugin); +} diff --git a/packages/node-experimental/src/integrations/tracing/hapi/types.ts b/packages/node-experimental/src/integrations/tracing/hapi/types.ts new file mode 100644 index 000000000000..a650667fe362 --- /dev/null +++ b/packages/node-experimental/src/integrations/tracing/hapi/types.ts @@ -0,0 +1,279 @@ +/* eslint-disable @typescript-eslint/no-misused-new */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/unified-signatures */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// Vendored and simplified from: +// - @types/hapi__hapi +// v17.8.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/hapi/v17/index.d.ts +// +// - @types/podium +// v1.0.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/podium/index.d.ts +// +// - @types/boom +// v7.3.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/boom/v4/index.d.ts + +import type * as stream from 'stream'; +import type * as url from 'url'; + +interface Podium { + new (events?: Events[]): Podium; + new (events?: Events): Podium; + + registerEvent(events: Events[]): void; + registerEvent(events: Events): void; + + registerPodium?(podiums: Podium[]): void; + registerPodium?(podiums: Podium): void; + + emit( + criteria: string | { name: string; channel?: string | undefined; tags?: string | string[] | undefined }, + data: any, + callback?: () => void, + ): void; + + on(criteria: string | Criteria, listener: Listener): void; + addListener(criteria: string | Criteria, listener: Listener): void; + once(criteria: string | Criteria, listener: Listener): void; + removeListener(name: string, listener: Listener): Podium; + removeAllListeners(name: string): Podium; + hasListeners(name: string): boolean; +} + +export interface Boom extends Error { + isBoom: boolean; + isServer: boolean; + message: string; + output: Output; + reformat: () => string; + isMissing?: boolean | undefined; + data: Data; +} + +export interface Output { + statusCode: number; + headers: { [index: string]: string }; + payload: Payload; +} + +export interface Payload { + statusCode: number; + error: string; + message: string; + attributes?: any; +} + +export type Events = string | EventOptionsObject | Podium; + +export interface EventOptionsObject { + name: string; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; + shared?: boolean | undefined; +} + +export interface CriteriaObject { + name: string; + block?: boolean | number | undefined; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + count?: number | undefined; + filter?: string | string[] | CriteriaFilterOptionsObject | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; + listener?: Listener | undefined; +} + +export interface CriteriaFilterOptionsObject { + tags?: string | string[] | undefined; + all?: boolean | undefined; +} + +export type Criteria = string | CriteriaObject; + +export interface Listener { + (data: any, tags?: Tags, callback?: () => void): void; +} + +export type Tags = { [tag: string]: boolean }; + +type Dependencies = + | string + | string[] + | { + [key: string]: string; + }; + +interface PluginNameVersion { + name: string; + version?: string | undefined; +} + +interface PluginPackage { + pkg: any; +} + +interface PluginBase { + register: (server: Server, options: T) => void | Promise; + multiple?: boolean | undefined; + dependencies?: Dependencies | undefined; + requirements?: + | { + node?: string | undefined; + hapi?: string | undefined; + } + | undefined; + + once?: boolean | undefined; +} + +type Plugin = PluginBase & (PluginNameVersion | PluginPackage); + +interface UserCredentials {} + +interface AppCredentials {} + +interface AuthCredentials { + scope?: string[] | undefined; + user?: UserCredentials | undefined; + app?: AppCredentials | undefined; +} + +interface RequestAuth { + artifacts: object; + credentials: AuthCredentials; + error: Error; + isAuthenticated: boolean; + isAuthorized: boolean; + mode: string; + strategy: string; +} + +interface RequestEvents extends Podium { + on(criteria: 'peek', listener: PeekListener): void; + on(criteria: 'finish' | 'disconnect', listener: (data: undefined) => void): void; + once(criteria: 'peek', listener: PeekListener): void; + once(criteria: 'finish' | 'disconnect', listener: (data: undefined) => void): void; +} + +namespace Lifecycle { + export type Method = (request: Request, h: ResponseToolkit, err?: Error) => ReturnValue; + export type ReturnValue = ReturnValueTypes | Promise; + export type ReturnValueTypes = + | (null | string | number | boolean) + | Buffer + | (Error | Boom) + | stream.Stream + | (object | object[]) + | symbol + | ResponseToolkit; + export type FailAction = 'error' | 'log' | 'ignore' | Method; +} + +namespace Util { + export interface Dictionary { + [key: string]: T; + } + + export type HTTP_METHODS_PARTIAL_LOWERCASE = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options'; + export type HTTP_METHODS_PARTIAL = + | 'GET' + | 'POST' + | 'PUT' + | 'PATCH' + | 'DELETE' + | 'OPTIONS' + | HTTP_METHODS_PARTIAL_LOWERCASE; + export type HTTP_METHODS = 'HEAD' | 'head' | HTTP_METHODS_PARTIAL; +} + +interface RequestRoute { + method: Util.HTTP_METHODS_PARTIAL; + path: string; + vhost?: string | string[] | undefined; + realm: any; + fingerprint: string; + + auth: { + access(request: Request): boolean; + }; +} + +interface Request extends Podium { + app: ApplicationState; + readonly auth: RequestAuth; + events: RequestEvents; + readonly headers: Util.Dictionary; + readonly path: string; + response: ResponseObject | Boom | null; + readonly route: RequestRoute; + readonly url: url.Url; +} + +interface ResponseObjectHeaderOptions { + append?: boolean | undefined; + separator?: string | undefined; + override?: boolean | undefined; + duplicate?: boolean | undefined; +} + +export interface ResponseObject extends Podium { + readonly statusCode: number; + header(name: string, value: string, options?: ResponseObjectHeaderOptions): ResponseObject; +} + +interface ResponseToolkit { + readonly continue: symbol; +} + +interface ServerEventCriteria { + name: T; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + count?: number | undefined; + filter?: string | string[] | { tags: string | string[]; all?: boolean | undefined } | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; +} + +export interface RequestEvent { + timestamp: string; + tags: string[]; + channel: 'internal' | 'app' | 'error'; + data: object; + error: object; +} + +type RequestEventHandler = (request: Request, event: RequestEvent, tags: { [key: string]: true }) => void; +interface ServerEvents { + on(criteria: 'request' | ServerEventCriteria<'request'>, listener: RequestEventHandler): void; +} + +type RouteRequestExtType = + | 'onPreAuth' + | 'onCredentials' + | 'onPostAuth' + | 'onPreHandler' + | 'onPostHandler' + | 'onPreResponse'; + +type ServerRequestExtType = RouteRequestExtType | 'onRequest'; + +export type Server = Record & { + events: ServerEvents; + ext(event: ServerRequestExtType, method: Lifecycle.Method, options?: Record): void; + initialize(): Promise; + register(plugins: Plugin | Array>, options?: Record): Promise; + start(): Promise; +}; + +interface ApplicationState {} + +type PeekListener = (chunk: string, encoding: string) => void; diff --git a/packages/node-experimental/src/nodeVersion.ts b/packages/node-experimental/src/nodeVersion.ts new file mode 100644 index 000000000000..792037ece168 --- /dev/null +++ b/packages/node-experimental/src/nodeVersion.ts @@ -0,0 +1,7 @@ +import { parseSemver } from '@sentry/utils'; + +export const NODE_VERSION = parseSemver(process.versions.node) as { + major: number | undefined; + minor: number | undefined; +}; +export const NODE_MAJOR = NODE_VERSION.major || 0; diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index c61a72155cda..2b41c235b234 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -1,13 +1,16 @@ import { endSession, + functionToStringIntegration, getClient, getCurrentScope, getIntegrationsToSetup, getIsolationScope, hasTracingEnabled, + inboundFiltersIntegration, + linkedErrorsIntegration, + requestDataIntegration, startSession, } from '@sentry/core'; -import { getDefaultIntegrations as getDefaultNodeIntegrations, spotlightIntegration } from '@sentry/node'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import type { Client, Integration, Options } from '@sentry/types'; import { @@ -18,9 +21,17 @@ import { stackParserFromStackParserOptions, } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; +import { consoleIntegration } from '../integrations/console'; +import { nodeContextIntegration } from '../integrations/context'; +import { contextLinesIntegration } from '../integrations/contextlines'; import { httpIntegration } from '../integrations/http'; +import { localVariablesIntegration } from '../integrations/local-variables'; +import { modulesIntegration } from '../integrations/modules'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; +import { onUncaughtExceptionIntegration } from '../integrations/onuncaughtexception'; +import { onUnhandledRejectionIntegration } from '../integrations/onunhandledrejection'; +import { spotlightIntegration } from '../integrations/spotlight'; import { getAutoPerformanceIntegrations } from '../integrations/tracing'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; @@ -28,12 +39,27 @@ import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initOtel } from './initOtel'; -const ignoredDefaultIntegrations = ['Http', 'Undici']; - /** Get the default integrations for the Node Experimental SDK. */ export function getDefaultIntegrations(options: Options): Integration[] { + // TODO return [ - ...getDefaultNodeIntegrations(options).filter(i => !ignoredDefaultIntegrations.includes(i.name)), + // Common + inboundFiltersIntegration(), + functionToStringIntegration(), + linkedErrorsIntegration(), + requestDataIntegration(), + // Native Wrappers + consoleIntegration(), + httpIntegration(), + nativeNodeFetchIntegration(), + // Global Handlers + onUncaughtExceptionIntegration(), + onUnhandledRejectionIntegration(), + // Event Info + contextLinesIntegration(), + localVariablesIntegration(), + nodeContextIntegration(), + modulesIntegration(), httpIntegration(), nativeNodeFetchIntegration(), ...(hasTracingEnabled(options) ? getAutoPerformanceIntegrations() : []), diff --git a/packages/node-experimental/src/utils/errorhandling.ts b/packages/node-experimental/src/utils/errorhandling.ts new file mode 100644 index 000000000000..3e08ca5d2ff4 --- /dev/null +++ b/packages/node-experimental/src/utils/errorhandling.ts @@ -0,0 +1,40 @@ +import { getClient } from '@sentry/core'; +import { consoleSandbox, logger } from '@sentry/utils'; +import { DEBUG_BUILD } from '../debug-build'; +import type { NodeClient } from '../sdk/client'; + +const DEFAULT_SHUTDOWN_TIMEOUT = 2000; + +/** + * @hidden + */ +export function logAndExitProcess(error: Error): void { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.error(error); + }); + + const client = getClient(); + + if (client === undefined) { + DEBUG_BUILD && logger.warn('No NodeClient was defined, we are exiting the process now.'); + global.process.exit(1); + return; + } + + const options = client.getOptions(); + const timeout = + (options && options.shutdownTimeout && options.shutdownTimeout > 0 && options.shutdownTimeout) || + DEFAULT_SHUTDOWN_TIMEOUT; + client.close(timeout).then( + (result: boolean) => { + if (!result) { + DEBUG_BUILD && logger.warn('We reached the timeout for emptying the request buffer, still exiting now!'); + } + global.process.exit(1); + }, + error => { + DEBUG_BUILD && logger.error(error); + }, + ); +} diff --git a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts b/packages/node-experimental/test/helpers/getDefaultNodeClientOptions.ts similarity index 100% rename from packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts rename to packages/node-experimental/test/helpers/getDefaultNodeClientOptions.ts diff --git a/packages/node-experimental/test/integrations/context.test.ts b/packages/node-experimental/test/integrations/context.test.ts new file mode 100644 index 000000000000..519e101187ff --- /dev/null +++ b/packages/node-experimental/test/integrations/context.test.ts @@ -0,0 +1,22 @@ +import * as os from 'os'; + +import { getDeviceContext } from '../../src/integrations/context'; + +describe('Context', () => { + describe('getDeviceContext', () => { + afterAll(() => { + jest.clearAllMocks(); + }); + + it('returns boot time if os.uptime is defined and returns a valid uptime', () => { + const deviceCtx = getDeviceContext({}); + expect(deviceCtx.boot_time).toEqual(expect.any(String)); + }); + + it('returns no boot time if os.uptime() returns undefined', () => { + jest.spyOn(os, 'uptime').mockReturnValue(undefined as unknown as number); + const deviceCtx = getDeviceContext({}); + expect(deviceCtx.boot_time).toBeUndefined(); + }); + }); +}); diff --git a/packages/node-experimental/test/integrations/contextlines.test.ts b/packages/node-experimental/test/integrations/contextlines.test.ts new file mode 100644 index 000000000000..ba3fa8575e79 --- /dev/null +++ b/packages/node-experimental/test/integrations/contextlines.test.ts @@ -0,0 +1,150 @@ +import * as fs from 'fs'; +import type { StackFrame } from '@sentry/types'; +import { parseStackFrames } from '@sentry/utils'; + +import { _contextLinesIntegration, resetFileContentCache } from '../../src/integrations/contextlines'; +import { defaultStackParser } from '../../src/sdk/api'; + +jest.mock('fs', () => { + const actual = jest.requireActual('fs'); + return { + ...actual, + promises: { + ...actual.promises, + readFile: jest.fn(actual.promises), + }, + }; +}); + +describe('ContextLines', () => { + const readFileSpy = fs.promises.readFile as unknown as jest.SpyInstance; + let contextLines: ReturnType; + + async function addContext(frames: StackFrame[]): Promise { + await contextLines.processEvent({ exception: { values: [{ stacktrace: { frames } }] } }); + } + + beforeEach(() => { + contextLines = _contextLinesIntegration(); + resetFileContentCache(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('lru file cache', () => { + test('parseStack with same file', async () => { + expect.assertions(1); + + const frames = parseStackFrames(defaultStackParser, new Error('test')); + + await addContext(Array.from(frames)); + + const numCalls = readFileSpy.mock.calls.length; + await addContext(frames); + + // Calls to `readFile` shouldn't increase if there isn't a new error to + // parse whose stacktrace contains a file we haven't yet seen + expect(readFileSpy).toHaveBeenCalledTimes(numCalls); + }); + + test('parseStack with ESM module names', async () => { + expect.assertions(1); + + const framesWithFilePath: StackFrame[] = [ + { + colno: 1, + filename: 'file:///var/task/index.js', + lineno: 1, + function: 'fxn1', + }, + ]; + + await addContext(framesWithFilePath); + expect(readFileSpy).toHaveBeenCalledTimes(1); + }); + + test('parseStack with adding different file', async () => { + expect.assertions(1); + const frames = parseStackFrames(defaultStackParser, new Error('test')); + + await addContext(frames); + + const numCalls = readFileSpy.mock.calls.length; + const parsedFrames = parseStackFrames(defaultStackParser, getError()); + await addContext(parsedFrames); + + const newErrorCalls = readFileSpy.mock.calls.length; + expect(newErrorCalls).toBeGreaterThan(numCalls); + }); + + test('parseStack with duplicate files', async () => { + expect.assertions(1); + const framesWithDuplicateFiles: StackFrame[] = [ + { + colno: 1, + filename: '/var/task/index.js', + lineno: 1, + function: 'fxn1', + }, + { + colno: 2, + filename: '/var/task/index.js', + lineno: 2, + function: 'fxn2', + }, + { + colno: 3, + filename: '/var/task/index.js', + lineno: 3, + function: 'fxn3', + }, + ]; + + await addContext(framesWithDuplicateFiles); + expect(readFileSpy).toHaveBeenCalledTimes(1); + }); + + test('parseStack with no context', async () => { + contextLines = _contextLinesIntegration({ frameContextLines: 0 }); + + expect.assertions(1); + const frames = parseStackFrames(defaultStackParser, new Error('test')); + + await addContext(frames); + expect(readFileSpy).toHaveBeenCalledTimes(0); + }); + }); + + test('does not attempt to readfile multiple times if it fails', async () => { + expect.assertions(1); + + readFileSpy.mockImplementation(() => { + throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'"); + }); + + await addContext([ + { + colno: 1, + filename: '/does/not/exist.js', + lineno: 1, + function: 'fxn1', + }, + ]); + await addContext([ + { + colno: 1, + filename: '/does/not/exist.js', + lineno: 1, + function: 'fxn1', + }, + ]); + + expect(readFileSpy).toHaveBeenCalledTimes(1); + }); +}); + +function getError(): Error { + return new Error('mock error'); +} diff --git a/packages/node-experimental/test/integrations/localvariables.test.ts b/packages/node-experimental/test/integrations/localvariables.test.ts new file mode 100644 index 000000000000..db9385214d42 --- /dev/null +++ b/packages/node-experimental/test/integrations/localvariables.test.ts @@ -0,0 +1,140 @@ +import { createRateLimiter } from '../../src/integrations/local-variables/common'; +import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync'; +import { NODE_MAJOR } from '../../src/nodeVersion'; + +jest.setTimeout(20_000); + +const describeIf = (condition: boolean) => (condition ? describe : describe.skip); + +describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { + describe('createCallbackList', () => { + it('Should call callbacks in reverse order', done => { + const log: number[] = []; + + const { add, next } = createCallbackList(n => { + expect(log).toEqual([5, 4, 3, 2, 1]); + expect(n).toBe(15); + done(); + }); + + add(n => { + log.push(1); + next(n + 1); + }); + + add(n => { + log.push(2); + next(n + 1); + }); + + add(n => { + log.push(3); + next(n + 1); + }); + + add(n => { + log.push(4); + next(n + 1); + }); + + add(n => { + log.push(5); + next(n + 11); + }); + + next(0); + }); + + it('only calls complete once even if multiple next', done => { + const { add, next } = createCallbackList(n => { + expect(n).toBe(1); + done(); + }); + + add(n => { + next(n + 1); + // We dont actually do this in our code... + next(n + 1); + }); + + next(0); + }); + + it('calls completed if added closure throws', done => { + const { add, next } = createCallbackList(n => { + expect(n).toBe(10); + done(); + }); + + add(n => { + throw new Error('test'); + next(n + 1); + }); + + next(10); + }); + }); + + describe('rateLimiter', () => { + it('calls disable if exceeded', done => { + const increment = createRateLimiter( + 5, + () => {}, + () => { + done(); + }, + ); + + for (let i = 0; i < 7; i++) { + increment(); + } + }); + + it('does not call disable if not exceeded', done => { + const increment = createRateLimiter( + 5, + () => { + throw new Error('Should not be called'); + }, + () => { + throw new Error('Should not be called'); + }, + ); + + let count = 0; + + const timer = setInterval(() => { + for (let i = 0; i < 4; i++) { + increment(); + } + + count += 1; + + if (count >= 5) { + clearInterval(timer); + done(); + } + }, 1_000); + }); + + it('re-enables after timeout', done => { + let called = false; + + const increment = createRateLimiter( + 5, + () => { + expect(called).toEqual(true); + done(); + }, + () => { + expect(called).toEqual(false); + called = true; + }, + ); + + for (let i = 0; i < 10; i++) { + increment(); + } + }); + }); +}); diff --git a/packages/node-experimental/test/integrations/spotlight.test.ts b/packages/node-experimental/test/integrations/spotlight.test.ts new file mode 100644 index 000000000000..6b888c22edcd --- /dev/null +++ b/packages/node-experimental/test/integrations/spotlight.test.ts @@ -0,0 +1,181 @@ +import * as http from 'http'; +import type { Envelope, EventEnvelope } from '@sentry/types'; +import { createEnvelope, logger } from '@sentry/utils'; + +import { spotlightIntegration } from '../../src/integrations/spotlight'; +import { NodeClient } from '../../src/sdk/client'; +import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; + +describe('Spotlight', () => { + const loggerSpy = jest.spyOn(logger, 'warn'); + + afterEach(() => { + loggerSpy.mockClear(); + jest.clearAllMocks(); + }); + + const options = getDefaultNodeClientOptions(); + const client = new NodeClient(options); + + it('has a name', () => { + const integration = spotlightIntegration(); + expect(integration.name).toEqual('Spotlight'); + }); + + it('registers a callback on the `beforeEnvelope` hook', () => { + const clientWithSpy = { + ...client, + on: jest.fn(), + }; + const integration = spotlightIntegration(); + // @ts-expect-error - this is fine in tests + integration.setup(clientWithSpy); + expect(clientWithSpy.on).toHaveBeenCalledWith('beforeEnvelope', expect.any(Function)); + }); + + it('sends an envelope POST request to the sidecar url', () => { + const httpSpy = jest.spyOn(http, 'request').mockImplementationOnce(() => { + return { + on: jest.fn(), + write: jest.fn(), + end: jest.fn(), + } as any; + }); + + let callback: (envelope: Envelope) => void = () => {}; + const clientWithSpy = { + ...client, + on: jest.fn().mockImplementationOnce((_, cb) => (callback = cb)), + }; + + const integration = spotlightIntegration(); + // @ts-expect-error - this is fine in tests + integration.setup(clientWithSpy); + + const envelope = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + ]); + + callback(envelope); + + expect(httpSpy).toHaveBeenCalledWith( + { + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + hostname: 'localhost', + method: 'POST', + path: '/stream', + port: '8969', + }, + expect.any(Function), + ); + }); + + it('sends an envelope POST request to a custom sidecar url', () => { + const httpSpy = jest.spyOn(http, 'request').mockImplementationOnce(() => { + return { + on: jest.fn(), + write: jest.fn(), + end: jest.fn(), + } as any; + }); + + let callback: (envelope: Envelope) => void = () => {}; + const clientWithSpy = { + ...client, + on: jest.fn().mockImplementationOnce((_, cb) => (callback = cb)), + }; + + const integration = spotlightIntegration({ sidecarUrl: 'http://mylocalhost:8888/abcd' }); + // @ts-expect-error - this is fine in tests + integration.setup(clientWithSpy); + + const envelope = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + ]); + + callback(envelope); + + expect(httpSpy).toHaveBeenCalledWith( + { + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + hostname: 'mylocalhost', + method: 'POST', + path: '/abcd', + port: '8888', + }, + expect.any(Function), + ); + }); + + describe('no-ops if', () => { + it('an invalid URL is passed', () => { + const integration = spotlightIntegration({ sidecarUrl: 'invalid-url' }); + integration.setup!(client); + expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sidecar URL: invalid-url')); + }); + }); + + it('warns if the NODE_ENV variable doesn\'t equal "development"', () => { + const oldEnvValue = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const integration = spotlightIntegration({ sidecarUrl: 'http://localhost:8969' }); + integration.setup!(client); + + expect(loggerSpy).toHaveBeenCalledWith( + expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), + ); + + process.env.NODE_ENV = oldEnvValue; + }); + + it('doesn\'t warn if the NODE_ENV variable equals "development"', () => { + const oldEnvValue = process.env.NODE_ENV; + process.env.NODE_ENV = 'development'; + + const integration = spotlightIntegration({ sidecarUrl: 'http://localhost:8969' }); + integration.setup!(client); + + expect(loggerSpy).not.toHaveBeenCalledWith( + expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), + ); + + process.env.NODE_ENV = oldEnvValue; + }); + + it('handles `process` not being available', () => { + const originalProcess = process; + + // @ts-expect-error - TS complains but we explicitly wanna test this + delete global.process; + + const integration = spotlightIntegration({ sidecarUrl: 'http://localhost:8969' }); + integration.setup!(client); + + expect(loggerSpy).not.toHaveBeenCalledWith( + expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), + ); + + global.process = originalProcess; + }); + + it('handles `process.env` not being available', () => { + const originalEnv = process.env; + + // @ts-expect-error - TS complains but we explicitly wanna test this + delete process.env; + + const integration = spotlightIntegration({ sidecarUrl: 'http://localhost:8969' }); + integration.setup!(client); + + expect(loggerSpy).not.toHaveBeenCalledWith( + expect.stringContaining("It seems you're not in dev mode. Do you really want to have Spotlight enabled?"), + ); + + process.env = originalEnv; + }); +}); diff --git a/packages/node-experimental/test/sdk/client.test.ts b/packages/node-experimental/test/sdk/client.test.ts index f9e69b0b7233..de2b6c6fdf6b 100644 --- a/packages/node-experimental/test/sdk/client.test.ts +++ b/packages/node-experimental/test/sdk/client.test.ts @@ -15,7 +15,7 @@ import type { Scope } from '@sentry/types'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import { NodeClient } from '../../src/sdk/client'; import { initOtel } from '../../src/sdk/initOtel'; -import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodePreviewClientOptions'; +import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; import { cleanupOtel } from '../helpers/mockSdkInit'; describe('NodeClient', () => { diff --git a/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts b/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts index 05d3f73858d7..58c0cf934316 100644 --- a/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts +++ b/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts @@ -3,7 +3,7 @@ import { getCurrentScope, getIsolationScope, setAsyncContextStrategy, setCurrent import type { Scope } from '@sentry/types'; import { NodeClient } from '../../../src/sdk/client'; import { errorHandler } from '../../../src/sdk/handlers/errorHandler'; -import { getDefaultNodeClientOptions } from '../../helpers/getDefaultNodePreviewClientOptions'; +import { getDefaultNodeClientOptions } from '../../helpers/getDefaultNodeClientOptions'; describe('errorHandler()', () => { beforeEach(() => { diff --git a/packages/node-experimental/tsconfig.json b/packages/node-experimental/tsconfig.json index bf45a09f2d71..5fc0658105eb 100644 --- a/packages/node-experimental/tsconfig.json +++ b/packages/node-experimental/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - // package-specific options + "lib": ["es6"] } } From f014ac6f809e96faeef0dd08e5ce921aabc026cd Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 21 Feb 2024 10:06:50 +0100 Subject: [PATCH 120/173] ref(node-experimental): Rename `errorHandler` to `expressErrorHandler` (#10746) Also provide a new `setupExpressErrorHandler(app)` utility. --- .../express/tracing-experimental/server.js | 2 +- packages/node-experimental/src/index.ts | 4 +- .../src/integrations/tracing/express.ts | 87 +++++++++++++++++++ .../src/sdk/handlers/errorHandler.ts | 76 ---------------- .../express.test.ts} | 10 +-- 5 files changed, 94 insertions(+), 85 deletions(-) delete mode 100644 packages/node-experimental/src/sdk/handlers/errorHandler.ts rename packages/node-experimental/test/{sdk/handlers/errorHandler.test.ts => integrations/express.test.ts} (93%) diff --git a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js index 0f9136789b12..207d11d3aa0e 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js +++ b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js @@ -34,6 +34,6 @@ app.get(['/test/arr/:id', /\/test\/arr[0-9]*\/required(path)?(\/optionalPath)?\/ res.send({ response: 'response 4' }); }); -app.use(Sentry.errorHandler()); +Sentry.setupExpressErrorHandler(app); startExpressServerAndSendPortToRunner(app); diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index ef9605afabe7..282b703d1194 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -1,5 +1,3 @@ -export { errorHandler } from './sdk/handlers/errorHandler'; - export { httpIntegration } from './integrations/http'; export { nativeNodeFetchIntegration } from './integrations/node-fetch'; @@ -11,7 +9,7 @@ export { modulesIntegration } from './integrations/modules'; export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception'; export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection'; -export { expressIntegration } from './integrations/tracing/express'; +export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express'; export { fastifyIntegration } from './integrations/tracing/fastify'; export { graphqlIntegration } from './integrations/tracing/graphql'; export { mongoIntegration } from './integrations/tracing/mongo'; diff --git a/packages/node-experimental/src/integrations/tracing/express.ts b/packages/node-experimental/src/integrations/tracing/express.ts index 0606702d8220..12e44199e16d 100644 --- a/packages/node-experimental/src/integrations/tracing/express.ts +++ b/packages/node-experimental/src/integrations/tracing/express.ts @@ -1,8 +1,11 @@ +import type * as http from 'http'; import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; import { defineIntegration } from '@sentry/core'; +import { captureException, getClient, getIsolationScope } from '@sentry/core'; import type { IntegrationFn } from '@sentry/types'; +import type { NodeClient } from '../../sdk/client'; import { addOriginToSpan } from '../../utils/addOriginToSpan'; const _expressIntegration = (() => { @@ -26,5 +29,89 @@ const _expressIntegration = (() => { * Express integration * * Capture tracing data for express. + * In order to capture exceptions, you have to call `setupExpressErrorHandler(app)` before any other middleware and after all controllers. */ export const expressIntegration = defineIntegration(_expressIntegration); + +interface MiddlewareError extends Error { + status?: number | string; + statusCode?: number | string; + status_code?: number | string; + output?: { + statusCode?: number | string; + }; +} + +type ExpressMiddleware = ( + error: MiddlewareError, + req: http.IncomingMessage, + res: http.ServerResponse, + next: (error: MiddlewareError) => void, +) => void; + +/** + * An Express-compatible error handler. + */ +export function expressErrorHandler(options?: { + /** + * Callback method deciding whether error should be captured and sent to Sentry + * @param error Captured middleware error + */ + shouldHandleError?(this: void, error: MiddlewareError): boolean; +}): ExpressMiddleware { + return function sentryErrorMiddleware( + error: MiddlewareError, + _req: http.IncomingMessage, + res: http.ServerResponse, + next: (error: MiddlewareError) => void, + ): void { + const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; + + if (shouldHandleError(error)) { + const client = getClient(); + if (client && client.getOptions().autoSessionTracking) { + // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the + // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only + // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be + // running in SessionAggregates mode + const isSessionAggregatesMode = client['_sessionFlusher'] !== undefined; + if (isSessionAggregatesMode) { + const requestSession = getIsolationScope().getRequestSession(); + // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a + // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within + // the bounds of a request, and if so the status is updated + if (requestSession && requestSession.status !== undefined) { + requestSession.status = 'crashed'; + } + } + } + + const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } }); + (res as { sentry?: string }).sentry = eventId; + next(error); + + return; + } + + next(error); + }; +} + +/** + * Setup an error handler for Express. + * The error handler must be before any other middleware and after all controllers. + */ +export function setupExpressErrorHandler(app: { use: (middleware: ExpressMiddleware) => unknown }): void { + app.use(expressErrorHandler()); +} + +function getStatusCodeFromResponse(error: MiddlewareError): number { + const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode); + return statusCode ? parseInt(statusCode as string, 10) : 500; +} + +/** Returns true if response code is internal server error */ +function defaultShouldHandleError(error: MiddlewareError): boolean { + const status = getStatusCodeFromResponse(error); + return status >= 500; +} diff --git a/packages/node-experimental/src/sdk/handlers/errorHandler.ts b/packages/node-experimental/src/sdk/handlers/errorHandler.ts deleted file mode 100644 index 2460ea11a0e4..000000000000 --- a/packages/node-experimental/src/sdk/handlers/errorHandler.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type * as http from 'http'; -import { captureException, getClient, getIsolationScope } from '@sentry/core'; -import type { NodeClient } from '../client'; - -interface MiddlewareError extends Error { - status?: number | string; - statusCode?: number | string; - status_code?: number | string; - output?: { - statusCode?: number | string; - }; -} - -/** - * An Express-compatible error handler. - */ -export function errorHandler(options?: { - /** - * Callback method deciding whether error should be captured and sent to Sentry - * @param error Captured middleware error - */ - shouldHandleError?(this: void, error: MiddlewareError): boolean; -}): ( - error: MiddlewareError, - req: http.IncomingMessage, - res: http.ServerResponse, - next: (error: MiddlewareError) => void, -) => void { - return function sentryErrorMiddleware( - error: MiddlewareError, - _req: http.IncomingMessage, - res: http.ServerResponse, - next: (error: MiddlewareError) => void, - ): void { - const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; - - if (shouldHandleError(error)) { - const client = getClient(); - if (client && client.getOptions().autoSessionTracking) { - // Check if the `SessionFlusher` is instantiated on the client to go into this branch that marks the - // `requestSession.status` as `Crashed`, and this check is necessary because the `SessionFlusher` is only - // instantiated when the the`requestHandler` middleware is initialised, which indicates that we should be - // running in SessionAggregates mode - const isSessionAggregatesMode = client['_sessionFlusher'] !== undefined; - if (isSessionAggregatesMode) { - const requestSession = getIsolationScope().getRequestSession(); - // If an error bubbles to the `errorHandler`, then this is an unhandled error, and should be reported as a - // Crashed session. The `_requestSession.status` is checked to ensure that this error is happening within - // the bounds of a request, and if so the status is updated - if (requestSession && requestSession.status !== undefined) { - requestSession.status = 'crashed'; - } - } - } - - const eventId = captureException(error, { mechanism: { type: 'middleware', handled: false } }); - (res as { sentry?: string }).sentry = eventId; - next(error); - - return; - } - - next(error); - }; -} - -function getStatusCodeFromResponse(error: MiddlewareError): number { - const statusCode = error.status || error.statusCode || error.status_code || (error.output && error.output.statusCode); - return statusCode ? parseInt(statusCode as string, 10) : 500; -} - -/** Returns true if response code is internal server error */ -function defaultShouldHandleError(error: MiddlewareError): boolean { - const status = getStatusCodeFromResponse(error); - return status >= 500; -} diff --git a/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts b/packages/node-experimental/test/integrations/express.test.ts similarity index 93% rename from packages/node-experimental/test/sdk/handlers/errorHandler.test.ts rename to packages/node-experimental/test/integrations/express.test.ts index 58c0cf934316..592ab7677db0 100644 --- a/packages/node-experimental/test/sdk/handlers/errorHandler.test.ts +++ b/packages/node-experimental/test/integrations/express.test.ts @@ -1,11 +1,11 @@ import * as http from 'http'; import { getCurrentScope, getIsolationScope, setAsyncContextStrategy, setCurrentClient, withScope } from '@sentry/core'; import type { Scope } from '@sentry/types'; -import { NodeClient } from '../../../src/sdk/client'; -import { errorHandler } from '../../../src/sdk/handlers/errorHandler'; -import { getDefaultNodeClientOptions } from '../../helpers/getDefaultNodeClientOptions'; +import { expressErrorHandler } from '../../src/integrations/tracing/express'; +import { NodeClient } from '../../src/sdk/client'; +import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; -describe('errorHandler()', () => { +describe('expressErrorHandler()', () => { beforeEach(() => { getCurrentScope().clear(); getIsolationScope().clear(); @@ -20,7 +20,7 @@ describe('errorHandler()', () => { const path = '/by/the/trees/'; const queryString = 'chase=me&please=thankyou'; - const sentryErrorMiddleware = errorHandler(); + const sentryErrorMiddleware = expressErrorHandler(); let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; let client: NodeClient; From 9ad4d9ba7a807774debcbe64e3fcd637cb8774f2 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 21 Feb 2024 10:14:55 +0100 Subject: [PATCH 121/173] ref: Remove `startTransaction` from integration and e2e tests (#10721) --- .../startTransaction/basic_usage/subject.js | 48 ++++++---------- .../startTransaction/basic_usage/test.ts | 10 ++-- .../startTransaction/circular_data/subject.js | 8 +-- .../backgroundtab-custom/init.js | 2 +- .../backgroundtab-custom/subject.js | 11 +++- .../backgroundtab-custom/template.html | 2 +- .../backgroundtab-custom/test.ts | 40 ++++++------- .../backgroundtab-pageload/test.ts | 4 +- .../backgroundtab-custom/init.js | 2 +- .../backgroundtab-custom/subject.js | 11 +++- .../backgroundtab-custom/template.html | 2 +- .../backgroundtab-custom/test.ts | 40 ++++++------- .../backgroundtab-pageload/test.ts | 4 +- .../nextjs-app-dir/app/layout.tsx | 4 +- .../components/client-error-debug-tools.tsx | 24 ++++---- .../components/span-context.tsx | 41 +++++++++++++ .../components/transaction-context.tsx | 41 ------------- .../node-express-app/src/app.ts | 22 +++---- .../test-applications/node-profiling/index.js | 7 +-- .../startTransaction/basic-usage/scenario.ts | 8 +-- .../startTransaction/basic-usage/test.ts | 4 +- .../with-nested-spans/scenario.ts | 57 +++++++------------ .../with-nested-spans/test.ts | 8 +-- .../tracing-new/apollo-graphql/scenario.ts | 12 +--- .../suites/tracing-new/apollo-graphql/test.ts | 2 +- .../auto-instrument/mongodb/scenario.ts | 52 ++++++++--------- .../auto-instrument/mongodb/test.ts | 2 +- .../mysql/withConnect/scenario.ts | 36 ------------ .../auto-instrument/mysql/withConnect/test.ts | 35 ------------ .../mysql/withoutCallback/scenario.ts | 45 --------------- .../mysql/withoutCallback/test.ts | 43 -------------- .../mysql/withoutConnect/scenario.ts | 30 ---------- .../mysql/withoutConnect/test.ts | 35 ------------ .../auto-instrument/pg/scenario.ts | 26 ++++----- .../tracing-new/auto-instrument/pg/test.ts | 2 +- .../suites/tracing-new/prisma-orm/scenario.ts | 57 ++++++++----------- .../suites/tracing-new/prisma-orm/test.ts | 2 +- .../tracePropagationTargets/scenario.ts | 19 ++----- .../suites/tracing/apollo-graphql/scenario.ts | 12 +--- .../suites/tracing/apollo-graphql/test.ts | 2 +- .../auto-instrument/mongodb/scenario.ts | 52 ++++++++--------- .../tracing/auto-instrument/mongodb/test.ts | 2 +- .../tracing/auto-instrument/mysql/scenario.ts | 37 ------------ .../tracing/auto-instrument/mysql/test.ts | 35 ------------ .../tracing/auto-instrument/pg/scenario.ts | 21 +++---- .../suites/tracing/auto-instrument/pg/test.ts | 2 +- .../suites/tracing/prisma-orm/scenario.ts | 57 ++++++++----------- .../suites/tracing/prisma-orm/test.ts | 2 +- .../tracePropagationTargets/scenario.ts | 21 ++----- packages/browser/src/exports.ts | 2 + packages/bun/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/serverless/src/index.ts | 1 + .../src/browser/backgroundtab.ts | 37 +++++++----- .../test/browser/backgroundtab.test.ts | 8 +-- 55 files changed, 345 insertions(+), 746 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/span-context.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/subject.js index 0d79bddf53a5..5942deca663b 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/subject.js @@ -1,35 +1,23 @@ -async function run() { - const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); - const span_1 = transaction.startChild({ - op: 'span_1', - data: { - foo: 'bar', - baz: [1, 2, 3], +Sentry.startSpan({ name: 'root_span' }, () => { + Sentry.startSpan( + { + name: 'span_1', + data: { + foo: 'bar', + baz: [1, 2, 3], + }, }, - }); - - await new Promise(resolve => setTimeout(resolve, 1)); - - // span_1 finishes - span_1.end(); + () => undefined, + ); // span_2 doesn't finish - const span_2 = transaction.startChild({ op: 'span_2' }); - await new Promise(resolve => setTimeout(resolve, 1)); - - const span_3 = transaction.startChild({ op: 'span_3' }); - await new Promise(resolve => setTimeout(resolve, 1)); + Sentry.startInactiveSpan({ name: 'span_2' }); - // span_4 is the child of span_3 but doesn't finish. - const span_4 = span_3.startChild({ op: 'span_4', data: { qux: 'quux' } }); + Sentry.startSpan({ name: 'span_3' }, () => { + // span_4 is the child of span_3 but doesn't finish. + Sentry.startInactiveSpan({ name: 'span_4', data: { qux: 'quux' } }); - // span_5 is another child of span_3 but finishes. - const span_5 = span_3.startChild({ op: 'span_5' }).end(); - - // span_3 also finishes - span_3.end(); - - transaction.end(); -} - -run(); + // span_5 is another child of span_3 but finishes. + Sentry.startInactiveSpan({ name: 'span_5' }).end(); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts index aa5a332ac748..1f9897d2e680 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts @@ -12,11 +12,11 @@ sentryTest('should report a transaction in an envelope', async ({ getLocalTestPa const url = await getLocalTestPath({ testDir: __dirname }); const transaction = await getFirstSentryEnvelopeRequest(page, url); - expect(transaction.transaction).toBe('test_transaction_1'); + expect(transaction.transaction).toBe('root_span'); expect(transaction.spans).toBeDefined(); }); -sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestPath, page }) => { +sentryTest('should report finished spans as children of the root span', async ({ getLocalTestPath, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } @@ -31,17 +31,17 @@ sentryTest('should report finished spans as children of the root transaction', a const span_1 = transaction.spans?.[0]; expect(span_1).toBeDefined(); - expect(span_1!.op).toBe('span_1'); + expect(span_1!.description).toBe('span_1'); expect(span_1!.parent_span_id).toEqual(rootSpanId); expect(span_1!.data).toMatchObject({ foo: 'bar', baz: [1, 2, 3] }); const span_3 = transaction.spans?.[1]; expect(span_3).toBeDefined(); - expect(span_3!.op).toBe('span_3'); + expect(span_3!.description).toBe('span_3'); expect(span_3!.parent_span_id).toEqual(rootSpanId); const span_5 = transaction.spans?.[2]; expect(span_5).toBeDefined(); - expect(span_5!.op).toBe('span_5'); + expect(span_5!.description).toBe('span_5'); expect(span_5!.parent_span_id).toEqual(span_3!.span_id); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/circular_data/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/circular_data/subject.js index 9d4da2e3027a..baa6bd3d2361 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/circular_data/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/circular_data/subject.js @@ -2,8 +2,6 @@ const chicken = {}; const egg = { contains: chicken }; chicken.lays = egg; -const transaction = Sentry.startTransaction({ name: 'circular_object_test_transaction', data: { chicken } }); -const span = transaction.startChild({ op: 'circular_object_test_span', data: { chicken } }); - -span.end(); -transaction.end(); +Sentry.startSpan({ name: 'circular_object_test_transaction', data: { chicken } }, () => { + Sentry.startSpan({ op: 'circular_object_test_span', data: { chicken } }, () => undefined); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js index e5453b648509..147a4aa26bfb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/init.js @@ -4,6 +4,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration({ idleTimeout: 9000 })], + integrations: [Sentry.browserTracingIntegration({ idleTimeout: 9000, instrumentPageLoad: false })], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js index 7221e2302b21..e40426fdbe26 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/subject.js @@ -5,7 +5,12 @@ document.getElementById('go-background').addEventListener('click', () => { document.dispatchEvent(ev); }); -document.getElementById('start-transaction').addEventListener('click', () => { - window.transaction = Sentry.startTransaction({ name: 'test-transaction' }); - Sentry.getCurrentScope().setSpan(window.transaction); +document.getElementById('start-span').addEventListener('click', () => { + const span = Sentry.startInactiveSpan({ name: 'test-span' }); + window.span = span; + Sentry.getCurrentScope().setSpan(span); }); + +window.getSpanJson = () => { + return Sentry.spanToJSON(window.span); +}; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/template.html index fac45ecebfaf..772158d31f51 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/template.html @@ -4,7 +4,7 @@ - + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts index 0338a6cd5c85..fad37e85d6b4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-custom/test.ts @@ -1,13 +1,8 @@ -import type { JSHandle } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SpanJSON } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -async function getPropertyValue(handle: JSHandle, prop: string) { - return (await handle.getProperty(prop))?.jsonValue(); -} +import { shouldSkipTracingTest } from '../../../../utils/helpers'; sentryTest('should finish a custom transaction when the page goes background', async ({ getLocalTestPath, page }) => { if (shouldSkipTracingTest()) { @@ -15,31 +10,28 @@ sentryTest('should finish a custom transaction when the page goes background', a } const url = await getLocalTestPath({ testDir: __dirname }); + page.goto(url); - const pageloadTransaction = await getFirstSentryEnvelopeRequest(page, url); - expect(pageloadTransaction).toBeDefined(); - - await page.locator('#start-transaction').click(); - const transactionHandle = await page.evaluateHandle('window.transaction'); + await page.locator('#start-span').click(); + const spanJsonBefore: SpanJSON = await page.evaluate('window.getSpanJson()'); - const id_before = await getPropertyValue(transactionHandle, 'span_id'); - const nameBefore = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; - const status_before = await getPropertyValue(transactionHandle, 'status'); - const tags_before = await getPropertyValue(transactionHandle, 'tags'); + const id_before = spanJsonBefore.span_id; + const description_before = spanJsonBefore.description; + const status_before = spanJsonBefore.status; - expect(nameBefore).toBe('test-transaction'); + expect(description_before).toBe('test-span'); expect(status_before).toBeUndefined(); - expect(tags_before).toStrictEqual({}); await page.locator('#go-background').click(); + const spanJsonAfter: SpanJSON = await page.evaluate('window.getSpanJson()'); - const id_after = await getPropertyValue(transactionHandle, 'span_id'); - const nameAfter = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; - const status_after = await getPropertyValue(transactionHandle, 'status'); - const tags_after = await getPropertyValue(transactionHandle, 'tags'); + const id_after = spanJsonAfter.span_id; + const description_after = spanJsonAfter.description; + const status_after = spanJsonAfter.status; + const data_after = spanJsonAfter.data; expect(id_before).toBe(id_after); - expect(nameAfter).toBe('test-transaction'); + expect(description_after).toBe(description_before); expect(status_after).toBe('cancelled'); - expect(tags_after).toStrictEqual({ visibilitychange: 'document.hidden' }); + expect(data_after?.['sentry.cancellation_reason']).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts index 8432245f9c9b..1feda2e850e5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload/test.ts @@ -17,7 +17,5 @@ sentryTest('should finish pageload transaction when the page goes background', a expect(pageloadTransaction.contexts?.trace?.op).toBe('pageload'); expect(pageloadTransaction.contexts?.trace?.status).toBe('cancelled'); - expect(pageloadTransaction.contexts?.trace?.tags).toMatchObject({ - visibilitychange: 'document.hidden', - }); + expect(pageloadTransaction.contexts?.trace?.data?.['sentry.cancellation_reason']).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js index a4bddcff1b21..7d13bc95852b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js @@ -5,6 +5,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ idleTimeout: 9000 })], + integrations: [new Integrations.BrowserTracing({ idleTimeout: 9000, startTransactionOnPageLoad: false })], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js index 7221e2302b21..e40426fdbe26 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js @@ -5,7 +5,12 @@ document.getElementById('go-background').addEventListener('click', () => { document.dispatchEvent(ev); }); -document.getElementById('start-transaction').addEventListener('click', () => { - window.transaction = Sentry.startTransaction({ name: 'test-transaction' }); - Sentry.getCurrentScope().setSpan(window.transaction); +document.getElementById('start-span').addEventListener('click', () => { + const span = Sentry.startInactiveSpan({ name: 'test-span' }); + window.span = span; + Sentry.getCurrentScope().setSpan(span); }); + +window.getSpanJson = () => { + return Sentry.spanToJSON(window.span); +}; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html index fac45ecebfaf..772158d31f51 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html @@ -4,7 +4,7 @@ - + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts index 0338a6cd5c85..fad37e85d6b4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts @@ -1,13 +1,8 @@ -import type { JSHandle } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SpanJSON } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -async function getPropertyValue(handle: JSHandle, prop: string) { - return (await handle.getProperty(prop))?.jsonValue(); -} +import { shouldSkipTracingTest } from '../../../../utils/helpers'; sentryTest('should finish a custom transaction when the page goes background', async ({ getLocalTestPath, page }) => { if (shouldSkipTracingTest()) { @@ -15,31 +10,28 @@ sentryTest('should finish a custom transaction when the page goes background', a } const url = await getLocalTestPath({ testDir: __dirname }); + page.goto(url); - const pageloadTransaction = await getFirstSentryEnvelopeRequest(page, url); - expect(pageloadTransaction).toBeDefined(); - - await page.locator('#start-transaction').click(); - const transactionHandle = await page.evaluateHandle('window.transaction'); + await page.locator('#start-span').click(); + const spanJsonBefore: SpanJSON = await page.evaluate('window.getSpanJson()'); - const id_before = await getPropertyValue(transactionHandle, 'span_id'); - const nameBefore = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; - const status_before = await getPropertyValue(transactionHandle, 'status'); - const tags_before = await getPropertyValue(transactionHandle, 'tags'); + const id_before = spanJsonBefore.span_id; + const description_before = spanJsonBefore.description; + const status_before = spanJsonBefore.status; - expect(nameBefore).toBe('test-transaction'); + expect(description_before).toBe('test-span'); expect(status_before).toBeUndefined(); - expect(tags_before).toStrictEqual({}); await page.locator('#go-background').click(); + const spanJsonAfter: SpanJSON = await page.evaluate('window.getSpanJson()'); - const id_after = await getPropertyValue(transactionHandle, 'span_id'); - const nameAfter = JSON.parse(await transactionHandle.evaluate((t: any) => JSON.stringify(t))).description; - const status_after = await getPropertyValue(transactionHandle, 'status'); - const tags_after = await getPropertyValue(transactionHandle, 'tags'); + const id_after = spanJsonAfter.span_id; + const description_after = spanJsonAfter.description; + const status_after = spanJsonAfter.status; + const data_after = spanJsonAfter.data; expect(id_before).toBe(id_after); - expect(nameAfter).toBe('test-transaction'); + expect(description_after).toBe(description_before); expect(status_after).toBe('cancelled'); - expect(tags_after).toStrictEqual({ visibilitychange: 'document.hidden' }); + expect(data_after?.['sentry.cancellation_reason']).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts index 8432245f9c9b..1feda2e850e5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts @@ -17,7 +17,5 @@ sentryTest('should finish pageload transaction when the page goes background', a expect(pageloadTransaction.contexts?.trace?.op).toBe('pageload'); expect(pageloadTransaction.contexts?.trace?.status).toBe('cancelled'); - expect(pageloadTransaction.contexts?.trace?.tags).toMatchObject({ - visibilitychange: 'document.hidden', - }); + expect(pageloadTransaction.contexts?.trace?.data?.['sentry.cancellation_reason']).toBe('document.hidden'); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx index 984df38d86c9..d2aae8c9cd8d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { TransactionContextProvider } from '../components/transaction-context'; +import { SpanContextProvider } from '../components/span-context'; export default function Layout({ children }: { children: React.ReactNode }) { return ( @@ -36,7 +36,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { /redirect - {children} + {children}
    diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx index 42aabe3e4475..278da75e850c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/client-error-debug-tools.tsx @@ -2,11 +2,11 @@ import { captureException } from '@sentry/nextjs'; import { useContext, useState } from 'react'; -import { TransactionContext } from './transaction-context'; +import { SpanContext } from './span-context'; export function ClientErrorDebugTools() { - const transactionContextValue = useContext(TransactionContext); - const [transactionName, setTransactionName] = useState(''); + const spanContextValue = useContext(SpanContext); + const [spanName, setSpanName] = useState(''); const [isFetchingAPIRoute, setIsFetchingAPIRoute] = useState(); const [isFetchingEdgeAPIRoute, setIsFetchingEdgeAPIRoute] = useState(); @@ -19,31 +19,31 @@ export function ClientErrorDebugTools() { return (
    - {transactionContextValue.transactionActive ? ( + {spanContextValue.spanActive ? ( ) : ( <> { - setTransactionName(e.target.value); + setSpanName(e.target.value); }} /> )} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/span-context.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/span-context.tsx new file mode 100644 index 000000000000..3ecd84019471 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/span-context.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { getCurrentScope, startInactiveSpan } from '@sentry/nextjs'; +import { Span } from '@sentry/types'; +import { PropsWithChildren, createContext, useState } from 'react'; + +export const SpanContext = createContext< + { spanActive: false; start: (spanName: string) => void } | { spanActive: true; stop: () => void } +>({ + spanActive: false, + start: () => undefined, +}); + +export function SpanContextProvider({ children }: PropsWithChildren) { + const [span, setSpan] = useState(undefined); + + return ( + { + span.end(); + setSpan(undefined); + }, + } + : { + spanActive: false, + start: (spanName: string) => { + const span = startInactiveSpan({ name: spanName }); + getCurrentScope().setSpan(span); + setSpan(span); + }, + } + } + > + {children} + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx deleted file mode 100644 index ef1915b98af7..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/components/transaction-context.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; - -import { getCurrentHub, startTransaction } from '@sentry/nextjs'; -import { Transaction } from '@sentry/types'; -import { PropsWithChildren, createContext, useState } from 'react'; - -export const TransactionContext = createContext< - { transactionActive: false; start: (transactionName: string) => void } | { transactionActive: true; stop: () => void } ->({ - transactionActive: false, - start: () => undefined, -}); - -export function TransactionContextProvider({ children }: PropsWithChildren) { - const [transaction, setTransaction] = useState(undefined); - - return ( - { - transaction.end(); - setTransaction(undefined); - }, - } - : { - transactionActive: false, - start: (transactionName: string) => { - const t = startTransaction({ name: transactionName }); - getCurrentHub().getScope().setSpan(t); - setTransaction(t); - }, - } - } - > - {children} - - ); -} diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts index 9006936c4e60..f3667c10bb46 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -33,21 +33,17 @@ app.get('/test-param/:param', function (req, res) { res.send({ paramWas: req.params.param }); }); -app.get('/test-transaction', async function (req, res) { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); - Sentry.getCurrentScope().setSpan(transaction); +app.get('/test-transaction', function (req, res) { + Sentry.withActiveSpan(null, async () => { + Sentry.startSpan({ name: 'test-transaction', op: 'e2e-test' }, () => { + Sentry.startSpan({ name: 'test-span' }, () => undefined); + }); - // eslint-disable-next-line deprecation/deprecation - const span = transaction.startChild(); + await Sentry.flush(); - span.end(); - transaction.end(); - - await Sentry.flush(); - - res.send({ - transactionIds: global.transactionIds || [], + res.send({ + transactionIds: global.transactionIds || [], + }); }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.js b/dev-packages/e2e-tests/test-applications/node-profiling/index.js index bd440f4f17be..be569e12f921 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling/index.js +++ b/dev-packages/e2e-tests/test-applications/node-profiling/index.js @@ -10,9 +10,6 @@ Sentry.init({ profilesSampleRate: 1.0, }); -const txn = Sentry.startTransaction('Precompile test'); - -(async () => { +Sentry.startSpan({ name: 'Precompile test' }, async () => { await wait(500); - txn.finish(); -})(); +}); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts index 70596da19716..a6889bb46a9a 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import '@sentry/tracing'; - import * as Sentry from '@sentry/node'; Sentry.init({ @@ -9,7 +6,4 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); - -transaction.end(); +Sentry.startSpan({ name: 'test_span' }, () => undefined); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts index 4628782f025c..87c0ffc5dad9 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts @@ -1,10 +1,10 @@ import { TestEnv, assertSentryTransaction } from '../../../../utils'; -test('should send a manually started transaction when @sentry/tracing is imported using unnamed import.', async () => { +test('should send a manually started root span', async () => { const env = await TestEnv.init(__dirname); const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); assertSentryTransaction(envelope[2], { - transaction: 'test_transaction_1', + transaction: 'test_span', }); }); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts index f82fe81d969a..bd5ad9ea96e0 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts @@ -8,39 +8,26 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); -// eslint-disable-next-line deprecation/deprecation -const span_1 = transaction.startChild({ - op: 'span_1', - data: { - foo: 'bar', - baz: [1, 2, 3], - }, +Sentry.startSpan({ name: 'root_span' }, () => { + Sentry.startSpan( + { + name: 'span_1', + data: { + foo: 'bar', + baz: [1, 2, 3], + }, + }, + () => undefined, + ); + + // span_2 doesn't finish + Sentry.startInactiveSpan({ name: 'span_2' }); + + Sentry.startSpan({ name: 'span_3' }, () => { + // span_4 is the child of span_3 but doesn't finish. + Sentry.startInactiveSpan({ name: 'span_4', data: { qux: 'quux' } }); + + // span_5 is another child of span_3 but finishes. + Sentry.startSpan({ name: 'span_5' }, () => undefined); + }); }); -for (let i = 0; i < 2000; i++); - -// span_1 finishes -span_1.end(); - -// span_2 doesn't finish -// eslint-disable-next-line deprecation/deprecation -transaction.startChild({ op: 'span_2' }); -for (let i = 0; i < 4000; i++); - -// eslint-disable-next-line deprecation/deprecation -const span_3 = transaction.startChild({ op: 'span_3' }); -for (let i = 0; i < 4000; i++); - -// span_4 is the child of span_3 but doesn't finish. -// eslint-disable-next-line deprecation/deprecation -span_3.startChild({ op: 'span_4', data: { qux: 'quux' } }); - -// span_5 is another child of span_3 but finishes. -// eslint-disable-next-line deprecation/deprecation -span_3.startChild({ op: 'span_5' }).end(); - -// span_3 also finishes -span_3.end(); - -transaction.end(); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts index 5c4e5564c09d..77b83661b8a8 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts @@ -13,10 +13,10 @@ test('should report finished spans as children of the root transaction.', async expect(span3Id).toEqual(expect.any(String)); assertSentryTransaction(envelope[2], { - transaction: 'test_transaction_1', + transaction: 'root_span', spans: [ { - op: 'span_1', + description: 'span_1', data: { foo: 'bar', baz: [1, 2, 3], @@ -24,11 +24,11 @@ test('should report finished spans as children of the root transaction.', async parent_span_id: rootSpanId, }, { - op: 'span_3', + description: 'span_3', parent_span_id: rootSpanId, }, { - op: 'span_5', + description: 'span_5', parent_span_id: span3Id, }, ], diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts index 1584274bce7d..58656d7bf7da 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts @@ -27,18 +27,10 @@ const server = new ApolloServer({ resolvers, }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction', op: 'transaction' }); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - // eslint-disable-next-line @typescript-eslint/no-floating-promises -(async () => { +Sentry.startSpan({ name: 'test_span' }, async () => { // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation await server.executeOperation({ query: '{hello}', }); - - transaction.end(); -})(); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts index bcf16ca1dfb4..8c1bbaad9c2b 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts @@ -15,7 +15,7 @@ describe('GraphQL/Apollo Tests', () => { expect(graphqlSpanId).toBeDefined(); assertSentryTransaction(transaction, { - transaction: 'test_transaction', + transaction: 'test_span', spans: [ { description: 'execute', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts index 67d8e13750de..95b5c69b8dfb 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts @@ -15,33 +15,27 @@ const client = new MongoClient(process.env.MONGO_URL || '', { useUnifiedTopology: true, }); -async function run(): Promise { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ - name: 'Test Transaction', - op: 'transaction', - }); - - // eslint-disable-next-line deprecation/deprecation - Sentry.getCurrentScope().setSpan(transaction); - - try { - await client.connect(); - - const database = client.db('admin'); - const collection = database.collection('movies'); - - await collection.insertOne({ title: 'Rick and Morty' }); - await collection.findOne({ title: 'Back to the Future' }); - await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); - await collection.findOne({ title: 'South Park' }); - - await collection.find({ title: 'South Park' }).toArray(); - } finally { - if (transaction) transaction.end(); - await client.close(); - } -} - // eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); +Sentry.startSpanManual( + { + name: 'Test Span', + }, + async span => { + try { + await client.connect(); + + const database = client.db('admin'); + const collection = database.collection('movies'); + + await collection.insertOne({ title: 'Rick and Morty' }); + await collection.findOne({ title: 'Back to the Future' }); + await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); + await collection.findOne({ title: 'South Park' }); + + await collection.find({ title: 'South Park' }).toArray(); + } finally { + span?.end(); + await client.close(); + } + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts index 76ae4706eeb0..c7a3d14ab098 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts @@ -26,7 +26,7 @@ describe('MongoDB Test', () => { expect(envelope).toHaveLength(3); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { data: { diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts deleted file mode 100644 index 8273d4dfa96a..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Sentry from '@sentry/node'; -import mysql from 'mysql'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], -}); - -const connection = mysql.createConnection({ - user: 'root', - password: 'docker', -}); - -connection.connect(function (err: unknown) { - if (err) { - return; - } -}); - -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -connection.query('SELECT 1 + 1 AS solution', function () { - connection.query('SELECT NOW()', ['1', '2'], () => { - if (transaction) transaction.end(); - connection.end(); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/test.ts deleted file mode 100644 index 14d4acbaec50..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../../utils'; - -test('should auto-instrument `mysql` package when using connection.connect()', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', - spans: [ - { - description: 'SELECT 1 + 1 AS solution', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts deleted file mode 100644 index 2b06970b5243..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as Sentry from '@sentry/node'; -import mysql from 'mysql'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], -}); - -const connection = mysql.createConnection({ - user: 'root', - password: 'docker', -}); - -connection.connect(function (err: unknown) { - if (err) { - return; - } -}); - -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -const query = connection.query('SELECT 1 + 1 AS solution'); -const query2 = connection.query('SELECT NOW()', ['1', '2']); - -query.on('end', () => { - transaction.setAttribute('result_done', 'yes'); - - query2.on('end', () => { - transaction.setAttribute('result_done2', 'yes'); - - // Wait a bit to ensure the queries completed - setTimeout(() => { - transaction.end(); - }, 500); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts deleted file mode 100644 index f83e4297b8ba..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../../utils'; - -test('should auto-instrument `mysql` package when using query without callback', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - contexts: { - trace: { - data: { - result_done: 'yes', - result_done2: 'yes', - }, - }, - }, - transaction: 'Test Transaction', - spans: [ - { - description: 'SELECT 1 + 1 AS solution', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts deleted file mode 100644 index d88b2d1c8d24..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as Sentry from '@sentry/node'; -import mysql from 'mysql'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], -}); - -const connection = mysql.createConnection({ - user: 'root', - password: 'docker', -}); - -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -connection.query('SELECT 1 + 1 AS solution', function () { - connection.query('SELECT NOW()', ['1', '2'], () => { - if (transaction) transaction.end(); - connection.end(); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/test.ts deleted file mode 100644 index e05dc7d9e85c..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../../utils'; - -test('should auto-instrument `mysql` package without connection.connect()', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', - spans: [ - { - description: 'SELECT 1 + 1 AS solution', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'mysql', - 'server.address': 'localhost', - 'server.port': 3306, - 'db.user': 'root', - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts index 47fb37e054f7..5d50a411f37f 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts @@ -8,18 +8,16 @@ Sentry.init({ integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -const client = new pg.Client(); -client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => - client.query('SELECT * FROM bazz', () => { - client.query('SELECT NOW()', () => transaction.end()); - }), +Sentry.startSpanManual( + { + name: 'Test Span', + }, + span => { + const client = new pg.Client(); + client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => + client.query('SELECT * FROM bazz', () => { + client.query('SELECT NOW()', () => span?.end()); + }), + ); + }, ); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts index 6efeb6281c05..de892bc43b18 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts @@ -35,7 +35,7 @@ test('should auto-instrument `pg` package.', async () => { expect(envelope).toHaveLength(3); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { description: 'SELECT * FROM foo where bar ilike "baz%"', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts index 6191dbf31d75..29c20bb772ba 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts @@ -1,4 +1,3 @@ -import { randomBytes } from 'crypto'; /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { PrismaClient } from '@prisma/client'; import * as Sentry from '@sentry/node'; @@ -12,37 +11,31 @@ Sentry.init({ integrations: [new Sentry.Integrations.Prisma({ client })], }); -async function run(): Promise { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ - name: 'Test Transaction', - op: 'transaction', - }); - - // eslint-disable-next-line deprecation/deprecation - Sentry.getCurrentScope().setSpan(transaction); - - try { - await client.user.create({ - data: { - name: 'Tilda', - email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, - }, - }); +// eslint-disable-next-line @typescript-eslint/no-floating-promises +Sentry.startSpanManual( + { + name: 'Test Span', + }, + async span => { + try { + await client.user.create({ + data: { + name: 'Dog', + email: 'dog@sentry.io', + }, + }); - await client.user.findMany(); + await client.user.findMany(); - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, }, - }, - }); - } finally { - if (transaction) transaction.end(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); + }); + } finally { + if (span) span.end(); + } + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts index 0d969c262413..70d704ec7017 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts @@ -6,7 +6,7 @@ describe('Prisma ORM Integration', () => { const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { description: 'User create', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts index 600b5ef71038..3dd6fbc2f7de 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts @@ -1,5 +1,4 @@ import * as http from 'http'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars import * as Sentry from '@sentry/node'; Sentry.init({ @@ -10,15 +9,9 @@ Sentry.init({ integrations: [new Sentry.Integrations.Http({ tracing: true })], }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction' }); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -http.get('http://match-this-url.com/api/v0'); -http.get('http://match-this-url.com/api/v1'); -http.get('http://dont-match-this-url.com/api/v2'); -http.get('http://dont-match-this-url.com/api/v3'); - -transaction.end(); +Sentry.startSpan({ name: 'test_span' }, () => { + http.get('http://match-this-url.com/api/v0'); + http.get('http://match-this-url.com/api/v1'); + http.get('http://dont-match-this-url.com/api/v2'); + http.get('http://dont-match-this-url.com/api/v3'); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts index 6a699fa07af7..e859b1e633c5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts @@ -29,18 +29,10 @@ const server = new ApolloServer({ resolvers, }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction', op: 'transaction' }); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - // eslint-disable-next-line @typescript-eslint/no-floating-promises -(async () => { +Sentry.startSpan({ name: 'root_span' }, async () => { // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation await server.executeOperation({ query: '{hello}', }); - - transaction.end(); -})(); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts index bcf16ca1dfb4..152b3e9f697b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts @@ -15,7 +15,7 @@ describe('GraphQL/Apollo Tests', () => { expect(graphqlSpanId).toBeDefined(); assertSentryTransaction(transaction, { - transaction: 'test_transaction', + transaction: 'root_span', spans: [ { description: 'execute', diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts index 51359ac726da..3e039d8f9fb8 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts @@ -16,33 +16,27 @@ const client = new MongoClient(process.env.MONGO_URL || '', { useUnifiedTopology: true, }); -async function run(): Promise { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ - name: 'Test Transaction', - op: 'transaction', - }); - - // eslint-disable-next-line deprecation/deprecation - Sentry.getCurrentScope().setSpan(transaction); - - try { - await client.connect(); - - const database = client.db('admin'); - const collection = database.collection('movies'); - - await collection.insertOne({ title: 'Rick and Morty' }); - await collection.findOne({ title: 'Back to the Future' }); - await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); - await collection.findOne({ title: 'South Park' }); - - await collection.find({ title: 'South Park' }).toArray(); - } finally { - if (transaction) transaction.end(); - await client.close(); - } -} - // eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); +Sentry.startSpanManual( + { + name: 'Test Span', + }, + async span => { + try { + await client.connect(); + + const database = client.db('admin'); + const collection = database.collection('movies'); + + await collection.insertOne({ title: 'Rick and Morty' }); + await collection.findOne({ title: 'Back to the Future' }); + await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); + await collection.findOne({ title: 'South Park' }); + + await collection.find({ title: 'South Park' }).toArray(); + } finally { + span?.end(); + await client.close(); + } + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts index 76ae4706eeb0..c7a3d14ab098 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts @@ -26,7 +26,7 @@ describe('MongoDB Test', () => { expect(envelope).toHaveLength(3); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { data: { diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts deleted file mode 100644 index ce53d776fe54..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts +++ /dev/null @@ -1,37 +0,0 @@ -import '@sentry/tracing'; - -import * as Sentry from '@sentry/node'; -import mysql from 'mysql'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, -}); - -const connection = mysql.createConnection({ - user: 'root', - password: 'docker', -}); - -connection.connect(function (err: unknown) { - if (err) { - return; - } -}); - -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', -}); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -connection.query('SELECT 1 + 1 AS solution', function () { - connection.query('SELECT NOW()', ['1', '2'], () => { - if (transaction) transaction.end(); - connection.end(); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/test.ts deleted file mode 100644 index 3fcf25d731a5..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -test('should auto-instrument `mysql` package.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', - spans: [ - { - description: 'SELECT 1 + 1 AS solution', - op: 'db', - data: { - 'db.system': 'mysql', - 'db.user': 'root', - 'server.address': expect.any(String), - 'server.port': expect.any(Number), - }, - }, - - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'mysql', - 'db.user': 'root', - 'server.address': expect.any(String), - 'server.port': expect.any(Number), - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts index f9bfa0de0294..3d0600cb81ec 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts @@ -9,18 +9,11 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ - op: 'transaction', - name: 'Test Transaction', +Sentry.startSpanManual({ name: 'Test Span' }, span => { + const client = new pg.Client(); + client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => + client.query('SELECT * FROM bazz', () => { + client.query('SELECT NOW()', () => span?.end()); + }), + ); }); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -const client = new pg.Client(); -client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => - client.query('SELECT * FROM bazz', () => { - client.query('SELECT NOW()', () => transaction.end()); - }), -); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts index 559f41fcb26a..16cd5fb8487c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts @@ -40,7 +40,7 @@ test('should auto-instrument `pg` package.', async () => { expect(envelope).toHaveLength(3); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { description: 'SELECT * FROM foo where bar ilike "baz%"', diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts index b5003141caec..0ab9fe938e7e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts @@ -1,4 +1,3 @@ -import { randomBytes } from 'crypto'; /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { PrismaClient } from '@prisma/client'; import * as Sentry from '@sentry/node'; @@ -14,37 +13,31 @@ Sentry.init({ integrations: [new Tracing.Integrations.Prisma({ client })], }); -async function run(): Promise { - // eslint-disable-next-line deprecation/deprecation - const transaction = Sentry.startTransaction({ - name: 'Test Transaction', - op: 'transaction', - }); - - // eslint-disable-next-line deprecation/deprecation - Sentry.getCurrentScope().setSpan(transaction); - - try { - await client.user.create({ - data: { - name: 'Tilda', - email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, - }, - }); +// eslint-disable-next-line @typescript-eslint/no-floating-promises +Sentry.startSpanManual( + { + name: 'Test Span', + }, + async span => { + try { + await client.user.create({ + data: { + name: 'David', + email: 'david_cramer@sentry.io', + }, + }); - await client.user.findMany(); + await client.user.findMany(); - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, }, - }, - }); - } finally { - if (transaction) transaction.end(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); + }); + } finally { + span?.end(); + } + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts index 0d969c262413..70d704ec7017 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts @@ -6,7 +6,7 @@ describe('Prisma ORM Integration', () => { const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); assertSentryTransaction(envelope[2], { - transaction: 'Test Transaction', + transaction: 'Test Span', spans: [ { description: 'User create', diff --git a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts index 8ecb7ed3cc61..3dd6fbc2f7de 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import '@sentry/tracing'; - import * as http from 'http'; import * as Sentry from '@sentry/node'; @@ -12,15 +9,9 @@ Sentry.init({ integrations: [new Sentry.Integrations.Http({ tracing: true })], }); -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction' }); - -// eslint-disable-next-line deprecation/deprecation -Sentry.getCurrentScope().setSpan(transaction); - -http.get('http://match-this-url.com/api/v0'); -http.get('http://match-this-url.com/api/v1'); -http.get('http://dont-match-this-url.com/api/v2'); -http.get('http://dont-match-this-url.com/api/v3'); - -transaction.end(); +Sentry.startSpan({ name: 'test_span' }, () => { + http.get('http://match-this-url.com/api/v0'); + http.get('http://match-this-url.com/api/v1'); + http.get('http://dont-match-this-url.com/api/v2'); + http.get('http://dont-match-this-url.com/api/v3'); +}); diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 7083d71ce05a..126ee9460230 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -59,6 +59,7 @@ export { setUser, withScope, withIsolationScope, + withActiveSpan, // eslint-disable-next-line deprecation/deprecation FunctionToString, // eslint-disable-next-line deprecation/deprecation @@ -69,6 +70,7 @@ export { startSession, captureSession, endSession, + spanToJSON, } from '@sentry/core'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 686583276f4b..e95d4d047c23 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -79,6 +79,7 @@ export { startSession, captureSession, endSession, + withActiveSpan, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 880ceed38ee2..a4ac4509b299 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -78,6 +78,7 @@ export { startSession, captureSession, endSession, + withActiveSpan, } from '@sentry/core'; export { diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 7f4c52a53e06..63a28a5ee4bb 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -100,4 +100,5 @@ export { startSession, captureSession, endSession, + withActiveSpan, } from '@sentry/node'; diff --git a/packages/tracing-internal/src/browser/backgroundtab.ts b/packages/tracing-internal/src/browser/backgroundtab.ts index 95f33c0c9fd3..a221a7c09c82 100644 --- a/packages/tracing-internal/src/browser/backgroundtab.ts +++ b/packages/tracing-internal/src/browser/backgroundtab.ts @@ -1,5 +1,5 @@ -import type { IdleTransaction, SpanStatusType } from '@sentry/core'; -import { getActiveTransaction, spanToJSON } from '@sentry/core'; +import { getActiveSpan, getRootSpan } from '@sentry/core'; +import { spanToJSON } from '@sentry/core'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; @@ -12,24 +12,33 @@ import { WINDOW } from './types'; export function registerBackgroundTabDetection(): void { if (WINDOW && WINDOW.document) { WINDOW.document.addEventListener('visibilitychange', () => { - // eslint-disable-next-line deprecation/deprecation - const activeTransaction = getActiveTransaction() as IdleTransaction; - if (WINDOW.document.hidden && activeTransaction) { - const statusType: SpanStatusType = 'cancelled'; + const activeSpan = getActiveSpan(); + if (!activeSpan) { + return; + } + + const rootSpan = getRootSpan(activeSpan); + if (!rootSpan) { + return; + } + + if (WINDOW.document.hidden && rootSpan) { + const cancelledStatus = 'cancelled'; - const { op, status } = spanToJSON(activeTransaction); + const { op, status } = spanToJSON(rootSpan); + + if (DEBUG_BUILD) { + logger.log(`[Tracing] Transaction: ${cancelledStatus} -> since tab moved to the background, op: ${op}`); + } - DEBUG_BUILD && - logger.log(`[Tracing] Transaction: ${statusType} -> since tab moved to the background, op: ${op}`); // We should not set status if it is already set, this prevent important statuses like // error or data loss from being overwritten on transaction. if (!status) { - activeTransaction.setStatus(statusType); + rootSpan.setStatus(cancelledStatus); } - // TODO: Can we rewrite this to an attribute? - // eslint-disable-next-line deprecation/deprecation - activeTransaction.setTag('visibilitychange', 'document.hidden'); - activeTransaction.end(); + + rootSpan.setAttribute('sentry.cancellation_reason', 'document.hidden'); + rootSpan.end(); } }); } else { diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index dd2eaec00a69..b04dc6705dae 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -54,13 +54,11 @@ describe('registerBackgroundTabDetection', () => { global.document.hidden = true; events.visibilitychange(); - const { status, timestamp } = spanToJSON(span!); + const { status, timestamp, data } = spanToJSON(span!); - // eslint-disable-next-line deprecation/deprecation - expect(span?.status).toBe('cancelled'); + expect(status).toBe('cancelled'); expect(status).toBeDefined(); - // eslint-disable-next-line deprecation/deprecation - expect(span?.tags.visibilitychange).toBe('document.hidden'); + expect(data!['sentry.cancellation_reason']).toBe('document.hidden'); expect(timestamp).toBeDefined(); }); }); From faae2cc9803c606a348f2e9f43731fdfddfc0eab Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:23:30 +0100 Subject: [PATCH 122/173] ref(node-integration-tests): Migrate to new Http integration (#10765) This PR migrates the Http integration in node-integration-tests (`new Sentry.Integrations.Http` gets `Sentry.httpIntegration`). I also exchanged `Tracing` to `Sentry`. --- .../multiple-routers/common-infix-parameterized/server.ts | 4 +--- .../suites/express/multiple-routers/common-infix/server.ts | 4 +--- .../common-prefix-parameterized-reverse/server.ts | 4 +--- .../multiple-routers/common-prefix-parameterized/server.ts | 4 +--- .../common-prefix-same-length-parameterized copy/server.ts | 4 +--- .../common-prefix-same-length-parameterized/server.ts | 4 +--- .../suites/express/multiple-routers/common-prefix/server.ts | 4 +--- .../suites/express/multiple-routers/complex-router/server.ts | 3 +-- .../multiple-routers/middle-layer-parameterized/server.ts | 4 +--- .../suites/express/sentry-trace/baggage-header-out/server.ts | 4 +--- .../baggage-other-vendors-with-sentry-entries/server.ts | 4 +--- .../express/sentry-trace/baggage-other-vendors/server.ts | 4 +--- .../express/sentry-trace/baggage-transaction-name/server.ts | 4 +--- .../suites/express/sentry-trace/server.ts | 4 +--- .../node-integration-tests/suites/express/tracing/server.ts | 2 +- .../suites/tracing-new/tracePropagationTargets/scenario.ts | 2 +- .../suites/tracing/apollo-graphql/scenario.ts | 4 +--- .../suites/tracing/prisma-orm/scenario.ts | 4 +--- .../suites/tracing/tracePropagationTargets/scenario.ts | 2 +- 19 files changed, 19 insertions(+), 50 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts index fe2d32009ded..7cc849cd366d 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts index afc5aeb4b66e..92ae56558646 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts index 38400a6455e2..a351837cdf3c 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts index a787aa288fcb..765f30c39448 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts index 0b51b70f80f7..ad72cbc3babf 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts index d32c6dadfe1f..1b17a85127bf 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts index 270c2fec5e49..dfe236aa2f64 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -9,8 +8,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts index 08b3a8f024cc..697e21837141 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts @@ -7,8 +7,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts index 7b92c2b6508d..d84c5bb1a358 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts @@ -1,6 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import express from 'express'; const app = express(); @@ -8,8 +7,7 @@ const app = express(); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts index 8669c5bb21b8..b5d08d42b5f8 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts @@ -1,7 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -14,8 +13,7 @@ Sentry.init({ release: '1.0', environment: 'prod', tracePropagationTargets: [/^(?!.*express).*$/], - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts index 2c226da111a1..f20addce854d 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts @@ -1,7 +1,6 @@ import * as http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -15,8 +14,7 @@ Sentry.init({ environment: 'prod', // disable requests to /express tracePropagationTargets: [/^(?!.*express).*$/], - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts index 5858f452c71d..5dc15c1c2a9b 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts @@ -1,7 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -15,8 +14,7 @@ Sentry.init({ environment: 'prod', // disable requests to /express tracePropagationTargets: [/^(?!.*express).*$/], - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts index 92d2f8a0195a..454bbfdf3d9a 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts @@ -2,7 +2,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -16,8 +15,7 @@ Sentry.init({ environment: 'prod', // disable requests to /express tracePropagationTargets: [/^(?!.*express).*$/], - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, // TODO: We're rethinking the mechanism for including Pii data in DSC, hence commenting out sendDefaultPii for now // sendDefaultPii: true, diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts index 77de1a188afa..92ec8d16e00e 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts @@ -1,7 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import cors from 'cors'; import express from 'express'; @@ -14,8 +13,7 @@ Sentry.init({ release: '1.0', environment: 'prod', tracePropagationTargets: [/^(?!.*express).*$/], - // eslint-disable-next-line deprecation/deprecation - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/express/tracing/server.ts b/dev-packages/node-integration-tests/suites/express/tracing/server.ts index dfd6df7526fd..530499fa39d2 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/server.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/server.ts @@ -10,7 +10,7 @@ Sentry.init({ release: '1.0', // disable attaching headers to /test/* endpoints tracePropagationTargets: [/^(?!.*test).*$/], - integrations: [new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app })], + integrations: [Sentry.httpIntegration({ tracing: true }), new Sentry.Integrations.Express({ app })], tracesSampleRate: 1.0, transport: loggingTransport, }); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts index 3dd6fbc2f7de..5eef548fd15d 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts @@ -6,7 +6,7 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [new Sentry.Integrations.Http({ tracing: true })], + integrations: [Sentry.httpIntegration({ tracing: true })], }); Sentry.startSpan({ name: 'test_span' }, () => { diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts index e859b1e633c5..9105f0490847 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts @@ -1,13 +1,11 @@ import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; import { ApolloServer, gql } from 'apollo-server'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, - // eslint-disable-next-line deprecation/deprecation - integrations: [new Tracing.Integrations.GraphQL(), new Tracing.Integrations.Apollo()], + integrations: [new Sentry.Integrations.GraphQL(), new Sentry.Integrations.Apollo()], }); const typeDefs = gql` diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts index 0ab9fe938e7e..51bccb519630 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { PrismaClient } from '@prisma/client'; import * as Sentry from '@sentry/node'; -import * as Tracing from '@sentry/tracing'; const client = new PrismaClient(); @@ -9,8 +8,7 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, - // eslint-disable-next-line deprecation/deprecation - integrations: [new Tracing.Integrations.Prisma({ client })], + integrations: [new Sentry.Integrations.Prisma({ client })], }); // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts index 3dd6fbc2f7de..5eef548fd15d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts @@ -6,7 +6,7 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [new Sentry.Integrations.Http({ tracing: true })], + integrations: [Sentry.httpIntegration({ tracing: true })], }); Sentry.startSpan({ name: 'test_span' }, () => { From 27273f68358f3569445506c809cd2a0cf3efd4cb Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 21 Feb 2024 08:27:08 -0500 Subject: [PATCH 123/173] feat(v8): Remove span.origin (#10753) ref https://github.com/getsentry/sentry-javascript/issues/10677 Removes `span.origin` as a getter. --- packages/core/src/tracing/sentrySpan.ts | 18 ------------------ .../opentelemetry-node/src/utils/spanData.ts | 4 ++-- packages/types/src/span.ts | 7 ------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 3faba55d8eab..b4566a55e399 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -315,24 +315,6 @@ export class SentrySpan implements SpanInterface { this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); } - /** - * The origin of the span, giving context about what created the span. - * - * @deprecated Use `spanToJSON().origin` to read the origin instead. - */ - public get origin(): SpanOrigin | undefined { - return this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined; - } - - /** - * The origin of the span, giving context about what created the span. - * - * @deprecated Use `startSpan()` functions to set the origin instead. - */ - public set origin(origin: SpanOrigin | undefined) { - this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, origin); - } - /* eslint-enable @typescript-eslint/member-ordering */ /** @inheritdoc */ diff --git a/packages/opentelemetry-node/src/utils/spanData.ts b/packages/opentelemetry-node/src/utils/spanData.ts index 5cec7ee0f93f..6556e5340b15 100644 --- a/packages/opentelemetry-node/src/utils/spanData.ts +++ b/packages/opentelemetry-node/src/utils/spanData.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { Transaction } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, Transaction } from '@sentry/core'; import type { Context, SpanOrigin } from '@sentry/types'; import { getSentrySpan } from './spanMap'; @@ -46,7 +46,7 @@ export function addOtelSpanData( } if (origin) { - sentrySpan.origin = origin; + sentrySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, origin); } if (sentrySpan instanceof Transaction) { diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index c6ab48e4ed7a..2e6e0b084900 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -260,13 +260,6 @@ export interface Span extends Omit Date: Wed, 21 Feb 2024 08:55:23 -0500 Subject: [PATCH 124/173] feat(v8/browser): Rename TryCatch integration to `browserApiErrorsIntegration` (#10755) Resolves https://github.com/getsentry/sentry-javascript/issues/8838 - Removes `TryCatch` integration class export - Updates angular to refer to browserApiErrorsIntegration by name instead of using `TryCatch` --- .../suites/public-api/debug/test.ts | 2 +- .../suites/replay/captureReplay/test.ts | 4 +-- .../captureReplayFromReplayPackage/test.ts | 4 +-- .../utils/replayEventTemplates.ts | 2 +- packages/angular-ivy/src/sdk.ts | 6 ++-- packages/angular/src/sdk.ts | 6 ++-- packages/angular/test/sdk.test.ts | 4 +-- packages/browser/src/exports.ts | 4 +-- .../{trycatch.ts => browserapierrors.ts} | 29 +++++-------------- packages/browser/src/integrations/index.ts | 1 - packages/browser/src/sdk.ts | 2 +- packages/replay/test/fixtures/error.ts | 2 +- packages/replay/test/fixtures/transaction.ts | 2 +- 13 files changed, 26 insertions(+), 42 deletions(-) rename packages/browser/src/integrations/{trycatch.ts => browserapierrors.ts} (91%) diff --git a/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts b/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts index ab417154ae55..8e4e953d69d9 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts @@ -25,7 +25,7 @@ sentryTest('logs debug messages correctly', async ({ getLocalTestUrl, page }) => ? [ 'Sentry Logger [log]: Integration installed: InboundFilters', 'Sentry Logger [log]: Integration installed: FunctionToString', - 'Sentry Logger [log]: Integration installed: TryCatch', + 'Sentry Logger [log]: Integration installed: BrowserApiErrors', 'Sentry Logger [log]: Integration installed: Breadcrumbs', 'Sentry Logger [log]: Global Handler attached: onerror', 'Sentry Logger [log]: Global Handler attached: onunhandledrejection', diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index f5634036dc13..0b1e056123e8 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -45,7 +45,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', @@ -82,7 +82,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index c42bfc692018..2235853ab5cc 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -45,7 +45,7 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', @@ -82,7 +82,7 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 3be00a1f9840..03354c6b3185 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -19,7 +19,7 @@ const DEFAULT_REPLAY_EVENT = { integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', diff --git a/packages/angular-ivy/src/sdk.ts b/packages/angular-ivy/src/sdk.ts index da73fb49a1a2..1333ac6f6112 100644 --- a/packages/angular-ivy/src/sdk.ts +++ b/packages/angular-ivy/src/sdk.ts @@ -11,14 +11,14 @@ import { IS_DEBUG_BUILD } from './flags'; */ export function init(options: BrowserOptions): void { const opts = { - // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`: - // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a + // Filter out BrowserApiErrors integration as it interferes with our Angular `ErrorHandler`: + // BrowserApiErrors would catch certain errors before they reach the `ErrorHandler` and thus provide a // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide. // see: // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097 // - https://github.com/getsentry/sentry-javascript/issues/2744 defaultIntegrations: getDefaultIntegrations(options).filter(integration => { - return integration.name !== 'TryCatch'; + return integration.name !== 'BrowserApiErrors'; }), ...options, }; diff --git a/packages/angular/src/sdk.ts b/packages/angular/src/sdk.ts index 8e6bbc5eb0d6..f2bca755782f 100755 --- a/packages/angular/src/sdk.ts +++ b/packages/angular/src/sdk.ts @@ -11,14 +11,14 @@ import { IS_DEBUG_BUILD } from './flags'; */ export function init(options: BrowserOptions): void { const opts = { - // Filter out TryCatch integration as it interferes with our Angular `ErrorHandler`: - // TryCatch would catch certain errors before they reach the `ErrorHandler` and thus provide a + // Filter out BrowserApiErrors integration as it interferes with our Angular `ErrorHandler`: + // BrowserApiErrors would catch certain errors before they reach the `ErrorHandler` and thus provide a // lower fidelity error than what `SentryErrorHandler` (see errorhandler.ts) would provide. // see: // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097 // - https://github.com/getsentry/sentry-javascript/issues/2744 defaultIntegrations: getDefaultIntegrations(options).filter(integration => { - return integration.name !== 'TryCatch'; + return integration.name !== 'BrowserApiErrors'; }), ...options, }; diff --git a/packages/angular/test/sdk.test.ts b/packages/angular/test/sdk.test.ts index 781d02c61005..115064cd6970 100644 --- a/packages/angular/test/sdk.test.ts +++ b/packages/angular/test/sdk.test.ts @@ -14,7 +14,7 @@ describe('init', () => { expect(setContextSpy).toHaveBeenCalledWith('angular', { version: 10 }); }); - describe('filtering out the `TryCatch` integration', () => { + describe('filtering out the `BrowserApiErrors` integration', () => { const browserInitSpy = jest.spyOn(SentryBrowser, 'init'); beforeEach(() => { @@ -27,7 +27,7 @@ describe('init', () => { expect(browserInitSpy).toHaveBeenCalledTimes(1); const options = browserInitSpy.mock.calls[0][0] || {}; - expect(options.defaultIntegrations).not.toContainEqual(expect.objectContaining({ name: 'TryCatch' })); + expect(options.defaultIntegrations).not.toContainEqual(expect.objectContaining({ name: 'BrowserApiErrors' })); }); it("doesn't filter if `defaultIntegrations` is set to `false`", () => { diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 126ee9460230..5e99eb33651b 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -112,7 +112,7 @@ export { dedupeIntegration } from './integrations/dedupe'; export { globalHandlersIntegration } from './integrations/globalhandlers'; export { httpContextIntegration } from './integrations/httpcontext'; export { linkedErrorsIntegration } from './integrations/linkederrors'; -export { browserApiErrorsIntegration } from './integrations/trycatch'; +export { browserApiErrorsIntegration } from './integrations/browserapierrors'; // eslint-disable-next-line deprecation/deprecation -export { TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; +export { Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; diff --git a/packages/browser/src/integrations/trycatch.ts b/packages/browser/src/integrations/browserapierrors.ts similarity index 91% rename from packages/browser/src/integrations/trycatch.ts rename to packages/browser/src/integrations/browserapierrors.ts index bd7c50331022..8ede786f5c43 100644 --- a/packages/browser/src/integrations/trycatch.ts +++ b/packages/browser/src/integrations/browserapierrors.ts @@ -1,5 +1,5 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn, WrappedFunction } from '@sentry/types'; import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils'; import { WINDOW, wrap } from '../helpers'; @@ -38,11 +38,11 @@ const DEFAULT_EVENT_TARGET = [ 'XMLHttpRequestUpload', ]; -const INTEGRATION_NAME = 'TryCatch'; +const INTEGRATION_NAME = 'BrowserApiErrors'; type XMLHttpRequestProp = 'onload' | 'onerror' | 'onprogress' | 'onreadystatechange'; -interface TryCatchOptions { +interface BrowserApiErrorsOptions { setTimeout: boolean; setInterval: boolean; requestAnimationFrame: boolean; @@ -50,7 +50,7 @@ interface TryCatchOptions { eventTarget: boolean | string[]; } -const _browserApiErrorsIntegration = ((options: Partial = {}) => { +const _browserApiErrorsIntegration = ((options: Partial = {}) => { const _options = { XMLHttpRequest: true, eventTarget: true, @@ -90,25 +90,10 @@ const _browserApiErrorsIntegration = ((options: Partial = {}) = }; }) satisfies IntegrationFn; -export const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration); - /** * Wrap timer functions and event targets to catch errors and provide better meta data. - * @deprecated Use `browserApiErrorsIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const TryCatch = convertIntegrationFnToClass( - INTEGRATION_NAME, - browserApiErrorsIntegration, -) as IntegrationClass & { - new (options?: { - setTimeout: boolean; - setInterval: boolean; - requestAnimationFrame: boolean; - XMLHttpRequest: boolean; - eventTarget: boolean | string[]; - }): Integration; -}; +export const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration); function _wrapTimeFunction(original: () => void): () => number { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -167,7 +152,7 @@ function _wrapXHR(originalSend: () => void): () => void { }, }; - // If Instrument integration has been called before TryCatch, get the name of original function + // If Instrument integration has been called before BrowserApiErrors, get the name of original function const originalFunction = getOriginalFunction(original); if (originalFunction) { wrapOptions.mechanism.data.handler = getFunctionName(originalFunction); diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts index 977cdabba8a4..a39db874045b 100644 --- a/packages/browser/src/integrations/index.ts +++ b/packages/browser/src/integrations/index.ts @@ -1,5 +1,4 @@ /* eslint-disable deprecation/deprecation */ -export { TryCatch } from './trycatch'; export { Breadcrumbs } from './breadcrumbs'; export { LinkedErrors } from './linkederrors'; export { HttpContext } from './httpcontext'; diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index dd7c08f77e56..3e89e43b3da1 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -21,11 +21,11 @@ import { BrowserClient } from './client'; import { DEBUG_BUILD } from './debug-build'; import { WINDOW, wrap as internalWrap } from './helpers'; import { breadcrumbsIntegration } from './integrations/breadcrumbs'; +import { browserApiErrorsIntegration } from './integrations/browserapierrors'; import { dedupeIntegration } from './integrations/dedupe'; import { globalHandlersIntegration } from './integrations/globalhandlers'; import { httpContextIntegration } from './integrations/httpcontext'; import { linkedErrorsIntegration } from './integrations/linkederrors'; -import { browserApiErrorsIntegration } from './integrations/trycatch'; import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport } from './transports/fetch'; diff --git a/packages/replay/test/fixtures/error.ts b/packages/replay/test/fixtures/error.ts index 046efa619539..1fde3a179ffc 100644 --- a/packages/replay/test/fixtures/error.ts +++ b/packages/replay/test/fixtures/error.ts @@ -41,7 +41,7 @@ export function Error(obj?: Event): any { integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', diff --git a/packages/replay/test/fixtures/transaction.ts b/packages/replay/test/fixtures/transaction.ts index b9d6fa309fa1..fe7d034e7403 100644 --- a/packages/replay/test/fixtures/transaction.ts +++ b/packages/replay/test/fixtures/transaction.ts @@ -249,7 +249,7 @@ export function Transaction(traceId?: string, obj?: Partial): any { integrations: [ 'InboundFilters', 'FunctionToString', - 'TryCatch', + 'BrowserApiErrors', 'Breadcrumbs', 'GlobalHandlers', 'LinkedErrors', From a60391a82ab46561012db8f31d8ee6e5873c008c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 21 Feb 2024 15:01:06 +0100 Subject: [PATCH 125/173] feat(opentelemetry): Align span options with core span options (#10761) This aligns the options for `startSpan` in otel with the core ones. Only the `kind` is there in addition for now (we may want to remove this, let's see). Especially, this also adds the ability to pass a `scope` there and pick up the correct context & span to continue, as well as adding tests for the options. --- packages/core/test/lib/tracing/trace.test.ts | 2 +- packages/opentelemetry/src/contextManager.ts | 4 +- packages/opentelemetry/src/trace.ts | 73 ++++++--- packages/opentelemetry/src/types.ts | 18 +-- .../opentelemetry/src/utils/contextData.ts | 11 +- packages/opentelemetry/test/trace.test.ts | 150 ++++++++++++++++-- 6 files changed, 204 insertions(+), 54 deletions(-) diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index a260b8854cd0..5ce2e3e01c6b 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -291,7 +291,7 @@ describe('startSpan', () => { expect(spanToJSON(_span!).timestamp).toBeDefined(); }); - it('allows to pass a `startTime` yyy', () => { + it('allows to pass a `startTime`', () => { const start = startSpan({ name: 'outer', startTime: [1234, 0] }, span => { return spanToJSON(span!).start_timestamp; }); diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index aed172533eea..73bbd145b978 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -8,7 +8,7 @@ import { SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, } from './constants'; import { getCurrentHub } from './custom/getCurrentHub'; -import { getScopesFromContext, setHubOnContext, setScopesOnContext } from './utils/contextData'; +import { getScopesFromContext, setContextOnScope, setHubOnContext, setScopesOnContext } from './utils/contextData'; /** * Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub. @@ -70,6 +70,8 @@ export function wrapContextManagerClass(spanContext: OpenTelemetrySpanContext, callback: (span: Span) => T): T { +export function startSpan(options: OpenTelemetrySpanContext, callback: (span: Span) => T): T { const tracer = getTracer(); - const { name } = spanContext; + const { name } = options; - const activeCtx = context.active(); - const shouldSkipSpan = spanContext.onlyIfParent && !trace.getSpan(activeCtx); + const activeCtx = getContext(options.scope); + const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx); const ctx = shouldSkipSpan ? suppressTracing(activeCtx) : activeCtx; + const spanContext = getSpanContext(options); + return tracer.startActiveSpan(name, spanContext, ctx, span => { - _applySentryAttributesToSpan(span, spanContext); + _applySentryAttributesToSpan(span, options); return handleCallbackErrors( () => callback(span), @@ -49,17 +52,19 @@ export function startSpan(spanContext: OpenTelemetrySpanContext, callback: (s * * Note that you'll always get a span passed to the callback, it may just be a NonRecordingSpan if the span is not sampled. */ -export function startSpanManual(spanContext: OpenTelemetrySpanContext, callback: (span: Span) => T): T { +export function startSpanManual(options: OpenTelemetrySpanContext, callback: (span: Span) => T): T { const tracer = getTracer(); - const { name } = spanContext; + const { name } = options; - const activeCtx = context.active(); - const shouldSkipSpan = spanContext.onlyIfParent && !trace.getSpan(activeCtx); + const activeCtx = getContext(options.scope); + const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx); const ctx = shouldSkipSpan ? suppressTracing(activeCtx) : activeCtx; + const spanContext = getSpanContext(options); + return tracer.startActiveSpan(name, spanContext, ctx, span => { - _applySentryAttributesToSpan(span, spanContext); + _applySentryAttributesToSpan(span, options); return handleCallbackErrors( () => callback(span), @@ -85,18 +90,20 @@ export const startActiveSpan = startSpan; * or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans * and the `span` returned from the callback will be undefined. */ -export function startInactiveSpan(spanContext: OpenTelemetrySpanContext): Span { +export function startInactiveSpan(options: OpenTelemetrySpanContext): Span { const tracer = getTracer(); - const { name } = spanContext; + const { name } = options; - const activeCtx = context.active(); - const shouldSkipSpan = spanContext.onlyIfParent && !trace.getSpan(activeCtx); + const activeCtx = getContext(options.scope); + const shouldSkipSpan = options.onlyIfParent && !trace.getSpan(activeCtx); const ctx = shouldSkipSpan ? suppressTracing(activeCtx) : activeCtx; + const spanContext = getSpanContext(options); + const span = tracer.startSpan(name, spanContext, ctx); - _applySentryAttributesToSpan(span, spanContext); + _applySentryAttributesToSpan(span, options); return span; } @@ -120,8 +127,9 @@ function getTracer(): Tracer { return (client && client.tracer) || trace.getTracer('@sentry/opentelemetry', SDK_VERSION); } -function _applySentryAttributesToSpan(span: Span, spanContext: OpenTelemetrySpanContext): void { - const { origin, op, source, metadata } = spanContext; +function _applySentryAttributesToSpan(span: Span, options: OpenTelemetrySpanContext): void { + // eslint-disable-next-line deprecation/deprecation + const { origin, op, source, metadata } = options; if (origin) { span.setAttribute(InternalSentrySemanticAttributes.ORIGIN, origin); @@ -139,3 +147,32 @@ function _applySentryAttributesToSpan(span: Span, spanContext: OpenTelemetrySpan setSpanMetadata(span, metadata); } } + +function getSpanContext(options: OpenTelemetrySpanContext): SpanOptions { + const { startTime, attributes, kind } = options; + + // OTEL expects timestamps in ms, not seconds + const fixedStartTime = typeof startTime === 'number' ? ensureTimestampInMilliseconds(startTime) : startTime; + + return { + attributes, + kind, + startTime: fixedStartTime, + }; +} + +function ensureTimestampInMilliseconds(timestamp: number): number { + const isMs = timestamp < 9999999999; + return isMs ? timestamp * 1000 : timestamp; +} + +function getContext(scope?: Scope): Context { + if (scope) { + const ctx = getContextFromScope(scope); + if (ctx) { + return ctx; + } + } + + return context.active(); +} diff --git a/packages/opentelemetry/src/types.ts b/packages/opentelemetry/src/types.ts index 5abbdeeb4f26..ce16499c2a25 100644 --- a/packages/opentelemetry/src/types.ts +++ b/packages/opentelemetry/src/types.ts @@ -1,25 +1,15 @@ -import type { Attributes, Span as WriteableSpan, SpanKind, TimeInput, Tracer } from '@opentelemetry/api'; +import type { Span as WriteableSpan, SpanKind, Tracer } from '@opentelemetry/api'; import type { BasicTracerProvider, ReadableSpan, Span } from '@opentelemetry/sdk-trace-base'; -import type { Scope, SpanOrigin, TransactionMetadata, TransactionSource } from '@sentry/types'; +import type { Scope, StartSpanOptions } from '@sentry/types'; export interface OpenTelemetryClient { tracer: Tracer; traceProvider: BasicTracerProvider | undefined; } -export interface OpenTelemetrySpanContext { - name: string; - op?: string; - metadata?: Partial; - origin?: SpanOrigin; - source?: TransactionSource; - scope?: Scope; - onlyIfParent?: boolean; - - // Base SpanOptions we support - attributes?: Attributes; +export interface OpenTelemetrySpanContext extends StartSpanOptions { + // Additional otel-only option, for now...? kind?: SpanKind; - startTime?: TimeInput; } /** diff --git a/packages/opentelemetry/src/utils/contextData.ts b/packages/opentelemetry/src/utils/contextData.ts index fa4dd56db99a..85c31ee822bf 100644 --- a/packages/opentelemetry/src/utils/contextData.ts +++ b/packages/opentelemetry/src/utils/contextData.ts @@ -55,12 +55,17 @@ export function getScopesFromContext(context: Context): CurrentScopes | undefine * This will return a forked context with the Propagation Context set. */ export function setScopesOnContext(context: Context, scopes: CurrentScopes): Context { - // So we can look up the context from the scope later - SCOPE_CONTEXT_MAP.set(scopes.scope, context); - return context.setValue(SENTRY_SCOPES_CONTEXT_KEY, scopes); } +/** + * Set the context on the scope so we can later look it up. + * We need this to get the context from the scope in the `trace` functions. + */ +export function setContextOnScope(scope: Scope, context: Context): void { + SCOPE_CONTEXT_MAP.set(scope, context); +} + /** * Get the context related to a scope. * TODO v8: Use this for the `trace` functions. diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index fa6e1d09dad8..969df0643da3 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -1,10 +1,10 @@ -import type { Span } from '@opentelemetry/api'; +import type { Span, TimeInput } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; import { Span as SpanClass } from '@opentelemetry/sdk-trace-base'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, getClient } from '@sentry/core'; -import type { PropagationContext } from '@sentry/types'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, getClient, getCurrentScope } from '@sentry/core'; +import type { PropagationContext, Scope } from '@sentry/types'; import { InternalSentrySemanticAttributes } from '../src/semanticAttributes'; import { startInactiveSpan, startSpan, startSpanManual } from '../src/trace'; @@ -238,7 +238,7 @@ describe('trace', () => { }); it('allows to pass base SpanOptions', () => { - const date = Date.now() - 1000; + const date = [5000, 0] as TimeInput; startSpan( { @@ -248,12 +248,12 @@ describe('trace', () => { test1: 'test 1', test2: 2, }, - startTime: date, }, span => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); + expect(getSpanStartTime(span)).toEqual(date); expect(getSpanAttributes(span)).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', @@ -264,6 +264,44 @@ describe('trace', () => { ); }); + it('allows to pass a startTime in seconds', () => { + const startTime = 1708504860.961; + const start = startSpan({ name: 'outer', startTime: startTime }, span => { + return getSpanStartTime(span); + }); + + expect(start).toEqual([1708504860, 961000000]); + }); + + it('allows to pass a scope', () => { + const initialScope = getCurrentScope(); + + let manualScope: Scope; + let parentSpan: Span; + + startSpanManual({ name: 'detached' }, span => { + parentSpan = span; + manualScope = getCurrentScope(); + manualScope.setTag('manual', 'tag'); + }); + + getCurrentScope().setTag('outer', 'tag'); + + startSpan({ name: 'GET users/[id]', scope: manualScope! }, span => { + expect(getCurrentScope()).not.toBe(initialScope); + + expect(getCurrentScope()).toEqual(manualScope); + expect(getActiveSpan()).toBe(span); + + expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId); + }); + + expect(getCurrentScope()).toBe(initialScope); + expect(getActiveSpan()).toBe(undefined); + }); + + // TODO: propagation scope is not picked up by spans... + describe('onlyIfParent', () => { it('does not create a span if there is no parent', () => { const span = startSpan({ name: 'test span', onlyIfParent: true }, span => { @@ -355,7 +393,7 @@ describe('trace', () => { }); it('allows to pass base SpanOptions', () => { - const date = Date.now() - 1000; + const date = [5000, 0] as TimeInput; const span = startInactiveSpan({ name: 'outer', @@ -369,6 +407,7 @@ describe('trace', () => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); + expect(getSpanStartTime(span)).toEqual(date); expect(getSpanAttributes(span)).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', @@ -377,6 +416,34 @@ describe('trace', () => { expect(getSpanKind(span)).toEqual(SpanKind.CLIENT); }); + it('allows to pass a startTime in seconds', () => { + const startTime = 1708504860.961; + const span = startInactiveSpan({ name: 'outer', startTime: startTime }); + + expect(getSpanStartTime(span)).toEqual([1708504860, 961000000]); + }); + + it('allows to pass a scope', () => { + const initialScope = getCurrentScope(); + + let manualScope: Scope; + let parentSpan: Span; + + startSpanManual({ name: 'detached' }, span => { + parentSpan = span; + manualScope = getCurrentScope(); + manualScope.setTag('manual', 'tag'); + }); + + getCurrentScope().setTag('outer', 'tag'); + + const span = startInactiveSpan({ name: 'GET users/[id]', scope: manualScope! }); + expect(getSpanParentSpanId(span)).toBe(parentSpan!.spanContext().spanId); + + expect(getCurrentScope()).toBe(initialScope); + expect(getActiveSpan()).toBe(undefined); + }); + describe('onlyIfParent', () => { it('does not create a span if there is no parent', () => { const span = startInactiveSpan({ name: 'test span', onlyIfParent: true }); @@ -439,7 +506,7 @@ describe('trace', () => { }); it('allows to pass base SpanOptions', () => { - const date = Date.now() - 1000; + const date = [5000, 0] as TimeInput; startSpanManual( { @@ -454,6 +521,7 @@ describe('trace', () => { span => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); + expect(getSpanStartTime(span)).toEqual(date); expect(getSpanAttributes(span)).toEqual({ [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', @@ -463,27 +531,67 @@ describe('trace', () => { }, ); }); - }); - describe('onlyIfParent', () => { - it('does not create a span if there is no parent', () => { - const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => { - return span; + it('allows to pass a startTime in seconds', () => { + const startTime = 1708504860.961; + const start = startSpanManual({ name: 'outer', startTime: startTime }, span => { + const start = getSpanStartTime(span); + span.end(); + return start; }); - expect(span).not.toBeInstanceOf(SpanClass); + expect(start).toEqual([1708504860, 961000000]); }); - it('creates a span if there is a parent', () => { - const span = startSpan({ name: 'parent span' }, () => { + it('allows to pass a scope', () => { + const initialScope = getCurrentScope(); + + let manualScope: Scope; + let parentSpan: Span; + + startSpanManual({ name: 'detached' }, span => { + parentSpan = span; + manualScope = getCurrentScope(); + manualScope.setTag('manual', 'tag'); + }); + + getCurrentScope().setTag('outer', 'tag'); + + startSpanManual({ name: 'GET users/[id]', scope: manualScope! }, span => { + expect(getCurrentScope()).not.toBe(initialScope); + + expect(getCurrentScope()).toEqual(manualScope); + expect(getActiveSpan()).toBe(span); + + expect(getSpanParentSpanId(span)).toBe(parentSpan.spanContext().spanId); + + span.end(); + }); + + expect(getCurrentScope()).toBe(initialScope); + expect(getActiveSpan()).toBe(undefined); + }); + + describe('onlyIfParent', () => { + it('does not create a span if there is no parent', () => { const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => { return span; }); - return span; + expect(span).not.toBeInstanceOf(SpanClass); }); - expect(span).toBeInstanceOf(SpanClass); + it('creates a span if there is a parent', () => { + const span = startSpan({ name: 'parent span' }, () => { + const span = startSpanManual({ name: 'test span', onlyIfParent: true }, span => { + return span; + }); + + return span; + }); + + expect(span).toBeInstanceOf(SpanClass); + }); }); }); }); @@ -835,6 +943,14 @@ function getSpanEndTime(span: AbstractSpan): [number, number] | undefined { return (span as ReadableSpan).endTime; } +function getSpanStartTime(span: AbstractSpan): [number, number] | undefined { + return (span as ReadableSpan).startTime; +} + function getSpanAttributes(span: AbstractSpan): Record | undefined { return spanHasAttributes(span) ? span.attributes : undefined; } + +function getSpanParentSpanId(span: AbstractSpan): string | undefined { + return (span as ReadableSpan).parentSpanId; +} From 7d4f791059096fe61c61c4c7fc27a4d097ac80c2 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:53:39 +0100 Subject: [PATCH 126/173] ref(astro): Remove deprecated Replay and BrowserTracing (#10768) Swaps the class implementation with the new function --- packages/astro/src/integration/snippets.ts | 4 ++-- packages/astro/test/integration/snippets.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/astro/src/integration/snippets.ts b/packages/astro/src/integration/snippets.ts index ad5047e9954a..c46267080aa8 100644 --- a/packages/astro/src/integration/snippets.ts +++ b/packages/astro/src/integration/snippets.ts @@ -57,7 +57,7 @@ const buildClientIntegrations = (options: SentryOptions): string => { const integrations: string[] = []; if (options.tracesSampleRate == null || options.tracesSampleRate) { - integrations.push('new Sentry.BrowserTracing()'); + integrations.push('Sentry.browserTracingIntegration()'); } if ( @@ -66,7 +66,7 @@ const buildClientIntegrations = (options: SentryOptions): string => { options.replaysOnErrorSampleRate == null || options.replaysOnErrorSampleRate ) { - integrations.push('new Sentry.Replay()'); + integrations.push('Sentry.replayIntegration()'); } return integrations.join(', '); diff --git a/packages/astro/test/integration/snippets.test.ts b/packages/astro/test/integration/snippets.test.ts index 172756847a5c..ddd7188d9b18 100644 --- a/packages/astro/test/integration/snippets.test.ts +++ b/packages/astro/test/integration/snippets.test.ts @@ -23,7 +23,7 @@ describe('buildClientSnippet', () => { environment: import.meta.env.PUBLIC_VERCEL_ENV, release: import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA, tracesSampleRate: 1, - integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()], + integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1, });" @@ -43,7 +43,7 @@ describe('buildClientSnippet', () => { release: \\"1.0.0\\", tracesSampleRate: 0.3, sampleRate: 0.2, - integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()], + integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], replaysSessionSampleRate: 0.5, replaysOnErrorSampleRate: 0.4, });" @@ -61,7 +61,7 @@ describe('buildClientSnippet', () => { environment: import.meta.env.PUBLIC_VERCEL_ENV, release: import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA, tracesSampleRate: 0, - integrations: [new Sentry.Replay()], + integrations: [Sentry.replayIntegration()], replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1, });" @@ -80,7 +80,7 @@ it('does not include Replay if replay sample ratest are 0', () => { environment: import.meta.env.PUBLIC_VERCEL_ENV, release: import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA, tracesSampleRate: 1, - integrations: [new Sentry.BrowserTracing()], + integrations: [Sentry.browserTracingIntegration()], replaysSessionSampleRate: 0, replaysOnErrorSampleRate: 0, });" From 972298d8514272d18f50afabb4b48c9d89297969 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 21 Feb 2024 16:27:31 +0100 Subject: [PATCH 127/173] feat(node): Make `@sentry/node` powered by OpenTelemetry (#10762) This PR makes the former node-experimental package `@sentry/node`, and the former node package `@sentry/node-experimental`. All meta-SDKs (nextjs, sveltekit, remix, astro) as well as bun, serverless, depend on `@sentry/node-experimental` for now. We'll need to change this over one by one to instead depend on `@sentry/node`, and fix any things we find along the way. I've moved the test as much as possible, but didn't specifically invest a lot of time to fix tests that failed - I left them to point to node-experimental for now. This mainly affects express tests (we also have some new express tests, but may need to port some over), and some select others - we'll also need to investigate these one by one and replace/update them until none are left. --- .../package.json | 6 +- .../node-experimental-fastify-app/src/app.js | 2 +- .../src/tracing.js | 3 +- .../node-exports-test-app/package.json | 2 +- .../scripts/consistentExports.ts | 2 +- .../node-express-app/src/app.ts | 5 +- .../node-hapi-app/src/app.js | 2 +- .../node-integration-tests/package.json | 3 - .../suites/anr/basic-session.js | 1 + .../suites/express/handle-error/server.ts | 2 +- .../common-infix-parameterized/server.ts | 2 +- .../multiple-routers/common-infix/server.ts | 2 +- .../server.ts | 2 +- .../common-prefix-parameterized/server.ts | 2 +- .../server.ts | 2 +- .../server.ts | 2 +- .../multiple-routers/common-prefix/server.ts | 2 +- .../multiple-routers/complex-router/server.ts | 2 +- .../middle-layer-parameterized/server.ts | 2 +- .../sentry-trace/baggage-header-out/server.ts | 2 +- .../server.ts | 2 +- .../baggage-other-vendors/server.ts | 2 +- .../baggage-transaction-name/server.ts | 2 +- .../suites/express/sentry-trace/server.ts | 2 +- .../express/tracing-experimental/server.js | 2 +- .../suites/express/tracing/server.ts | 2 +- .../suites/public-api/LocalVariables/test.ts | 4 +- ...haviour-additional-listener-test-script.js | 16 ++-- ...iour-no-additional-listener-test-script.js | 16 ++-- .../basic-usage/scenario.ts | 0 .../basic-usage/test.ts | 0 .../with-nested-spans/scenario.ts | 4 +- .../with-nested-spans/test.ts | 0 .../suites/sessions/server.ts | 2 +- .../apollo-graphql/scenario-mutation.js | 2 +- .../apollo-graphql/scenario-query.js | 2 +- .../tracing-experimental/hapi/scenario.js | 2 +- .../tracing-experimental/mongodb/scenario.js | 2 +- .../tracing-experimental/mongoose/scenario.js | 2 +- .../mysql/scenario-withConnect.js | 2 +- .../mysql/scenario-withoutCallback.js | 2 +- .../mysql/scenario-withoutConnect.js | 2 +- .../tracing-experimental/mysql2/scenario.js | 2 +- .../tracing-experimental/nestjs/scenario.ts | 2 +- .../tracing-experimental/postgres/scenario.js | 2 +- .../tracing-new/apollo-graphql/scenario.ts | 36 -------- .../suites/tracing-new/apollo-graphql/test.ts | 33 -------- .../auto-instrument/mongodb/scenario.ts | 41 --------- .../auto-instrument/mongodb/test.ts | 84 ------------------- .../auto-instrument/pg/scenario.ts | 23 ----- .../tracing-new/auto-instrument/pg/test.ts | 63 -------------- .../httpIntegration/spansDisabled/scenario.ts | 20 ----- .../httpIntegration/spansDisabled/test.ts | 21 ----- .../tracePropagationTargets/scenario.ts | 20 ----- .../tracePropagationTargets/test.ts | 42 ---------- .../scenario.ts | 19 ----- .../tracing-new/prisma-orm/docker-compose.yml | 13 --- .../tracing-new/prisma-orm/package.json | 22 ----- .../prisma/migrations/migration_lock.toml | 3 - .../migrations/sentry_test/migration.sql | 12 --- .../prisma-orm/prisma/schema.prisma | 15 ---- .../suites/tracing-new/prisma-orm/scenario.ts | 41 --------- .../suites/tracing-new/prisma-orm/setup.ts | 16 ---- .../suites/tracing-new/prisma-orm/test.ts | 29 ------- .../suites/tracing-new/prisma-orm/yarn.lock | 27 ------ .../tracePropagationTargets/test.ts | 42 ---------- .../suites/tracing/apollo-graphql/scenario.ts | 36 -------- .../suites/tracing/apollo-graphql/test.ts | 33 -------- .../auto-instrument/mongodb/scenario.ts | 42 ---------- .../tracing/auto-instrument/mongodb/test.ts | 84 ------------------- .../tracing/auto-instrument/pg/scenario.ts | 19 ----- .../suites/tracing/auto-instrument/pg/test.ts | 80 ------------------ .../tracing/metric-summaries/scenario.js | 2 +- .../tracing/prisma-orm/docker-compose.yml | 13 --- .../suites/tracing/prisma-orm/package.json | 22 ----- .../prisma/migrations/migration_lock.toml | 3 - .../migrations/sentry_test/migration.sql | 12 --- .../tracing/prisma-orm/prisma/schema.prisma | 15 ---- .../suites/tracing/prisma-orm/scenario.ts | 41 --------- .../suites/tracing/prisma-orm/setup.ts | 16 ---- .../suites/tracing/prisma-orm/test.ts | 29 ------- .../suites/tracing/prisma-orm/yarn.lock | 27 ------ .../spans/scenario.ts | 3 +- .../httpIntegration => tracing}/spans/test.ts | 2 +- .../tracePropagationTargets/scenario.ts | 2 +- .../scenario.ts | 4 +- .../tracePropagationTargetsDisabled/test.ts | 2 +- packages/astro/package.json | 2 +- packages/astro/src/index.server.ts | 6 +- packages/astro/src/integration/index.ts | 2 +- packages/astro/src/server/middleware.ts | 2 +- packages/astro/src/server/sdk.ts | 4 +- packages/astro/test/server/middleware.test.ts | 2 +- packages/astro/test/server/sdk.test.ts | 4 +- packages/bun/package.json | 2 +- packages/bun/src/index.ts | 6 +- packages/bun/src/sdk.ts | 6 +- packages/nextjs/package.json | 2 +- packages/nextjs/src/config/webpack.ts | 2 +- packages/nextjs/src/server/httpIntegration.ts | 2 +- packages/nextjs/src/server/index.ts | 10 ++- .../server/onUncaughtExceptionIntegration.ts | 2 +- packages/nextjs/test/integration/package.json | 3 +- packages/nextjs/test/serverSdk.test.ts | 4 +- packages/node-experimental/package.json | 4 +- .../rollup.anr-worker.config.mjs | 35 ++++++++ .../node-experimental/rollup.npm.config.mjs | 7 +- packages/node-experimental/src/index.ts | 10 ++- .../src/integrations/onuncaughtexception.ts | 25 +++--- packages/node-experimental/src/sdk/api.ts | 12 +++ packages/node/package.json | 4 +- packages/opentelemetry-node/package.json | 2 +- .../test/spanprocessor.test.ts | 2 +- packages/profiling-node/package.json | 8 +- packages/profiling-node/src/hubextensions.ts | 21 ++--- packages/profiling-node/src/integration.ts | 9 +- packages/profiling-node/src/utils.ts | 2 +- .../test/hubextensions.hub.test.ts | 2 +- .../profiling-node/test/hubextensions.test.ts | 2 +- packages/profiling-node/test/index.test.ts | 2 +- packages/remix/package.json | 2 +- packages/remix/src/index.server.ts | 8 +- packages/remix/src/utils/instrumentServer.ts | 2 +- packages/remix/src/utils/remixOptions.ts | 2 +- .../remix/src/utils/serverAdapters/express.ts | 2 +- packages/remix/test/index.server.test.ts | 2 +- packages/remix/test/integration/package.json | 3 +- packages/serverless/package.json | 2 +- .../serverless/scripts/buildLambdaLayer.ts | 2 +- packages/serverless/src/awslambda.ts | 8 +- packages/serverless/src/awsservices.ts | 2 +- .../src/gcpfunction/cloud_events.ts | 2 +- packages/serverless/src/gcpfunction/events.ts | 2 +- packages/serverless/src/gcpfunction/http.ts | 6 +- packages/serverless/src/gcpfunction/index.ts | 8 +- packages/serverless/src/google-cloud-grpc.ts | 2 +- packages/serverless/src/google-cloud-http.ts | 2 +- packages/serverless/src/index.awslambda.ts | 2 +- packages/serverless/src/index.ts | 4 +- packages/serverless/test/awslambda.test.ts | 4 +- packages/serverless/test/awsservices.test.ts | 6 +- packages/serverless/test/gcpfunction.test.ts | 4 +- .../serverless/test/google-cloud-grpc.test.ts | 6 +- .../serverless/test/google-cloud-http.test.ts | 6 +- packages/sveltekit/package.json | 2 +- packages/sveltekit/src/server/handle.ts | 2 +- packages/sveltekit/src/server/handleError.ts | 2 +- packages/sveltekit/src/server/index.ts | 6 +- packages/sveltekit/src/server/load.ts | 2 +- packages/sveltekit/src/server/sdk.ts | 6 +- packages/sveltekit/src/server/utils.ts | 2 +- packages/sveltekit/src/vite/sourceMaps.ts | 2 +- packages/sveltekit/test/server/handle.test.ts | 4 +- .../sveltekit/test/server/handleError.test.ts | 2 +- packages/sveltekit/test/server/load.test.ts | 2 +- packages/sveltekit/test/server/sdk.test.ts | 6 +- yarn.lock | 40 --------- 157 files changed, 261 insertions(+), 1347 deletions(-) rename dev-packages/node-integration-tests/suites/public-api/{startTransaction => startSpan}/basic-usage/scenario.ts (100%) rename dev-packages/node-integration-tests/suites/public-api/{startTransaction => startSpan}/basic-usage/test.ts (100%) rename dev-packages/node-integration-tests/suites/public-api/{startTransaction => startSpan}/with-nested-spans/scenario.ts (87%) rename dev-packages/node-integration-tests/suites/public-api/{startTransaction => startSpan}/with-nested-spans/test.ts (100%) delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts delete mode 100755 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock delete mode 100644 dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts delete mode 100755 dev-packages/node-integration-tests/suites/tracing/prisma-orm/setup.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts delete mode 100644 dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock rename dev-packages/node-integration-tests/suites/{tracing-new/httpIntegration => tracing}/spans/scenario.ts (85%) rename dev-packages/node-integration-tests/suites/{tracing-new/httpIntegration => tracing}/spans/test.ts (93%) rename dev-packages/node-integration-tests/suites/{tracing-new/tracePropagationTargets => tracing/tracePropagationTargetsDisabled}/scenario.ts (80%) rename dev-packages/node-integration-tests/suites/{tracing-new/httpIntegration => tracing}/tracePropagationTargetsDisabled/test.ts (95%) create mode 100644 packages/node-experimental/rollup.anr-worker.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json index 8ada1cb5d82e..cfa2c8be0f61 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/package.json @@ -10,13 +10,11 @@ "test:assert": "pnpm test" }, "dependencies": { - "@sentry/node-experimental": "latest || *", + "@sentry/node": "latest || *", "@sentry/types": "latest || *", "@sentry/core": "latest || *", "@sentry/utils": "latest || *", - "@sentry/node": "latest || *", - "@sentry/opentelemetry-node": "latest || *", - "@sentry-internal/tracing": "latest || *", + "@sentry/opentelemetry": "latest || *", "@types/node": "18.15.1", "fastify": "4.23.2", "fastify-plugin": "4.5.1", diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js index 50fe45767504..22f17ca4695c 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/app.js @@ -1,6 +1,6 @@ require('./tracing'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); const { fastify } = require('fastify'); const fastifyPlugin = require('fastify-plugin'); const http = require('http'); diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js index e571a4374a9e..4cf352cda681 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/src/tracing.js @@ -1,10 +1,9 @@ -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, integrations: [], - debug: true, tracesSampleRate: 1, tunnel: 'http://localhost:3031/', // proxy server }); diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json index 8965bb7de982..6d187f14c245 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json @@ -12,7 +12,7 @@ "test:assert": "pnpm test" }, "dependencies": { - "@sentry/node": "latest || *", + "@sentry/node-experimental": "latest || *", "@sentry/sveltekit": "latest || *", "@sentry/remix": "latest || *", "@sentry/astro": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 20ee664b8053..5e7344588d56 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -1,7 +1,7 @@ import * as SentryAstro from '@sentry/astro'; import * as SentryBun from '@sentry/bun'; import * as SentryNextJs from '@sentry/nextjs'; -import * as SentryNode from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; import * as SentryRemix from '@sentry/remix'; import * as SentryServerless from '@sentry/serverless'; import * as SentrySvelteKit from '@sentry/sveltekit'; diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts index f3667c10bb46..e2015a70825c 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -22,9 +22,6 @@ Sentry.init({ const app = express(); const port = 3030; -app.use(Sentry.Handlers.requestHandler()); -app.use(Sentry.Handlers.tracingHandler()); - app.get('/test-success', function (req, res) { res.send({ version: 'v1' }); }); @@ -73,7 +70,7 @@ app.get('/test-local-variables-caught', function (req, res) { res.send({ exceptionId, randomVariableToRecord }); }); -app.use(Sentry.Handlers.errorHandler()); +Sentry.setupExpressErrorHandler(app); // @ts-ignore app.use(function onError(err, req, res, next) { diff --git a/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js b/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js index 5a7712dd4495..95564255b60f 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js +++ b/dev-packages/e2e-tests/test-applications/node-hapi-app/src/app.js @@ -10,7 +10,6 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: process.env.E2E_TEST_DSN, includeLocalVariables: true, - integrations: [Sentry.hapiIntegration({ server })], debug: true, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, @@ -56,6 +55,7 @@ const init = async () => { (async () => { init(); + await Sentry.setupHapiErrorHandler(server); await server.start(); console.log('Server running on %s', server.info.uri); })(); diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index b0e4156bfb14..5480445e69bb 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -16,12 +16,9 @@ "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 && ts-node ./setup.ts)", - "prisma:init:new": "(cd suites/tracing-new/prisma-orm && ts-node ./setup.ts)", "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", "type-check": "tsc", - "pretest": "run-s --silent prisma:init prisma:init:new", "test": "ts-node ./utils/run-tests.ts", "jest": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" 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 7b22e9a6a83e..153acf83f16f 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -12,6 +12,7 @@ Sentry.init({ release: '1.0', debug: true, integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], + autoSessionTracking: true, }); function longWork() { diff --git a/dev-packages/node-integration-tests/suites/express/handle-error/server.ts b/dev-packages/node-integration-tests/suites/express/handle-error/server.ts index 7ac7a8d05a24..da163f524b87 100644 --- a/dev-packages/node-integration-tests/suites/express/handle-error/server.ts +++ b/dev-packages/node-integration-tests/suites/express/handle-error/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts index 7cc849cd366d..daac56d420e1 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix-parameterized/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts index 92ae56558646..7b9b9981d03d 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-infix/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts index a351837cdf3c..93bd6040c8c0 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized-reverse/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts index 765f30c39448..70579abb1b5b 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-parameterized/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts index ad72cbc3babf..e601c325ef02 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized copy/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts index 1b17a85127bf..eecaef18bfcc 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix-same-length-parameterized/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts index dfe236aa2f64..b4a7b184f8e7 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/common-prefix/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts index 697e21837141..32257b000481 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts index d84c5bb1a358..fbdb8a185c77 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts index b5d08d42b5f8..64f697ca086c 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-header-out/server.ts @@ -1,6 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts index f20addce854d..4a791a8e73cd 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors-with-sentry-entries/server.ts @@ -1,6 +1,6 @@ import * as http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts index 5dc15c1c2a9b..5146b809854b 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-other-vendors/server.ts @@ -1,6 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts index 454bbfdf3d9a..8616908dd6d5 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts @@ -1,7 +1,7 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts index 92ec8d16e00e..efb9fc3c92bf 100644 --- a/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts +++ b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts @@ -1,6 +1,6 @@ import http from 'http'; import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js index 207d11d3aa0e..06c8416eb5eb 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js +++ b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js @@ -1,5 +1,5 @@ const { loggingTransport, startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); const cors = require('cors'); Sentry.init({ diff --git a/dev-packages/node-integration-tests/suites/express/tracing/server.ts b/dev-packages/node-integration-tests/suites/express/tracing/server.ts index 530499fa39d2..e6faa39956c9 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/server.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/server.ts @@ -1,5 +1,5 @@ import { loggingTransport, startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts index 3458d74f46b1..50dd06dfcd3b 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts @@ -80,8 +80,8 @@ conditionalTest({ min: 18 })('LocalVariables integration', () => { child.on('message', msg => { reportedCount++; const rssMb = msg.memUsage.rss / 1024 / 1024; - // We shouldn't use more than 100MB of memory - expect(rssMb).toBeLessThan(100); + // We shouldn't use more than 120MB of memory + expect(rssMb).toBeLessThan(120); }); // Wait for 20 seconds diff --git a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-additional-listener-test-script.js b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-additional-listener-test-script.js index db68624592e5..ed907fd35ba6 100644 --- a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-additional-listener-test-script.js +++ b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-additional-listener-test-script.js @@ -2,17 +2,11 @@ const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: integrations => { - return integrations.map(integration => { - if (integration.name === 'OnUncaughtException') { - return new Sentry.Integrations.OnUncaughtException({ - exitEvenIfOtherHandlersAreRegistered: false, - }); - } else { - return integration; - } - }); - }, + integrations: [ + Sentry.onUncaughtExceptionIntegration({ + exitEvenIfOtherHandlersAreRegistered: false, + }), + ], }); process.on('uncaughtException', () => { diff --git a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-no-additional-listener-test-script.js b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-no-additional-listener-test-script.js index 6f27c6b5cb05..e1a78cce10c8 100644 --- a/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-no-additional-listener-test-script.js +++ b/dev-packages/node-integration-tests/suites/public-api/OnUncaughtException/mimic-native-behaviour-no-additional-listener-test-script.js @@ -2,17 +2,11 @@ const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: integrations => { - return integrations.map(integration => { - if (integration.name === 'OnUncaughtException') { - return new Sentry.Integrations.OnUncaughtException({ - exitEvenIfOtherHandlersAreRegistered: false, - }); - } else { - return integration; - } - }); - }, + integrations: [ + Sentry.onUncaughtExceptionIntegration({ + exitEvenIfOtherHandlersAreRegistered: false, + }), + ], }); setTimeout(() => { diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/test.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts similarity index 87% rename from dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts index bd5ad9ea96e0..46585ea8913d 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts @@ -12,7 +12,7 @@ Sentry.startSpan({ name: 'root_span' }, () => { Sentry.startSpan( { name: 'span_1', - data: { + attributes: { foo: 'bar', baz: [1, 2, 3], }, @@ -25,7 +25,7 @@ Sentry.startSpan({ name: 'root_span' }, () => { Sentry.startSpan({ name: 'span_3' }, () => { // span_4 is the child of span_3 but doesn't finish. - Sentry.startInactiveSpan({ name: 'span_4', data: { qux: 'quux' } }); + Sentry.startInactiveSpan({ name: 'span_4', attributes: { qux: 'quux' } }); // span_5 is another child of span_3 but finishes. Sentry.startSpan({ name: 'span_5' }, () => undefined); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/test.ts rename to dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/test.ts diff --git a/dev-packages/node-integration-tests/suites/sessions/server.ts b/dev-packages/node-integration-tests/suites/sessions/server.ts index 1a92ced15a19..e2d2e23ccd4a 100644 --- a/dev-packages/node-integration-tests/suites/sessions/server.ts +++ b/dev-packages/node-integration-tests/suites/sessions/server.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import type { SessionFlusher } from '@sentry/core'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import express from 'express'; const app = express(); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-mutation.js b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-mutation.js index ebe4f7cd3e4d..9cecf2302315 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-mutation.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-mutation.js @@ -1,4 +1,4 @@ -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); const { loggingTransport } = require('@sentry-internal/node-integration-tests'); Sentry.init({ diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-query.js b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-query.js index 42bb8edacb8f..f0c140fd4b24 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-query.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario-query.js @@ -1,4 +1,4 @@ -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); const { loggingTransport } = require('@sentry-internal/node-integration-tests'); Sentry.init({ diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js index 2c86ac704618..9a00fa36957d 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport, sendPortToRunner } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/scenario.js index 360c943e0724..7da8a0b800fc 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongodb/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js index 3cfa9409917c..99c04cde2667 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withConnect.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withConnect.js index 0d1b517209a0..6eea534310bf 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withConnect.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withConnect.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutCallback.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutCallback.js index cd35953b0504..00049bd31b92 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutCallback.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutCallback.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutConnect.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutConnect.js index 844b25d63f2d..a58a4c98fa3f 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutConnect.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql/scenario-withoutConnect.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/scenario.js index 8858e4ef587f..5b915867426f 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mysql2/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts index 06ed91626b3b..f0a97953b2d9 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/explicit-member-accessibility */ import { loggingTransport, sendPortToRunner } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node-experimental'; +import * as Sentry from '@sentry/node'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/scenario.js index fa81bd00b938..20dc9fe738ad 100644 --- a/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/postgres/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node-experimental'); +const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts deleted file mode 100644 index 58656d7bf7da..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { ApolloServer, gql } from 'apollo-server'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [new Sentry.Integrations.GraphQL(), new Sentry.Integrations.Apollo()], -}); - -const typeDefs = gql` - type Query { - hello: String - } -`; - -const resolvers = { - Query: { - hello: () => { - return 'Hello world!'; - }, - }, -}; - -const server = new ApolloServer({ - typeDefs, - resolvers, -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpan({ name: 'test_span' }, async () => { - // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation - await server.executeOperation({ - query: '{hello}', - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts deleted file mode 100644 index 8c1bbaad9c2b..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../utils'; - -describe('GraphQL/Apollo Tests', () => { - test('should instrument GraphQL and Apollo Server.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - const transaction = envelope[2]; - const parentSpanId = (transaction as any)?.contexts?.trace?.span_id; - const graphqlSpanId = (transaction as any)?.spans?.[0].span_id; - - expect(parentSpanId).toBeDefined(); - expect(graphqlSpanId).toBeDefined(); - - assertSentryTransaction(transaction, { - transaction: 'test_span', - spans: [ - { - description: 'execute', - op: 'graphql.execute', - parent_span_id: parentSpanId, - }, - { - description: 'Query.hello', - op: 'graphql.resolve', - parent_span_id: graphqlSpanId, - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts deleted file mode 100644 index 95b5c69b8dfb..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { MongoClient } from 'mongodb'; - -// suppress logging of the mongo download -global.console.log = () => null; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], -}); - -const client = new MongoClient(process.env.MONGO_URL || '', { - useUnifiedTopology: true, -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpanManual( - { - name: 'Test Span', - }, - async span => { - try { - await client.connect(); - - const database = client.db('admin'); - const collection = database.collection('movies'); - - await collection.insertOne({ title: 'Rick and Morty' }); - await collection.findOne({ title: 'Back to the Future' }); - await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); - await collection.findOne({ title: 'South Park' }); - - await collection.find({ title: 'South Park' }).toArray(); - } finally { - span?.end(); - await client.close(); - } - }, -); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts deleted file mode 100644 index c7a3d14ab098..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { MongoMemoryServer } from 'mongodb-memory-server-global'; - -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -// This test can take longer. -jest.setTimeout(15000); - -describe('MongoDB Test', () => { - let mongoServer: MongoMemoryServer; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - process.env.MONGO_URL = mongoServer.getUri(); - }, 10000); - - afterAll(async () => { - if (mongoServer) { - await mongoServer.stop(); - } - }); - - test('should auto-instrument `mongodb` package.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'insertOne', - 'db.mongodb.collection': 'movies', - }, - description: 'insertOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'findOne', - 'db.mongodb.collection': 'movies', - }, - description: 'findOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'updateOne', - 'db.mongodb.collection': 'movies', - }, - description: 'updateOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'findOne', - 'db.mongodb.collection': 'movies', - }, - description: 'findOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'find', - 'db.mongodb.collection': 'movies', - }, - description: 'find', - op: 'db', - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts deleted file mode 100644 index 5d50a411f37f..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from '@sentry/node'; -import pg from 'pg'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], -}); - -Sentry.startSpanManual( - { - name: 'Test Span', - }, - span => { - const client = new pg.Client(); - client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => - client.query('SELECT * FROM bazz', () => { - client.query('SELECT NOW()', () => span?.end()); - }), - ); - }, -); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts deleted file mode 100644 index de892bc43b18..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -class PgClient { - // https://node-postgres.com/api/client#clientquery - public query(_text: unknown, values: unknown, callback?: () => void) { - if (typeof callback === 'function') { - callback(); - return; - } - - if (typeof values === 'function') { - values(); - return; - } - - return Promise.resolve(); - } -} - -beforeAll(() => { - jest.mock('pg', () => { - return { - Client: PgClient, - native: { - Client: PgClient, - }, - }; - }); -}); - -test('should auto-instrument `pg` package.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - description: 'SELECT * FROM foo where bar ilike "baz%"', - op: 'db', - data: { - 'db.system': 'postgresql', - }, - }, - { - description: 'SELECT * FROM bazz', - op: 'db', - data: { - 'db.system': 'postgresql', - }, - }, - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'postgresql', - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/scenario.ts deleted file mode 100644 index 61711e974f7d..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/scenario.ts +++ /dev/null @@ -1,20 +0,0 @@ -import '@sentry/tracing'; - -import * as http from 'http'; -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [Sentry.httpIntegration({ tracing: false })], -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpan({ name: 'test_transaction' }, async () => { - http.get('http://match-this-url.com/api/v0'); - http.get('http://match-this-url.com/api/v1'); - - // Give it a tick to resolve... - await new Promise(resolve => setTimeout(resolve, 100)); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/test.ts deleted file mode 100644 index bacf5eaf1882..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spansDisabled/test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import nock from 'nock'; - -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -test('should not capture spans for outgoing http requests if tracing is disabled', async () => { - const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200); - const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200); - - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(match1.isDone()).toBe(true); - expect(match2.isDone()).toBe(true); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'test_transaction', - spans: [], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts deleted file mode 100644 index 7794b20911f9..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import '@sentry/tracing'; - -import * as http from 'http'; -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [Sentry.httpIntegration({})], -}); - -Sentry.startSpan({ name: 'test_transaction' }, () => { - http.get('http://match-this-url.com/api/v0'); - http.get('http://match-this-url.com/api/v1'); - http.get('http://dont-match-this-url.com/api/v2'); - http.get('http://dont-match-this-url.com/api/v3'); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/test.ts deleted file mode 100644 index 59e4eff9e105..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import nock from 'nock'; - -import { TestEnv, runScenario } from '../../../../utils'; - -test('httpIntegration should instrument correct requests when tracePropagationTargets option is provided & tracing is enabled', async () => { - const match1 = nock('http://match-this-url.com') - .get('/api/v0') - .matchHeader('baggage', val => typeof val === 'string') - .matchHeader('sentry-trace', val => typeof val === 'string') - .reply(200); - - const match2 = nock('http://match-this-url.com') - .get('/api/v1') - .matchHeader('baggage', val => typeof val === 'string') - .matchHeader('sentry-trace', val => typeof val === 'string') - .reply(200); - - const match3 = nock('http://dont-match-this-url.com') - .get('/api/v2') - .matchHeader('baggage', val => val === undefined) - .matchHeader('sentry-trace', val => val === undefined) - .reply(200); - - const match4 = nock('http://dont-match-this-url.com') - .get('/api/v3') - .matchHeader('baggage', val => val === undefined) - .matchHeader('sentry-trace', val => val === undefined) - .reply(200); - - const env = await TestEnv.init(__dirname); - await runScenario(env.url); - - env.server.close(); - nock.cleanAll(); - - await new Promise(resolve => env.server.close(resolve)); - - expect(match1.isDone()).toBe(true); - expect(match2.isDone()).toBe(true); - expect(match3.isDone()).toBe(true); - expect(match4.isDone()).toBe(true); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/scenario.ts deleted file mode 100644 index c04616f7db89..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/scenario.ts +++ /dev/null @@ -1,19 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import '@sentry/tracing'; - -import * as http from 'http'; -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({})], -}); - -Sentry.startSpan({ name: 'test_transaction' }, () => { - http.get('http://match-this-url.com/api/v0'); - http.get('http://match-this-url.com/api/v1'); - http.get('http://dont-match-this-url.com/api/v2'); - http.get('http://dont-match-this-url.com/api/v3'); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml deleted file mode 100644 index 45caa4bb3179..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.9' - -services: - db: - image: postgres:13 - restart: always - container_name: integration-tests-prisma - ports: - - '5433:5432' - environment: - POSTGRES_USER: prisma - POSTGRES_PASSWORD: prisma - POSTGRES_DB: tests diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json deleted file mode 100644 index f8b24d7d0465..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "sentry-prisma-test", - "version": "1.0.0", - "description": "", - "main": "index.js", - "engines": { - "node": ">=12" - }, - "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": "3.12.0", - "prisma": "^3.12.0" - } -} diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92c2bb7..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# 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-new/prisma-orm/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql deleted file mode 100644 index 8619aaceb2b0..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ --- 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-new/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma deleted file mode 100644 index 4363c97738ee..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma +++ /dev/null @@ -1,15 +0,0 @@ -datasource db { - url = "postgresql://prisma:prisma@localhost:5433/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-new/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts deleted file mode 100644 index 29c20bb772ba..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { PrismaClient } from '@prisma/client'; -import * as Sentry from '@sentry/node'; - -const client = new PrismaClient(); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [new Sentry.Integrations.Prisma({ client })], -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpanManual( - { - name: 'Test Span', - }, - async span => { - try { - await client.user.create({ - data: { - name: 'Dog', - email: 'dog@sentry.io', - }, - }); - - await client.user.findMany(); - - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', - }, - }, - }); - } finally { - if (span) span.end(); - } - }, -); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts deleted file mode 100755 index d5c4e552b397..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { execSync } from 'child_process'; -import { parseSemver } from '@sentry/utils'; - -const NODE_VERSION = parseSemver(process.versions.node); - -if (NODE_VERSION.major && NODE_VERSION.major < 12) { - // eslint-disable-next-line no-console - console.warn(`Skipping Prisma tests on Node: ${NODE_VERSION.major}`); - process.exit(0); -} - -try { - execSync('yarn && yarn setup'); -} catch (_) { - process.exit(1); -} diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts deleted file mode 100644 index 70d704ec7017..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../utils'; - -describe('Prisma ORM Integration', () => { - test('should instrument Prisma client for tracing.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - description: 'User create', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'create', 'db.prisma.version': '3.12.0' }, - }, - { - description: 'User findMany', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'findMany', 'db.prisma.version': '3.12.0' }, - }, - { - description: 'User deleteMany', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'deleteMany', 'db.prisma.version': '3.12.0' }, - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock deleted file mode 100644 index d228adebd621..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/yarn.lock +++ /dev/null @@ -1,27 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@prisma/client@3.12.0": - version "3.12.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.12.0.tgz#a0eb49ffea5c128dd11dffb896d7139a60073d12" - integrity sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw== - dependencies: - "@prisma/engines-version" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - -"@prisma/engines-version@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": - version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#829ca3d9d0d92555f44644606d4edfd45b2f5886" - integrity sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw== - -"@prisma/engines@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": - version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#e52e364084c4d05278f62768047b788665e64a45" - integrity sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ== - -prisma@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.12.0.tgz#9675e0e72407122759d3eadcb6d27cdccd3497bd" - integrity sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg== - dependencies: - "@prisma/engines" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" diff --git a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts deleted file mode 100644 index 01b75ab10330..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import nock from 'nock'; - -import { TestEnv, runScenario } from '../../../utils'; - -test('HttpIntegration should instrument correct requests when tracePropagationTargets option is provided', async () => { - const match1 = nock('http://match-this-url.com') - .get('/api/v0') - .matchHeader('baggage', val => typeof val === 'string') - .matchHeader('sentry-trace', val => typeof val === 'string') - .reply(200); - - const match2 = nock('http://match-this-url.com') - .get('/api/v1') - .matchHeader('baggage', val => typeof val === 'string') - .matchHeader('sentry-trace', val => typeof val === 'string') - .reply(200); - - const match3 = nock('http://dont-match-this-url.com') - .get('/api/v2') - .matchHeader('baggage', val => val === undefined) - .matchHeader('sentry-trace', val => val === undefined) - .reply(200); - - const match4 = nock('http://dont-match-this-url.com') - .get('/api/v3') - .matchHeader('baggage', val => val === undefined) - .matchHeader('sentry-trace', val => val === undefined) - .reply(200); - - const env = await TestEnv.init(__dirname); - await runScenario(env.url); - - env.server.close(); - nock.cleanAll(); - - await new Promise(resolve => env.server.close(resolve)); - - expect(match1.isDone()).toBe(true); - expect(match2.isDone()).toBe(true); - expect(match3.isDone()).toBe(true); - expect(match4.isDone()).toBe(true); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts deleted file mode 100644 index 9105f0490847..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { ApolloServer, gql } from 'apollo-server'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [new Sentry.Integrations.GraphQL(), new Sentry.Integrations.Apollo()], -}); - -const typeDefs = gql` - type Query { - hello: String - } -`; - -const resolvers = { - Query: { - hello: () => { - return 'Hello world!'; - }, - }, -}; - -const server = new ApolloServer({ - typeDefs, - resolvers, -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpan({ name: 'root_span' }, async () => { - // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation - await server.executeOperation({ - query: '{hello}', - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts deleted file mode 100644 index 152b3e9f697b..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../utils'; - -describe('GraphQL/Apollo Tests', () => { - test('should instrument GraphQL and Apollo Server.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - const transaction = envelope[2]; - const parentSpanId = (transaction as any)?.contexts?.trace?.span_id; - const graphqlSpanId = (transaction as any)?.spans?.[0].span_id; - - expect(parentSpanId).toBeDefined(); - expect(graphqlSpanId).toBeDefined(); - - assertSentryTransaction(transaction, { - transaction: 'root_span', - spans: [ - { - description: 'execute', - op: 'graphql.execute', - parent_span_id: parentSpanId, - }, - { - description: 'Query.hello', - op: 'graphql.resolve', - parent_span_id: graphqlSpanId, - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts deleted file mode 100644 index 3e039d8f9fb8..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts +++ /dev/null @@ -1,42 +0,0 @@ -import '@sentry/tracing'; - -import * as Sentry from '@sentry/node'; -import { MongoClient } from 'mongodb'; - -// suppress logging of the mongo download -global.console.log = () => null; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, -}); - -const client = new MongoClient(process.env.MONGO_URL || '', { - useUnifiedTopology: true, -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpanManual( - { - name: 'Test Span', - }, - async span => { - try { - await client.connect(); - - const database = client.db('admin'); - const collection = database.collection('movies'); - - await collection.insertOne({ title: 'Rick and Morty' }); - await collection.findOne({ title: 'Back to the Future' }); - await collection.updateOne({ title: 'Back to the Future' }, { $set: { title: 'South Park' } }); - await collection.findOne({ title: 'South Park' }); - - await collection.find({ title: 'South Park' }).toArray(); - } finally { - span?.end(); - await client.close(); - } - }, -); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts deleted file mode 100644 index c7a3d14ab098..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { MongoMemoryServer } from 'mongodb-memory-server-global'; - -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -// This test can take longer. -jest.setTimeout(15000); - -describe('MongoDB Test', () => { - let mongoServer: MongoMemoryServer; - - beforeAll(async () => { - mongoServer = await MongoMemoryServer.create(); - process.env.MONGO_URL = mongoServer.getUri(); - }, 10000); - - afterAll(async () => { - if (mongoServer) { - await mongoServer.stop(); - } - }); - - test('should auto-instrument `mongodb` package.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'insertOne', - 'db.mongodb.collection': 'movies', - }, - description: 'insertOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'findOne', - 'db.mongodb.collection': 'movies', - }, - description: 'findOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'updateOne', - 'db.mongodb.collection': 'movies', - }, - description: 'updateOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'findOne', - 'db.mongodb.collection': 'movies', - }, - description: 'findOne', - op: 'db', - }, - { - data: { - 'db.system': 'mongodb', - 'db.name': 'admin', - 'db.operation': 'find', - 'db.mongodb.collection': 'movies', - }, - description: 'find', - op: 'db', - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts deleted file mode 100644 index 3d0600cb81ec..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts +++ /dev/null @@ -1,19 +0,0 @@ -import '@sentry/tracing'; - -import * as Sentry from '@sentry/node'; -import pg from 'pg'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, -}); - -Sentry.startSpanManual({ name: 'Test Span' }, span => { - const client = new pg.Client(); - client.query('SELECT * FROM foo where bar ilike "baz%"', ['a', 'b'], () => - client.query('SELECT * FROM bazz', () => { - client.query('SELECT NOW()', () => span?.end()); - }), - ); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts deleted file mode 100644 index 16cd5fb8487c..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../../utils'; - -class PgClient { - database?: string = 'test'; - user?: string = 'user'; - host?: string = 'localhost'; - port?: number = 5432; - - // https://node-postgres.com/api/client#clientquery - public query(_text: unknown, values: unknown, callback?: () => void) { - if (typeof callback === 'function') { - callback(); - return; - } - - if (typeof values === 'function') { - values(); - return; - } - - return Promise.resolve(); - } -} - -beforeAll(() => { - jest.mock('pg', () => { - return { - Client: PgClient, - native: { - Client: PgClient, - }, - }; - }); -}); - -test('should auto-instrument `pg` package.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - expect(envelope).toHaveLength(3); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - description: 'SELECT * FROM foo where bar ilike "baz%"', - op: 'db', - data: { - 'db.system': 'postgresql', - 'db.user': 'user', - 'db.name': 'test', - 'server.address': 'localhost', - 'server.port': 5432, - }, - }, - { - description: 'SELECT * FROM bazz', - op: 'db', - data: { - 'db.system': 'postgresql', - 'db.user': 'user', - 'db.name': 'test', - 'server.address': 'localhost', - 'server.port': 5432, - }, - }, - { - description: 'SELECT NOW()', - op: 'db', - data: { - 'db.system': 'postgresql', - 'db.user': 'user', - 'db.name': 'test', - 'server.address': 'localhost', - 'server.port': 5432, - }, - }, - ], - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js index e2921db180af..4a0a3ec792e5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -1,5 +1,5 @@ const { loggingTransport } = require('@sentry-internal/node-integration-tests'); -const Sentry = require('@sentry/node'); +const Sentry = require('@sentry/node-experimental'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', 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/docker-compose.yml deleted file mode 100644 index 45caa4bb3179..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3.9' - -services: - db: - image: postgres:13 - restart: always - container_name: integration-tests-prisma - ports: - - '5433: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/package.json deleted file mode 100644 index f8b24d7d0465..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "sentry-prisma-test", - "version": "1.0.0", - "description": "", - "main": "index.js", - "engines": { - "node": ">=12" - }, - "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": "3.12.0", - "prisma": "^3.12.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/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92c2bb7..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# 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/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql deleted file mode 100644 index 8619aaceb2b0..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ --- 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/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma deleted file mode 100644 index 4363c97738ee..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma +++ /dev/null @@ -1,15 +0,0 @@ -datasource db { - url = "postgresql://prisma:prisma@localhost:5433/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.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts deleted file mode 100644 index 51bccb519630..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { PrismaClient } from '@prisma/client'; -import * as Sentry from '@sentry/node'; - -const client = new PrismaClient(); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - integrations: [new Sentry.Integrations.Prisma({ client })], -}); - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -Sentry.startSpanManual( - { - name: 'Test Span', - }, - async span => { - try { - await client.user.create({ - data: { - name: 'David', - email: 'david_cramer@sentry.io', - }, - }); - - await client.user.findMany(); - - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', - }, - }, - }); - } finally { - span?.end(); - } - }, -); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/setup.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/setup.ts deleted file mode 100755 index d5c4e552b397..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/setup.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { execSync } from 'child_process'; -import { parseSemver } from '@sentry/utils'; - -const NODE_VERSION = parseSemver(process.versions.node); - -if (NODE_VERSION.major && NODE_VERSION.major < 12) { - // eslint-disable-next-line no-console - console.warn(`Skipping Prisma tests on Node: ${NODE_VERSION.major}`); - process.exit(0); -} - -try { - execSync('yarn && yarn setup'); -} catch (_) { - process.exit(1); -} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts deleted file mode 100644 index 70d704ec7017..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TestEnv, assertSentryTransaction } from '../../../utils'; - -describe('Prisma ORM Integration', () => { - test('should instrument Prisma client for tracing.', async () => { - const env = await TestEnv.init(__dirname); - const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' }); - - assertSentryTransaction(envelope[2], { - transaction: 'Test Span', - spans: [ - { - description: 'User create', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'create', 'db.prisma.version': '3.12.0' }, - }, - { - description: 'User findMany', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'findMany', 'db.prisma.version': '3.12.0' }, - }, - { - description: 'User deleteMany', - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.operation': 'deleteMany', 'db.prisma.version': '3.12.0' }, - }, - ], - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock deleted file mode 100644 index d228adebd621..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock +++ /dev/null @@ -1,27 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@prisma/client@3.12.0": - version "3.12.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.12.0.tgz#a0eb49ffea5c128dd11dffb896d7139a60073d12" - integrity sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw== - dependencies: - "@prisma/engines-version" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - -"@prisma/engines-version@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": - version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#829ca3d9d0d92555f44644606d4edfd45b2f5886" - integrity sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw== - -"@prisma/engines@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": - version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#e52e364084c4d05278f62768047b788665e64a45" - integrity sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ== - -prisma@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.12.0.tgz#9675e0e72407122759d3eadcb6d27cdccd3497bd" - integrity sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg== - dependencies: - "@prisma/engines" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts similarity index 85% rename from dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts index eba5aa576e86..dfdd065fd2ee 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts @@ -1,13 +1,12 @@ import '@sentry/tracing'; import * as http from 'http'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', tracesSampleRate: 1.0, - integrations: [Sentry.httpIntegration({})], }); // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/test.ts b/dev-packages/node-integration-tests/suites/tracing/spans/test.ts similarity index 93% rename from dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/test.ts rename to dev-packages/node-integration-tests/suites/tracing/spans/test.ts index bd95db22de6e..dac3ec15dd74 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/spans/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/spans/test.ts @@ -1,6 +1,6 @@ import nock from 'nock'; -import { TestEnv, assertSentryTransaction } from '../../../../utils'; +import { TestEnv, assertSentryTransaction } from '../../../utils'; test('should capture spans for outgoing http requests', async () => { const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200); diff --git a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts index 5eef548fd15d..9d8da27fa7b7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts @@ -1,5 +1,5 @@ import * as http from 'http'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts similarity index 80% rename from dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts index 5eef548fd15d..cfad7894d2b8 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts @@ -1,5 +1,5 @@ import * as http from 'http'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', @@ -9,7 +9,7 @@ Sentry.init({ integrations: [Sentry.httpIntegration({ tracing: true })], }); -Sentry.startSpan({ name: 'test_span' }, () => { +Sentry.startSpan({ name: 'test_transaction' }, () => { http.get('http://match-this-url.com/api/v0'); http.get('http://match-this-url.com/api/v1'); http.get('http://dont-match-this-url.com/api/v2'); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/test.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/test.ts similarity index 95% rename from dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/test.ts rename to dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/test.ts index abc1ff025b78..6fa28a13c5e1 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargetsDisabled/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/test.ts @@ -1,6 +1,6 @@ import nock from 'nock'; -import { TestEnv, runScenario } from '../../../../utils'; +import { TestEnv, runScenario } from '../../../utils'; test('httpIntegration should not instrument when tracing is enabled', async () => { const match1 = nock('http://match-this-url.com') diff --git a/packages/astro/package.json b/packages/astro/package.json index b26f7561d644..825da5812ac0 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -51,7 +51,7 @@ "dependencies": { "@sentry/browser": "7.100.0", "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", "@sentry/vite-plugin": "^2.8.0" diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 7a8ff4427e12..d3aba19e9ac6 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -1,5 +1,5 @@ // Node SDK exports -// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds, +// Unfortunately, we cannot `export * from '@sentry/node-experimental'` because in prod builds, // Vite puts these exports into a `default` property (Sentry.default) rather than // on the top - level namespace. @@ -72,10 +72,10 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, -} from '@sentry/node'; +} from '@sentry/node-experimental'; // We can still leave this for the carrier init and type exports -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; export { init } from './server/sdk'; diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index 15e48cb49f12..eedbd9407adf 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -91,7 +91,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { // @sentry/node is required in case we have 2 different @sentry/node // packages installed in the same project. // Ref: https://github.com/getsentry/sentry-javascript/issues/10121 - noExternal: ['@sentry/astro', '@sentry/node'], + noExternal: ['@sentry/astro', '@sentry/node-experimental', '@sentry/node'], }, }, }); diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 7c0246174202..20da1f7d407c 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -7,7 +7,7 @@ import { getCurrentScope, startSpan, withIsolationScope, -} from '@sentry/node'; +} from '@sentry/node-experimental'; import type { Client, Scope, Span } from '@sentry/types'; import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils'; import type { APIContext, MiddlewareResponseHandler } from 'astro'; diff --git a/packages/astro/src/server/sdk.ts b/packages/astro/src/server/sdk.ts index 6a529b60b74e..d7b4983a7bc3 100644 --- a/packages/astro/src/server/sdk.ts +++ b/packages/astro/src/server/sdk.ts @@ -1,6 +1,6 @@ import { applySdkMetadata } from '@sentry/core'; -import type { NodeOptions } from '@sentry/node'; -import { init as initNodeSdk, setTag } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { init as initNodeSdk, setTag } from '@sentry/node-experimental'; /** * diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 442e144bccb8..eb0ca9a9daba 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import * as SentryNode from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; import type { Client, Span } from '@sentry/types'; import { vi } from 'vitest'; diff --git a/packages/astro/test/server/sdk.test.ts b/packages/astro/test/server/sdk.test.ts index 1b042ee5331a..b1dc3821c88f 100644 --- a/packages/astro/test/server/sdk.test.ts +++ b/packages/astro/test/server/sdk.test.ts @@ -1,5 +1,5 @@ -import * as SentryNode from '@sentry/node'; -import { SDK_VERSION } from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; +import { SDK_VERSION } from '@sentry/node-experimental'; import { vi } from 'vitest'; import { init } from '../../src/server/sdk'; diff --git a/packages/bun/package.json b/packages/bun/package.json index bac554fee6dd..30aa8241b998 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0" }, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index e95d4d047c23..7aa59dc81bef 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -18,7 +18,7 @@ export type { } from '@sentry/types'; export type { AddRequestDataToEventOptions } from '@sentry/utils'; -export type { TransactionNamingScheme } from '@sentry/node'; +export type { TransactionNamingScheme } from '@sentry/node-experimental'; export type { BunOptions } from './types'; export { @@ -109,7 +109,7 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, -} from '@sentry/node'; +} from '@sentry/node-experimental'; export { BunClient } from './client'; export { @@ -118,7 +118,7 @@ export { } from './sdk'; import { Integrations as CoreIntegrations } from '@sentry/core'; -import { Integrations as NodeIntegrations } from '@sentry/node'; +import { Integrations as NodeIntegrations } from '@sentry/node-experimental'; import { BunServer } from './integrations/bunserver'; export { bunServerIntegration } from './integrations/bunserver'; diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index dacc78120ae1..b7eddfed9c73 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -13,7 +13,7 @@ import { modulesIntegration, nativeNodeFetchintegration, nodeContextIntegration, -} from '@sentry/node'; +} from '@sentry/node-experimental'; import type { Integration, Options } from '@sentry/types'; import { BunClient } from './client'; @@ -67,7 +67,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] { * @example * ``` * - * const { addBreadcrumb } = require('@sentry/node'); + * const { addBreadcrumb } = require('@sentry/node-experimental'); * addBreadcrumb({ * message: 'My Breadcrumb', * // ... @@ -77,7 +77,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] { * @example * ``` * - * const Sentry = require('@sentry/node'); + * const Sentry = require('@sentry/node-experimental'); * Sentry.captureMessage('Hello, world!'); * Sentry.captureException(new Error('Good bye')); * Sentry.captureEvent({ diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 3f6ef36ca15f..e6767dbb95d3 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -27,7 +27,7 @@ "@rollup/plugin-commonjs": "24.0.0", "@sentry/core": "7.100.0", "@sentry/integrations": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/react": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index dcaa757cb767..a6555c3b3343 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; /* eslint-disable complexity */ /* eslint-disable max-lines */ -import { getSentryRelease } from '@sentry/node'; +import { getSentryRelease } from '@sentry/node-experimental'; import { arrayify, dropUndefinedKeys, escapeStringForRegex, loadModule, logger } from '@sentry/utils'; import type SentryCliPlugin from '@sentry/webpack-plugin'; import * as chalk from 'chalk'; diff --git a/packages/nextjs/src/server/httpIntegration.ts b/packages/nextjs/src/server/httpIntegration.ts index 4252cffcaa86..a170e86bd4ea 100644 --- a/packages/nextjs/src/server/httpIntegration.ts +++ b/packages/nextjs/src/server/httpIntegration.ts @@ -1,4 +1,4 @@ -import { Integrations } from '@sentry/node'; +import { Integrations } from '@sentry/node-experimental'; /** * A custom HTTP integration where we always enable tracing. diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index b4f42b2fbad7..5820b783b1b9 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,6 +1,10 @@ import { addEventProcessor, addTracingExtensions, applySdkMetadata, getClient, setTag } from '@sentry/core'; -import type { NodeOptions } from '@sentry/node'; -import { Integrations as OriginalIntegrations, getDefaultIntegrations, init as nodeInit } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { + Integrations as OriginalIntegrations, + getDefaultIntegrations, + init as nodeInit, +} from '@sentry/node-experimental'; import type { EventProcessor } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -13,7 +17,7 @@ import { Http } from './httpIntegration'; import { OnUncaughtException } from './onUncaughtExceptionIntegration'; export { createReduxEnhancer } from '@sentry/react'; -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; export { captureUnderscoreErrorException } from '../common/_error'; export const Integrations = { diff --git a/packages/nextjs/src/server/onUncaughtExceptionIntegration.ts b/packages/nextjs/src/server/onUncaughtExceptionIntegration.ts index 6e9e0c034676..e88520cf9534 100644 --- a/packages/nextjs/src/server/onUncaughtExceptionIntegration.ts +++ b/packages/nextjs/src/server/onUncaughtExceptionIntegration.ts @@ -1,4 +1,4 @@ -import { Integrations } from '@sentry/node'; +import { Integrations } from '@sentry/node-experimental'; /** * A custom OnUncaughtException integration that does not exit by default. diff --git a/packages/nextjs/test/integration/package.json b/packages/nextjs/test/integration/package.json index 14ac5a38aa6b..bd45c952e67a 100644 --- a/packages/nextjs/test/integration/package.json +++ b/packages/nextjs/test/integration/package.json @@ -28,7 +28,8 @@ "@sentry/browser": "file:../../../browser", "@sentry/core": "file:../../../core", "@sentry/integrations": "file:../../../integrations", - "@sentry/node": "file:../../../node", + "@sentry/node": "file:../../../node-experimental", + "@sentry/node-experimental": "file:../../../node", "@sentry/react": "file:../../../react", "@sentry/replay": "file:../../../replay", "@sentry-internal/replay-canvas": "file:../../../replay-canvas", diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index f89e0f771349..a9ce73588e7a 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -1,5 +1,5 @@ -import * as SentryNode from '@sentry/node'; -import { getClient, getCurrentScope } from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; +import { getClient, getCurrentScope } from '@sentry/node-experimental'; import type { Integration } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json index 62c1072001cc..ca58b1cd5186 100644 --- a/packages/node-experimental/package.json +++ b/packages/node-experimental/package.json @@ -1,7 +1,7 @@ { - "name": "@sentry/node-experimental", + "name": "@sentry/node", "version": "7.100.0", - "description": "Experimental version of a Node SDK using OpenTelemetry for performance instrumentation", + "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-experimental", "author": "Sentry", diff --git a/packages/node-experimental/rollup.anr-worker.config.mjs b/packages/node-experimental/rollup.anr-worker.config.mjs new file mode 100644 index 000000000000..9887342c63fd --- /dev/null +++ b/packages/node-experimental/rollup.anr-worker.config.mjs @@ -0,0 +1,35 @@ +import { makeBaseBundleConfig } from '@sentry-internal/rollup-utils'; + +function createAnrWorkerConfig(destDir, esm) { + return makeBaseBundleConfig({ + bundleType: 'node-worker', + entrypoints: ['src/integrations/anr/worker.ts'], + jsVersion: 'es6', + licenseTitle: '@sentry/node', + outputFileBase: () => 'worker-script.js', + packageSpecificConfig: { + output: { + dir: destDir, + sourcemap: false, + }, + plugins: [ + { + name: 'output-base64-worker-script', + renderChunk(code) { + const base64Code = Buffer.from(code).toString('base64'); + if (esm) { + return `export const base64WorkerScript = '${base64Code}';`; + } else { + return `exports.base64WorkerScript = '${base64Code}';`; + } + }, + }, + ], + }, + }); +} + +export const anrWorkerConfigs = [ + createAnrWorkerConfig('build/esm/integrations/anr', true), + createAnrWorkerConfig('build/cjs/integrations/anr', false), +]; diff --git a/packages/node-experimental/rollup.npm.config.mjs b/packages/node-experimental/rollup.npm.config.mjs index 84a06f2fb64a..88c90de4825f 100644 --- a/packages/node-experimental/rollup.npm.config.mjs +++ b/packages/node-experimental/rollup.npm.config.mjs @@ -1,3 +1,8 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; +import { anrWorkerConfigs } from './rollup.anr-worker.config.mjs'; -export default makeNPMConfigVariants(makeBaseNPMConfig()); +export default [ + ...makeNPMConfigVariants(makeBaseNPMConfig()), + // The ANR worker builds must come after the main build because they overwrite the worker-script.js file + ...anrWorkerConfigs, +]; diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 282b703d1194..c1a55ce959f7 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -8,6 +8,7 @@ export { localVariablesIntegration } from './integrations/local-variables'; export { modulesIntegration } from './integrations/modules'; export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception'; export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection'; +export { anrIntegration } from './integrations/anr'; export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express'; export { fastifyIntegration } from './integrations/tracing/fastify'; @@ -23,9 +24,16 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h export { init, getDefaultIntegrations } from './sdk/init'; export { getAutoPerformanceIntegrations } from './integrations/tracing'; -export { getClient, getSentryRelease, defaultStackParser } from './sdk/api'; +export { + getClient, + getSentryRelease, + defaultStackParser, + // eslint-disable-next-line deprecation/deprecation + makeMain, +} from './sdk/api'; export { createGetModuleFromFilename } from './utils/module'; export { makeNodeTransport } from './transports'; +export { NodeClient } from './sdk/client'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHub } from './sdk/hub'; export { cron } from './cron'; diff --git a/packages/node-experimental/src/integrations/onuncaughtexception.ts b/packages/node-experimental/src/integrations/onuncaughtexception.ts index 84cae854ab81..e56c3c0801d7 100644 --- a/packages/node-experimental/src/integrations/onuncaughtexception.ts +++ b/packages/node-experimental/src/integrations/onuncaughtexception.ts @@ -86,20 +86,19 @@ export function makeErrorHandler(client: NodeClient, options: OnUncaughtExceptio // exit behaviour of the SDK accordingly: // - If other listeners are attached, do not exit. // - If the only listener attached is ours, exit. - const userProvidedListenersCount = ( - global.process.listeners('uncaughtException') as TaggedListener[] - ).reduce((acc, listener) => { - if ( + const userProvidedListenersCount = (global.process.listeners('uncaughtException') as TaggedListener[]).filter( + listener => { // There are 3 listeners we ignore: - listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself - (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing - (listener as ErrorHandler)._errorHandler // the handler we register in this integration - ) { - return acc; - } else { - return acc + 1; - } - }, 0); + return ( + // as soon as we're using domains this listener is attached by node itself + listener.name !== 'domainUncaughtExceptionClear' && + // the handler we register for tracing + listener.tag !== 'sentry_tracingErrorCallback' && + // the handler we register in this integration + (listener as ErrorHandler)._errorHandler !== true + ); + }, + ).length; const processWouldExit = userProvidedListenersCount === 0; const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit; diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node-experimental/src/sdk/api.ts index 2608795a8264..735766e994f1 100644 --- a/packages/node-experimental/src/sdk/api.ts +++ b/packages/node-experimental/src/sdk/api.ts @@ -2,6 +2,7 @@ import { getCurrentScope } from '@sentry/core'; import type { Client, StackParser } from '@sentry/types'; +import type { Hub } from '@sentry/types'; import { GLOBAL_OBJ, createStackParser, nodeStackLineParser } from '@sentry/utils'; import { createGetModuleFromFilename } from '../utils/module'; @@ -54,3 +55,14 @@ export function getSentryRelease(fallback?: string): string | undefined { /** Node.js stack parser */ export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename())); + +/** + * This method is a noop and only here to ensure vite-plugin v0.6.0 (which is used by Sveltekit) still works, + * as that uses this. + * + * @deprecated This will be removed before v8 is finalized. + */ +export function makeMain(hub: Hub): Hub { + // noop + return hub; +} diff --git a/packages/node/package.json b/packages/node/package.json index 28d51122a57c..8dd73523d1e4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,7 +1,7 @@ { - "name": "@sentry/node", + "name": "@sentry/node-experimental", "version": "7.100.0", - "description": "Official Sentry SDK for Node.js", + "description": "The old version of Sentry SDK for Node.js, without OpenTelemetry support.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node", "author": "Sentry", diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index 9574933a1c86..78812137e8e1 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -45,7 +45,7 @@ "@opentelemetry/sdk-trace-base": "^1.17.1", "@opentelemetry/sdk-trace-node": "^1.17.1", "@opentelemetry/semantic-conventions": "^1.17.1", - "@sentry/node": "7.100.0" + "@sentry/node-experimental": "7.100.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index b5e2a26fa4c2..1f293b6f80d8 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -8,7 +8,7 @@ import type { SpanStatusType } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; import { SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; -import { NodeClient } from '@sentry/node'; +import { NodeClient } from '@sentry/node-experimental'; import { resolvedSyncPromise } from '@sentry/utils'; import { SentrySpanProcessor } from '../src/spanprocessor'; diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index fd999c68b3ba..0b4bf0447a75 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -68,10 +68,10 @@ "node-abi": "^3.52.0" }, "devDependencies": { - "@sentry/core": "7.93.0", - "@sentry/node": "7.93.0", - "@sentry/types": "7.93.0", - "@sentry/utils": "7.93.0", + "@sentry/core": "7.100.0", + "@sentry/node-experimental": "7.100.0", + "@sentry/types": "7.100.0", + "@sentry/utils": "7.100.0", "@types/node": "16.18.70", "@types/node-abi": "^3.0.0", "clang-format": "^1.8.0", diff --git a/packages/profiling-node/src/hubextensions.ts b/packages/profiling-node/src/hubextensions.ts index 66a43d3fcb28..19fe86777bff 100644 --- a/packages/profiling-node/src/hubextensions.ts +++ b/packages/profiling-node/src/hubextensions.ts @@ -1,5 +1,5 @@ -import { getMainCarrier } from '@sentry/core'; -import type { NodeClient } from '@sentry/node'; +import { getMainCarrier, spanToJSON } from '@sentry/core'; +import type { NodeClient } from '@sentry/node-experimental'; import type { CustomSamplingContext, Hub, Transaction, TransactionContext } from '@sentry/types'; import { logger, uuid4 } from '@sentry/utils'; @@ -89,9 +89,7 @@ export function maybeProfileTransaction( const profile_id = uuid4(); CpuProfilerBindings.startProfiling(profile_id); - DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log(`[Profiling] started profiling transaction: ${transaction.name}`); + DEBUG_BUILD && logger.log(`[Profiling] started profiling transaction: ${spanToJSON(transaction).description}`); // set transaction context - do this regardless if profiling fails down the line // so that we can still see the profile_id in the transaction context @@ -115,16 +113,13 @@ export function stopTransactionProfile( const profile = CpuProfilerBindings.stopProfiling(profile_id); - DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log(`[Profiling] stopped profiling of transaction: ${transaction.name}`); + DEBUG_BUILD && logger.log(`[Profiling] stopped profiling of transaction: ${spanToJSON(transaction).description}`); // In case of an overlapping transaction, stopProfiling may return null and silently ignore the overlapping profile. if (!profile) { DEBUG_BUILD && logger.log( - // eslint-disable-next-line deprecation/deprecation - `[Profiling] profiler returned null profile for: ${transaction.name}`, + `[Profiling] profiler returned null profile for: ${spanToJSON(transaction).description}`, 'this may indicate an overlapping transaction or a call to stopProfiling with a profile title that was never started', ); return null; @@ -179,8 +174,10 @@ export function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction: S // Enqueue a timeout to prevent profiles from running over max duration. let maxDurationTimeoutID: NodeJS.Timeout | void = global.setTimeout(() => { DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', transaction.name); + logger.log( + '[Profiling] max profile duration elapsed, stopping profiling for:', + spanToJSON(transaction).description, + ); profile = stopTransactionProfile(transaction, profile_id); }, maxProfileDurationMs); diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index 28bf24f0d784..3e9cfd99b844 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -1,4 +1,5 @@ -import type { NodeClient } from '@sentry/node'; +import { spanToJSON } from '@sentry/core'; +import type { NodeClient } from '@sentry/node-experimental'; import type { Event, EventProcessor, Hub, Integration, Transaction } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -79,8 +80,10 @@ export class ProfilingIntegration implements Integration { // Enqueue a timeout to prevent profiles from running over max duration. PROFILE_TIMEOUTS[profile_id] = global.setTimeout(() => { DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log('[Profiling] max profile duration elapsed, stopping profiling for:', transaction.name); + logger.log( + '[Profiling] max profile duration elapsed, stopping profiling for:', + spanToJSON(transaction).description, + ); const profile = stopTransactionProfile(transaction, profile_id); if (profile) { diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index 1e34fbfd8974..8885c1c12f9e 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -17,7 +17,7 @@ import type { import { env, versions } from 'process'; import { isMainThread, threadId } from 'worker_threads'; -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import { GLOBAL_OBJ, createEnvelope, dropUndefinedKeys, dsnToString, forEachEnvelopeItem, logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; diff --git a/packages/profiling-node/test/hubextensions.hub.test.ts b/packages/profiling-node/test/hubextensions.hub.test.ts index 954f3300ffca..87b2154deeaf 100644 --- a/packages/profiling-node/test/hubextensions.hub.test.ts +++ b/packages/profiling-node/test/hubextensions.hub.test.ts @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import { getMainCarrier } from '@sentry/core'; import type { Transport } from '@sentry/types'; diff --git a/packages/profiling-node/test/hubextensions.test.ts b/packages/profiling-node/test/hubextensions.test.ts index df90200c2a5d..ffa4131b96ab 100644 --- a/packages/profiling-node/test/hubextensions.test.ts +++ b/packages/profiling-node/test/hubextensions.test.ts @@ -7,7 +7,7 @@ import type { TransactionMetadata, } from '@sentry/types'; -import type { NodeClient } from '@sentry/node'; +import type { NodeClient } from '@sentry/node-experimental'; import { CpuProfilerBindings } from '../src/cpu_profiler'; import { __PRIVATE__wrapStartTransactionWithProfiling } from '../src/hubextensions'; diff --git a/packages/profiling-node/test/index.test.ts b/packages/profiling-node/test/index.test.ts index ab6aaebfb86a..d0ac92ad65d6 100644 --- a/packages/profiling-node/test/index.test.ts +++ b/packages/profiling-node/test/index.test.ts @@ -1,4 +1,4 @@ -import * as Sentry from '@sentry/node'; +import * as Sentry from '@sentry/node-experimental'; import type { Transport } from '@sentry/types'; import { getMainCarrier } from '@sentry/core'; diff --git a/packages/remix/package.json b/packages/remix/package.json index 54c5c980db55..81711cead0f2 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -36,7 +36,7 @@ "dependencies": { "@sentry/cli": "^2.28.6", "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/react": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 54afd27f8bfa..7715deb7d0a4 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -1,6 +1,6 @@ import { applySdkMetadata } from '@sentry/core'; -import type { NodeOptions } from '@sentry/node'; -import { getClient, init as nodeInit, setTag } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { getClient, init as nodeInit, setTag } from '@sentry/node-experimental'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from './utils/debug-build'; @@ -91,10 +91,10 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, -} from '@sentry/node'; +} from '@sentry/node-experimental'; // Keeping the `*` exports for backwards compatibility and types -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; export { captureRemixServerException, wrapRemixHandleError } from './utils/instrumentServer'; export { ErrorBoundary, withErrorBoundary } from '@sentry/react'; diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 76c181199541..f7b9429dc7a8 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -13,7 +13,7 @@ import { spanToTraceHeader, withIsolationScope, } from '@sentry/core'; -import { captureException, getCurrentHub } from '@sentry/node'; +import { captureException, getCurrentHub } from '@sentry/node-experimental'; import type { Hub, Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; import { addExceptionMechanism, diff --git a/packages/remix/src/utils/remixOptions.ts b/packages/remix/src/utils/remixOptions.ts index 4a1fe13e18e1..6ac57b5a2f1e 100644 --- a/packages/remix/src/utils/remixOptions.ts +++ b/packages/remix/src/utils/remixOptions.ts @@ -1,4 +1,4 @@ -import type { NodeOptions } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; import type { BrowserOptions } from '@sentry/react'; import type { Options } from '@sentry/types'; diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index dea4357e07db..399ba531f23a 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -1,5 +1,5 @@ import { getClient, getCurrentHub, hasTracingEnabled, setHttpStatus, withIsolationScope } from '@sentry/core'; -import { flush } from '@sentry/node'; +import { flush } from '@sentry/node-experimental'; import type { Transaction } from '@sentry/types'; import { extractRequestData, fill, isString, logger } from '@sentry/utils'; import { cwd } from 'process'; diff --git a/packages/remix/test/index.server.test.ts b/packages/remix/test/index.server.test.ts index 79c138203012..71d2933f9d0e 100644 --- a/packages/remix/test/index.server.test.ts +++ b/packages/remix/test/index.server.test.ts @@ -1,4 +1,4 @@ -import * as SentryNode from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; import { Integrations, init } from '../src/index.server'; diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index 03068150e6f8..d151771125b9 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -26,7 +26,8 @@ "@sentry/browser": "file:../../../browser", "@sentry/core": "file:../../../core", "@sentry/integrations": "file:../../../integrations", - "@sentry/node": "file:../../../node", + "@sentry/node": "file:../../../node-experimental", + "@sentry/node-experimental": "file:../../../node", "@sentry/react": "file:../../../react", "@sentry/replay": "file:../../../replay", "@sentry-internal/replay-canvas": "file:../../../replay-canvas", diff --git a/packages/serverless/package.json b/packages/serverless/package.json index e94ed265dcdc..08f790c487ca 100644 --- a/packages/serverless/package.json +++ b/packages/serverless/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", "@types/aws-lambda": "^8.10.62", diff --git a/packages/serverless/scripts/buildLambdaLayer.ts b/packages/serverless/scripts/buildLambdaLayer.ts index a462a33c6804..18016434a529 100644 --- a/packages/serverless/scripts/buildLambdaLayer.ts +++ b/packages/serverless/scripts/buildLambdaLayer.ts @@ -18,7 +18,7 @@ async function buildLambdaLayer(): Promise { // Create the main SDK bundle // TODO: Check if we can get rid of this, after the lerna 6/nx update?? await ensureBundleBuildPrereqs({ - dependencies: ['@sentry/utils', '@sentry/core', '@sentry/node'], + dependencies: ['@sentry/utils', '@sentry/core', '@sentry/node-experimental'], }); run('yarn rollup --config rollup.aws.config.mjs'); diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index 668132dd92ff..5a6fcea389fa 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -2,8 +2,8 @@ import { existsSync } from 'fs'; import { hostname } from 'os'; import { basename, resolve } from 'path'; import { types } from 'util'; -import type { NodeOptions } from '@sentry/node'; -import { SDK_VERSION } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { SDK_VERSION } from '@sentry/node-experimental'; import { captureException, captureMessage, @@ -14,7 +14,7 @@ import { init as initNode, startSpanManual, withScope, -} from '@sentry/node'; +} from '@sentry/node-experimental'; import type { Integration, Options, Scope, SdkMetadata, Span } from '@sentry/types'; import { isString, logger } from '@sentry/utils'; import type { Context, Handler } from 'aws-lambda'; @@ -26,7 +26,7 @@ import { awsServicesIntegration } from './awsservices'; import { DEBUG_BUILD } from './debug-build'; import { markEventUnhandled } from './utils'; -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; const { isPromise } = types; diff --git a/packages/serverless/src/awsservices.ts b/packages/serverless/src/awsservices.ts index 084fdbf0238a..33e4816ac836 100644 --- a/packages/serverless/src/awsservices.ts +++ b/packages/serverless/src/awsservices.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import { getClient, startInactiveSpan } from '@sentry/node'; +import { getClient, startInactiveSpan } from '@sentry/node-experimental'; import type { Client, Integration, IntegrationClass, IntegrationFn, Span } from '@sentry/types'; import { fill } from '@sentry/utils'; // 'aws-sdk/global' import is expected to be type-only so it's erased in the final .js file. diff --git a/packages/serverless/src/gcpfunction/cloud_events.ts b/packages/serverless/src/gcpfunction/cloud_events.ts index 533c74bb7653..3d5ac9d3d1dc 100644 --- a/packages/serverless/src/gcpfunction/cloud_events.ts +++ b/packages/serverless/src/gcpfunction/cloud_events.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors } from '@sentry/core'; -import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node'; +import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node-experimental'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; diff --git a/packages/serverless/src/gcpfunction/events.ts b/packages/serverless/src/gcpfunction/events.ts index 501b6f7d6da3..3503a427ee1f 100644 --- a/packages/serverless/src/gcpfunction/events.ts +++ b/packages/serverless/src/gcpfunction/events.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors } from '@sentry/core'; -import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node'; +import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node-experimental'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts index fe5279ddc6cf..17ee38d18e0e 100644 --- a/packages/serverless/src/gcpfunction/http.ts +++ b/packages/serverless/src/gcpfunction/http.ts @@ -5,9 +5,9 @@ import { handleCallbackErrors, setHttpStatus, } from '@sentry/core'; -import { continueTrace, startSpanManual } from '@sentry/node'; -import { getCurrentScope } from '@sentry/node'; -import { captureException, flush } from '@sentry/node'; +import { continueTrace, startSpanManual } from '@sentry/node-experimental'; +import { getCurrentScope } from '@sentry/node-experimental'; +import { captureException, flush } from '@sentry/node-experimental'; import { isString, logger, stripUrlQueryAndFragment } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts index 194f9a3a92cd..ad415b2fb761 100644 --- a/packages/serverless/src/gcpfunction/index.ts +++ b/packages/serverless/src/gcpfunction/index.ts @@ -1,5 +1,9 @@ -import type { NodeOptions } from '@sentry/node'; -import { SDK_VERSION, getDefaultIntegrations as getDefaultNodeIntegrations, init as initNode } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { + SDK_VERSION, + getDefaultIntegrations as getDefaultNodeIntegrations, + init as initNode, +} from '@sentry/node-experimental'; import type { Integration, Options, SdkMetadata } from '@sentry/types'; import { googleCloudGrpcIntegration } from '../google-cloud-grpc'; diff --git a/packages/serverless/src/google-cloud-grpc.ts b/packages/serverless/src/google-cloud-grpc.ts index a32ac9dae9ac..e982563fb36e 100644 --- a/packages/serverless/src/google-cloud-grpc.ts +++ b/packages/serverless/src/google-cloud-grpc.ts @@ -5,7 +5,7 @@ import { defineIntegration, getClient, } from '@sentry/core'; -import { startInactiveSpan } from '@sentry/node'; +import { startInactiveSpan } from '@sentry/node-experimental'; import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { fill } from '@sentry/utils'; diff --git a/packages/serverless/src/google-cloud-http.ts b/packages/serverless/src/google-cloud-http.ts index 5e7558f4299d..78abb2f97ff1 100644 --- a/packages/serverless/src/google-cloud-http.ts +++ b/packages/serverless/src/google-cloud-http.ts @@ -5,7 +5,7 @@ import { defineIntegration, getClient, } from '@sentry/core'; -import { startInactiveSpan } from '@sentry/node'; +import { startInactiveSpan } from '@sentry/node-experimental'; import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import { fill } from '@sentry/utils'; diff --git a/packages/serverless/src/index.awslambda.ts b/packages/serverless/src/index.awslambda.ts index c097591ab9dc..481c9b13d62f 100644 --- a/packages/serverless/src/index.awslambda.ts +++ b/packages/serverless/src/index.awslambda.ts @@ -5,4 +5,4 @@ import * as AWSLambda from './awslambda'; export { AWSLambda }; export * from './awsservices'; -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 63a28a5ee4bb..6a0f395ca4ac 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -7,7 +7,7 @@ export { AWSLambda, GCPFunction }; export { AWSServices, awsServicesIntegration } from './awsservices'; // TODO(v8): We have to explicitly export these because of the namespace exports -// above. This is because just doing `export * from '@sentry/node'` will not +// above. This is because just doing `export * from '@sentry/node-experimental'` will not // work with Node native esm while we also have namespace exports in a package. // What we should do is get rid of the namespace exports. export { @@ -101,4 +101,4 @@ export { captureSession, endSession, withActiveSpan, -} from '@sentry/node'; +} from '@sentry/node-experimental'; diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts index 772502057a34..964c760f0ca3 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/serverless/test/awslambda.test.ts @@ -20,8 +20,8 @@ const mockScope = { addEventProcessor: jest.fn(), }; -jest.mock('@sentry/node', () => { - const original = jest.requireActual('@sentry/node'); +jest.mock('@sentry/node-experimental', () => { + const original = jest.requireActual('@sentry/node-experimental'); return { ...original, init: (options: unknown) => { diff --git a/packages/serverless/test/awsservices.test.ts b/packages/serverless/test/awsservices.test.ts index 3170f9056ec0..c48abb173e16 100644 --- a/packages/serverless/test/awsservices.test.ts +++ b/packages/serverless/test/awsservices.test.ts @@ -1,4 +1,4 @@ -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node'; +import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; import * as AWS from 'aws-sdk'; import * as nock from 'nock'; @@ -8,9 +8,9 @@ import { awsServicesIntegration } from '../src/awsservices'; const mockSpanEnd = jest.fn(); const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); -jest.mock('@sentry/node', () => { +jest.mock('@sentry/node-experimental', () => { return { - ...jest.requireActual('@sentry/node'), + ...jest.requireActual('@sentry/node-experimental'), startInactiveSpan: (ctx: unknown) => { mockStartInactiveSpan(ctx); return { end: mockSpanEnd }; diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index 798284a6d334..c9566200a46d 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -36,8 +36,8 @@ const mockSpan = { end: jest.fn(), }; -jest.mock('@sentry/node', () => { - const original = jest.requireActual('@sentry/node'); +jest.mock('@sentry/node-experimental', () => { + const original = jest.requireActual('@sentry/node-experimental'); return { ...original, init: (options: unknown) => { diff --git a/packages/serverless/test/google-cloud-grpc.test.ts b/packages/serverless/test/google-cloud-grpc.test.ts index 0ce19ee7db8b..d9d789d33480 100644 --- a/packages/serverless/test/google-cloud-grpc.test.ts +++ b/packages/serverless/test/google-cloud-grpc.test.ts @@ -9,7 +9,7 @@ import * as http2 from 'http2'; import * as nock from 'nock'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node'; +import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; import { googleCloudGrpcIntegration } from '../src/google-cloud-grpc'; const spyConnect = jest.spyOn(http2, 'connect'); @@ -17,9 +17,9 @@ const spyConnect = jest.spyOn(http2, 'connect'); const mockSpanEnd = jest.fn(); const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); -jest.mock('@sentry/node', () => { +jest.mock('@sentry/node-experimental', () => { return { - ...jest.requireActual('@sentry/node'), + ...jest.requireActual('@sentry/node-experimental'), startInactiveSpan: (ctx: unknown) => { mockStartInactiveSpan(ctx); return { end: mockSpanEnd }; diff --git a/packages/serverless/test/google-cloud-http.test.ts b/packages/serverless/test/google-cloud-http.test.ts index 3389130565f9..804156a1bf02 100644 --- a/packages/serverless/test/google-cloud-http.test.ts +++ b/packages/serverless/test/google-cloud-http.test.ts @@ -4,15 +4,15 @@ import { BigQuery } from '@google-cloud/bigquery'; import * as nock from 'nock'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node'; +import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; import { googleCloudHttpIntegration } from '../src/google-cloud-http'; const mockSpanEnd = jest.fn(); const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); -jest.mock('@sentry/node', () => { +jest.mock('@sentry/node-experimental', () => { return { - ...jest.requireActual('@sentry/node'), + ...jest.requireActual('@sentry/node-experimental'), startInactiveSpan: (ctx: unknown) => { mockStartInactiveSpan(ctx); return { end: mockSpanEnd }; diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 9b62eb76fa57..99f55c0e2ca4 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -40,7 +40,7 @@ "@sentry-internal/tracing": "7.100.0", "@sentry/core": "7.100.0", "@sentry/integrations": "7.100.0", - "@sentry/node": "7.100.0", + "@sentry/node-experimental": "7.100.0", "@sentry/svelte": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0", diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index cc53f7541923..988abf3604ec 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -9,7 +9,7 @@ import { withIsolationScope, } from '@sentry/core'; import { getActiveTransaction, startSpan } from '@sentry/core'; -import { captureException } from '@sentry/node'; +import { captureException } from '@sentry/node-experimental'; /* eslint-disable @sentry-internal/sdk/no-optional-chaining */ import type { Span } from '@sentry/types'; import { dynamicSamplingContextToSentryBaggageHeader, objectify } from '@sentry/utils'; diff --git a/packages/sveltekit/src/server/handleError.ts b/packages/sveltekit/src/server/handleError.ts index 1289e76a5ee2..b556342635f3 100644 --- a/packages/sveltekit/src/server/handleError.ts +++ b/packages/sveltekit/src/server/handleError.ts @@ -1,4 +1,4 @@ -import { captureException } from '@sentry/node'; +import { captureException } from '@sentry/node-experimental'; import type { HandleServerError } from '@sveltejs/kit'; import { flushIfServerless } from './utils'; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 6d948636feee..706da419a84e 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -1,5 +1,5 @@ // Node SDK exports -// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds, +// Unfortunately, we cannot `export * from '@sentry/node-experimental'` because in prod builds, // Vite puts these exports into a `default` property (Sentry.default) rather than // on the top - level namespace. // Hence, we export everything from the Node SDK explicitly: @@ -85,10 +85,10 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, -} from '@sentry/node'; +} from '@sentry/node-experimental'; // We can still leave this for the carrier init and type exports -export * from '@sentry/node'; +export * from '@sentry/node-experimental'; // ------------------------- // SvelteKit SDK exports: diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts index 0d73b27a9d1b..6a8c62ccd7c5 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server/load.ts @@ -5,7 +5,7 @@ import { getCurrentScope, startSpan, } from '@sentry/core'; -import { captureException } from '@sentry/node'; +import { captureException } from '@sentry/node-experimental'; import { addNonEnumerableProperty, objectify } from '@sentry/utils'; import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index f16220775d3a..faf584b7d0e6 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -1,7 +1,7 @@ import { applySdkMetadata, setTag } from '@sentry/core'; -import type { NodeOptions } from '@sentry/node'; -import { getDefaultIntegrations as getDefaultNodeIntegrations } from '@sentry/node'; -import { init as initNodeSdk } from '@sentry/node'; +import type { NodeOptions } from '@sentry/node-experimental'; +import { getDefaultIntegrations as getDefaultNodeIntegrations } from '@sentry/node-experimental'; +import { init as initNodeSdk } from '@sentry/node-experimental'; import { rewriteFramesIntegration } from './rewriteFramesIntegration'; diff --git a/packages/sveltekit/src/server/utils.ts b/packages/sveltekit/src/server/utils.ts index 1f3719745bca..ad6765209548 100644 --- a/packages/sveltekit/src/server/utils.ts +++ b/packages/sveltekit/src/server/utils.ts @@ -1,4 +1,4 @@ -import { flush } from '@sentry/node'; +import { flush } from '@sentry/node-experimental'; import { logger, tracingContextFromHeaders } from '@sentry/utils'; import type { RequestEvent } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index 2f5fd28ac1f8..d423c9fec5f5 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -1,7 +1,7 @@ import * as child_process from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import { getSentryRelease } from '@sentry/node'; +import { getSentryRelease } from '@sentry/node-experimental'; import { escapeStringForRegex, uuid4 } from '@sentry/utils'; import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; import { sentryVitePlugin } from '@sentry/vite-plugin'; diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index efd96107e651..42e64affbcc1 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,6 +1,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions, spanToJSON } from '@sentry/core'; -import { NodeClient, setCurrentClient } from '@sentry/node'; -import * as SentryNode from '@sentry/node'; +import { NodeClient, setCurrentClient } from '@sentry/node-experimental'; +import * as SentryNode from '@sentry/node-experimental'; import type { Transaction } from '@sentry/types'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; diff --git a/packages/sveltekit/test/server/handleError.test.ts b/packages/sveltekit/test/server/handleError.test.ts index 611fac1f9a4d..58d525d9a9db 100644 --- a/packages/sveltekit/test/server/handleError.test.ts +++ b/packages/sveltekit/test/server/handleError.test.ts @@ -1,4 +1,4 @@ -import * as SentryNode from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; import type { HandleServerError, RequestEvent } from '@sveltejs/kit'; import { vi } from 'vitest'; diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 790ded799106..906e12553500 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions } from '@sentry/core'; -import * as SentryNode from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; import type { Load, ServerLoad } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; diff --git a/packages/sveltekit/test/server/sdk.test.ts b/packages/sveltekit/test/server/sdk.test.ts index f6f5e96e1620..96aed4103510 100644 --- a/packages/sveltekit/test/server/sdk.test.ts +++ b/packages/sveltekit/test/server/sdk.test.ts @@ -1,6 +1,6 @@ -import * as SentryNode from '@sentry/node'; -import type { NodeClient } from '@sentry/node'; -import { SDK_VERSION, getClient } from '@sentry/node'; +import * as SentryNode from '@sentry/node-experimental'; +import type { NodeClient } from '@sentry/node-experimental'; +import { SDK_VERSION, getClient } from '@sentry/node-experimental'; import { init } from '../../src/server/sdk'; diff --git a/yarn.lock b/yarn.lock index f618c7d7d485..522fb4df4a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5621,15 +5621,6 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry-internal/tracing@7.93.0": - version "7.93.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.93.0.tgz#8cee8b610695d828af75edd2929b64b7caf0385d" - integrity sha512-DjuhmQNywPp+8fxC9dvhGrqgsUb6wI/HQp25lS2Re7VxL1swCasvpkg8EOYP4iBniVQ86QK0uITkOIRc5tdY1w== - dependencies: - "@sentry/core" "7.93.0" - "@sentry/types" "7.93.0" - "@sentry/utils" "7.93.0" - "@sentry/bundler-plugin-core@0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-0.6.1.tgz#6c6a2ff3cdc98cd0ff1c30c59408cee9f067adf2" @@ -5724,37 +5715,6 @@ "@sentry/cli-win32-i686" "2.28.6" "@sentry/cli-win32-x64" "2.28.6" -"@sentry/core@7.93.0": - version "7.93.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.93.0.tgz#50a14bf305130dfef51810e4c97fcba4972a57ef" - integrity sha512-vZQSUiDn73n+yu2fEcH+Wpm4GbRmtxmnXnYCPgM6IjnXqkVm3awWAkzrheADblx3kmxrRiOlTXYHw9NTWs56fg== - dependencies: - "@sentry/types" "7.93.0" - "@sentry/utils" "7.93.0" - -"@sentry/node@7.93.0": - version "7.93.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.93.0.tgz#7786d05d1e3e984207a866b07df1bf891355892e" - integrity sha512-nUXPCZQm5Y9Ipv7iWXLNp5dbuyi1VvbJ3RtlwD7utgsNkRYB4ixtKE9w2QU8DZZAjaEF6w2X94OkYH6C932FWw== - dependencies: - "@sentry-internal/tracing" "7.93.0" - "@sentry/core" "7.93.0" - "@sentry/types" "7.93.0" - "@sentry/utils" "7.93.0" - https-proxy-agent "^5.0.0" - -"@sentry/types@7.93.0": - version "7.93.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.93.0.tgz#d76d26259b40cd0688e1d634462fbff31476c1ec" - integrity sha512-UnzUccNakhFRA/esWBWP+0v7cjNg+RilFBQC03Mv9OEMaZaS29zSbcOGtRzuFOXXLBdbr44BWADqpz3VW0XaNw== - -"@sentry/utils@7.93.0": - version "7.93.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.93.0.tgz#36225038661fe977baf01e4695ef84794d591e45" - integrity sha512-Iovj7tUnbgSkh/WrAaMrd5UuYjW7AzyzZlFDIUrwidsyIdUficjCG2OIxYzh76H6nYIx9SxewW0R54Q6XoB4uA== - dependencies: - "@sentry/types" "7.93.0" - "@sentry/vite-plugin@^0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-0.6.1.tgz#31eb744e8d87b1528eed8d41433647727a62e7c0" From deb41bc9cd6143ec20db7e5a033553c44f9af135 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 21 Feb 2024 17:35:29 +0100 Subject: [PATCH 128/173] ref: Cleanup browser profiling integration (#10766) This does two things: 1. Remove the deprecated class-based `BrowserProfilingIntegration` 2. Remove the `onProfilingStartRouteTransaction` method - this shouldn't be needed anymore, as now the execution order of browser tracing is ensured to be after browser profiling, ensuring that we had the chance to register the `startTransaction` hook before browser tracing started a transaction (as that now runs in `afterAllSetup`) --- packages/browser/src/index.ts | 7 +- packages/browser/src/profiling/integration.ts | 25 +--- ...sions.ts => startProfileForTransaction.ts} | 24 ---- .../test/unit/profiling/hubextensions.test.ts | 108 ------------------ .../test/unit/profiling/integration.test.ts | 6 +- 5 files changed, 7 insertions(+), 163 deletions(-) rename packages/browser/src/profiling/{hubextensions.ts => startProfileForTransaction.ts} (89%) delete mode 100644 packages/browser/test/unit/profiling/hubextensions.test.ts diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 15c5adee2cc4..9957e290acf5 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -79,9 +79,4 @@ export { export type { SpanStatusType } from '@sentry/core'; export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; -export { onProfilingStartRouteTransaction } from './profiling/hubextensions'; -export { - // eslint-disable-next-line deprecation/deprecation - BrowserProfilingIntegration, - browserProfilingIntegration, -} from './profiling/integration'; +export { browserProfilingIntegration } from './profiling/integration'; diff --git a/packages/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index f9b924d9102c..5b6137e10027 100644 --- a/packages/browser/src/profiling/integration.ts +++ b/packages/browser/src/profiling/integration.ts @@ -1,10 +1,10 @@ -import { convertIntegrationFnToClass, defineIntegration, getCurrentScope } from '@sentry/core'; -import type { Client, EventEnvelope, Integration, IntegrationClass, IntegrationFn, Transaction } from '@sentry/types'; +import { defineIntegration, getCurrentScope } from '@sentry/core'; +import type { EventEnvelope, IntegrationFn, Transaction } from '@sentry/types'; import type { Profile } from '@sentry/types/src/profiling'; import { logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import { startProfileForTransaction } from './hubextensions'; +import { startProfileForTransaction } from './startProfileForTransaction'; import type { ProfiledEvent } from './utils'; import { addProfilesToEnvelope, @@ -97,22 +97,3 @@ const _browserProfilingIntegration = (() => { }) satisfies IntegrationFn; export const browserProfilingIntegration = defineIntegration(_browserProfilingIntegration); - -/** - * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"] - * This exists because we do not want to await async profiler.stop calls as transaction.finish is called - * in a synchronous context. Instead, we handle sending the profile async from the promise callback and - * rely on being able to pull the event from the cache when we need to construct the envelope. This makes the - * integration less reliable as we might be dropping profiles when the cache is full. - * - * @experimental - * @deprecated Use `browserProfilingIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const BrowserProfilingIntegration = convertIntegrationFnToClass( - INTEGRATION_NAME, - browserProfilingIntegration, -) as IntegrationClass void }>; - -// eslint-disable-next-line deprecation/deprecation -export type BrowserProfilingIntegration = typeof BrowserProfilingIntegration; diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/startProfileForTransaction.ts similarity index 89% rename from packages/browser/src/profiling/hubextensions.ts rename to packages/browser/src/profiling/startProfileForTransaction.ts index 9fd156a1b90b..7d62cd8b1c46 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/startProfileForTransaction.ts @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { spanToJSON } from '@sentry/core'; import type { Transaction } from '@sentry/types'; import { logger, timestampInSeconds, uuid4 } from '@sentry/utils'; @@ -10,32 +9,9 @@ import { MAX_PROFILE_DURATION_MS, addProfileToGlobalCache, isAutomatedPageLoadTransaction, - shouldProfileTransaction, startJSSelfProfile, } from './utils'; -/** - * Safety wrapper for startTransaction for the unlikely case that transaction starts before tracing is imported - - * if that happens we want to avoid throwing an error from profiling code. - * see https://github.com/getsentry/sentry-javascript/issues/4731. - * - * @experimental - */ -export function onProfilingStartRouteTransaction(transaction: Transaction | undefined): Transaction | undefined { - if (!transaction) { - if (DEBUG_BUILD) { - logger.log('[Profiling] Transaction is undefined, skipping profiling'); - } - return transaction; - } - - if (shouldProfileTransaction(transaction)) { - return startProfileForTransaction(transaction); - } - - return transaction; -} - /** * Wraps startTransaction and stopTransaction with profiling related logic. * startProfileForTransaction is called after the call to startTransaction in order to avoid our own code from diff --git a/packages/browser/test/unit/profiling/hubextensions.test.ts b/packages/browser/test/unit/profiling/hubextensions.test.ts deleted file mode 100644 index 362379b3f224..000000000000 --- a/packages/browser/test/unit/profiling/hubextensions.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { TextDecoder, TextEncoder } from 'util'; -// @ts-expect-error patch the encoder on the window, else importing JSDOM fails (deleted in afterAll) -const patchedEncoder = (!global.window.TextEncoder && (global.window.TextEncoder = TextEncoder)) || true; -// @ts-expect-error patch the encoder on the window, else importing JSDOM fails (deleted in afterAll) -const patchedDecoder = (!global.window.TextDecoder && (global.window.TextDecoder = TextDecoder)) || true; - -import { setCurrentClient } from '@sentry/core'; -import type { Transaction } from '@sentry/types'; -import { JSDOM } from 'jsdom'; - -import { onProfilingStartRouteTransaction } from '../../../src'; - -// eslint-disable-next-line no-bitwise -const TraceFlagSampled = 0x1 << 0; - -// @ts-expect-error store a reference so we can reset it later -const globalDocument = global.document; -// @ts-expect-error store a reference so we can reset it later -const globalWindow = global.window; -// @ts-expect-error store a reference so we can reset it later -const globalLocation = global.location; - -describe('BrowserProfilingIntegration', () => { - beforeEach(() => { - const dom = new JSDOM(); - // @ts-expect-error need to override global document - global.document = dom.window.document; - // @ts-expect-error need to override global document - global.window = dom.window; - // @ts-expect-error need to override global document - global.location = dom.window.location; - - const client: any = { - getDsn() { - return {}; - }, - getTransport() { - return { - send() {}, - }; - }, - getOptions() { - return { - profilesSampleRate: 1, - }; - }, - }; - - setCurrentClient(client); - }); - - // Reset back to previous values - afterEach(() => { - // @ts-expect-error need to override global document - global.document = globalDocument; - // @ts-expect-error need to override global document - global.window = globalWindow; - // @ts-expect-error need to override global document - global.location = globalLocation; - }); - afterAll(() => { - // @ts-expect-error patch the encoder on the window, else importing JSDOM fails - patchedEncoder && delete global.window.TextEncoder; - // @ts-expect-error patch the encoder on the window, else importing JSDOM fails - patchedDecoder && delete global.window.TextDecoder; - }); - - it('does not throw if Profiler is not available', () => { - // @ts-expect-error force api to be undefined - global.window.Profiler = undefined; - // set sampled to true so that profiling does not early return - const mockTransaction = { - isRecording: () => true, - spanContext: () => ({ - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TraceFlagSampled, - }), - } as Transaction; - expect(() => onProfilingStartRouteTransaction(mockTransaction)).not.toThrow(); - }); - - it('does not throw if constructor throws', () => { - const spy = jest.fn(); - - class Profiler { - constructor() { - spy(); - throw new Error('Profiler constructor error'); - } - } - - // set sampled to true so that profiling does not early return - const mockTransaction = { - isRecording: () => true, - spanContext: () => ({ - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TraceFlagSampled, - }), - } as Transaction; - - // @ts-expect-error override with our own constructor - global.window.Profiler = Profiler; - expect(() => onProfilingStartRouteTransaction(mockTransaction)).not.toThrow(); - expect(spy).toHaveBeenCalled(); - }); -}); diff --git a/packages/browser/test/unit/profiling/integration.test.ts b/packages/browser/test/unit/profiling/integration.test.ts index 9394221b0e4b..fe95cd5ec83c 100644 --- a/packages/browser/test/unit/profiling/integration.test.ts +++ b/packages/browser/test/unit/profiling/integration.test.ts @@ -48,9 +48,9 @@ describe('BrowserProfilingIntegration', () => { const client = Sentry.getClient(); - // eslint-disable-next-line deprecation/deprecation - const currentTransaction = Sentry.getCurrentScope().getTransaction(); - expect(currentTransaction?.op).toBe('pageload'); + const currentTransaction = Sentry.getActiveSpan(); + expect(currentTransaction).toBeDefined(); + expect(Sentry.spanToJSON(currentTransaction!).op).toBe('pageload'); currentTransaction?.end(); await client?.flush(1000); From 15216cdefd4a21a823d1a8455ae338004ceef95d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 21 Feb 2024 11:54:18 -0500 Subject: [PATCH 129/173] feat(v8/node): Remove getModuleFromFilename export (#10754) ref https://github.com/getsentry/sentry-javascript/issues/10100 --- packages/bun/src/index.ts | 2 -- packages/node/src/index.ts | 7 +------ packages/remix/src/index.server.ts | 2 -- packages/serverless/src/index.ts | 2 -- packages/sveltekit/src/server/index.ts | 2 -- 5 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 7aa59dc81bef..215bc5db9573 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -83,8 +83,6 @@ export { } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { - // eslint-disable-next-line deprecation/deprecation - getModuleFromFilename, DEFAULT_USER_INCLUDES, autoDiscoverNodePerformanceMonitoringIntegrations, cron, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index a4ac4509b299..7cb48b4651da 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -102,12 +102,7 @@ export { } from './sdk'; export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils'; -import { createGetModuleFromFilename } from './module'; -/** - * @deprecated use `createGetModuleFromFilename` instead. - */ -export const getModuleFromFilename = createGetModuleFromFilename(); -export { createGetModuleFromFilename }; +export { createGetModuleFromFilename } from './module'; import { Integrations as CoreIntegrations } from '@sentry/core'; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 7715deb7d0a4..d943c27b1611 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -81,8 +81,6 @@ export { cron, parameterize, metrics, - // eslint-disable-next-line deprecation/deprecation - getModuleFromFilename, createGetModuleFromFilename, hapiErrorPlugin, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 6a0f395ca4ac..7fcc2ede3470 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -75,8 +75,6 @@ export { linkedErrorsIntegration, inboundFiltersIntegration, functionToStringIntegration, - // eslint-disable-next-line deprecation/deprecation - getModuleFromFilename, createGetModuleFromFilename, metrics, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 706da419a84e..c0079e34fe22 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -74,8 +74,6 @@ export { continueTrace, cron, parameterize, - // eslint-disable-next-line deprecation/deprecation - getModuleFromFilename, createGetModuleFromFilename, hapiErrorPlugin, metrics, From 743c6c48e870243dc551ff765907f023ba03913e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 21 Feb 2024 12:15:27 -0500 Subject: [PATCH 130/173] feat(v8/core): Remove getters for span.op (#10767) --- .../event-proxy-server.ts | 18 +++---- packages/browser/src/profiling/utils.ts | 4 +- .../bun/test/integrations/bunserver.test.ts | 4 +- packages/core/src/tracing/idletransaction.ts | 8 ++-- packages/core/src/tracing/sampling.ts | 8 ++-- packages/core/src/tracing/sentrySpan.ts | 28 ++--------- packages/core/src/tracing/transaction.ts | 4 +- packages/core/test/lib/tracing/trace.test.ts | 47 +------------------ packages/node/src/handlers.ts | 3 +- packages/node/test/integrations/http.test.ts | 12 +---- .../test/spanprocessor.test.ts | 25 ---------- .../src/browser/browserTracingIntegration.ts | 15 +++--- .../src/browser/browsertracing.ts | 17 ++++--- .../src/browser/metrics/index.ts | 3 +- .../tracing-internal/src/browser/router.ts | 5 +- .../test/browser/browsertracing.test.ts | 20 ++++---- .../test/browser/metrics/utils.test.ts | 3 +- packages/tracing/test/span.test.ts | 4 +- packages/types/src/span.ts | 10 +--- packages/types/src/transaction.ts | 2 +- 20 files changed, 69 insertions(+), 171 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts index d14ca5cb5e72..2a4dc2b525d9 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; +import type { Envelope, EnvelopeItem, SerializedEvent } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -210,13 +210,13 @@ export function waitForEnvelopeItem( export function waitForError( proxyServerName: string, - callback: (transactionEvent: Event) => Promise | boolean, -): Promise { + callback: (transactionEvent: SerializedEvent) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { - resolve(envelopeItemBody as Event); + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as SerializedEvent))) { + resolve(envelopeItemBody as SerializedEvent); return true; } return false; @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: Event) => Promise | boolean, -): Promise { + callback: (transactionEvent: SerializedEvent) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { - resolve(envelopeItemBody as Event); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { + resolve(envelopeItemBody as SerializedEvent); return true; } return false; diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 9114884384b7..f4f700a2ab6f 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ -import { DEFAULT_ENVIRONMENT, getClient } from '@sentry/core'; +import { DEFAULT_ENVIRONMENT, getClient, spanToJSON } from '@sentry/core'; import type { DebugImage, Envelope, Event, EventEnvelope, StackFrame, StackParser, Transaction } from '@sentry/types'; import type { Profile, ThreadCpuProfile } from '@sentry/types/src/profiling'; import { GLOBAL_OBJ, browserPerformanceTimeOrigin, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils'; @@ -202,7 +202,7 @@ export function isProfiledTransactionEvent(event: Event): event is ProfiledEvent * */ export function isAutomatedPageLoadTransaction(transaction: Transaction): boolean { - return transaction.op === 'pageload'; + return spanToJSON(transaction).op === 'pageload'; } /** diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index b51fb649fd25..c6bcf62de229 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -29,7 +29,7 @@ describe('Bun Serve Integration', () => { expect(transaction.tags).toEqual({ 'http.status_code': '200', }); - expect(transaction.op).toEqual('http.server'); + expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('GET /'); }); @@ -52,7 +52,7 @@ describe('Bun Serve Integration', () => { expect(transaction.tags).toEqual({ 'http.status_code': '200', }); - expect(transaction.op).toEqual('http.server'); + expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('POST /'); }); diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index 3e9381edc83b..7fae765c60df 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -162,16 +162,16 @@ export class IdleTransaction extends Transaction { this._finished = true; this.activities = {}; - // eslint-disable-next-line deprecation/deprecation - if (this.op === 'ui.action.click') { + const op = spanToJSON(this).op; + + if (op === 'ui.action.click') { this.setAttribute(FINISH_REASON_TAG, this._finishReason); } // eslint-disable-next-line deprecation/deprecation if (this.spanRecorder) { DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log('[Tracing] finishing IdleTransaction', new Date(endTimestampInS * 1000).toISOString(), this.op); + logger.log('[Tracing] finishing IdleTransaction', new Date(endTimestampInS * 1000).toISOString(), op); for (const callback of this._beforeFinishCallbacks) { callback(this, endTimestampInS); diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 427a7076d6d0..6bae27ffe1a1 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -94,9 +94,11 @@ export function sampleTransaction( return transaction; } - DEBUG_BUILD && - // eslint-disable-next-line deprecation/deprecation - logger.log(`[Tracing] starting ${transaction.op} transaction - ${spanToJSON(transaction).description}`); + if (DEBUG_BUILD) { + const { op, description } = spanToJSON(transaction); + logger.log(`[Tracing] starting ${op} transaction - ${description}`); + } + return transaction; } diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index b4566a55e399..904136d3d04d 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import type { Instrumenter, Primitive, @@ -296,25 +295,6 @@ export class SentrySpan implements SpanInterface { this._status = status; } - /** - * Operation of the span - * - * @deprecated Use `spanToJSON().op` to read the op instead. - */ - public get op(): string | undefined { - return this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] as string | undefined; - } - - /** - * Operation of the span - * - * @deprecated Use `startSpan()` functions to set or `span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'op') - * to update the span instead. - */ - public set op(op: string | undefined) { - this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); - } - /* eslint-enable @typescript-eslint/member-ordering */ /** @inheritdoc */ @@ -493,8 +473,7 @@ export class SentrySpan implements SpanInterface { data: this._getData(), name: this._name, endTimestamp: this._endTime, - // eslint-disable-next-line deprecation/deprecation - op: this.op, + op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP], parentSpanId: this._parentSpanId, sampled: this._sampled, spanId: this._spanId, @@ -516,8 +495,7 @@ export class SentrySpan implements SpanInterface { this.data = spanContext.data || {}; this._name = spanContext.name; this._endTime = spanContext.endTimestamp; - // eslint-disable-next-line deprecation/deprecation - this.op = spanContext.op; + this._attributes = { ...this._attributes, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: spanContext.op }; this._parentSpanId = spanContext.parentSpanId; this._sampled = spanContext.sampled; this._spanId = spanContext.spanId || this._spanId; @@ -551,7 +529,7 @@ export class SentrySpan implements SpanInterface { return dropUndefinedKeys({ data: this._getData(), description: this._name, - op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] as string | undefined, + op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP], parent_span_id: this._parentSpanId, span_id: this._spanId, start_timestamp: this._startTime, diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 8b3f5e62288d..903554b08c9b 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -323,9 +323,7 @@ export class Transaction extends SentrySpan implements TransactionInterface { transaction.measurements = this._measurements; } - // eslint-disable-next-line deprecation/deprecation - DEBUG_BUILD && logger.log(`[Tracing] Finishing ${this.op} transaction: ${this._name}.`); - + DEBUG_BUILD && logger.log(`[Tracing] Finishing ${spanToJSON(this).op} transaction: ${this._name}.`); return transaction; } } diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 5ce2e3e01c6b..8c18bea9afe9 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -136,27 +136,6 @@ describe('startSpan', () => { expect(ref.parentSpanId).toEqual('1234567890123456'); }); - // TODO (v8): Remove this test in favour of the one below - it('(deprecated op) allows for transaction to be mutated', async () => { - let ref: any = undefined; - client.on('finishTransaction', transaction => { - ref = transaction; - }); - try { - await startSpan({ name: 'GET users/[id]' }, span => { - if (span) { - // eslint-disable-next-line deprecation/deprecation - span.op = 'http.server'; - } - return callback(); - }); - } catch (e) { - // - } - - expect(spanToJSON(ref).op).toEqual('http.server'); - }); - it('allows for transaction to be mutated', async () => { let ref: any = undefined; client.on('finishTransaction', transaction => { @@ -173,7 +152,7 @@ describe('startSpan', () => { // } - expect(ref.op).toEqual('http.server'); + expect(spanToJSON(ref).op).toEqual('http.server'); }); it('creates a span with correct description', async () => { @@ -197,30 +176,6 @@ describe('startSpan', () => { expect(ref.spanRecorder.spans[1].status).toEqual(isError ? 'internal_error' : undefined); }); - // TODO (v8): Remove this test in favour of the one below - it('(deprecated op) allows for span to be mutated', async () => { - let ref: any = undefined; - client.on('finishTransaction', transaction => { - ref = transaction; - }); - try { - await startSpan({ name: 'GET users/[id]', parentSampled: true }, () => { - return startSpan({ name: 'SELECT * from users' }, childSpan => { - if (childSpan) { - // eslint-disable-next-line deprecation/deprecation - childSpan.op = 'db.query'; - } - return callback(); - }); - }); - } catch (e) { - // - } - - expect(ref.spanRecorder.spans).toHaveLength(2); - expect(ref.spanRecorder.spans[1].op).toEqual('db.query'); - }); - it('allows for span to be mutated', async () => { let ref: any = undefined; client.on('finishTransaction', transaction => { diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 8e1cb695c05b..b66315afcb73 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -1,6 +1,7 @@ import type * as http from 'http'; /* eslint-disable @typescript-eslint/no-explicit-any */ import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, continueTrace, @@ -306,7 +307,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { if (sentryTransaction) { sentryTransaction.updateName(`trpc/${path}`); sentryTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - sentryTransaction.op = 'rpc.server'; + sentryTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'rpc.server'); const trpcContext: Record = { procedure_type: type, diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 6dbc6b9c8b62..63559d9102d7 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -90,8 +90,6 @@ describe('tracing', () => { // our span is at index 1 because the transaction itself is at index 0 expect(spanToJSON(spans[1]).description).toEqual('GET http://dogs.are.great/'); - // eslint-disable-next-line deprecation/deprecation - expect(spans[1].op).toEqual('http.client'); expect(spanToJSON(spans[1]).op).toEqual('http.client'); }); @@ -301,8 +299,6 @@ describe('tracing', () => { // our span is at index 1 because the transaction itself is at index 0 expect(spanToJSON(spans[1]).description).toEqual('GET http://dogs.are.great/spaniel'); - // eslint-disable-next-line deprecation/deprecation - expect(spans[1].op).toEqual('http.client'); expect(spanToJSON(spans[1]).op).toEqual('http.client'); const spanAttributes = spanToJSON(spans[1]).data || {}; @@ -328,8 +324,6 @@ describe('tracing', () => { // our span is at index 1 because the transaction itself is at index 0 expect(spanToJSON(spans[1]).description).toEqual('GET http://dogs.are.great/spaniel'); - // eslint-disable-next-line deprecation/deprecation - expect(spans[1].op).toEqual('http.client'); expect(spanToJSON(spans[1]).op).toEqual('http.client'); expect(spanAttributes['http.method']).toEqual('GET'); expect(spanAttributes.url).toEqual('http://dogs.are.great/spaniel'); @@ -413,8 +407,7 @@ describe('tracing', () => { const request = http.get(url); // There should be no http spans - // eslint-disable-next-line deprecation/deprecation - const httpSpans = spans.filter(span => span.op?.startsWith('http')); + const httpSpans = spans.filter(span => spanToJSON(span).op?.startsWith('http')); expect(httpSpans.length).toBe(0); // And headers are not attached without span creation @@ -523,8 +516,7 @@ describe('tracing', () => { const request = http.get(url); // There should be no http spans - // eslint-disable-next-line deprecation/deprecation - const httpSpans = spans.filter(span => span.op?.startsWith('http')); + const httpSpans = spans.filter(span => spanToJSON(span).op?.startsWith('http')); expect(httpSpans.length).toBe(0); // And headers are not attached without span creation diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 1f293b6f80d8..91da61c43118 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -486,15 +486,11 @@ describe('SentrySpanProcessor', () => { child.updateName('new name'); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe(undefined); expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); child.end(); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe(undefined); expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('new name'); @@ -514,8 +510,6 @@ describe('SentrySpanProcessor', () => { child.end(); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('http.client'); expect(spanToJSON(sentrySpan!).op).toBe('http.client'); parentOtelSpan.end(); @@ -534,8 +528,6 @@ describe('SentrySpanProcessor', () => { child.end(); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('http.server'); expect(spanToJSON(sentrySpan!).op).toBe('http.server'); parentOtelSpan.end(); @@ -721,9 +713,6 @@ describe('SentrySpanProcessor', () => { child.end(); const { description, op } = spanToJSON(sentrySpan!); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('db'); expect(op).toBe('db'); expect(description).toBe('SELECT * FROM users'); @@ -744,9 +733,6 @@ describe('SentrySpanProcessor', () => { child.end(); const { description, op } = spanToJSON(sentrySpan!); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('db'); expect(op).toBe('db'); expect(description).toBe('fetch users from DB'); @@ -767,8 +753,6 @@ describe('SentrySpanProcessor', () => { child.end(); const { op, description } = spanToJSON(sentrySpan!); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('rpc'); expect(op).toBe('rpc'); expect(description).toBe('test operation'); @@ -789,9 +773,6 @@ describe('SentrySpanProcessor', () => { child.end(); const { op, description } = spanToJSON(sentrySpan!); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('message'); expect(op).toBe('message'); expect(description).toBe('test operation'); @@ -812,9 +793,6 @@ describe('SentrySpanProcessor', () => { child.end(); const { op, description } = spanToJSON(sentrySpan!); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.op).toBe('test faas trigger'); expect(op).toBe('test faas trigger'); expect(description).toBe('test operation'); @@ -832,10 +810,7 @@ describe('SentrySpanProcessor', () => { parentOtelSpan.setAttribute(SemanticAttributes.FAAS_TRIGGER, 'test faas trigger'); parentOtelSpan.end(); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.op).toBe('test faas trigger'); expect(spanToJSON(transaction).op).toBe('test faas trigger'); - expect(spanToJSON(transaction).description).toBe('test operation'); }); }); diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index ad70af5aa666..82de3577a6da 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -433,12 +433,15 @@ function registerInteractionListener( // eslint-disable-next-line deprecation/deprecation const currentTransaction = getActiveTransaction(); - if (currentTransaction && currentTransaction.op && ['navigation', 'pageload'].includes(currentTransaction.op)) { - DEBUG_BUILD && - logger.warn( - `[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`, - ); - return undefined; + if (currentTransaction) { + const currentTransactionOp = spanToJSON(currentTransaction).op; + if (currentTransactionOp && ['navigation', 'pageload'].includes(currentTransactionOp)) { + DEBUG_BUILD && + logger.warn( + `[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`, + ); + return undefined; + } } if (inflightInteractionTransaction) { diff --git a/packages/tracing-internal/src/browser/browsertracing.ts b/packages/tracing-internal/src/browser/browsertracing.ts index 2aceb97bd6a1..300bc5895d16 100644 --- a/packages/tracing-internal/src/browser/browsertracing.ts +++ b/packages/tracing-internal/src/browser/browsertracing.ts @@ -1,5 +1,5 @@ -/* eslint-disable max-lines */ import type { Hub, IdleTransaction } from '@sentry/core'; +import { spanToJSON } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, TRACING_DEFAULTS, @@ -389,12 +389,15 @@ export class BrowserTracing implements Integration { // eslint-disable-next-line deprecation/deprecation const currentTransaction = getActiveTransaction(); - if (currentTransaction && currentTransaction.op && ['navigation', 'pageload'].includes(currentTransaction.op)) { - DEBUG_BUILD && - logger.warn( - `[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`, - ); - return undefined; + if (currentTransaction) { + const currentTransactionOp = spanToJSON(currentTransaction).op; + if (currentTransactionOp && ['navigation', 'pageload'].includes(currentTransactionOp)) { + DEBUG_BUILD && + logger.warn( + `[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`, + ); + return undefined; + } } if (inflightInteractionTransaction) { diff --git a/packages/tracing-internal/src/browser/metrics/index.ts b/packages/tracing-internal/src/browser/metrics/index.ts index 114ac74bd4e9..9bd3a19e770d 100644 --- a/packages/tracing-internal/src/browser/metrics/index.ts +++ b/packages/tracing-internal/src/browser/metrics/index.ts @@ -194,8 +194,7 @@ export function addPerformanceEntries(transaction: Transaction): void { const startTime = msToSec(entry.startTime); const duration = msToSec(entry.duration); - // eslint-disable-next-line deprecation/deprecation - if (transaction.op === 'navigation' && transactionStartTime && timeOrigin + startTime < transactionStartTime) { + if (op === 'navigation' && transactionStartTime && timeOrigin + startTime < transactionStartTime) { return; } diff --git a/packages/tracing-internal/src/browser/router.ts b/packages/tracing-internal/src/browser/router.ts index 895a37c60ff1..ff08af13a0cd 100644 --- a/packages/tracing-internal/src/browser/router.ts +++ b/packages/tracing-internal/src/browser/router.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; import type { Transaction, TransactionContext } from '@sentry/types'; import { addHistoryInstrumentationHandler, browserPerformanceTimeOrigin, logger } from '@sentry/utils'; @@ -53,7 +53,8 @@ export function instrumentRoutingWithDefaults( if (from !== to) { startingUrl = undefined; if (activeTransaction) { - DEBUG_BUILD && logger.log(`[Tracing] Finishing current transaction with op: ${activeTransaction.op}`); + DEBUG_BUILD && + logger.log(`[Tracing] Finishing current transaction with op: ${spanToJSON(activeTransaction).op}`); // If there's an open transaction on the scope, we need to finish it before creating an new one. activeTransaction.end(); } diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index f57c3c78f1ec..12e455d13e8e 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -176,7 +176,7 @@ describe('BrowserTracing', () => { const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); expect(spanToJSON(transaction).description).toBe('a/path'); - expect(transaction.op).toBe('pageload'); + expect(spanToJSON(transaction).op).toBe('pageload'); }); it('trims all transactions', () => { @@ -230,7 +230,7 @@ describe('BrowserTracing', () => { }); const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); - expect(transaction.op).toBe('not-pageload'); + expect(spanToJSON(transaction).op).toBe('not-pageload'); expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); }); @@ -373,7 +373,7 @@ describe('BrowserTracing', () => { const transaction = getActiveTransaction() as IdleTransaction; expect(transaction).toBeDefined(); - expect(transaction.op).toBe('pageload'); + expect(spanToJSON(transaction).op).toBe('pageload'); }); it('is not created if the option is false', () => { @@ -399,12 +399,12 @@ describe('BrowserTracing', () => { it('is created on location change', () => { createBrowserTracing(true); const transaction1 = getActiveTransaction() as IdleTransaction; - expect(transaction1.op).toBe('pageload'); + expect(spanToJSON(transaction1).op).toBe('pageload'); expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); const transaction2 = getActiveTransaction() as IdleTransaction; - expect(transaction2.op).toBe('navigation'); + expect(spanToJSON(transaction2).op).toBe('navigation'); expect(spanToJSON(transaction1).timestamp).toBeDefined(); }); @@ -412,12 +412,12 @@ describe('BrowserTracing', () => { it('is not created if startTransactionOnLocationChange is false', () => { createBrowserTracing(true, { startTransactionOnLocationChange: false }); const transaction1 = getActiveTransaction() as IdleTransaction; - expect(transaction1.op).toBe('pageload'); + expect(spanToJSON(transaction1).op).toBe('pageload'); expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); mockChangeHistory({ to: 'here', from: 'there' }); const transaction2 = getActiveTransaction() as IdleTransaction; - expect(transaction2.op).toBe('pageload'); + expect(spanToJSON(transaction2).op).toBe('pageload'); }); }); }); @@ -480,7 +480,7 @@ describe('BrowserTracing', () => { const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); - expect(transaction.op).toBe('pageload'); + expect(spanToJSON(transaction).op).toBe('pageload'); expect(transaction.traceId).toEqual('12312012123120121231201212312012'); expect(transaction.parentSpanId).toEqual('1121201211212012'); expect(transaction.sampled).toBe(false); @@ -500,7 +500,7 @@ describe('BrowserTracing', () => { const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); - expect(transaction.op).toBe('pageload'); + expect(spanToJSON(transaction).op).toBe('pageload'); expect(transaction.traceId).toEqual('12312012123120121231201212312012'); expect(transaction.parentSpanId).toEqual('1121201211212012'); expect(transaction.sampled).toBe(false); @@ -520,7 +520,7 @@ describe('BrowserTracing', () => { const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; expect(transaction).toBeDefined(); - expect(transaction.op).toBe('navigation'); + expect(spanToJSON(transaction).op).toBe('navigation'); expect(transaction.traceId).not.toEqual('12312012123120121231201212312012'); expect(transaction.parentSpanId).toBeUndefined(); expect(dynamicSamplingContext).toStrictEqual({ diff --git a/packages/tracing-internal/test/browser/metrics/utils.test.ts b/packages/tracing-internal/test/browser/metrics/utils.test.ts index a7de3e37acf8..9cbae997cd7e 100644 --- a/packages/tracing-internal/test/browser/metrics/utils.test.ts +++ b/packages/tracing-internal/test/browser/metrics/utils.test.ts @@ -12,8 +12,7 @@ describe('_startChild()', () => { expect(span).toBeInstanceOf(SentrySpan); expect(spanToJSON(span).description).toBe('evaluation'); - // eslint-disable-next-line deprecation/deprecation - expect(span.op).toBe('script'); + expect(spanToJSON(span).op).toBe('script'); expect(spanToJSON(span).op).toBe('script'); }); diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 03b286b23c12..483cc5cb3987 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -507,7 +507,7 @@ describe('SentrySpan', () => { expect(span.spanContext().spanId).toBe('d'); expect(span.sampled).toBe(true); expect(spanToJSON(span).description).toBe(undefined); - expect(span.op).toBe(undefined); + expect(spanToJSON(span).op).toBe(undefined); expect(span.tags).toStrictEqual({}); }); @@ -545,7 +545,7 @@ describe('SentrySpan', () => { expect(span.spanContext().spanId).toBe('b'); expect(spanToJSON(span).description).toBe('new'); expect(spanToJSON(span).timestamp).toBe(1); - expect(span.op).toBe('new-op'); + expect(spanToJSON(span).op).toBe('new-op'); expect(span.sampled).toBe(true); expect(span.tags).toStrictEqual({ tag1: 'bye' }); expect(span.data).toStrictEqual({ diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 2e6e0b084900..bb4a97dea860 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -174,15 +174,7 @@ export interface SpanContext { } /** Span holding trace_id, span_id */ -export interface Span extends Omit { - /** - * Operation of the Span. - * - * @deprecated Use `startSpan()` functions to set, `span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'op') - * to update and `spanToJSON().op` to read the op instead - */ - op?: string | undefined; - +export interface Span extends Omit { /** * The ID of the span. * @deprecated Use `spanContext().spanId` instead. diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index f129cfebb3de..11bcdf290a29 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -43,7 +43,7 @@ export type TraceparentData = Pick, Span { +export interface Transaction extends Omit, Span { /** * The ID of the transaction. * @deprecated Use `spanContext().spanId` instead. From c4acfe9e2fc134174f4c14bf41f6f51ba651536f Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 21 Feb 2024 12:36:24 -0500 Subject: [PATCH 131/173] feat(v8): Remove span.toTraceparent method (#10698) ref https://github.com/getsentry/sentry-javascript/issues/10677 --- .../debug-id-sourcemaps/package.json | 2 +- packages/core/src/tracing/sentrySpan.ts | 10 ---------- packages/tracing/test/span.test.ts | 11 +---------- packages/types/src/span.ts | 6 ------ 4 files changed, 2 insertions(+), 27 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json index ec6eeba0066c..3a656ff4f784 100644 --- a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json +++ b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json @@ -15,7 +15,7 @@ "devDependencies": { "rollup": "^4.0.2", "vitest": "^0.34.6", - "@sentry/rollup-plugin": "2.8.0" + "@sentry/rollup-plugin": "2.14.2" }, "pnpm": { "overrides": { diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 904136d3d04d..2f1fa54c5306 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -24,7 +24,6 @@ import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext, - spanToTraceHeader, } from '../utils/spanUtils'; import type { SpanStatusType } from './spanstatus'; import { setHttpStatus } from './spanstatus'; @@ -454,15 +453,6 @@ export class SentrySpan implements SpanInterface { this._endTime = spanTimeInputToSeconds(endTimestamp); } - /** - * @inheritDoc - * - * @deprecated Use `spanToTraceHeader()` instead. - */ - public toTraceparent(): string { - return spanToTraceHeader(this); - } - /** * @inheritDoc * diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 483cc5cb3987..3fc72e58e6d4 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -14,7 +14,7 @@ import { } from '@sentry/core'; import type { BaseTransportOptions, ClientOptions } from '@sentry/types'; -import { TRACEPARENT_REGEXP, Transaction } from '../src'; +import { Transaction } from '../src'; import { getDefaultBrowserClientOptions } from './testutils'; describe('SentrySpan', () => { @@ -125,15 +125,6 @@ describe('SentrySpan', () => { }); }); - describe('toTraceparent', () => { - test('simple', () => { - expect(new SentrySpan().toTraceparent()).toMatch(TRACEPARENT_REGEXP); - }); - test('with sample', () => { - expect(new SentrySpan({ sampled: true }).toTraceparent()).toMatch(TRACEPARENT_REGEXP); - }); - }); - describe('toJSON', () => { test('simple', () => { const span = JSON.parse( diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index bb4a97dea860..5cfb5abdad1d 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -330,12 +330,6 @@ export interface Span extends Omit>): Span; - /** - * Return a traceparent compatible header string. - * @deprecated Use `spanToTraceHeader()` instead. - */ - toTraceparent(): string; - /** * Returns the current span properties as a `SpanContext`. * @deprecated Use `toJSON()` or access the fields directly instead. From 674a8f086f6743661e2717db55a99ba2151eba83 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 21 Feb 2024 20:27:54 +0000 Subject: [PATCH 132/173] ref(remix): Make `@remix-run/router` a dependency. (#10479) Fixes: https://github.com/getsentry/sentry-javascript/issues/10349 Related: https://github.com/getsentry/sentry-javascript/issues/5860 Related: https://github.com/getsentry/sentry-javascript/pull/10458 Removes dynamic loading of `react-router-dom` and makes `@remix-run/router` a peer dependency. We don't need to dynamically load `react-router-dom` as our TypeScript version is now up-to-date. --- packages/remix/package.json | 1 + packages/remix/src/utils/instrumentServer.ts | 18 ++++------------- .../remix/src/utils/serverAdapters/express.ts | 18 +---------------- packages/remix/src/utils/vendor/response.ts | 20 ++++++++++--------- packages/remix/src/utils/vendor/types.ts | 6 +----- yarn.lock | 5 +++++ 6 files changed, 23 insertions(+), 45 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index 81711cead0f2..6e96b0067a2e 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -34,6 +34,7 @@ "access": "public" }, "dependencies": { + "@remix-run/router": "1.x", "@sentry/cli": "^2.28.6", "@sentry/core": "7.100.0", "@sentry/node-experimental": "7.100.0", diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index f7b9429dc7a8..1b9487ccb31a 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -47,7 +47,6 @@ import type { EntryContext, FutureConfig, HandleDocumentRequestFunction, - ReactRouterDomPkg, RemixRequest, RequestHandler, ServerBuild, @@ -384,10 +383,6 @@ export function createRoutes(manifest: ServerRouteManifest, parentId?: string): /** * Starts a new transaction for the given request to be used by different `RequestHandler` wrappers. - * - * @param request - * @param routes - * @param pkg */ export function startRequestHandlerTransaction( hub: Hub, @@ -435,19 +430,14 @@ export function startRequestHandlerTransaction( /** * Get transaction name from routes and url */ -export function getTransactionName( - routes: ServerRoute[], - url: URL, - pkg?: ReactRouterDomPkg, -): [string, TransactionSource] { - const matches = matchServerRoutes(routes, url.pathname, pkg); +export function getTransactionName(routes: ServerRoute[], url: URL): [string, TransactionSource] { + const matches = matchServerRoutes(routes, url.pathname); const match = matches && getRequestMatch(url, matches); - return match === null ? [url.pathname, 'url'] : [match.route.id, 'route']; + return match === null ? [url.pathname, 'url'] : [match.route.id || 'no-route-id', 'route']; } function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBuild): RequestHandler { const routes = createRoutes(build.routes); - const pkg = loadModule('react-router-dom'); return async function (this: unknown, request: RemixRequest, loadContext?: AppLoadContext): Promise { // This means that the request handler of the adapter (ex: express) is already wrapped. @@ -470,7 +460,7 @@ function wrapRequestHandler(origRequestHandler: RequestHandler, build: ServerBui } const url = new URL(request.url); - const [name, source] = getTransactionName(routes, url, pkg); + const [name, source] = getTransactionName(routes, url); isolationScope.setSDKProcessingMetadata({ request: { diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index 399ba531f23a..d1a9c4cf656b 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -2,7 +2,6 @@ import { getClient, getCurrentHub, hasTracingEnabled, setHttpStatus, withIsolati import { flush } from '@sentry/node-experimental'; import type { Transaction } from '@sentry/types'; import { extractRequestData, fill, isString, logger } from '@sentry/utils'; -import { cwd } from 'process'; import { DEBUG_BUILD } from '../debug-build'; import { createRoutes, getTransactionName, instrumentBuild, startRequestHandlerTransaction } from '../instrumentServer'; @@ -15,12 +14,9 @@ import type { ExpressRequestHandler, ExpressResponse, GetLoadContextFunction, - ReactRouterDomPkg, ServerBuild, } from '../vendor/types'; -let pkg: ReactRouterDomPkg; - function wrapExpressRequestHandler( origRequestHandler: ExpressRequestHandler, build: ServerBuild, @@ -33,18 +29,6 @@ function wrapExpressRequestHandler( res: ExpressResponse, next: ExpressNextFunction, ): Promise { - if (!pkg) { - try { - pkg = await import('react-router-dom'); - } catch (e) { - pkg = await import(`${cwd()}/node_modules/react-router-dom`); - } finally { - if (!pkg) { - DEBUG_BUILD && logger.error('Could not find `react-router-dom` package.'); - } - } - } - await withIsolationScope(async isolationScope => { // eslint-disable-next-line @typescript-eslint/unbound-method res.end = wrapEndMethod(res.end); @@ -62,7 +46,7 @@ function wrapExpressRequestHandler( const url = new URL(request.url); - const [name, source] = getTransactionName(routes, url, pkg); + const [name, source] = getTransactionName(routes, url); const transaction = startRequestHandlerTransaction(hub, name, source, { headers: { 'sentry-trace': (req.headers && isString(req.headers['sentry-trace']) && req.headers['sentry-trace']) || '', diff --git a/packages/remix/src/utils/vendor/response.ts b/packages/remix/src/utils/vendor/response.ts index 5def5cd28d99..8a8421ab06fd 100644 --- a/packages/remix/src/utils/vendor/response.ts +++ b/packages/remix/src/utils/vendor/response.ts @@ -6,7 +6,9 @@ // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import type { DeferredData, ErrorResponse, ReactRouterDomPkg, RouteMatch, ServerRoute } from './types'; +import { matchRoutes } from '@remix-run/router'; +import type { AgnosticRouteMatch, AgnosticRouteObject } from '@remix-run/router'; +import type { DeferredData, ErrorResponse, ServerRoute } from './types'; /** * Based on Remix Implementation @@ -76,13 +78,9 @@ export const json: JsonFunction = (data, init = {}) => { export function matchServerRoutes( routes: ServerRoute[], pathname: string, - pkg?: ReactRouterDomPkg, -): RouteMatch[] | null { - if (!pkg) { - return null; - } +): AgnosticRouteMatch[] | null { + const matches = matchRoutes(routes, pathname); - const matches = pkg.matchRoutes(routes, pathname); if (!matches) { return null; } @@ -91,6 +89,7 @@ export function matchServerRoutes( params: match.params, pathname: match.pathname, route: match.route, + pathnameBase: match.pathnameBase, })); } @@ -115,10 +114,13 @@ export function isIndexRequestUrl(url: URL): boolean { /** * https://github.com/remix-run/remix/blob/97999d02493e8114c39d48b76944069d58526e8d/packages/remix-server-runtime/server.ts#L588-L596 */ -export function getRequestMatch(url: URL, matches: RouteMatch[]): RouteMatch { +export function getRequestMatch( + url: URL, + matches: AgnosticRouteMatch[], +): AgnosticRouteMatch { const match = matches.slice(-1)[0]; - if (!isIndexRequestUrl(url) && match.route.id.endsWith('/index')) { + if (!isIndexRequestUrl(url) && match.route.id?.endsWith('/index')) { return matches.slice(-2)[0]; } diff --git a/packages/remix/src/utils/vendor/types.ts b/packages/remix/src/utils/vendor/types.ts index c3041dd4805e..e3a3fa42fbea 100644 --- a/packages/remix/src/utils/vendor/types.ts +++ b/packages/remix/src/utils/vendor/types.ts @@ -78,7 +78,7 @@ export type ExpressResponse = Express.Response; export type ExpressNextFunction = Express.NextFunction; export interface Route { - index?: boolean; + index: false | undefined; caseSensitive?: boolean; id: string; parentId?: string; @@ -210,10 +210,6 @@ export interface DataFunction { (args: DataFunctionArgs): Promise | Response | Promise | AppData; } -export interface ReactRouterDomPkg { - matchRoutes: (routes: ServerRoute[], pathname: string) => RouteMatch[] | null; -} - // Taken from Remix Implementation // https://github.com/remix-run/remix/blob/97999d02493e8114c39d48b76944069d58526e8d/packages/remix-server-runtime/routeMatching.ts#L6-L10 export interface RouteMatch { diff --git a/yarn.lock b/yarn.lock index 522fb4df4a61..4129f1dc7db5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5285,6 +5285,11 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.2.tgz#1c17eadb2fa77f80a796ad5ea9bf108e6993ef06" integrity sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ== +"@remix-run/router@1.x": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.0.tgz#461a952c2872dd82c8b2e9b74c4dfaff569123e2" + integrity sha512-HOil5aFtme37dVQTB6M34G95kPM3MMuqSmIRVCC52eKV+Y/tGSqw9P3rWhlAx6A+mz+MoX+XxsGsNJbaI5qCgQ== + "@remix-run/server-runtime@1.5.1": version "1.5.1" resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-1.5.1.tgz#5272b01e6dce109dc10bd68447ceae2d039315b2" From be90d821c60eef19d39ce61440c0e43cad266b47 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 22 Feb 2024 13:16:04 +0100 Subject: [PATCH 133/173] feat: Remove `@sentry/hub` package (#10783) Now that our craft configs can diverge (https://github.com/getsentry/publish/issues/3441) let's try this and delete the `@sentry/hub` package again. --- .craft.yml | 3 --- .../e2e-tests/verdaccio-config/config.yaml | 6 ----- package.json | 1 - packages/hub/LICENSE | 14 ---------- packages/hub/README.md | 21 --------------- packages/hub/package.json | 26 ------------------- 6 files changed, 71 deletions(-) delete mode 100644 packages/hub/LICENSE delete mode 100644 packages/hub/README.md delete mode 100644 packages/hub/package.json diff --git a/.craft.yml b/.craft.yml index 99efaf3d095e..f795d317b05e 100644 --- a/.craft.yml +++ b/.craft.yml @@ -132,9 +132,6 @@ targets: includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ ## 8. Deprecated packages we still release (but no packages depend on them anymore) - - name: npm - id: '@sentry/hub' - includeNames: /^sentry-hub-\d.*\.tgz$/ - name: npm id: '@sentry/tracing' includeNames: /^sentry-tracing-\d.*\.tgz$/ diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 4952397bc706..143596b74849 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -200,12 +200,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/hub': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry-internal/*': access: $all publish: $all diff --git a/package.json b/package.json index 9ecacd34c252..74fbfe2a650f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "packages/eslint-plugin-sdk", "packages/feedback", "packages/gatsby", - "packages/hub", "packages/integrations", "packages/integration-shims", "packages/nextjs", diff --git a/packages/hub/LICENSE b/packages/hub/LICENSE deleted file mode 100644 index 535ef0561e1b..000000000000 --- a/packages/hub/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. All rights reserved. - -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 the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/hub/README.md b/packages/hub/README.md deleted file mode 100644 index 91f796ac0b68..000000000000 --- a/packages/hub/README.md +++ /dev/null @@ -1,21 +0,0 @@ -

    - - Sentry - -

    - -# Sentry JavaScript SDK Hub - -[![npm version](https://img.shields.io/npm/v/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) -[![npm dm](https://img.shields.io/npm/dm/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) -[![npm dt](https://img.shields.io/npm/dt/@sentry/hub.svg)](https://www.npmjs.com/package/@sentry/hub) - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -## IMPORTANT - -This package only exists anymore to enable a seamless publishing process between v7 and v8 of the JS SDK packages. It -will be removed in a future version. diff --git a/packages/hub/package.json b/packages/hub/package.json deleted file mode 100644 index 982e739a680e..000000000000 --- a/packages/hub/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "@sentry/hub", - "version": "7.100.0", - "description": "Placeholder package for the former @sentry/hub package for publishing", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/hub", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "files": [], - "publishConfig": { - "access": "public" - }, - "scripts": { - "build": "mkdir -p build && touch build/dummy.js", - "build:transpile": "yarn build", - "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", - "clean": "rimraf build coverage sentry-hub-*.tgz" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": false -} From fdeaadf3fdf05546431db1bd61c3c7d13a262dd6 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 22 Feb 2024 13:21:13 +0100 Subject: [PATCH 134/173] ci: Upload playwright traces (#10786) --- .github/workflows/build.yml | 13 +++++++++++++ .../browser-integration-tests/playwright.config.ts | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f42cbff1743..a488a285816f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -715,6 +715,13 @@ jobs: 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) || '' }} + - name: Upload Playwright Traces + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-traces + path: dev-packages/browser-integration-tests/test-results + job_browser_loader_tests: name: Playwright Loader (${{ matrix.bundle }}) Tests @@ -772,6 +779,12 @@ jobs: run: | cd dev-packages/browser-integration-tests yarn test:loader + - name: Upload Playwright Traces + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-traces + path: dev-packages/browser-integration-tests/test-results job_browser_integration_tests: name: Browser (${{ matrix.browser }}) Tests diff --git a/dev-packages/browser-integration-tests/playwright.config.ts b/dev-packages/browser-integration-tests/playwright.config.ts index 91568658d316..78a71108837f 100644 --- a/dev-packages/browser-integration-tests/playwright.config.ts +++ b/dev-packages/browser-integration-tests/playwright.config.ts @@ -10,6 +10,10 @@ const config: PlaywrightTestConfig = { workers: process.env.CI ? 3 : undefined, testMatch: /test.ts/, + use: { + trace: process.env.CI ? 'retry-with-trace' : 'off', + }, + projects: [ { name: 'chromium', From 40300ea522a477602f42584890dc68ab23a4e5f0 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Thu, 22 Feb 2024 14:12:20 +0000 Subject: [PATCH 135/173] test(node): Add prisma OpenTelemetry integration tests. (#10778) --- .../node-integration-tests/package.json | 4 +- .../prisma-orm/docker-compose.yml | 13 ++ .../prisma-orm/package.json | 22 ++++ .../prisma/migrations/migration_lock.toml | 3 + .../migrations/sentry_test/migration.sql | 12 ++ .../prisma-orm/prisma/schema.prisma | 16 +++ .../prisma-orm/scenario.js | 52 ++++++++ .../tracing-experimental/prisma-orm/setup.ts | 18 +++ .../tracing-experimental/prisma-orm/test.ts | 112 ++++++++++++++++++ .../tracing-experimental/prisma-orm/yarn.lock | 51 ++++++++ 10 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/migration_lock.toml create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/sentry_test/migration.sql create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/scenario.js create mode 100755 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/yarn.lock diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 5480445e69bb..1594d8e837d0 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -16,9 +16,11 @@ "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-experimental/prisma-orm && ts-node ./setup.ts)", "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", "type-check": "tsc", + "pretest": "run-s --silent prisma:init", "test": "ts-node ./utils/run-tests.ts", "jest": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" @@ -28,7 +30,7 @@ "@nestjs/core": "^10.3.3", "@nestjs/common": "^10.3.3", "@nestjs/platform-express": "^10.3.3", - "@prisma/client": "3.15.2", + "@prisma/client": "5.9.1", "@sentry/node": "7.100.0", "@sentry/tracing": "7.100.0", "@sentry/types": "7.100.0", diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml new file mode 100644 index 000000000000..45caa4bb3179 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma + ports: + - '5433:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json new file mode 100644 index 000000000000..b9a5e7998269 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json @@ -0,0 +1,22 @@ +{ + "name": "sentry-prisma-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=16" + }, + "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.9.1", + "prisma": "^5.9.1" + } +} diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000000..fbffa92c2bb7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/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-experimental/prisma-orm/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/sentry_test/migration.sql new file mode 100644 index 000000000000..8619aaceb2b0 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/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-experimental/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma new file mode 100644 index 000000000000..52682f1b6cf5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma @@ -0,0 +1,16 @@ +datasource db { + url = "postgresql://prisma:prisma@localhost:5433/tests" + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["tracing"] +} + +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-experimental/prisma-orm/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/scenario.js new file mode 100644 index 000000000000..58b46ac1cf3a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/scenario.js @@ -0,0 +1,52 @@ +const { randomBytes } = require('crypto'); +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +const { PrismaClient } = require('@prisma/client'); +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, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +async function run() { + const client = new PrismaClient(); + + await Sentry.startSpan( + { + 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); + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts new file mode 100755 index 000000000000..a71bec82f893 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts @@ -0,0 +1,18 @@ +import { execSync } from 'child_process'; +import { parseSemver } from '@sentry/utils'; + +const NODE_VERSION = parseSemver(process.versions.node); + +// Prisma v5 requires Node.js v16+ +// https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#nodejs-minimum-version-change +if (NODE_VERSION.major && NODE_VERSION.major < 16) { + // eslint-disable-next-line no-console + console.warn(`Skipping Prisma tests on Node: ${NODE_VERSION.major}`); + process.exit(0); +} + +try { + execSync('yarn && yarn setup'); +} catch (_) { + process.exit(1); +} diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/test.ts new file mode 100644 index 000000000000..32bcdf168555 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/test.ts @@ -0,0 +1,112 @@ +import { conditionalTest } from '../../../utils'; +import { createRunner } from '../../../utils/runner'; + +conditionalTest({ min: 16 })('Prisma ORM Tests', () => { + test('CJS - should instrument PostgreSQL queries from Prisma ORM', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + method: 'create', + model: 'User', + name: 'User.create', + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:client:operation', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:client:serialize', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:client:connect', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.type': 'postgres', + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine:connection', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'db.statement': expect.stringContaining( + 'INSERT INTO "public"."User" ("createdAt","email","name") VALUES ($1,$2,$3) RETURNING "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" /* traceparent', + ), + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine:db_query', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine:serialize', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine:response_json_serialization', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + method: 'findMany', + model: 'User', + name: 'User.findMany', + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:client:operation', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:client:serialize', + status: 'ok', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'otel.kind': 'INTERNAL', + 'sentry.origin': 'manual', + }), + description: 'prisma:engine', + status: 'ok', + }), + ]), + }; + + createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/yarn.lock new file mode 100644 index 000000000000..9c0fc47be4be --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/yarn.lock @@ -0,0 +1,51 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@5.9.1": + version "5.9.1" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.9.1.tgz#d92bd2f7f006e0316cb4fda9d73f235965cf2c64" + integrity sha512-caSOnG4kxcSkhqC/2ShV7rEoWwd3XrftokxJqOCMVvia4NYV/TPtJlS9C2os3Igxw/Qyxumj9GBQzcStzECvtQ== + +"@prisma/debug@5.9.1": + version "5.9.1" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.9.1.tgz#906274e73d3267f88b69459199fa3c51cd9511a3" + integrity sha512-yAHFSFCg8KVoL0oRUno3m60GAjsUKYUDkQ+9BA2X2JfVR3kRVSJFc/GpQ2fSORi4pSHZR9orfM4UC9OVXIFFTA== + +"@prisma/engines-version@5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64": + version "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64.tgz#54d2164f28d23e09d41cf9eb0bddbbe7f3aaa660" + integrity sha512-HFl7275yF0FWbdcNvcSRbbu9JCBSLMcurYwvWc8WGDnpu7APxQo2ONtZrUggU3WxLxUJ2uBX+0GOFIcJeVeOOQ== + +"@prisma/engines@5.9.1": + version "5.9.1" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.9.1.tgz#767539afc6f193a182d0495b30b027f61f279073" + integrity sha512-gkdXmjxQ5jktxWNdDA5aZZ6R8rH74JkoKq6LD5mACSvxd2vbqWeWIOV0Py5wFC8vofOYShbt6XUeCIUmrOzOnQ== + dependencies: + "@prisma/debug" "5.9.1" + "@prisma/engines-version" "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64" + "@prisma/fetch-engine" "5.9.1" + "@prisma/get-platform" "5.9.1" + +"@prisma/fetch-engine@5.9.1": + version "5.9.1" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.9.1.tgz#5d3b2c9af54a242e37b3f9561b59ab72f8e92818" + integrity sha512-l0goQOMcNVOJs1kAcwqpKq3ylvkD9F04Ioe1oJoCqmz05mw22bNAKKGWuDd3zTUoUZr97va0c/UfLNru+PDmNA== + dependencies: + "@prisma/debug" "5.9.1" + "@prisma/engines-version" "5.9.0-32.23fdc5965b1e05fc54e5f26ed3de66776b93de64" + "@prisma/get-platform" "5.9.1" + +"@prisma/get-platform@5.9.1": + version "5.9.1" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.9.1.tgz#a66bb46ab4d30db786c84150ef074ab0aad4549e" + integrity sha512-6OQsNxTyhvG+T2Ksr8FPFpuPeL4r9u0JF0OZHUBI/Uy9SS43sPyAIutt4ZEAyqWQt104ERh70EZedkHZKsnNbg== + dependencies: + "@prisma/debug" "5.9.1" + +prisma@^5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.9.1.tgz#baa3dd635fbf71504980978f10f55ea11068f6aa" + integrity sha512-Hy/8KJZz0ELtkw4FnG9MS9rNWlXcJhf98Z2QMqi0QiVMoS8PzsBkpla0/Y5hTlob8F3HeECYphBjqmBxrluUrQ== + dependencies: + "@prisma/engines" "5.9.1" From 288d9e97a14cb4defc9fa972ad51e90d0bfd30a9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 22 Feb 2024 09:15:44 -0500 Subject: [PATCH 136/173] feat(v8): Remove deprecated `traceHeaders` method (#10776) --- packages/core/src/hub.ts | 8 -------- packages/core/src/tracing/hubextensions.ts | 18 ------------------ packages/node-experimental/src/sdk/hub.ts | 5 ----- .../opentelemetry/src/custom/getCurrentHub.ts | 5 ----- packages/types/src/hub.ts | 7 ------- 5 files changed, 43 deletions(-) diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 0fa6d239a26f..abfb22b612b3 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -478,14 +478,6 @@ Sentry.init({...}); return result; } - /** - * @inheritDoc - * @deprecated Use `spanToTraceHeader()` instead. - */ - public traceHeaders(): { [key: string]: string } { - return this._callExtensionMethod<{ [key: string]: string }>('traceHeaders'); - } - /** * @inheritDoc * diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts index 9d789d2d2620..6172d32b23a7 100644 --- a/packages/core/src/tracing/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -3,26 +3,11 @@ import { logger } from '@sentry/utils'; import { getMainCarrier } from '../asyncContext'; import { DEBUG_BUILD } from '../debug-build'; -import { spanToTraceHeader } from '../utils/spanUtils'; import { registerErrorInstrumentation } from './errors'; import { IdleTransaction } from './idletransaction'; import { sampleTransaction } from './sampling'; import { Transaction } from './transaction'; -/** Returns all trace headers that are currently on the top scope. */ -function traceHeaders(this: Hub): { [key: string]: string } { - // eslint-disable-next-line deprecation/deprecation - const scope = this.getScope(); - // eslint-disable-next-line deprecation/deprecation - const span = scope.getSpan(); - - return span - ? { - 'sentry-trace': spanToTraceHeader(span), - } - : {}; -} - /** * Creates a new transaction and adds a sampling decision if it doesn't yet have one. * @@ -142,9 +127,6 @@ export function addTracingExtensions(): void { if (!carrier.__SENTRY__.extensions.startTransaction) { carrier.__SENTRY__.extensions.startTransaction = _startTransaction; } - if (!carrier.__SENTRY__.extensions.traceHeaders) { - carrier.__SENTRY__.extensions.traceHeaders = traceHeaders; - } registerErrorInstrumentation(); } diff --git a/packages/node-experimental/src/sdk/hub.ts b/packages/node-experimental/src/sdk/hub.ts index 4de88d4788c7..42a7c8c7ea22 100644 --- a/packages/node-experimental/src/sdk/hub.ts +++ b/packages/node-experimental/src/sdk/hub.ts @@ -26,7 +26,6 @@ import { withScope, } from '@sentry/core'; import { getClient } from './api'; -import { callExtensionMethod } from './globals'; /** * This is for legacy reasons, and returns a proxy object instead of a hub to be used. @@ -77,10 +76,6 @@ export function getCurrentHub(): Hub { return getClient().getIntegration(integration); }, - traceHeaders(): { [key: string]: string } { - return callExtensionMethod<{ [key: string]: string }>(this, 'traceHeaders'); - }, - startTransaction( _context: TransactionContext, _customSamplingContext?: CustomSamplingContext, diff --git a/packages/opentelemetry/src/custom/getCurrentHub.ts b/packages/opentelemetry/src/custom/getCurrentHub.ts index 5df6e1e1d36f..36699561aa99 100644 --- a/packages/opentelemetry/src/custom/getCurrentHub.ts +++ b/packages/opentelemetry/src/custom/getCurrentHub.ts @@ -76,11 +76,6 @@ export function getCurrentHub(): Hub { return getClient()?.getIntegration(integration) || null; }, - traceHeaders(): { [key: string]: string } { - // TODO: Do we need this?? - return {}; - }, - startTransaction( _context: TransactionContext, _customSamplingContext?: CustomSamplingContext, diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index 7eec8d42b91a..e0864d461c72 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -204,13 +204,6 @@ export interface Hub { */ getIntegration(integration: IntegrationClass): T | null; - /** - * Returns all trace headers that are currently on the top scope. - * - * @deprecated Use `spanToTraceHeader()` instead. - */ - traceHeaders(): { [key: string]: string }; - /** * Starts a new `Transaction` and returns it. This is the entry point to manual tracing instrumentation. * From 32e4effed9aa34a8f55f43a4c10098154233fb49 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 22 Feb 2024 09:15:59 -0500 Subject: [PATCH 137/173] feat(v8): Remove deprecated runWithAsyncContext API (#10780) --- packages/bun/src/index.ts | 2 -- packages/core/src/currentScopes.ts | 13 ------------- packages/core/src/index.ts | 2 -- packages/deno/src/index.ts | 2 -- packages/node/src/index.ts | 2 -- packages/opentelemetry/src/asyncContextStrategy.ts | 2 +- packages/remix/src/index.server.ts | 2 -- packages/serverless/src/index.ts | 2 -- packages/sveltekit/src/server/index.ts | 2 -- packages/vercel-edge/src/index.ts | 2 -- 10 files changed, 1 insertion(+), 30 deletions(-) diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 215bc5db9573..9ff54c0e7c78 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -46,8 +46,6 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, diff --git a/packages/core/src/currentScopes.ts b/packages/core/src/currentScopes.ts index a58aaf836be5..07e26d9a96b6 100644 --- a/packages/core/src/currentScopes.ts +++ b/packages/core/src/currentScopes.ts @@ -103,19 +103,6 @@ export function withIsolationScope(callback: (isolationScope: Scope) => T): T return acs.withIsolationScope(callback); } -/** - * Runs the supplied callback in its own async context. Async Context strategies are defined per SDK. - * - * @param callback The callback to run in its own async context - * @param options Options to pass to the async context strategy - * @returns The result of the callback - * - * @deprecated Use `Sentry.withScope()` instead. - */ -export function runWithAsyncContext(callback: () => T): T { - return withScope(() => callback()); -} - /** * Get the currently active client. */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 56d86d3426c9..198f25bcb74b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -49,8 +49,6 @@ export { setGlobalScope, withScope, withIsolationScope, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, getClient, } from './currentScopes'; export { diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index d554c7aa1e69..3aec144b58c6 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -45,8 +45,6 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 7cb48b4651da..ac17a5d0b5ed 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -45,8 +45,6 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index 397bd55f2685..95c4abaa3138 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -55,7 +55,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void { // We set the `SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY` context value, which is picked up by // the OTEL context manager, which uses the presence of this key to determine if it should // fork the isolation scope, or not - // as by default, we don't want to fork this, unless triggered explicitly by `runWithAsyncContext` + // as by default, we don't want to fork this, unless triggered explicitly by `withScope` return api.context.with(ctx, () => { return callback(getCurrentScope()); }); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index d943c27b1611..86c74cc5c052 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -83,8 +83,6 @@ export { metrics, createGetModuleFromFilename, hapiErrorPlugin, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 7fcc2ede3470..ed2debfa9508 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -77,8 +77,6 @@ export { functionToStringIntegration, createGetModuleFromFilename, metrics, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, consoleIntegration, onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index c0079e34fe22..e77b6fecec34 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -77,8 +77,6 @@ export { createGetModuleFromFilename, hapiErrorPlugin, metrics, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 4dbca6124461..f387bef70369 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -45,8 +45,6 @@ export { // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - // eslint-disable-next-line deprecation/deprecation - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, From 1782d8015b01ed1d402a25ba66331466ac5c2c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Thu, 22 Feb 2024 15:28:43 +0100 Subject: [PATCH 138/173] fix(vercel-edge): Make breadcrumbs option optional in WinterCGFetch integration (#10785) Right now it's not possible to use it as: ```js new Sentry.Integrations.WinterCGFetch({ shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`); } }) ``` due to `breadcrubs` not being marked as optional. Instead of changing it to such, reuse already existing `Options` interface to keep it in sync with the integration itself. --- packages/vercel-edge/src/integrations/wintercg-fetch.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/vercel-edge/src/integrations/wintercg-fetch.ts b/packages/vercel-edge/src/integrations/wintercg-fetch.ts index 436e7f6f9b2d..2a6ffa5f749b 100644 --- a/packages/vercel-edge/src/integrations/wintercg-fetch.ts +++ b/packages/vercel-edge/src/integrations/wintercg-fetch.ts @@ -129,10 +129,7 @@ export const WinterCGFetch = convertIntegrationFnToClass( INTEGRATION_NAME, winterCGFetchIntegration, ) as IntegrationClass void }> & { - new (options?: { - breadcrumbs: boolean; - shouldCreateSpanForRequest?: (url: string) => boolean; - }): Integration; + new (options?: Partial): Integration; }; // eslint-disable-next-line deprecation/deprecation From 722183b718c4ec485a83701d3a276423e3e45e87 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 22 Feb 2024 15:29:41 +0100 Subject: [PATCH 139/173] ref(eslint): Remove unused eslint-disable directives (#10771) Remove some unused directives to for less eslint warnings. Related to https://github.com/getsentry/sentry-javascript/pull/10610 --- dev-packages/e2e-tests/prepare.ts | 1 - dev-packages/e2e-tests/publish-packages.ts | 1 - dev-packages/e2e-tests/run.ts | 1 - packages/browser/test/unit/index.test.ts | 8 +++----- packages/bun/src/index.ts | 1 - packages/core/src/baseclient.ts | 4 ++-- packages/core/src/integrations/inboundfilters.ts | 2 -- packages/core/src/tracing/hubextensions.ts | 1 - packages/core/src/tracing/idletransaction.ts | 1 - packages/core/src/tracing/sampling.ts | 1 - packages/core/src/tracing/transaction.ts | 1 - packages/core/test/lib/api.test.ts | 1 - packages/core/test/lib/session.test.ts | 2 -- packages/core/test/lib/sessionflusher.test.ts | 2 -- packages/core/test/lib/tracing/span.test.ts | 1 - packages/core/test/mocks/client.ts | 3 --- packages/feedback/test/utils/TestClient.ts | 1 - packages/node/src/integrations/http.ts | 1 - packages/profiling-node/src/cpu_profiler.ts | 1 - packages/profiling-node/src/integration.ts | 1 - packages/profiling-node/src/utils.ts | 1 - packages/replay/src/util/getReplay.ts | 1 - .../src/browser/browserTracingIntegration.ts | 1 - packages/tracing-internal/src/browser/browsertracing.ts | 1 - packages/tracing-internal/src/node/integrations/mongo.ts | 1 - packages/types/src/client.ts | 1 - packages/types/src/startSpanOptions.ts | 1 - packages/utils/src/browser.ts | 1 - packages/utils/src/instrument/fetch.ts | 1 - packages/utils/src/is.ts | 1 - packages/utils/src/node-stack-trace.ts | 1 - packages/utils/src/node.ts | 2 +- packages/utils/src/object.ts | 1 - packages/utils/src/syncpromise.ts | 3 --- packages/utils/test/normalize.test.ts | 2 -- packages/utils/test/syncpromise.test.ts | 1 - 36 files changed, 6 insertions(+), 49 deletions(-) diff --git a/dev-packages/e2e-tests/prepare.ts b/dev-packages/e2e-tests/prepare.ts index 9d9c233f580d..3a230723d9a7 100644 --- a/dev-packages/e2e-tests/prepare.ts +++ b/dev-packages/e2e-tests/prepare.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ /* eslint-disable no-console */ import * as dotenv from 'dotenv'; diff --git a/dev-packages/e2e-tests/publish-packages.ts b/dev-packages/e2e-tests/publish-packages.ts index b3978d18002a..2be19b173bd3 100644 --- a/dev-packages/e2e-tests/publish-packages.ts +++ b/dev-packages/e2e-tests/publish-packages.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import * as childProcess from 'child_process'; import * as path from 'path'; import * as glob from 'glob'; diff --git a/dev-packages/e2e-tests/run.ts b/dev-packages/e2e-tests/run.ts index a5002d7bcff9..4ea385c20472 100644 --- a/dev-packages/e2e-tests/run.ts +++ b/dev-packages/e2e-tests/run.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ /* eslint-disable no-console */ import { spawn } from 'child_process'; import { resolve } from 'path'; diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index 6272adc653b8..a7e4cf6afd2a 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -91,7 +91,6 @@ describe('SentryBrowser', () => { getCurrentScope().setUser(EX_USER); setCurrentClient(client); - // eslint-disable-next-line deprecation/deprecation showReportDialog({ eventId: 'foobar' }); expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1); @@ -106,7 +105,6 @@ describe('SentryBrowser', () => { setCurrentClient(client); const DIALOG_OPTION_USER = { email: 'option@example.com' }; - // eslint-disable-next-line deprecation/deprecation showReportDialog({ eventId: 'foobar', user: DIALOG_OPTION_USER }); expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1); @@ -140,7 +138,7 @@ describe('SentryBrowser', () => { it('should call `onClose` when receiving `__sentry_reportdialog_closed__` MessageEvent', async () => { const onClose = jest.fn(); - // eslint-disable-next-line deprecation/deprecation + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('__sentry_reportdialog_closed__'); @@ -155,7 +153,7 @@ describe('SentryBrowser', () => { const onClose = jest.fn(() => { throw new Error(); }); - // eslint-disable-next-line deprecation/deprecation + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('__sentry_reportdialog_closed__'); @@ -168,7 +166,7 @@ describe('SentryBrowser', () => { it('should not call `onClose` for other MessageEvents', async () => { const onClose = jest.fn(); - // eslint-disable-next-line deprecation/deprecation + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('some_message'); diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 9ff54c0e7c78..0f4f431401bc 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -121,7 +121,6 @@ export { bunServerIntegration } from './integrations/bunserver'; const INTEGRATIONS = { // eslint-disable-next-line deprecation/deprecation ...CoreIntegrations, - // eslint-disable-next-line deprecation/deprecation ...NodeIntegrations, BunServer, }; diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 583e329b4beb..87704f111977 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -150,7 +150,7 @@ export abstract class BaseClient implements Client { /** * @inheritDoc */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + // eslint-disable-next-line @typescript-eslint/no-explicit-any public captureException(exception: any, hint?: EventHint, scope?: Scope): string | undefined { // ensure we haven't captured this very object before if (checkOrSetAlreadyCaught(exception)) { @@ -857,7 +857,7 @@ export abstract class BaseClient implements Client { /** * @inheritDoc */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + // eslint-disable-next-line @typescript-eslint/no-explicit-any public abstract eventFromException(_exception: any, _hint?: EventHint): PromiseLike; /** diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index dff1130cb708..60b4b885a718 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -173,7 +173,6 @@ function _getPossibleEventMessages(event: Event): string[] { let lastException; try { // @ts-expect-error Try catching to save bundle size - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access lastException = event.exception.values[event.exception.values.length - 1]; } catch (e) { // try catching to save bundle size checking existence of variables @@ -198,7 +197,6 @@ function _getPossibleEventMessages(event: Event): string[] { function _isSentryError(event: Event): boolean { try { // @ts-expect-error can't be a sentry error if undefined - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return event.exception.values[0].type === 'SentryError'; } catch (e) { // ignore diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts index 6172d32b23a7..f705f676e660 100644 --- a/packages/core/src/tracing/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -42,7 +42,6 @@ function _startTransaction( The transaction will not be sampled. Please use the ${configInstrumenter} instrumentation to start transactions.`, ); - // eslint-disable-next-line deprecation/deprecation transactionContext.sampled = false; } diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index 7fae765c60df..8d0db5d92762 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import type { Hub, SpanTimeInput, TransactionContext } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 6bae27ffe1a1..1670d38b740d 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -107,7 +107,6 @@ export function sampleTransaction( */ function isValidSampleRate(rate: unknown): boolean { // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck - // eslint-disable-next-line @typescript-eslint/no-explicit-any if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) { DEBUG_BUILD && logger.warn( diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 903554b08c9b..d3d0c19718a0 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -89,7 +89,6 @@ export class Transaction extends SentrySpan implements TransactionInterface { } // This sadly conflicts with the getter/setter ordering :( - /* eslint-disable @typescript-eslint/member-ordering */ /** * Get the metadata for this transaction. diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index 61c7820c29de..e370d7026516 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable deprecation/deprecation */ import type { ClientOptions, DsnComponents } from '@sentry/types'; import { makeDsn } from '@sentry/utils'; diff --git a/packages/core/test/lib/session.test.ts b/packages/core/test/lib/session.test.ts index fcded43eb740..41eb0c234d13 100644 --- a/packages/core/test/lib/session.test.ts +++ b/packages/core/test/lib/session.test.ts @@ -1,5 +1,3 @@ -/* eslint-disable deprecation/deprecation */ - import type { SessionContext } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; diff --git a/packages/core/test/lib/sessionflusher.test.ts b/packages/core/test/lib/sessionflusher.test.ts index f2c019839bcd..808ba6308069 100644 --- a/packages/core/test/lib/sessionflusher.test.ts +++ b/packages/core/test/lib/sessionflusher.test.ts @@ -1,5 +1,3 @@ -/* eslint-disable deprecation/deprecation */ - import type { Client } from '@sentry/types'; import { SessionFlusher } from '../../src/sessionflusher'; diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 2209b9b8285a..94bd1d29eabe 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -4,7 +4,6 @@ import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON } from '../../../src/ut describe('span', () => { describe('name', () => { - /* eslint-disable deprecation/deprecation */ it('works with name', () => { const span = new SentrySpan({ name: 'span name' }); expect(spanToJSON(span).description).toEqual('span name'); diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 59a792cf4723..473028ea4b12 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -49,13 +49,11 @@ export class TestClient extends BaseClient { TestClient.instance = this; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types public eventFromException(exception: any): PromiseLike { const event: Event = { exception: { values: [ { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ type: exception.name, value: exception.message, /* eslint-enable @typescript-eslint/no-unsafe-member-access */ @@ -86,7 +84,6 @@ export class TestClient extends BaseClient { super.sendEvent(event, hint); return; } - // eslint-disable-next-line @typescript-eslint/no-unused-expressions TestClient.sendEventCalled && TestClient.sendEventCalled(event); } diff --git a/packages/feedback/test/utils/TestClient.ts b/packages/feedback/test/utils/TestClient.ts index ad39b82084a9..61156a3be8b0 100644 --- a/packages/feedback/test/utils/TestClient.ts +++ b/packages/feedback/test/utils/TestClient.ts @@ -14,7 +14,6 @@ export class TestClient extends BaseClient { exception: { values: [ { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ type: exception.name, value: exception.message, /* eslint-enable @typescript-eslint/no-unsafe-member-access */ diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 8e9df91f6dc5..b8010b083d5b 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -314,7 +314,6 @@ function _createWrappedRequestMethodFactory( return function wrappedMethod(this: unknown, ...args: RequestMethodArgs): http.ClientRequest { const requestArgs = normalizeRequestArgs(httpModule, args); const requestOptions = requestArgs[0]; - // eslint-disable-next-line deprecation/deprecation const rawRequestUrl = extractRawUrl(requestOptions); const requestUrl = extractUrl(requestOptions); const client = getClient(); diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts index e4ee11fee6a4..7bd23c4eca4b 100644 --- a/packages/profiling-node/src/cpu_profiler.ts +++ b/packages/profiling-node/src/cpu_profiler.ts @@ -34,7 +34,6 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { return require(`${binaryPath}.node`); } - /* eslint-disable no-fallthrough */ // We need the fallthrough so that in the end, we can fallback to the require dynamice require. // This is for cases where precompiled binaries were not provided, but may have been compiled from source. if (platform === 'darwin') { diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index 3e9cfd99b844..202578f49d88 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -140,7 +140,6 @@ export class ProfilingIntegration implements Integration { // Remove the profile from the transaction context before sending, relay will take care of the rest. if (profileContext) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete profiledTransaction.contexts?.['profile']; } diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index 8885c1c12f9e..97e17eab8888 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -26,7 +26,6 @@ import type { DebugImage } from './types'; // We require the file because if we import it, it will be included in the bundle. // I guess tsc does not check file contents when it's imported. -// eslint-disable-next-line const THREAD_ID_STRING = String(threadId); const THREAD_NAME = isMainThread ? 'main' : 'worker'; const FORMAT_VERSION = '1'; diff --git a/packages/replay/src/util/getReplay.ts b/packages/replay/src/util/getReplay.ts index 75d794da8e69..0d09def81585 100644 --- a/packages/replay/src/util/getReplay.ts +++ b/packages/replay/src/util/getReplay.ts @@ -4,7 +4,6 @@ import type { replayIntegration } from '../integration'; /** * This is a small utility to get a type-safe instance of the Replay integration. */ -// eslint-disable-next-line deprecation/deprecation export function getReplay(): ReturnType | undefined { const client = getClient(); return client && client.getIntegrationByName>('Replay'); diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index 82de3577a6da..02ef63d13aa7 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ import type { IdleTransaction } from '@sentry/core'; import { getActiveSpan } from '@sentry/core'; import { getCurrentHub } from '@sentry/core'; diff --git a/packages/tracing-internal/src/browser/browsertracing.ts b/packages/tracing-internal/src/browser/browsertracing.ts index 300bc5895d16..e499fd9ff11b 100644 --- a/packages/tracing-internal/src/browser/browsertracing.ts +++ b/packages/tracing-internal/src/browser/browsertracing.ts @@ -340,7 +340,6 @@ export class BrowserTracing implements Integration { this._latestRouteSource = finalContext.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]; } - // eslint-disable-next-line deprecation/deprecation if (finalContext.sampled === false) { DEBUG_BUILD && logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`); } diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index cd5779eca5c0..75e03649dba2 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -174,7 +174,6 @@ export class Mongo implements LazyLoadedIntegration { fill(collection.prototype, operation, function (orig: () => void | Promise) { return function (this: unknown, ...args: unknown[]) { const lastArg = args[args.length - 1]; - // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation const scope = hub.getScope(); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 62956f0c00d0..198f792d6235 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -159,7 +159,6 @@ export interface Client { init(): void; /** Creates an {@link Event} from all inputs to `captureException` and non-primitive inputs to `captureMessage`. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any eventFromException(exception: any, hint?: EventHint): PromiseLike; /** Creates an {@link Event} from primitive inputs to `captureMessage`. */ diff --git a/packages/types/src/startSpanOptions.ts b/packages/types/src/startSpanOptions.ts index 31d57c39f50d..d65cb424b4f7 100644 --- a/packages/types/src/startSpanOptions.ts +++ b/packages/types/src/startSpanOptions.ts @@ -85,7 +85,6 @@ export interface StartSpanOptions extends TransactionContext { /** * @deprecated Use attributes instead. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: { [key: string]: any }; /** diff --git a/packages/utils/src/browser.ts b/packages/utils/src/browser.ts index 53549a878adf..91a62eaafced 100644 --- a/packages/utils/src/browser.ts +++ b/packages/utils/src/browser.ts @@ -111,7 +111,6 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { out.push(`#${elem.id}`); } - // eslint-disable-next-line prefer-const className = elem.className; if (className && isString(className)) { classes = className.split(/\s+/); diff --git a/packages/utils/src/instrument/fetch.ts b/packages/utils/src/instrument/fetch.ts index a02dc5db42ab..472f48283a47 100644 --- a/packages/utils/src/instrument/fetch.ts +++ b/packages/utils/src/instrument/fetch.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/ban-types */ import type { HandlerDataFetch } from '@sentry/types'; import { fill } from '../object'; diff --git a/packages/utils/src/is.ts b/packages/utils/src/is.ts index 1e84e1873006..fea0402053cc 100644 --- a/packages/utils/src/is.ts +++ b/packages/utils/src/is.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import type { ParameterizedString, PolymorphicEvent, Primitive } from '@sentry/types'; diff --git a/packages/utils/src/node-stack-trace.ts b/packages/utils/src/node-stack-trace.ts index be858433d43a..6bb1877c870d 100644 --- a/packages/utils/src/node-stack-trace.ts +++ b/packages/utils/src/node-stack-trace.ts @@ -50,7 +50,6 @@ export function filenameIsInApp(filename: string, isNative: boolean = false): bo } /** Node Stack line parser */ -// eslint-disable-next-line complexity export function node(getModule?: GetModuleFn): StackLineParserFn { const FILENAME_MATCH = /^\s*[-]{4,}$/; const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/; diff --git a/packages/utils/src/node.ts b/packages/utils/src/node.ts index 33bece5ef62f..39b4258eccc7 100644 --- a/packages/utils/src/node.ts +++ b/packages/utils/src/node.ts @@ -24,7 +24,7 @@ export function isNodeEnv(): boolean { * * @param request The module path to resolve */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function dynamicRequire(mod: any, request: string): any { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return mod.require(request); diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index a5300bf586fd..9ae3e00e05ce 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { WrappedFunction } from '@sentry/types'; diff --git a/packages/utils/src/syncpromise.ts b/packages/utils/src/syncpromise.ts index e4ebda979300..015b76b39086 100644 --- a/packages/utils/src/syncpromise.ts +++ b/packages/utils/src/syncpromise.ts @@ -1,6 +1,4 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/typedef */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { isThenable } from './is'; @@ -182,7 +180,6 @@ class SyncPromise implements PromiseLike { } if (this._state === States.RESOLVED) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises handler[1](this._value as unknown as any); } diff --git a/packages/utils/test/normalize.test.ts b/packages/utils/test/normalize.test.ts index b01c887abedf..c0bbe0298ba2 100644 --- a/packages/utils/test/normalize.test.ts +++ b/packages/utils/test/normalize.test.ts @@ -190,7 +190,6 @@ describe('normalize()', () => { }); test('circular arrays', () => { - // eslint-disable-next-line @typescript-eslint/ban-types const obj: object[] = []; obj.push(obj); obj.push(obj); @@ -198,7 +197,6 @@ describe('normalize()', () => { }); test('circular arrays with intermediaries', () => { - // eslint-disable-next-line @typescript-eslint/ban-types const obj: object[] = []; obj.push({ name: 'Alice', self: obj }); obj.push({ name: 'Bob', self: obj }); diff --git a/packages/utils/test/syncpromise.test.ts b/packages/utils/test/syncpromise.test.ts index 69e57b3ac533..53dff943fa48 100644 --- a/packages/utils/test/syncpromise.test.ts +++ b/packages/utils/test/syncpromise.test.ts @@ -138,7 +138,6 @@ describe('SyncPromise', () => { let foo: number = 1; - // eslint-disable-next-line @typescript-eslint/no-floating-promises new SyncPromise(_ => { foo = 2; }); From dddac2c27edbd3203fa1bfb007bd62f0075f1916 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 22 Feb 2024 11:27:27 -0400 Subject: [PATCH 140/173] fix(node): import `worker_threads` and fix node v14 types (#10791) Since v8 increases our minimum supported node version, we no longer need to conditionally `dynamicRequire` the `worker_threads` module. --- .../src/integrations/anr/index.ts | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/node-experimental/src/integrations/anr/index.ts b/packages/node-experimental/src/integrations/anr/index.ts index 30ff52011d31..2670f30db558 100644 --- a/packages/node-experimental/src/integrations/anr/index.ts +++ b/packages/node-experimental/src/integrations/anr/index.ts @@ -1,8 +1,8 @@ import { URL } from 'url'; import { defineIntegration, getCurrentScope } from '@sentry/core'; import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; -import { dynamicRequire, logger } from '@sentry/utils'; -import type { Worker, WorkerOptions } from 'worker_threads'; +import { logger } from '@sentry/utils'; +import { Worker } from 'worker_threads'; import { NODE_MAJOR, NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; import type { AnrIntegrationOptions, WorkerStartData } from './common'; @@ -11,24 +11,10 @@ import { base64WorkerScript } from './worker-script'; const DEFAULT_INTERVAL = 50; const DEFAULT_HANG_THRESHOLD = 5000; -type WorkerNodeV14 = Worker & { new (filename: string | URL, options?: WorkerOptions): Worker }; - -type WorkerThreads = { - Worker: WorkerNodeV14; -}; - function log(message: string, ...args: unknown[]): void { logger.log(`[ANR] ${message}`, ...args); } -/** - * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when - * targeting those versions - */ -function getWorkerThreads(): WorkerThreads { - return dynamicRequire(module, 'worker_threads'); -} - /** * Gets contexts by calling all event processors. This relies on being called after all integrations are setup */ @@ -112,8 +98,6 @@ async function _startWorker(client: NodeClient, _options: Partial Date: Thu, 22 Feb 2024 11:38:40 -0400 Subject: [PATCH 141/173] feat(metrics): Remove metrics method from `BaseClient` (#10789) This PR removes the remaining metrics code from the `BaseClient` --- docs/event-sending.md | 8 +-- packages/browser/src/client.ts | 8 +-- packages/core/src/baseclient.ts | 55 ++++++------------- packages/core/src/metrics/aggregator.ts | 7 ++- .../core/src/metrics/browser-aggregator.ts | 8 ++- packages/core/src/metrics/envelope.ts | 31 ++++++++++- packages/core/src/server-runtime-client.ts | 4 +- .../core/test/lib/metrics/aggregator.test.ts | 29 ++-------- .../core/test/lib/serverruntimeclient.test.ts | 6 +- .../node-experimental/test/sdk/client.test.ts | 9 +-- packages/node/test/client.test.ts | 9 +-- packages/types/src/client.ts | 8 --- 12 files changed, 79 insertions(+), 103 deletions(-) diff --git a/docs/event-sending.md b/docs/event-sending.md index 894da827508b..b72eda1d0746 100644 --- a/docs/event-sending.md +++ b/docs/event-sending.md @@ -25,7 +25,7 @@ This document gives an outline for how event sending works, and which which plac - `createEnvelope()` - `addItemToEnvelope()` - `createAttachmentEnvelopeItem()` - - `baseclient._sendEnvelope()` + - `baseclient.sendEnvelope()` - `transport.send()` ## Transactions @@ -54,7 +54,7 @@ This document gives an outline for how event sending works, and which which plac - `createEnvelope()` - `addItemToEnvelope()` - `createAttachmentEnvelopeItem()` - - `baseclient._sendEnvelope()` + - `baseclient.sendEnvelope()` - `transport.send()` ## Sessions @@ -70,7 +70,7 @@ This document gives an outline for how event sending works, and which which plac - `createSessionEnvelope()` - `getSdkMetadataForEnvelopeHeader()` - `createEnvelope()` - - `baseclient._sendEnvelope()` + - `baseclient.sendEnvelope()` - `transport.send()` - `updateSession()` @@ -94,5 +94,5 @@ This document gives an outline for how event sending works, and which which plac - `browser.client._flushOutcomes()` - `getEnvelopeEndpointWithUrlEncodedAuth()` - `createClientReportEnvelope()` - - `baseclient._sendEnvelope()` + - `baseclient.sendEnvelope()` - `transport.send()` diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index 97ea8e3b5e0c..869d28c16c50 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -96,9 +96,9 @@ export class BrowserClient extends BaseClient { tunnel: this.getOptions().tunnel, }); - // _sendEnvelope should not throw + // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._sendEnvelope(envelope); + this.sendEnvelope(envelope); } /** @@ -130,8 +130,8 @@ export class BrowserClient extends BaseClient { const envelope = createClientReportEnvelope(outcomes, this._options.tunnel && dsnToString(this._dsn)); - // _sendEnvelope should not throw + // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._sendEnvelope(envelope); + this.sendEnvelope(envelope); } } diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 87704f111977..21341bef1e42 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -16,7 +16,6 @@ import type { FeedbackEvent, Integration, IntegrationClass, - MetricBucketItem, Outcome, ParameterizedString, SdkMetadata, @@ -52,7 +51,6 @@ import { createEventEnvelope, createSessionEnvelope } from './envelope'; import type { IntegrationIndex } from './integration'; import { afterSetupIntegrations } from './integration'; import { setupIntegration, setupIntegrations } from './integration'; -import { createMetricEnvelope } from './metrics/envelope'; import type { Scope } from './scope'; import { updateSession } from './session'; import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; @@ -377,7 +375,7 @@ export abstract class BaseClient implements Client { env = addItemToEnvelope(env, createAttachmentEnvelopeItem(attachment)); } - const promise = this._sendEnvelope(env); + const promise = this.sendEnvelope(env); if (promise) { promise.then(sendResponse => this.emit('afterSendEvent', event, sendResponse), null); } @@ -389,9 +387,9 @@ export abstract class BaseClient implements Client { public sendSession(session: Session | SessionAggregates): void { const env = createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel); - // _sendEnvelope should not throw + // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._sendEnvelope(env); + this.sendEnvelope(env); } /** @@ -415,23 +413,6 @@ export abstract class BaseClient implements Client { } } - /** - * @inheritDoc - */ - public captureAggregateMetrics(metricBucketItems: Array): void { - DEBUG_BUILD && logger.log(`Flushing aggregated metrics, number of metrics: ${metricBucketItems.length}`); - const metricsEnvelope = createMetricEnvelope( - metricBucketItems, - this._dsn, - this._options._metadata, - this._options.tunnel, - ); - - // _sendEnvelope should not throw - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._sendEnvelope(metricsEnvelope); - } - // Keep on() & emit() signatures in sync with types' client.ts interface /* eslint-disable @typescript-eslint/unified-signatures */ @@ -540,6 +521,21 @@ export abstract class BaseClient implements Client { } } + /** + * @inheritdoc + */ + public sendEnvelope(envelope: Envelope): PromiseLike | void { + this.emit('beforeEnvelope', envelope); + + if (this._isEnabled() && this._transport) { + return this._transport.send(envelope).then(null, reason => { + DEBUG_BUILD && logger.error('Error while sending event:', reason); + }); + } else { + DEBUG_BUILD && logger.error('Transport disabled'); + } + } + /* eslint-enable @typescript-eslint/unified-signatures */ /** Setup integrations for this client. */ @@ -823,21 +819,6 @@ export abstract class BaseClient implements Client { ); } - /** - * @inheritdoc - */ - protected _sendEnvelope(envelope: Envelope): PromiseLike | void { - this.emit('beforeEnvelope', envelope); - - if (this._isEnabled() && this._transport) { - return this._transport.send(envelope).then(null, reason => { - DEBUG_BUILD && logger.error('Error while sending event:', reason); - }); - } else { - DEBUG_BUILD && logger.error('Transport disabled'); - } - } - /** * Clears outcomes on this client and returns them. */ diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 24caf9de6610..5f0337e804f3 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -1,12 +1,13 @@ import type { - Client, ClientOptions, MeasurementUnit, MetricsAggregator as MetricsAggregatorBase, Primitive, } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; +import type { BaseClient } from '../baseclient'; import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants'; +import { captureAggregateMetrics } from './envelope'; import { METRIC_MAP } from './instance'; import { updateMetricSummaryOnActiveSpan } from './metric-summary'; import type { MetricBucket, MetricType } from './types'; @@ -39,7 +40,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { // Force flush is used on either shutdown, flush() or when we exceed the max weight. private _forceFlush: boolean; - public constructor(private readonly _client: Client) { + public constructor(private readonly _client: BaseClient) { this._buckets = new Map(); this._bucketsTotalWeight = 0; this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL); @@ -166,7 +167,7 @@ export class MetricsAggregator implements MetricsAggregatorBase { // TODO(@anonrig): Optimization opportunity. // This copy operation can be avoided if we store the key in the bucketItem. const buckets = Array.from(flushedBuckets).map(([, bucketItem]) => bucketItem); - this._client.captureAggregateMetrics(buckets); + captureAggregateMetrics(this._client, buckets); } } } diff --git a/packages/core/src/metrics/browser-aggregator.ts b/packages/core/src/metrics/browser-aggregator.ts index 9d24be11a843..d19aa441aef3 100644 --- a/packages/core/src/metrics/browser-aggregator.ts +++ b/packages/core/src/metrics/browser-aggregator.ts @@ -1,6 +1,8 @@ -import type { Client, ClientOptions, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types'; +import type { ClientOptions, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; +import type { BaseClient } from '../baseclient'; import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants'; +import { captureAggregateMetrics } from './envelope'; import { METRIC_MAP } from './instance'; import { updateMetricSummaryOnActiveSpan } from './metric-summary'; import type { MetricBucket, MetricType } from './types'; @@ -19,7 +21,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator { private _buckets: MetricBucket; private readonly _interval: ReturnType; - public constructor(private readonly _client: Client) { + public constructor(private readonly _client: BaseClient) { this._buckets = new Map(); this._interval = setInterval(() => this.flush(), DEFAULT_BROWSER_FLUSH_INTERVAL); } @@ -80,7 +82,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator { // TODO(@anonrig): Use Object.values() when we support ES6+ const metricBuckets = Array.from(this._buckets).map(([, bucketItem]) => bucketItem); - this._client.captureAggregateMetrics(metricBuckets); + captureAggregateMetrics(this._client, metricBuckets); this._buckets.clear(); } diff --git a/packages/core/src/metrics/envelope.ts b/packages/core/src/metrics/envelope.ts index 95622e109740..47ccc2740834 100644 --- a/packages/core/src/metrics/envelope.ts +++ b/packages/core/src/metrics/envelope.ts @@ -1,7 +1,34 @@ -import type { DsnComponents, MetricBucketItem, SdkMetadata, StatsdEnvelope, StatsdItem } from '@sentry/types'; -import { createEnvelope, dsnToString } from '@sentry/utils'; +import type { + ClientOptions, + DsnComponents, + MetricBucketItem, + SdkMetadata, + StatsdEnvelope, + StatsdItem, +} from '@sentry/types'; +import { createEnvelope, dsnToString, logger } from '@sentry/utils'; +import type { BaseClient } from '../baseclient'; import { serializeMetricBuckets } from './utils'; +/** + * Captures aggregated metrics to the supplied client. + */ +export function captureAggregateMetrics( + client: BaseClient, + metricBucketItems: Array, +): void { + logger.log(`Flushing aggregated metrics, number of metrics: ${metricBucketItems.length}`); + const dsn = client.getDsn(); + const metadata = client.getSdkMetadata(); + const tunnel = client.getOptions().tunnel; + + const metricsEnvelope = createMetricEnvelope(metricBucketItems, dsn, metadata, tunnel); + + // sendEnvelope should not throw + // eslint-disable-next-line @typescript-eslint/no-floating-promises + client.sendEnvelope(metricsEnvelope); +} + /** * Create envelope from a metric aggregate. */ diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index ecf0fc8ae2b6..682e58e80355 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -199,9 +199,9 @@ export class ServerRuntimeClient< DEBUG_BUILD && logger.info('Sending checkin:', checkIn.monitorSlug, checkIn.status); - // _sendEnvelope should not throw + // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises - this._sendEnvelope(envelope); + this.sendEnvelope(envelope); return id; } diff --git a/packages/core/test/lib/metrics/aggregator.test.ts b/packages/core/test/lib/metrics/aggregator.test.ts index 32396cfedcc2..2a471d12bb04 100644 --- a/packages/core/test/lib/metrics/aggregator.test.ts +++ b/packages/core/test/lib/metrics/aggregator.test.ts @@ -81,7 +81,7 @@ describe('MetricsAggregator', () => { describe('close', () => { test('should flush immediately', () => { - const capture = jest.spyOn(testClient, 'captureAggregateMetrics'); + const capture = jest.spyOn(testClient, 'sendEnvelope'); const aggregator = new MetricsAggregator(testClient); aggregator.add('c', 'requests', 1); aggregator.close(); @@ -89,37 +89,18 @@ describe('MetricsAggregator', () => { expect(clearInterval).toHaveBeenCalled(); expect(capture).toBeCalled(); expect(capture).toBeCalledTimes(1); - expect(capture).toBeCalledWith([ - { - metric: { _value: 1 }, - metricType: 'c', - name: 'requests', - tags: {}, - timestamp: expect.any(Number), - unit: 'none', - }, - ]); }); }); describe('flush', () => { test('should flush immediately', () => { - const capture = jest.spyOn(testClient, 'captureAggregateMetrics'); + const capture = jest.spyOn(testClient, 'sendEnvelope'); const aggregator = new MetricsAggregator(testClient); aggregator.add('c', 'requests', 1); aggregator.flush(); expect(capture).toBeCalled(); expect(capture).toBeCalledTimes(1); - expect(capture).toBeCalledWith([ - { - metric: { _value: 1 }, - metricType: 'c', - name: 'requests', - tags: {}, - timestamp: expect.any(Number), - unit: 'none', - }, - ]); + capture.mockReset(); aggregator.close(); // It should clear the interval. @@ -130,7 +111,7 @@ describe('MetricsAggregator', () => { }); test('should not capture if empty', () => { - const capture = jest.spyOn(testClient, 'captureAggregateMetrics'); + const capture = jest.spyOn(testClient, 'sendEnvelope'); const aggregator = new MetricsAggregator(testClient); aggregator.add('c', 'requests', 1); aggregator.flush(); @@ -143,7 +124,7 @@ describe('MetricsAggregator', () => { describe('add', () => { test('it should respect the max weight and flush if exceeded', () => { - const capture = jest.spyOn(testClient, 'captureAggregateMetrics'); + const capture = jest.spyOn(testClient, 'sendEnvelope'); const aggregator = new MetricsAggregator(testClient); for (let i = 0; i < MAX_WEIGHT; i++) { diff --git a/packages/core/test/lib/serverruntimeclient.test.ts b/packages/core/test/lib/serverruntimeclient.test.ts index 4ffed6c68f81..4d5cc8f33ce4 100644 --- a/packages/core/test/lib/serverruntimeclient.test.ts +++ b/packages/core/test/lib/serverruntimeclient.test.ts @@ -78,8 +78,7 @@ describe('ServerRuntimeClient', () => { }); client = new ServerRuntimeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn( { monitorSlug: 'foo', status: 'in_progress' }, @@ -145,8 +144,7 @@ describe('ServerRuntimeClient', () => { const options = getDefaultClientOptions({ dsn: PUBLIC_DSN, serverName: 'bar', enabled: false }); client = new ServerRuntimeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' }); diff --git a/packages/node-experimental/test/sdk/client.test.ts b/packages/node-experimental/test/sdk/client.test.ts index de2b6c6fdf6b..bde4ad23e291 100644 --- a/packages/node-experimental/test/sdk/client.test.ts +++ b/packages/node-experimental/test/sdk/client.test.ts @@ -393,8 +393,7 @@ describe('NodeClient', () => { }); const client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn( { monitorSlug: 'foo', status: 'in_progress' }, @@ -464,8 +463,7 @@ describe('NodeClient', () => { }); const client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn({ monitorSlug: 'heartbeat-monitor', status: 'ok' }); @@ -491,8 +489,7 @@ describe('NodeClient', () => { const options = getDefaultNodeClientOptions({ serverName: 'bar', enabled: false }); const client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' }); diff --git a/packages/node/test/client.test.ts b/packages/node/test/client.test.ts index 64c1251d1ce2..b80c3eced700 100644 --- a/packages/node/test/client.test.ts +++ b/packages/node/test/client.test.ts @@ -310,8 +310,7 @@ describe('NodeClient', () => { }); client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn( { monitorSlug: 'foo', status: 'in_progress' }, @@ -382,8 +381,7 @@ describe('NodeClient', () => { }); client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn({ monitorSlug: 'heartbeat-monitor', status: 'ok' }); @@ -409,8 +407,7 @@ describe('NodeClient', () => { const options = getDefaultNodeClientOptions({ dsn: PUBLIC_DSN, serverName: 'bar', enabled: false }); client = new NodeClient(options); - // @ts-expect-error accessing private method - const sendEnvelopeSpy = jest.spyOn(client, '_sendEnvelope'); + const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' }); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 198f792d6235..7c6b562a2f73 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -8,7 +8,6 @@ import type { Event, EventHint } from './event'; import type { EventProcessor } from './eventprocessor'; import type { FeedbackEvent } from './feedback'; import type { Integration, IntegrationClass } from './integration'; -import type { MetricBucketItem } from './metrics'; import type { ClientOptions } from './options'; import type { ParameterizedString } from './parameterize'; import type { Scope } from './scope'; @@ -179,13 +178,6 @@ export interface Client { */ recordDroppedEvent(reason: EventDropReason, dataCategory: DataCategory, event?: Event): void; - /** - * Captures serialized metrics and sends them to Sentry. - * - * @experimental This API is experimental and might experience breaking changes - */ - captureAggregateMetrics(metricBucketItems: Array): void; - // HOOKS // TODO(v8): Make the hooks non-optional. /* eslint-disable @typescript-eslint/unified-signatures */ From 437d20a578bdeccee7db114716cfc18061fdd668 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 22 Feb 2024 19:51:10 -0500 Subject: [PATCH 142/173] feat(v8): Remove @sentry/tracing (#10625) Aside from deleting the tracing package and all of it's references, it also moves some relevant tests into core. --- .craft.yml | 7 +- MIGRATION.md | 8 +- README.md | 2 - .../browser-integration-tests/package.json | 1 - .../suites/public-api/startSpan/init.js | 5 +- .../public-api/startTransaction/init.js | 5 +- .../long-tasks-disabled/test.ts | 5 +- .../long-tasks-enabled/test.ts | 9 +- .../browserTracingIntegrationShim/init.js | 2 +- .../backgroundtab-custom/init.js | 3 +- .../browsertracing/http-timings/init.js | 3 +- .../suites/tracing/browsertracing/init.js | 3 +- .../browsertracing/interactions/init.js | 3 +- .../long-tasks-disabled/init.js | 3 +- .../long-tasks-disabled/test.ts | 5 +- .../browsertracing/long-tasks-enabled/init.js | 3 +- .../browsertracing/long-tasks-enabled/test.ts | 9 +- .../tracing/browsertracing/meta/init.js | 3 +- .../tracing/browsertracing/pageload/init.js | 3 +- .../browsertracing/pageloadDelayed/init.js | 3 +- .../pageloadWithHeartbeatTimeout/init.js | 3 +- .../customTargetsAndOrigins/init.js | 9 + .../customTracingOrigins/init.js | 9 + .../envelope-header-transaction-name/init.js | 3 +- .../suites/tracing/metrics/init.js | 3 +- .../metrics/pageload-browser-spans/test.ts | 5 +- .../metrics/pageload-resource-spans/test.ts | 5 +- .../suites/tracing/request/fetch/test.ts | 5 +- .../suites/tracing/request/xhr/test.ts | 5 +- .../utils/generatePlugin.ts | 3 +- .../create-react-app/package.json | 1 - .../create-react-app/src/index.tsx | 3 +- .../test-applications/generic-ts3.8/index.ts | 2 - .../generic-ts3.8/package.json | 1 - .../node-exports-test-app/package.json | 6 + .../node-express-app/package.json | 1 - .../node-express-app/src/app.ts | 1 - .../node-hapi-app/package.json | 1 - .../package.json | 1 - .../sveltekit-2/package.json | 2 +- .../test-applications/sveltekit/package.json | 2 +- .../e2e-tests/tracing-shim-esm/index.cjs | 3 + .../e2e-tests/tracing-shim-esm/index.mjs | 3 + .../e2e-tests/tracing-shim-esm/package.json | 10 + dev-packages/e2e-tests/tracing-shim/index.js | 3 + .../e2e-tests/tracing-shim/package.json | 9 + .../e2e-tests/verdaccio-config/config.yaml | 6 - .../node-integration-tests/package.json | 1 - .../suites/tracing/spans/scenario.ts | 2 - dev-packages/rollup-utils/bundleHelpers.mjs | 2 +- package.json | 1 - packages/browser/rollup.bundle.config.mjs | 6 +- .../index.bundle.tracing.replay.feedback.ts | 7 +- .../src/index.bundle.tracing.replay.ts | 7 +- packages/browser/src/index.bundle.tracing.ts | 7 +- packages/core/test/lib/tracing/errors.test.ts | 7 +- .../test/lib/tracing}/idletransaction.test.ts | 13 +- packages/core/test/lib/tracing/span.test.ts | 104 ++- packages/nextjs/test/integration/package.json | 1 - packages/remix/test/integration/package.json | 1 - packages/tracing-internal/src/extensions.ts | 67 -- packages/tracing-internal/src/index.ts | 2 - .../test/browser/backgroundtab.test.ts | 12 +- .../test/browser/browsertracing.test.ts | 13 +- .../test/browser/router.test.ts | 2 +- packages/tracing/.eslintrc.js | 11 - packages/tracing/LICENSE | 14 - packages/tracing/README.md | 163 ----- packages/tracing/jest.config.js | 1 - packages/tracing/package.json | 73 -- packages/tracing/rollup.npm.config.mjs | 8 - packages/tracing/src/index.ts | 240 ------- packages/tracing/test/index.test.ts | 33 - .../test/integrations/apollo-nestjs.test.ts | 127 ---- .../tracing/test/integrations/apollo.test.ts | 127 ---- .../tracing/test/integrations/graphql.test.ts | 79 --- .../test/integrations/node/mongo.test.ts | 161 ----- .../test/integrations/node/postgres.test.ts | 154 ---- .../test/integrations/node/prisma.test.ts | 85 --- packages/tracing/test/span.test.ts | 656 ------------------ packages/tracing/test/testutils.ts | 100 --- packages/tracing/test/transaction.test.ts | 210 ------ packages/tracing/test/utils.test.ts | 35 - packages/tracing/tsconfig.json | 9 - packages/tracing/tsconfig.test.json | 12 - packages/tracing/tsconfig.types.json | 10 - packages/types/src/span.ts | 6 +- yarn.lock | 36 + 88 files changed, 278 insertions(+), 2517 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/init.js create mode 100644 dev-packages/e2e-tests/tracing-shim-esm/index.cjs create mode 100644 dev-packages/e2e-tests/tracing-shim-esm/index.mjs create mode 100644 dev-packages/e2e-tests/tracing-shim-esm/package.json create mode 100644 dev-packages/e2e-tests/tracing-shim/index.js create mode 100644 dev-packages/e2e-tests/tracing-shim/package.json rename packages/{tracing/test => core/test/lib/tracing}/idletransaction.test.ts (98%) delete mode 100644 packages/tracing-internal/src/extensions.ts delete mode 100644 packages/tracing/.eslintrc.js delete mode 100644 packages/tracing/LICENSE delete mode 100644 packages/tracing/README.md delete mode 100644 packages/tracing/jest.config.js delete mode 100644 packages/tracing/package.json delete mode 100644 packages/tracing/rollup.npm.config.mjs delete mode 100644 packages/tracing/src/index.ts delete mode 100644 packages/tracing/test/index.test.ts delete mode 100644 packages/tracing/test/integrations/apollo-nestjs.test.ts delete mode 100644 packages/tracing/test/integrations/apollo.test.ts delete mode 100644 packages/tracing/test/integrations/graphql.test.ts delete mode 100644 packages/tracing/test/integrations/node/mongo.test.ts delete mode 100644 packages/tracing/test/integrations/node/postgres.test.ts delete mode 100644 packages/tracing/test/integrations/node/prisma.test.ts delete mode 100644 packages/tracing/test/span.test.ts delete mode 100644 packages/tracing/test/testutils.ts delete mode 100644 packages/tracing/test/transaction.test.ts delete mode 100644 packages/tracing/test/utils.test.ts delete mode 100644 packages/tracing/tsconfig.json delete mode 100644 packages/tracing/tsconfig.test.json delete mode 100644 packages/tracing/tsconfig.types.json diff --git a/.craft.yml b/.craft.yml index f795d317b05e..5ffdda2338ae 100644 --- a/.craft.yml +++ b/.craft.yml @@ -131,12 +131,7 @@ targets: id: '@sentry-internal/eslint-config-sdk' includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ - ## 8. Deprecated packages we still release (but no packages depend on them anymore) - - name: npm - id: '@sentry/tracing' - includeNames: /^sentry-tracing-\d.*\.tgz$/ - - ## 9. Experimental packages + ## 8. Experimental packages - name: npm id: '@sentry/node-experimental' includeNames: /^sentry-node-experimental-\d.*\.tgz$/ diff --git a/MIGRATION.md b/MIGRATION.md index fd2e4cf6ccfd..a2a9803071e7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -88,7 +88,13 @@ to access and mutate the current scope. ## Deletion of `@sentry/hub` package (#10530) -`@sentry/hub` has been removed. All exports from `@sentry.hub` should be available in `@sentry/core`. +`@sentry/hub` has been removed. All exports from `@sentry/tracing` should be available in `@sentry/core` or in +`@sentry/browser` and `@sentry/node`. + +## Deletion of `@sentry/tracing` package + +`@sentry/tracing` has been removed. All exports from `@sentry/tracing` should be available in `@sentry/core` or in +`@sentry/browser` and `@sentry/node`. ## Removal of `makeXHRTransport` transport (#10703) diff --git a/README.md b/README.md index 97ec4dadb89a..80790d36c4a2 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,6 @@ Besides the high-level SDKs, this repository contains shared packages, helpers a development. If you're thinking about contributing to or creating a JavaScript-based SDK, have a look at the resources below: -- [`@sentry/tracing`](https://github.com/getsentry/sentry-javascript/tree/master/packages/tracing): Provides - integrations and extensions for Performance Monitoring / Tracing. - [`@sentry/replay`](https://github.com/getsentry/sentry-javascript/tree/master/packages/replay): Provides the integration for Session Replay. - [`@sentry/core`](https://github.com/getsentry/sentry-javascript/tree/master/packages/core): The base for all diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 08320f47628c..3d6622561fdf 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -48,7 +48,6 @@ "@playwright/test": "^1.40.1", "@sentry-internal/rrweb": "2.11.0", "@sentry/browser": "7.100.0", - "@sentry/tracing": "7.100.0", "axios": "1.6.7", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js index b0bf1e869254..5724df357e8b 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js @@ -1,10 +1,9 @@ -/* eslint-disable no-unused-vars */ import * as Sentry from '@sentry/browser'; -// biome-ignore lint/nursery/noUnusedImports: Need to import tracing for side effect -import * as _ from '@sentry/tracing'; window.Sentry = Sentry; +Sentry.addTracingExtensions(); + Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/init.js b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/init.js index e1903e2cc268..3fb0df7a75d4 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/init.js @@ -1,10 +1,9 @@ -/* eslint-disable no-unused-vars */ import * as Sentry from '@sentry/browser'; -// biome-ignore lint/nursery/noUnusedImports: Need to import tracing for side effect -import * as _ from '@sentry/tracing'; window.Sentry = Sentry; +Sentry.addTracingExtensions(); + Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts index 1f7bb54bb36a..d460d2883afd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,8 +15,7 @@ sentryTest('should not capture long task when flag is disabled.', async ({ brows const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBe(0); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts index 32819fd784e0..1ed0bcda2a89 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,8 +15,7 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBeGreaterThan(0); @@ -29,8 +28,8 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, parent_span_id: eventData.contexts?.trace?.span_id, }), ); - const start = (firstUISpan as Event)['start_timestamp'] ?? 0; - const end = (firstUISpan as Event)['timestamp'] ?? 0; + const start = firstUISpan.start_timestamp ?? 0; + const end = firstUISpan.timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js index e8ba5702cff8..d7cfd52aedcc 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js @@ -5,7 +5,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', sampleRate: 1, - integrations: [new Sentry.browserTracingIntegration()], + integrations: [Sentry.browserTracingIntegration()], }); // This should not fail diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js index 7d13bc95852b..62685d4b6d18 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js @@ -1,10 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ idleTimeout: 9000, startTransactionOnPageLoad: false })], + integrations: [new Sentry.BrowserTracing({ idleTimeout: 9000, startTransactionOnPageLoad: false })], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js index efe1e2ef9778..ec8b0cb08034 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ - new Integrations.BrowserTracing({ + new Sentry.BrowserTracing({ idleTimeout: 1000, _experiments: { enableHTTPTimings: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js index ce4e0c4ad7f7..14e743361fd7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js @@ -1,10 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [new Sentry.BrowserTracing()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js index d30222b7f47e..f8f8cf526a6b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ - new Integrations.BrowserTracing({ + new Sentry.BrowserTracing({ idleTimeout: 1000, _experiments: { enableInteractions: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js index c1d5e8a8a7ae..8dba00211a01 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js @@ -1,10 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ enableLongTask: false, idleTimeout: 9000 })], + integrations: [new Sentry.BrowserTracing({ enableLongTask: false, idleTimeout: 9000 })], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts index 1f7bb54bb36a..d460d2883afd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,8 +15,7 @@ sentryTest('should not capture long task when flag is disabled.', async ({ brows const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBe(0); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js index 037e2dc88517..155966847b1c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ - new Integrations.BrowserTracing({ + new Sentry.BrowserTracing({ idleTimeout: 9000, }), ], diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts index 32819fd784e0..1ed0bcda2a89 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,8 +15,7 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBeGreaterThan(0); @@ -29,8 +28,8 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, parent_span_id: eventData.contexts?.trace?.span_id, }), ); - const start = (firstUISpan as Event)['start_timestamp'] ?? 0; - const end = (firstUISpan as Event)['timestamp'] ?? 0; + const start = firstUISpan.start_timestamp ?? 0; + const end = firstUISpan.timestamp ?? 0; const duration = end - start; expect(duration).toBeGreaterThanOrEqual(0.1); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js index 50f4a898e251..e6f49fa89562 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js @@ -1,11 +1,10 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [new Sentry.BrowserTracing()], tracesSampleRate: 1, environment: 'staging', }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js index 2340df528aa7..a1e77dae58c2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js @@ -1,11 +1,10 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [new Sentry.BrowserTracing()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js index ff6345dec8f2..6e4774650261 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; window._testBaseTimestamp = performance.timeOrigin / 1000; @@ -8,7 +7,7 @@ setTimeout(() => { window._testTimeoutTimestamp = (performance.timeOrigin + performance.now()) / 1000; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [new Sentry.BrowserTracing()], tracesSampleRate: 1, }); }, 250); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js index ce0d16f0f0db..2a4797a74a15 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; import { startSpanManual } from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing()], + integrations: [new Sentry.BrowserTracing()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js new file mode 100644 index 000000000000..d4fd36f8302c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Sentry.BrowserTracing({ tracePropagationTargets: [], tracingOrigins: ['http://example.com'] })], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/init.js new file mode 100644 index 000000000000..c43686bbc598 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Sentry.BrowserTracing({ tracingOrigins: ['http://example.com'] })], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index 579523ee2015..647e6af5e963 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] })], + integrations: [new Sentry.BrowserTracing({ tracingOrigins: [/.*/] })], environment: 'production', tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js index 037e2dc88517..155966847b1c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js @@ -1,12 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ - new Integrations.BrowserTracing({ + new Sentry.BrowserTracing({ idleTimeout: 9000, }), ], diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts index 504ac975621e..0730d2ba4645 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,8 +11,7 @@ sentryTest('should add browser-related spans to pageload transaction', async ({ const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const browserSpans = eventData.spans?.filter(({ op }) => op === 'browser'); // Spans `connect`, `cache` and `DNS` are not always inside `pageload` transaction. 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 9ce848384f7b..40159d39ea25 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,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -17,8 +17,7 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource')); // Webkit 16.0 (which is linked to Playwright 1.27.1) consistently creates 2 consectutive spans for `css`, diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts index 7bffc0131b2f..ab4c29906f5c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -21,10 +21,9 @@ sentryTest('should create spans for multiple fetch requests', async ({ getLocalT // If we are on FF or webkit: // 1st envelope contains CORS error // 2nd envelope contains the tracing data we want to check here - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); + const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers - // eslint-disable-next-line deprecation/deprecation const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); expect(requestSpans).toHaveLength(3); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts index d1c64e253e71..bd4ee0bbb003 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; +import type { SerializedEvent } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,8 +11,7 @@ sentryTest('should create spans for multiple XHR requests', async ({ getLocalTes const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - // eslint-disable-next-line deprecation/deprecation + const eventData = await getFirstSentryEnvelopeRequest(page, url); const requestSpans = eventData.spans?.filter(({ op }) => op === 'http.client'); expect(requestSpans).toHaveLength(3); diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index fe583e271ca3..0ef9a73a91d2 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -168,14 +168,13 @@ class SentryScenarioGenerationPlugin { ? { // To help Webpack resolve Sentry modules in `import` statements in cases where they're provided in bundles rather than in `node_modules` '@sentry/browser': 'Sentry', - '@sentry/tracing': 'Sentry', '@sentry/replay': 'Sentry', '@sentry/integrations': 'Sentry', '@sentry/wasm': 'Sentry', } : {}; - // Checking if the current scenario has imported `@sentry/tracing` or `@sentry/integrations`. + // Checking if the current scenario has imported `@sentry/integrations`. compiler.hooks.normalModuleFactory.tap(this._name, factory => { factory.hooks.parser.for('javascript/auto').tap(this._name, parser => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access diff --git a/dev-packages/e2e-tests/test-applications/create-react-app/package.json b/dev-packages/e2e-tests/test-applications/create-react-app/package.json index 07c4210736a8..a6f33f5372f4 100644 --- a/dev-packages/e2e-tests/test-applications/create-react-app/package.json +++ b/dev-packages/e2e-tests/test-applications/create-react-app/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@sentry/react": "latest || *", - "@sentry/tracing": "latest || *", "@testing-library/jest-dom": "5.14.1", "@testing-library/react": "13.0.0", "@testing-library/user-event": "13.2.1", diff --git a/dev-packages/e2e-tests/test-applications/create-react-app/src/index.tsx b/dev-packages/e2e-tests/test-applications/create-react-app/src/index.tsx index f4f58b689b9e..1c10b3e92da6 100644 --- a/dev-packages/e2e-tests/test-applications/create-react-app/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/create-react-app/src/index.tsx @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/react'; -import { BrowserTracing } from '@sentry/tracing'; import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; @@ -9,7 +8,7 @@ import reportWebVitals from './reportWebVitals'; Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new BrowserTracing()], + integrations: [Sentry.browserTracingIntegration()], // We recommend adjusting this value in production, or using tracesSampler // for finer control diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts index 3ec4677b17bc..34a7ba15fe24 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts @@ -11,8 +11,6 @@ import * as _SentryOpentelemetry from '@sentry/opentelemetry-node'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryReplay from '@sentry/replay'; // biome-ignore lint/nursery/noUnusedImports: -import * as _SentryTracing from '@sentry/tracing'; -// biome-ignore lint/nursery/noUnusedImports: import * as _SentryTypes from '@sentry/types'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryUtils from '@sentry/utils'; diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json index 919d2fb99e84..82be8f36bd5f 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json @@ -19,7 +19,6 @@ "@sentry/node": "latest || *", "@sentry/opentelemetry-node": "latest || *", "@sentry/replay": "latest || *", - "@sentry/tracing": "latest || *", "@sentry/types": "latest || *", "@sentry/utils": "latest || *", "@sentry/wasm": "latest || *" diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json index 6d187f14c245..31c3a9207879 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json @@ -23,6 +23,12 @@ "@types/node": "18.15.1", "typescript": "4.9.5" }, + "pnpm": { + "overrides": { + "@sentry/node": "latest || *", + "@sentry/tracing": "file:../../tracing-shim-esm" + } + }, "devDependencies": { "ts-node": "10.9.1" }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/package.json b/dev-packages/e2e-tests/test-applications/node-express-app/package.json index 2d44043b94e7..2c721361b212 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-app/package.json @@ -13,7 +13,6 @@ "dependencies": { "@sentry/integrations": "latest || *", "@sentry/node": "latest || *", - "@sentry/tracing": "latest || *", "@sentry/types": "latest || *", "express": "4.18.2", "@types/express": "4.17.17", diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts index e2015a70825c..c6cabed89382 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -1,6 +1,5 @@ import { httpClientIntegration } from '@sentry/integrations'; import * as Sentry from '@sentry/node'; -import '@sentry/tracing'; import express from 'express'; declare global { diff --git a/dev-packages/e2e-tests/test-applications/node-hapi-app/package.json b/dev-packages/e2e-tests/test-applications/node-hapi-app/package.json index 1f667abc8987..1aba49ac4d3e 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-hapi-app/package.json @@ -14,7 +14,6 @@ "@hapi/hapi": "21.3.2", "@sentry/integrations": "latest || *", "@sentry/node": "latest || *", - "@sentry/tracing": "latest || *", "@sentry/types": "latest || *", "@types/node": "18.15.1", "typescript": "4.9.5" diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json b/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json index be15956c2ca6..cde5ad8225ee 100644 --- a/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json +++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react-tracing-import/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@sentry/react": "latest || *", - "@sentry/tracing": "latest || *", "@testing-library/jest-dom": "5.14.1", "@testing-library/react": "13.0.0", "@testing-library/user-event": "13.2.1", 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 dbdb431960de..4a6f4318548b 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -35,7 +35,7 @@ "pnpm": { "overrides": { "@sentry/node": "latest || *", - "@sentry/tracing": "latest || *" + "@sentry/tracing": "file:../../tracing-shim" } }, "type": "module" diff --git a/dev-packages/e2e-tests/test-applications/sveltekit/package.json b/dev-packages/e2e-tests/test-applications/sveltekit/package.json index ea21787939c3..5871b00d31fd 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit/package.json @@ -33,7 +33,7 @@ "pnpm": { "overrides": { "@sentry/node": "latest || *", - "@sentry/tracing": "latest || *" + "@sentry/tracing": "file:../../tracing-shim-esm" } }, "type": "module" diff --git a/dev-packages/e2e-tests/tracing-shim-esm/index.cjs b/dev-packages/e2e-tests/tracing-shim-esm/index.cjs new file mode 100644 index 000000000000..e245f86e5c40 --- /dev/null +++ b/dev-packages/e2e-tests/tracing-shim-esm/index.cjs @@ -0,0 +1,3 @@ +// TODO(v8): Remove this file once we get rid of tracing dependency from sveltekit vite plugin +// This file is used as a shim for @sentry/tracing +module.exports = {}; diff --git a/dev-packages/e2e-tests/tracing-shim-esm/index.mjs b/dev-packages/e2e-tests/tracing-shim-esm/index.mjs new file mode 100644 index 000000000000..5ba2afe713b3 --- /dev/null +++ b/dev-packages/e2e-tests/tracing-shim-esm/index.mjs @@ -0,0 +1,3 @@ +// TODO(v8): Remove this file once we get rid of tracing dependency from sveltekit vite plugin +// This file is used as a shim for @sentry/tracing +export {}; diff --git a/dev-packages/e2e-tests/tracing-shim-esm/package.json b/dev-packages/e2e-tests/tracing-shim-esm/package.json new file mode 100644 index 000000000000..577f7e2cef51 --- /dev/null +++ b/dev-packages/e2e-tests/tracing-shim-esm/package.json @@ -0,0 +1,10 @@ +{ + "name": "tracing-shim-esm", + "version": "0.0.1", + "private": true, + "main": "index.cjs", + "module": "index.mjs", + "scripts": {}, + "dependencies": {}, + "devDependencies": {} +} diff --git a/dev-packages/e2e-tests/tracing-shim/index.js b/dev-packages/e2e-tests/tracing-shim/index.js new file mode 100644 index 000000000000..e245f86e5c40 --- /dev/null +++ b/dev-packages/e2e-tests/tracing-shim/index.js @@ -0,0 +1,3 @@ +// TODO(v8): Remove this file once we get rid of tracing dependency from sveltekit vite plugin +// This file is used as a shim for @sentry/tracing +module.exports = {}; diff --git a/dev-packages/e2e-tests/tracing-shim/package.json b/dev-packages/e2e-tests/tracing-shim/package.json new file mode 100644 index 000000000000..0ee353b22b5b --- /dev/null +++ b/dev-packages/e2e-tests/tracing-shim/package.json @@ -0,0 +1,9 @@ +{ + "name": "tracing-shim", + "version": "0.0.1", + "private": true, + "main": "index.js", + "scripts": {}, + "dependencies": {}, + "devDependencies": {} +} diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 143596b74849..2ff38d3f63d1 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -164,12 +164,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/tracing': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/types': access: $all publish: $all diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 1594d8e837d0..6bb428d5f73f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -32,7 +32,6 @@ "@nestjs/platform-express": "^10.3.3", "@prisma/client": "5.9.1", "@sentry/node": "7.100.0", - "@sentry/tracing": "7.100.0", "@sentry/types": "7.100.0", "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", diff --git a/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts index dfdd065fd2ee..1a3faae3805c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/spans/scenario.ts @@ -1,5 +1,3 @@ -import '@sentry/tracing'; - import * as http from 'http'; import * as Sentry from '@sentry/node-experimental'; diff --git a/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs index 66bded3b62de..2b77db6c7a9e 100644 --- a/dev-packages/rollup-utils/bundleHelpers.mjs +++ b/dev-packages/rollup-utils/bundleHelpers.mjs @@ -45,7 +45,7 @@ export function makeBaseBundleConfig(options) { // at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.) const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true }); - // used by `@sentry/browser`, `@sentry/tracing`, and `@sentry/vue` (bundles which are a full SDK in and of themselves) + // used by `@sentry/browser` const standAloneBundleConfig = { output: { format: 'iife', diff --git a/package.json b/package.json index 74fbfe2a650f..f1cbdfd8c726 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "packages/serverless", "packages/svelte", "packages/sveltekit", - "packages/tracing", "packages/tracing-internal", "packages/types", "packages/typescript", diff --git a/packages/browser/rollup.bundle.config.mjs b/packages/browser/rollup.bundle.config.mjs index c64a88931a33..66b1c5bb5992 100644 --- a/packages/browser/rollup.bundle.config.mjs +++ b/packages/browser/rollup.bundle.config.mjs @@ -21,7 +21,7 @@ targets.forEach(jsVersion => { bundleType: 'standalone', entrypoints: ['src/index.bundle.tracing.ts'], jsVersion, - licenseTitle: '@sentry/browser & @sentry/tracing', + licenseTitle: '@sentry/browser (Performance Monitoring)', outputFileBase: () => `bundles/bundle.tracing${jsVersion === 'es5' ? '.es5' : ''}`, }); @@ -50,7 +50,7 @@ if (targets.includes('es6')) { bundleType: 'standalone', entrypoints: ['src/index.bundle.tracing.replay.ts'], jsVersion: 'es6', - licenseTitle: '@sentry/browser & @sentry/tracing & @sentry/replay', + licenseTitle: '@sentry/browser (Performance Monitoring and Replay)', outputFileBase: () => 'bundles/bundle.tracing.replay', }); @@ -58,7 +58,7 @@ if (targets.includes('es6')) { bundleType: 'standalone', entrypoints: ['src/index.bundle.tracing.replay.feedback.ts'], jsVersion: 'es6', - licenseTitle: '@sentry/browser & @sentry/tracing & @sentry/replay & @sentry/feedback', + licenseTitle: '@sentry/browser (Performance Monitoring, Replay, and Feedback)', outputFileBase: () => 'bundles/bundle.tracing.replay.feedback', }); diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 8a4a05dba6b6..b103b6f4583d 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,5 +1,6 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/feedback'; -import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing } from '@sentry-internal/tracing'; +import { addTracingExtensions } from '@sentry/core'; import { Replay, replayIntegration } from '@sentry/replay'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; @@ -15,7 +16,7 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods -addExtensionMethods(); +addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation @@ -27,6 +28,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - addExtensionMethods, + addTracingExtensions, }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index 20bbf135ace5..e817390aa39d 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,5 +1,6 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing } from '@sentry-internal/tracing'; +import { addTracingExtensions } from '@sentry/core'; import { Replay, replayIntegration } from '@sentry/replay'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; @@ -15,7 +16,7 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods -addExtensionMethods(); +addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation @@ -27,6 +28,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - addExtensionMethods, + addTracingExtensions, }; export * from './index.bundle.base'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 5b1dc7f5d2de..704a646dcdb8 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,6 +1,7 @@ // This is exported so the loader does not fail when switching off Replay import { Feedback, Replay, feedbackIntegration, replayIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing, addExtensionMethods } from '@sentry-internal/tracing'; +import { BrowserTracing } from '@sentry-internal/tracing'; +import { addTracingExtensions } from '@sentry/core'; import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; import * as Sentry from './index.bundle.base'; @@ -15,7 +16,7 @@ Sentry.Integrations.Replay = Replay; Sentry.Integrations.BrowserTracing = BrowserTracing; // We are patching the global object with our hub extension methods -addExtensionMethods(); +addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation @@ -27,6 +28,6 @@ export { // eslint-disable-next-line deprecation/deprecation BrowserTracing, browserTracingIntegration, - addExtensionMethods, + addTracingExtensions, }; export * from './index.bundle.base'; diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index e448f3ccb240..f4ea6dd09846 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -1,9 +1,8 @@ -import { BrowserClient } from '@sentry/browser'; import { addTracingExtensions, setCurrentClient, spanToJSON, startInactiveSpan, startSpan } from '@sentry/core'; import type { HandlerDataError, HandlerDataUnhandledRejection } from '@sentry/types'; -import { getDefaultBrowserClientOptions } from '../../../../tracing/test/testutils'; import { registerErrorInstrumentation } from '../../../src/tracing/errors'; +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; const mockAddGlobalErrorInstrumentationHandler = jest.fn(); const mockAddGlobalUnhandledRejectionInstrumentationHandler = jest.fn(); @@ -33,8 +32,8 @@ describe('registerErrorHandlers()', () => { beforeEach(() => { mockAddGlobalErrorInstrumentationHandler.mockClear(); mockAddGlobalUnhandledRejectionInstrumentationHandler.mockClear(); - const options = getDefaultBrowserClientOptions({ enableTracing: true }); - const client = new BrowserClient(options); + const options = getDefaultTestClientOptions({ enableTracing: true }); + const client = new TestClient(options); setCurrentClient(client); client.init(); }); diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/core/test/lib/tracing/idletransaction.test.ts similarity index 98% rename from packages/tracing/test/idletransaction.test.ts rename to packages/core/test/lib/tracing/idletransaction.test.ts index b1c1f498e46d..8f61b43164c3 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/core/test/lib/tracing/idletransaction.test.ts @@ -1,5 +1,3 @@ -/* eslint-disable deprecation/deprecation */ -import { BrowserClient } from '@sentry/browser'; import { TRACING_DEFAULTS, Transaction, @@ -13,10 +11,11 @@ import { startSpan, startSpanManual, } from '@sentry/core'; +/* eslint-disable deprecation/deprecation */ +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; -import { IdleTransaction, SentrySpan, getClient } from '../../core/src'; -import { IdleTransactionSpanRecorder } from '../../core/src/tracing/idletransaction'; -import { getDefaultBrowserClientOptions } from './testutils'; +import { IdleTransaction, SentrySpan, getClient } from '../../../src'; +import { IdleTransactionSpanRecorder } from '../../../src/tracing/idletransaction'; const dsn = 'https://123@sentry.io/42'; beforeEach(() => { @@ -24,8 +23,8 @@ beforeEach(() => { getIsolationScope().clear(); getGlobalScope().clear(); - const options = getDefaultBrowserClientOptions({ dsn, tracesSampleRate: 1 }); - const client = new BrowserClient(options); + const options = getDefaultTestClientOptions({ dsn, tracesSampleRate: 1 }); + const client = new TestClient(options); setCurrentClient(client); client.init(); }); diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 94bd1d29eabe..0a89b5acc5ff 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -1,6 +1,6 @@ import { timestampInSeconds } from '@sentry/utils'; import { SentrySpan } from '../../../src'; -import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON } from '../../../src/utils/spanUtils'; +import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils'; describe('span', () => { describe('name', () => { @@ -18,7 +18,107 @@ describe('span', () => { expect(spanToJSON(span).description).toEqual('new name'); }); }); - /* eslint-enable deprecation/deprecation */ + + describe('new SentrySpan', () => { + test('simple', () => { + const span = new SentrySpan({ sampled: true }); + // eslint-disable-next-line deprecation/deprecation + const span2 = span.startChild(); + expect((span2 as any).parentSpanId).toBe((span as any).spanId); + expect((span2 as any).traceId).toBe((span as any).traceId); + expect((span2 as any).sampled).toBe((span as any).sampled); + }); + }); + + describe('setters', () => { + test('setName', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).description).toBeUndefined(); + span.updateName('foo'); + expect(spanToJSON(span).description).toBe('foo'); + }); + }); + + describe('status', () => { + test('setStatus', () => { + const span = new SentrySpan({}); + span.setStatus('permission_denied'); + expect(spanToTraceContext(span).status).toBe('permission_denied'); + }); + }); + + describe('toJSON', () => { + test('simple', () => { + const span = spanToJSON( + new SentrySpan({ traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', spanId: 'bbbbbbbbbbbbbbbb' }), + ); + expect(span).toHaveProperty('span_id', 'bbbbbbbbbbbbbbbb'); + expect(span).toHaveProperty('trace_id', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + }); + + test('with parent', () => { + const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; + const spanB = new SentrySpan({ traceId: 'c', spanId: 'd', sampled: false, parentSpanId: spanA.spanId }); + const serialized = spanToJSON(spanB); + expect(serialized).toHaveProperty('parent_span_id', 'b'); + expect(serialized).toHaveProperty('span_id', 'd'); + expect(serialized).toHaveProperty('trace_id', 'c'); + }); + + test('should drop all `undefined` values', () => { + const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; + const spanB = new SentrySpan({ + parentSpanId: spanA.spanId, + spanId: 'd', + traceId: 'c', + }); + const serialized = spanToJSON(spanB); + expect(serialized).toStrictEqual({ + start_timestamp: expect.any(Number), + parent_span_id: 'b', + span_id: 'd', + trace_id: 'c', + origin: 'manual', + data: { + 'sentry.origin': 'manual', + }, + }); + }); + }); + + describe('finish', () => { + test('simple', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + span.end(); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); + }); + }); + + describe('end', () => { + test('simple', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + span.end(); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); + }); + + test('with endTime in seconds', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + const endTime = Date.now() / 1000; + span.end(endTime); + expect(spanToJSON(span).timestamp).toBe(endTime); + }); + + test('with endTime in milliseconds', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + const endTime = Date.now(); + span.end(endTime); + expect(spanToJSON(span).timestamp).toBe(endTime / 1000); + }); + }); describe('setAttribute', () => { it('allows to set attributes', () => { diff --git a/packages/nextjs/test/integration/package.json b/packages/nextjs/test/integration/package.json index bd45c952e67a..d73c8fa688f9 100644 --- a/packages/nextjs/test/integration/package.json +++ b/packages/nextjs/test/integration/package.json @@ -33,7 +33,6 @@ "@sentry/react": "file:../../../react", "@sentry/replay": "file:../../../replay", "@sentry-internal/replay-canvas": "file:../../../replay-canvas", - "@sentry/tracing": "file:../../../tracing", "@sentry-internal/tracing": "file:../../../tracing-internal", "@sentry-internal/feedback": "file:../../../feedback", "@sentry/types": "file:../../../types", diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index d151771125b9..f112babe40f6 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -31,7 +31,6 @@ "@sentry/react": "file:../../../react", "@sentry/replay": "file:../../../replay", "@sentry-internal/replay-canvas": "file:../../../replay-canvas", - "@sentry/tracing": "file:../../../tracing", "@sentry-internal/tracing": "file:../../../tracing-internal", "@sentry-internal/feedback": "file:../../../feedback", "@sentry/types": "file:../../../types", diff --git a/packages/tracing-internal/src/extensions.ts b/packages/tracing-internal/src/extensions.ts deleted file mode 100644 index 0d4c2b112e47..000000000000 --- a/packages/tracing-internal/src/extensions.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { addTracingExtensions, getMainCarrier } from '@sentry/core'; -import type { Integration, IntegrationClass } from '@sentry/types'; -import { dynamicRequire, isNodeEnv, loadModule } from '@sentry/utils'; - -/** - * @private - */ -function _autoloadDatabaseIntegrations(): void { - const carrier = getMainCarrier(); - if (!carrier.__SENTRY__) { - return; - } - - const packageToIntegrationMapping: Record Integration> = { - mongodb() { - const integration = dynamicRequire(module, './node/integrations/mongo') as { - Mongo: IntegrationClass; - }; - return new integration.Mongo(); - }, - mongoose() { - const integration = dynamicRequire(module, './node/integrations/mongo') as { - Mongo: IntegrationClass; - }; - return new integration.Mongo(); - }, - mysql() { - const integration = dynamicRequire(module, './node/integrations/mysql') as { - Mysql: IntegrationClass; - }; - return new integration.Mysql(); - }, - pg() { - const integration = dynamicRequire(module, './node/integrations/postgres') as { - Postgres: IntegrationClass; - }; - return new integration.Postgres(); - }, - }; - - const mappedPackages = Object.keys(packageToIntegrationMapping) - .filter(moduleName => !!loadModule(moduleName)) - .map(pkg => { - try { - return packageToIntegrationMapping[pkg](); - } catch (e) { - return undefined; - } - }) - .filter(p => p) as Integration[]; - - if (mappedPackages.length > 0) { - carrier.__SENTRY__.integrations = [...(carrier.__SENTRY__.integrations || []), ...mappedPackages]; - } -} - -/** - * This patches the global object and injects the Tracing extensions methods - */ -export function addExtensionMethods(): void { - addTracingExtensions(); - - // Detect and automatically load specified integrations. - if (isNodeEnv()) { - _autoloadDatabaseIntegrations(); - } -} diff --git a/packages/tracing-internal/src/index.ts b/packages/tracing-internal/src/index.ts index 4f09ca6a2e96..cbcd5324759a 100644 --- a/packages/tracing-internal/src/index.ts +++ b/packages/tracing-internal/src/index.ts @@ -30,5 +30,3 @@ export { export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './common/fetch'; export type { RequestInstrumentationOptions } from './browser'; - -export { addExtensionMethods } from './extensions'; diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index b04dc6705dae..bb90927ec099 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -1,11 +1,9 @@ -import { getCurrentScope } from '@sentry/core'; +import { addTracingExtensions, getCurrentScope } from '@sentry/core'; import { setCurrentClient, spanToJSON, startSpan } from '@sentry/core'; import { JSDOM } from 'jsdom'; -import { addExtensionMethods } from '../../../tracing/src'; -import { getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; import { registerBackgroundTabDetection } from '../../src/browser/backgroundtab'; -import { TestClient } from '../utils/TestClient'; +import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; describe('registerBackgroundTabDetection', () => { let events: Record = {}; @@ -14,14 +12,12 @@ describe('registerBackgroundTabDetection', () => { // @ts-expect-error need to override global document global.document = dom.window.document; - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); + const options = getDefaultClientOptions({ tracesSampleRate: 1 }); const client = new TestClient(options); setCurrentClient(client); client.init(); - // If we do not add extension methods, invoking hub.startTransaction returns undefined - // eslint-disable-next-line deprecation/deprecation - addExtensionMethods(); + addTracingExtensions(); // @ts-expect-error need to override global document global.document.addEventListener = jest.fn((event, callback) => { diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts index 12e455d13e8e..e24e4d166099 100644 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ b/packages/tracing-internal/test/browser/browsertracing.test.ts @@ -2,6 +2,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, TRACING_DEFAULTS, + getActiveTransaction, getClient, getCurrentHub, setCurrentClient, @@ -11,16 +12,14 @@ import * as hubExtensions from '@sentry/core'; import type { BaseTransportOptions, ClientOptions, DsnComponents, HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; +import type { IdleTransaction } from '@sentry/core'; import { timestampInSeconds } from '@sentry/utils'; -import type { IdleTransaction } from '../../../tracing/src'; -import { getActiveTransaction } from '../../../tracing/src'; -import { getDefaultBrowserClientOptions } from '../../../tracing/test/testutils'; import type { BrowserTracingOptions } from '../../src/browser/browsertracing'; import { BrowserTracing, getMetaContent } from '../../src/browser/browsertracing'; import { defaultRequestInstrumentationOptions } from '../../src/browser/request'; import { instrumentRoutingWithDefaults } from '../../src/browser/router'; import { WINDOW } from '../../src/browser/types'; -import { TestClient } from '../utils/TestClient'; +import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; let mockChangeHistory: (data: HandlerDataHistory) => void = () => {}; @@ -65,7 +64,7 @@ beforeAll(() => { describe('BrowserTracing', () => { beforeEach(() => { jest.useFakeTimers(); - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); + const options = getDefaultClientOptions({ tracesSampleRate: 1 }); const client = new TestClient(options); setCurrentClient(client); client.init(); @@ -551,7 +550,7 @@ describe('BrowserTracing', () => { WINDOW.location = dogParkLocation as any; const tracesSampler = jest.fn(); - const options = getDefaultBrowserClientOptions({ tracesSampler }); + const options = getDefaultClientOptions({ tracesSampler }); const client = new TestClient(options); setCurrentClient(client); client.init(); @@ -570,7 +569,7 @@ describe('BrowserTracing', () => { WINDOW.location = dogParkLocation as any; const tracesSampler = jest.fn(); - const options = getDefaultBrowserClientOptions({ tracesSampler }); + const options = getDefaultClientOptions({ tracesSampler }); const client = new TestClient(options); setCurrentClient(client); client.init(); diff --git a/packages/tracing-internal/test/browser/router.test.ts b/packages/tracing-internal/test/browser/router.test.ts index e7a12ea91506..96f1fb661fe5 100644 --- a/packages/tracing-internal/test/browser/router.test.ts +++ b/packages/tracing-internal/test/browser/router.test.ts @@ -1,8 +1,8 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import type { HandlerDataHistory } from '@sentry/types'; import { JSDOM } from 'jsdom'; +import { conditionalTest } from '../../../node/test/utils'; -import { conditionalTest } from '../../../tracing/test/testutils'; import { instrumentRoutingWithDefaults } from '../../src/browser/router'; let mockChangeHistory: undefined | ((data: HandlerDataHistory) => void); diff --git a/packages/tracing/.eslintrc.js b/packages/tracing/.eslintrc.js deleted file mode 100644 index 7a937173064e..000000000000 --- a/packages/tracing/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - overrides: [ - { - files: ['src/node/**'], - rules: { - '@sentry-internal/sdk/no-optional-chaining': 'off', - }, - }, - ], -}; diff --git a/packages/tracing/LICENSE b/packages/tracing/LICENSE deleted file mode 100644 index 5113ccb2ac3d..000000000000 --- a/packages/tracing/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2020 Sentry (https://sentry.io) and individual contributors. All rights reserved. - -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 the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/tracing/README.md b/packages/tracing/README.md deleted file mode 100644 index 09bf79eb2e89..000000000000 --- a/packages/tracing/README.md +++ /dev/null @@ -1,163 +0,0 @@ -

    - - Sentry - -

    - -> ⚠️ **Deprecation Notice:** From SDK versions >= `7.47.0` onwards, the `@sentry/tracing` package is officially deprecated. This package will be removed in a future major release. More details can be found in the [migration docs](https://github.com/getsentry/sentry-javascript/blob/7.47.0/MIGRATION.md/#remove-requirement-for-sentrytracing-package-since-7460). - -# Sentry Tracing Extensions - -[![npm version](https://img.shields.io/npm/v/@sentry/tracing.svg)](https://www.npmjs.com/package/@sentry/tracing) -[![npm dm](https://img.shields.io/npm/dm/@sentry/tracing.svg)](https://www.npmjs.com/package/@sentry/tracing) -[![npm dt](https://img.shields.io/npm/dt/@sentry/tracing.svg)](https://www.npmjs.com/package/@sentry/tracing) - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -## General - -This package contains extensions to the Sentry SDKs to enable Sentry AM related functionality. It also provides integrations for Browser and Node that provide a good experience out of the box. - -## Migrating from @sentry/apm to @sentry/tracing - -The tracing integration for JavaScript SDKs has moved from -[`@sentry/apm`](https://www.npmjs.com/package/@sentry/apm) to -[`@sentry/tracing`](https://www.npmjs.com/package/@sentry/tracing). While the -two packages are similar, some imports and APIs have changed slightly. - -The old package `@sentry/apm` is deprecated in favor of `@sentry/tracing`. -Future support for `@sentry/apm` is limited to bug fixes only. - -## Migrating from @sentry/apm to @sentry/tracing - -### Browser (CDN bundle) - -If you were using the Browser CDN bundle, switch from the old -`bundle.apm.min.js` to the new tracing bundle: - -```html - -``` - -And then update `Sentry.init`: - -```diff - Sentry.init({ -- integrations: [new Sentry.Integrations.Tracing()] -+ integrations: [new Sentry.Integrations.BrowserTracing()] - }); -``` - -### Browser (npm package) - -If you were using automatic instrumentation, update the import statement and -update `Sentry.init` to use the new `BrowserTracing` integration: - -```diff - import * as Sentry from "@sentry/browser"; --import { Integrations } from "@sentry/apm"; -+import { Integrations } from "@sentry/tracing"; - - Sentry.init({ - integrations: [ -- new Integrations.Tracing(), -+ new Integrations.BrowserTracing(), - ] - }); -``` - -If you were using the `beforeNavigate` option from the `Tracing` integration, -the API in `BrowserTracing` has changed slightly. Instead of passing in a -location and returning a string representing transaction name, `beforeNavigate` -now accepts a transaction context and is expected to return a transaction -context. You can now add extra tags or change the `op` based on different -parameters. If you want to access the location like before, you can get it from -`window.location`. - -For example, if you had a function like so that computed a custom transaction -name: - -```javascript -import * as Sentry from "@sentry/browser"; -import { Integrations } from "@sentry/apm"; - -Sentry.init({ - integrations: [ - new Integrations.Tracing({ - beforeNavigate: location => { - return getTransactionName(location); - }, - }), - ], -}); -``` - -You would now leverage the context to do the same thing: - -```javascript -import * as Sentry from "@sentry/browser"; -import { Integrations } from "@sentry/tracing"; - -Sentry.init({ - integrations: [ - new Integrations.BrowserTracing({ - beforeNavigate: context => { - return { - ...context, - // Can even look at context tags or other data to adjust - // transaction name - name: getTransactionName(window.location), - }; - }, - }), - ], -}); -``` - -For the full diff: - -```diff - import * as Sentry from "@sentry/browser"; --import { Integrations } from "@sentry/apm"; -+import { Integrations } from "@sentry/tracing"; - - Sentry.init({ - integrations: [ -- new Integrations.Tracing({ -- beforeNavigate: (location) => { -- return getTransactionName(location) -+ new Integrations.BrowserTracing({ -+ beforeNavigate: (ctx) => { -+ return { -+ ...ctx, -+ name: getTransactionName(ctx.name, window.location) -+ } - } - }), - ] - }); -``` - -### Node - -If you were using the Express integration for automatic instrumentation, the -only necessary change is to update the import statement: - -```diff - import * as Sentry from "@sentry/node"; --import { Integrations } from "@sentry/apm"; -+import { Integrations } from "@sentry/tracing"; - - Sentry.init({ - integrations: [ - new Integrations.Express(), - ] - }); -``` diff --git a/packages/tracing/jest.config.js b/packages/tracing/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/tracing/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/tracing/package.json b/packages/tracing/package.json deleted file mode 100644 index aa8b44ecdc5c..000000000000 --- a/packages/tracing/package.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "name": "@sentry/tracing", - "version": "7.100.0", - "description": "Sentry Performance Monitoring Package", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tracing", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "files": [ - "cjs", - "esm", - "types", - "types-ts3.8" - ], - "main": "build/npm/cjs/index.js", - "module": "build/npm/esm/index.js", - "types": "build/npm/types/index.d.ts", - "typesVersions": { - "<4.9": { - "build/npm/types/index.d.ts": [ - "build/npm/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@sentry-internal/tracing": "7.100.0" - }, - "devDependencies": { - "@sentry-internal/integration-shims": "7.100.0", - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", - "@types/express": "^4.17.14" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "run-p build:transpile build:types", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8", - "build:watch": "run-p build:transpile:watch build:types:watch", - "build:dev:watch": "run-p build:transpile:watch build:types:watch", - "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", - "clean": "rimraf build coverage sentry-tracing-*.tgz", - "circularDepCheck": "madge --circular src/index.ts", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "test:unit": "jest", - "test": "jest", - "test:watch": "jest --watch", - "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": [ - "./cjs/index.js", - "./esm/index.js", - "./build/npm/cjs/index.js", - "./build/npm/esm/index.js", - "./src/index.ts" - ] -} diff --git a/packages/tracing/rollup.npm.config.mjs b/packages/tracing/rollup.npm.config.mjs deleted file mode 100644 index 6d09adefc859..000000000000 --- a/packages/tracing/rollup.npm.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants( - makeBaseNPMConfig({ - // packages with bundles have a different build directory structure - hasBundles: true, - }), -); diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts deleted file mode 100644 index 2887963e1149..000000000000 --- a/packages/tracing/src/index.ts +++ /dev/null @@ -1,240 +0,0 @@ -import type { - RequestInstrumentationOptions as RequestInstrumentationOptionsT, - SpanStatusType as SpanStatusTypeT, -} from '@sentry-internal/tracing'; -import { - Apollo, - BROWSER_TRACING_INTEGRATION_ID as BROWSER_TRACING_INTEGRATION_ID_T, - BrowserTracing as BrowserTracingT, - Express, - GraphQL, - IdleTransaction as IdleTransactionT, - Mongo, - Mysql, - Postgres, - Prisma, - SpanStatus as SpanStatusT, - TRACEPARENT_REGEXP as TRACEPARENT_REGEXP_T, - Transaction as TransactionT, - addExtensionMethods as addExtensionMethodsT, - defaultRequestInstrumentationOptions as defaultRequestInstrumentationOptionsT, - getActiveTransaction as getActiveTransactionT, - hasTracingEnabled as hasTracingEnabledT, - instrumentOutgoingRequests as instrumentOutgoingRequestsT, - startIdleTransaction as startIdleTransactionT, - stripUrlQueryAndFragment as stripUrlQueryAndFragmentT, -} from '@sentry-internal/tracing'; - -// BrowserTracing is already exported as part of `Integrations` below (and for the moment will remain so for -// backwards compatibility), but that interferes with treeshaking, so we also export it separately -// here. -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `BrowserTracing` can be imported from `@sentry/browser` or your framework SDK - * - * import { BrowserTracing } from '@sentry/browser'; - * new BrowserTracing() - */ -// eslint-disable-next-line deprecation/deprecation -export const BrowserTracing = BrowserTracingT; - -// BrowserTracing is already exported as part of `Integrations` below (and for the moment will remain so for -// backwards compatibility), but that interferes with treeshaking, so we also export it separately -// here. -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `BrowserTracing` can be imported from `@sentry/browser` or your framework SDK - * - * import { BrowserTracing } from '@sentry/browser'; - * new BrowserTracing() - */ -// eslint-disable-next-line deprecation/deprecation -export type BrowserTracing = BrowserTracingT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export const addExtensionMethods = addExtensionMethodsT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `getActiveTransaction` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -// eslint-disable-next-line deprecation/deprecation -export const getActiveTransaction = getActiveTransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `SpanStatusType` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -export type SpanStatusType = SpanStatusTypeT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `Transaction` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -export const Transaction = TransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `Transaction` can be imported from `@sentry/node`, `@sentry/browser`, or your framework SDK - */ -export type Transaction = TransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export const BROWSER_TRACING_INTEGRATION_ID = BROWSER_TRACING_INTEGRATION_ID_T; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `defaultRequestInstrumentationOptions` can be imported from `@sentry/browser`, or your framework SDK - */ -export const defaultRequestInstrumentationOptions = defaultRequestInstrumentationOptionsT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `hasTracingEnabled` can be imported from `@sentry/utils` - */ -export const hasTracingEnabled = hasTracingEnabledT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `stripUrlQueryAndFragment` can be imported from `@sentry/utils` - */ -export const stripUrlQueryAndFragment = stripUrlQueryAndFragmentT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * - * `TRACEPARENT_REGEXP` can be imported from `@sentry/utils` - */ -export const TRACEPARENT_REGEXP = TRACEPARENT_REGEXP_T; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export const IdleTransaction = IdleTransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export type IdleTransaction = IdleTransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export const instrumentOutgoingRequests = instrumentOutgoingRequestsT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export const startIdleTransaction = startIdleTransactionT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -// eslint-disable-next-line deprecation/deprecation -export const SpanStatus = SpanStatusT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -// eslint-disable-next-line deprecation/deprecation -export type SpanStatus = SpanStatusT; - -/** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - */ -export type RequestInstrumentationOptions = RequestInstrumentationOptionsT; - -export const Integrations = { - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `BrowserTracing` can be imported from `@sentry/browser` or your framework SDK - * - * import { BrowserTracing } from '@sentry/browser'; - * new BrowserTracing() - */ - // eslint-disable-next-line deprecation/deprecation - BrowserTracing: BrowserTracing, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Apollo` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Apollo({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Apollo: Apollo, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Express` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Express({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Express: Express, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `GraphQL` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.GraphQL({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - GraphQL: GraphQL, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Mongo` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Mongo({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Mongo: Mongo, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Mysql` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Mysql({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Mysql: Mysql, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Postgres` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Postgres({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Postgres: Postgres, - /** - * @deprecated `@sentry/tracing` has been deprecated and will be moved to to `@sentry/node`, `@sentry/browser`, or your framework SDK in the next major version. - * `Prisma` can be imported from `@sentry/node` - * - * import { Integrations } from '@sentry/node'; - * new Integrations.Prisma({ ... }) - */ - // eslint-disable-next-line deprecation/deprecation - Prisma: Prisma, -}; - -// Treeshakable guard to remove all code related to tracing -declare const __SENTRY_TRACING__: boolean; - -// Guard for tree -if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) { - // We are patching the global object with our hub extension methods - addExtensionMethodsT(); -} diff --git a/packages/tracing/test/index.test.ts b/packages/tracing/test/index.test.ts deleted file mode 100644 index ea35353868ca..000000000000 --- a/packages/tracing/test/index.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; - -import { BrowserTracing, Integrations } from '../src'; - -describe('index', () => { - it('patches the global hub to add an implementation for `Hub.startTransaction` as a side effect', () => { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - // eslint-disable-next-line deprecation/deprecation - const transaction = hub.startTransaction({ name: 'test', endTimestamp: 123 }); - expect(transaction).toBeDefined(); - }); - - describe('Integrations', () => { - it('is exported correctly', () => { - Object.keys(Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - - expect(Integrations[key as keyof Omit].id).toStrictEqual( - expect.any(String), - ); - }); - }); - - it('contains BrowserTracing', () => { - // eslint-disable-next-line deprecation/deprecation - expect(Integrations.BrowserTracing).toEqual(BrowserTracing); - }); - }); -}); diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts deleted file mode 100644 index dbb9a4841c9c..000000000000 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, SentrySpan } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { Integrations } from '../../src'; -import { getTestClient } from '../testutils'; - -type ApolloResolverGroup = { - [key: string]: () => unknown; -}; - -type ApolloModelResolvers = { - [key: string]: ApolloResolverGroup; -}; - -class GraphQLFactory { - _resolvers: ApolloModelResolvers[]; - resolversExplorerService = { - explore: () => this._resolvers, - }; - constructor() { - this._resolvers = [ - { - Query: { - res_1(..._args: unknown[]) { - return 'foo'; - }, - }, - Mutation: { - res_2(..._args: unknown[]) { - return 'bar'; - }, - }, - }, - ]; - - this.mergeWithSchema(); - } - - public mergeWithSchema(..._args: unknown[]) { - return this.resolversExplorerService.explore(); - } -} - -// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. -/* eslint-disable no-var */ -var mockFactory = GraphQLFactory; - -// mock for @nestjs/graphql package -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - loadModule() { - return { - GraphQLFactory: mockFactory, - }; - }, - }; -}); - -describe('setupOnce', () => { - let scope = new Scope(); - let parentSpan: Span; - let childSpan: Span; - let GraphQLFactoryInstance: GraphQLFactory; - - beforeAll(() => { - new Integrations.Apollo({ - useNestjs: true, - }).setupOnce( - () => undefined, - () => new Hub(undefined, scope), - ); - - GraphQLFactoryInstance = new GraphQLFactory(); - }); - - beforeEach(() => { - scope = new Scope(); - parentSpan = new SentrySpan(); - childSpan = parentSpan.startChild(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); - jest.spyOn(scope, 'setSpan'); - jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); - jest.spyOn(childSpan, 'end'); - }); - - it('should wrap a simple resolver', () => { - GraphQLFactoryInstance._resolvers[0]?.['Query']?.['res_1']?.(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'Query.res_1', - op: 'graphql.resolve', - origin: 'auto.graphql.apollo', - }); - expect(childSpan.end).toBeCalled(); - }); - - it('should wrap another simple resolver', () => { - GraphQLFactoryInstance._resolvers[0]?.['Mutation']?.['res_2']?.(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'Mutation.res_2', - op: 'graphql.resolve', - origin: 'auto.graphql.apollo', - }); - expect(childSpan.end).toBeCalled(); - }); - - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - const integration = new Integrations.Apollo({ useNestjs: true }); - integration.setupOnce( - () => {}, - () => hub, - ); - - expect(loggerLogSpy).toBeCalledWith('Apollo Integration is skipped because of instrumenter configuration.'); - }); -}); diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts deleted file mode 100644 index 305acdcee186..000000000000 --- a/packages/tracing/test/integrations/apollo.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, SentrySpan } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { Integrations } from '../../src'; -import { getTestClient } from '../testutils'; - -type ApolloResolverGroup = { - [key: string]: () => any; -}; - -type ApolloModelResolvers = { - [key: string]: ApolloResolverGroup; -}; - -class ApolloServerBase { - config: { - resolvers: ApolloModelResolvers[]; - }; - - constructor() { - this.config = { - resolvers: [ - { - Query: { - res_1(..._args: unknown[]) { - return 'foo'; - }, - }, - Mutation: { - res_2(..._args: unknown[]) { - return 'bar'; - }, - }, - }, - ], - }; - - this.constructSchema(); - } - - public constructSchema(..._args: unknown[]) { - return null; - } -} - -// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. -/* eslint-disable no-var */ -var mockClient = ApolloServerBase; - -// mock for ApolloServer package -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - loadModule() { - return { - ApolloServerBase: mockClient, - }; - }, - }; -}); - -describe('setupOnce', () => { - let scope = new Scope(); - let parentSpan: Span; - let childSpan: Span; - let ApolloServer: ApolloServerBase; - - beforeAll(() => { - new Integrations.Apollo().setupOnce( - () => undefined, - () => new Hub(undefined, scope), - ); - - ApolloServer = new ApolloServerBase(); - }); - - beforeEach(() => { - scope = new Scope(); - parentSpan = new SentrySpan(); - childSpan = parentSpan.startChild(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); - jest.spyOn(scope, 'setSpan'); - jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); - jest.spyOn(childSpan, 'end'); - }); - - it('should wrap a simple resolver', () => { - ApolloServer.config.resolvers[0]?.['Query']?.['res_1']?.(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'Query.res_1', - op: 'graphql.resolve', - origin: 'auto.graphql.apollo', - }); - expect(childSpan.end).toBeCalled(); - }); - - it('should wrap another simple resolver', () => { - ApolloServer.config.resolvers[0]?.['Mutation']?.['res_2']?.(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'Mutation.res_2', - op: 'graphql.resolve', - origin: 'auto.graphql.apollo', - }); - expect(childSpan.end).toBeCalled(); - }); - - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - const integration = new Integrations.Apollo(); - integration.setupOnce( - () => {}, - () => hub, - ); - - expect(loggerLogSpy).toBeCalledWith('Apollo Integration is skipped because of instrumenter configuration.'); - }); -}); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts deleted file mode 100644 index 2b3c3aa7e307..000000000000 --- a/packages/tracing/test/integrations/graphql.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, SentrySpan } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { Integrations } from '../../src'; -import { getTestClient } from '../testutils'; - -const GQLExecute = { - execute() { - return Promise.resolve(); - }, -}; - -// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. -/* eslint-disable no-var */ -var mockClient = GQLExecute; - -// mock for 'graphql/execution/execution.js' package -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - loadModule() { - return mockClient; - }, - }; -}); - -describe('setupOnce', () => { - let scope = new Scope(); - let parentSpan: Span; - let childSpan: Span; - - beforeAll(() => { - new Integrations.GraphQL().setupOnce( - () => undefined, - () => new Hub(undefined, scope), - ); - }); - - beforeEach(() => { - scope = new Scope(); - parentSpan = new SentrySpan(); - childSpan = parentSpan.startChild(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); - jest.spyOn(scope, 'setSpan'); - jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); - jest.spyOn(childSpan, 'end'); - }); - - it('should wrap execute method', async () => { - await GQLExecute.execute(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'execute', - op: 'graphql.execute', - origin: 'auto.graphql.graphql', - }); - expect(childSpan.end).toBeCalled(); - expect(scope.setSpan).toHaveBeenCalledTimes(2); - }); - - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - const integration = new Integrations.GraphQL(); - integration.setupOnce( - () => {}, - () => hub, - ); - - expect(loggerLogSpy).toBeCalledWith('GraphQL Integration is skipped because of instrumenter configuration.'); - }); -}); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts deleted file mode 100644 index d6baafa71e7c..000000000000 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, SentrySpan } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { Integrations } from '../../../src'; -import { getTestClient } from '../../testutils'; - -class Collection { - public collectionName: string = 'mockedCollectionName'; - public dbName: string = 'mockedDbName'; - public namespace: string = 'mockedNamespace'; - - // Method that can have a callback as last argument, or return a promise otherwise. - public insertOne(_doc: unknown, _options: unknown, callback?: () => void) { - if (typeof callback === 'function') { - callback(); - return; - } - return Promise.resolve(); - } - // Method that has no callback as last argument, and doesnt return promise. - public initializeOrderedBulkOp() { - return {}; - } -} - -// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. -/* eslint-disable no-var */ -var mockCollection = Collection; - -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - loadModule() { - return { - Collection: mockCollection, - }; - }, - }; -}); - -describe('patchOperation()', () => { - const doc = { - name: 'PickleRick', - answer: 42, - }; - const collection: Collection = new Collection(); - let scope = new Scope(); - let parentSpan: Span; - let childSpan: Span; - let testClient = getTestClient({}); - - beforeAll(() => { - new Integrations.Mongo({ - operations: ['insertOne', 'initializeOrderedBulkOp'], - }).setupOnce( - () => undefined, - () => new Hub(testClient, scope), - ); - }); - - beforeEach(() => { - scope = new Scope(); - parentSpan = new SentrySpan(); - childSpan = parentSpan.startChild(); - testClient = getTestClient({}); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); - jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); - jest.spyOn(childSpan, 'end'); - }); - - it('should wrap method accepting callback as the last argument', done => { - collection.insertOne(doc, {}, function () { - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - data: { - 'db.mongodb.collection': 'mockedCollectionName', - 'db.name': 'mockedDbName', - 'db.operation': 'insertOne', - 'db.system': 'mongodb', - }, - op: 'db', - origin: 'auto.db.mongo', - name: 'insertOne', - }); - expect(childSpan.end).toBeCalled(); - done(); - }) as void; - }); - - it('should wrap method accepting no callback as the last argument but returning promise', async () => { - await collection.insertOne(doc, {}); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - data: { - 'db.mongodb.collection': 'mockedCollectionName', - 'db.name': 'mockedDbName', - 'db.operation': 'insertOne', - 'db.system': 'mongodb', - }, - op: 'db', - origin: 'auto.db.mongo', - name: 'insertOne', - }); - expect(childSpan.end).toBeCalled(); - }); - - it('attaches mongodb operation spans if sendDefaultPii is enabled', async () => { - testClient.getOptions().sendDefaultPii = true; - await collection.insertOne(doc, {}); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - data: { - 'db.mongodb.collection': 'mockedCollectionName', - 'db.mongodb.doc': '{"name":"PickleRick","answer":42}', - 'db.name': 'mockedDbName', - 'db.operation': 'insertOne', - 'db.system': 'mongodb', - }, - op: 'db', - origin: 'auto.db.mongo', - name: 'insertOne', - }); - expect(childSpan.end).toBeCalled(); - }); - - it('should wrap method accepting no callback as the last argument and not returning promise', () => { - collection.initializeOrderedBulkOp(); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - data: { - 'db.mongodb.collection': 'mockedCollectionName', - 'db.name': 'mockedDbName', - 'db.operation': 'initializeOrderedBulkOp', - 'db.system': 'mongodb', - }, - op: 'db', - origin: 'auto.db.mongo', - name: 'initializeOrderedBulkOp', - }); - expect(childSpan.end).toBeCalled(); - }); - - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - const integration = new Integrations.Mongo(); - integration.setupOnce( - () => {}, - () => hub, - ); - - expect(loggerLogSpy).toBeCalledWith('Mongo Integration is skipped because of instrumenter configuration.'); - }); -}); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts deleted file mode 100644 index 0608fbaf85ad..000000000000 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -/* eslint-disable @typescript-eslint/unbound-method */ -import { Hub, Scope, SentrySpan } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { loadModule, logger } from '@sentry/utils'; -import pg from 'pg'; - -import { Integrations } from '../../../src'; -import { getTestClient } from '../../testutils'; - -class PgClient { - // https://node-postgres.com/api/client#clientquery - public query(_text: unknown, values: unknown, callback?: (err: unknown, result: unknown) => void) { - if (typeof callback === 'function') { - callback(null, null); - return; - } - - if (typeof values === 'function') { - values(); - return; - } - - return Promise.resolve(); - } -} - -// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports. -/* eslint-disable no-var */ -var mockModule = { - Client: PgClient, - native: { - Client: PgClient, - }, -}; - -// mock for 'pg' / 'pg-native' package -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - loadModule: jest.fn(() => mockModule), - }; -}); - -describe('setupOnce', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - ['pg', 'pg-native'].forEach(pgApi => { - const Client: PgClient = new PgClient(); - let scope = new Scope(); - let parentSpan: Span; - let childSpan: Span; - - beforeAll(() => { - (pgApi === 'pg' ? new Integrations.Postgres() : new Integrations.Postgres({ usePgNative: true })).setupOnce( - () => undefined, - () => new Hub(undefined, scope), - ); - }); - - beforeEach(() => { - scope = new Scope(); - parentSpan = new SentrySpan(); - childSpan = parentSpan.startChild(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(parentSpan); - jest.spyOn(parentSpan, 'startChild').mockReturnValueOnce(childSpan); - jest.spyOn(childSpan, 'end'); - }); - - it(`should wrap ${pgApi}'s query method accepting callback as the last argument`, done => { - Client.query('SELECT NOW()', {}, function () { - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'SELECT NOW()', - op: 'db', - origin: 'auto.db.postgres', - data: { - 'db.system': 'postgresql', - }, - }); - expect(childSpan.end).toBeCalled(); - done(); - }) as void; - }); - - it(`should wrap ${pgApi}'s query method accepting callback as the second argument`, done => { - Client.query('SELECT NOW()', function () { - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'SELECT NOW()', - op: 'db', - origin: 'auto.db.postgres', - data: { - 'db.system': 'postgresql', - }, - }); - expect(childSpan.end).toBeCalled(); - done(); - }) as void; - }); - - it(`should wrap ${pgApi}'s query method accepting no callback as the last argument but returning promise`, async () => { - await Client.query('SELECT NOW()', null); - expect(scope.getSpan).toBeCalled(); - expect(parentSpan.startChild).toBeCalledWith({ - name: 'SELECT NOW()', - op: 'db', - origin: 'auto.db.postgres', - data: { - 'db.system': 'postgresql', - }, - }); - expect(childSpan.end).toBeCalled(); - }); - }); - - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - const integration = new Integrations.Postgres(); - integration.setupOnce( - () => {}, - () => hub, - ); - - expect(loggerLogSpy).toBeCalledWith('Postgres Integration is skipped because of instrumenter configuration.'); - }); - - it('does not attempt resolution when module is passed directly', async () => { - const scope = new Scope(); - jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new SentrySpan()); - - new Integrations.Postgres({ module: mockModule }).setupOnce( - () => undefined, - () => new Hub(undefined, scope), - ); - - await new PgClient().query('SELECT NOW()', null); - - expect(loadModule).not.toBeCalled(); - expect(scope.getSpan).toBeCalled(); - }); - - it('has valid module type', () => { - expect(() => new Integrations.Postgres({ module: pg })).not.toThrow(); - }); -}); diff --git a/packages/tracing/test/integrations/node/prisma.test.ts b/packages/tracing/test/integrations/node/prisma.test.ts deleted file mode 100644 index 2541eebf8f91..000000000000 --- a/packages/tracing/test/integrations/node/prisma.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import * as sentryCore from '@sentry/core'; -import { Hub } from '@sentry/core'; - -import { Integrations } from '../../../src'; -import { getTestClient } from '../../testutils'; - -const mockStartSpan = jest.fn(); - -jest.mock('@sentry/core', () => { - const original = jest.requireActual('@sentry/core'); - return { - ...original, - startSpan: (...args: unknown[]) => { - mockStartSpan(...args); - return original.startSpan(...args); - }, - }; -}); - -type PrismaMiddleware = (params: unknown, next: (params?: unknown) => Promise) => Promise; - -class PrismaClient { - public user: { create: () => Promise | undefined } = { - create: () => this._middleware?.({ action: 'create', model: 'user' }, () => Promise.resolve('result')), - }; - - public _engineConfig = { - activeProvider: 'postgresql', - clientVersion: '3.1.2', - }; - - private _middleware?: PrismaMiddleware; - - constructor() { - this._middleware = undefined; - } - - public $use(cb: PrismaMiddleware) { - this._middleware = cb; - } -} - -describe('setupOnce', function () { - beforeEach(() => { - mockStartSpan.mockClear(); - mockStartSpan.mockReset(); - }); - - it('should add middleware with $use method correctly', done => { - const prismaClient = new PrismaClient(); - new Integrations.Prisma({ client: prismaClient }); - void prismaClient.user.create()?.then(() => { - expect(mockStartSpan).toHaveBeenCalledTimes(1); - expect(mockStartSpan).toHaveBeenLastCalledWith( - { - attributes: { - 'sentry.origin': 'auto.db.prisma', - }, - name: 'user create', - onlyIfParent: true, - op: 'db.prisma', - data: { 'db.system': 'postgresql', 'db.prisma.version': '3.1.2', 'db.operation': 'create' }, - }, - expect.any(Function), - ); - done(); - }); - }); - - it("doesn't trace when using otel instrumenter", done => { - const prismaClient = new PrismaClient(); - new Integrations.Prisma({ client: prismaClient }); - - const client = getTestClient({ instrumenter: 'otel' }); - const hub = new Hub(client); - - jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); - - void prismaClient.user.create()?.then(() => { - expect(mockStartSpan).not.toHaveBeenCalled(); - done(); - }); - }); -}); diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts deleted file mode 100644 index 3fc72e58e6d4..000000000000 --- a/packages/tracing/test/span.test.ts +++ /dev/null @@ -1,656 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import { BrowserClient } from '@sentry/browser'; -import { - Hub, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - SentrySpan, - getClient, - getCurrentHub, - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - spanToJSON, -} from '@sentry/core'; -import type { BaseTransportOptions, ClientOptions } from '@sentry/types'; - -import { Transaction } from '../src'; -import { getDefaultBrowserClientOptions } from './testutils'; - -describe('SentrySpan', () => { - beforeEach(() => { - getGlobalScope().clear(); - getIsolationScope().clear(); - getCurrentScope().clear(); - - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const client = new BrowserClient(options); - setCurrentClient(client); - client.init(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('new SentrySpan', () => { - test('simple', () => { - const span = new SentrySpan({ sampled: true }); - const span2 = span.startChild(); - expect((span2 as any).parentSpanId).toBe((span as any).spanId); - expect((span2 as any).traceId).toBe((span as any).traceId); - expect((span2 as any).sampled).toBe((span as any).sampled); - }); - - test('sets instrumenter to `sentry` if not specified in constructor', () => { - const span = new SentrySpan({}); - - expect(span.instrumenter).toBe('sentry'); - }); - - test('allows to set instrumenter in constructor', () => { - const span = new SentrySpan({ instrumenter: 'otel' }); - - expect(span.instrumenter).toBe('otel'); - }); - }); - - describe('new Transaction', () => { - test('simple', () => { - const transaction = new Transaction({ name: 'test', sampled: true }); - const span2 = transaction.startChild(); - expect((span2 as any).parentSpanId).toBe((transaction as any).spanId); - expect((span2 as any).traceId).toBe((transaction as any).traceId); - expect((span2 as any).sampled).toBe((transaction as any).sampled); - }); - - test('gets currentHub', () => { - const transaction = new Transaction({ name: 'test' }); - expect((transaction as any)._hub).toBeInstanceOf(Hub); - }); - - test('inherit span list', () => { - const transaction = new Transaction({ name: 'test', sampled: true }); - const span2 = transaction.startChild(); - const span3 = span2.startChild(); - span3.end(); - expect(transaction.spanRecorder).toBe((span2 as SentrySpan).spanRecorder); - expect(transaction.spanRecorder).toBe((span3 as SentrySpan).spanRecorder); - }); - }); - - describe('setters', () => { - test('setTag', () => { - const span = new SentrySpan({}); - expect(span.tags.foo).toBeUndefined(); - span.setTag('foo', 'bar'); - expect(span.tags.foo).toBe('bar'); - span.setTag('foo', 'baz'); - expect(span.tags.foo).toBe('baz'); - }); - - test('setData', () => { - const span = new SentrySpan({}); - expect(span.data.foo).toBeUndefined(); - span.setData('foo', null); - expect(span.data.foo).toBe(null); - span.setData('foo', 2); - expect(span.data.foo).toBe(2); - span.setData('foo', true); - expect(span.data.foo).toBe(true); - }); - - test('setName', () => { - const span = new SentrySpan({}); - expect(spanToJSON(span).description).toBeUndefined(); - span.updateName('foo'); - expect(spanToJSON(span).description).toBe('foo'); - }); - }); - - describe('status', () => { - test('setStatus', () => { - const span = new SentrySpan({}); - span.setStatus('permission_denied'); - expect((span.getTraceContext() as any).status).toBe('permission_denied'); - }); - - // TODO (v8): Remove - test('setHttpStatus', () => { - const span = new SentrySpan({}); - span.setHttpStatus(404); - expect((span.getTraceContext() as any).status).toBe('not_found'); - expect(span.tags['http.status_code']).toBe('404'); - expect(span.data['http.response.status_code']).toBe(404); - }); - }); - - describe('toJSON', () => { - test('simple', () => { - const span = JSON.parse( - JSON.stringify(new SentrySpan({ traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', spanId: 'bbbbbbbbbbbbbbbb' })), - ); - expect(span).toHaveProperty('span_id', 'bbbbbbbbbbbbbbbb'); - expect(span).toHaveProperty('trace_id', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); - }); - - test('with parent', () => { - const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; - const spanB = new SentrySpan({ traceId: 'c', spanId: 'd', sampled: false, parentSpanId: spanA.spanId }); - const serialized = JSON.parse(JSON.stringify(spanB)); - expect(serialized).toHaveProperty('parent_span_id', 'b'); - expect(serialized).toHaveProperty('span_id', 'd'); - expect(serialized).toHaveProperty('trace_id', 'c'); - }); - - test('should drop all `undefined` values', () => { - const spanA = new SentrySpan({ traceId: 'a', spanId: 'b' }) as any; - const spanB = new SentrySpan({ - parentSpanId: spanA.spanId, - spanId: 'd', - traceId: 'c', - }); - const serialized = spanB.toJSON(); - expect(serialized).toStrictEqual({ - start_timestamp: expect.any(Number), - parent_span_id: 'b', - span_id: 'd', - trace_id: 'c', - origin: 'manual', - data: { - 'sentry.origin': 'manual', - }, - }); - }); - }); - - describe('finish', () => { - test('simple', () => { - const span = new SentrySpan({}); - expect(spanToJSON(span).timestamp).toBeUndefined(); - span.end(); - expect(spanToJSON(span).timestamp).toBeGreaterThan(1); - }); - - describe('hub.startTransaction', () => { - let hub: Hub; - - beforeEach(() => { - hub = getCurrentHub() as Hub; - }); - - test('finish a transaction', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(0); - expect(spy.mock.calls[0][0].timestamp).toBeTruthy(); - expect(spy.mock.calls[0][0].start_timestamp).toBeTruthy(); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test('finish a transaction + child span', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpan = transaction.startChild(); - childSpan.end(); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(1); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - // See https://github.com/getsentry/sentry-javascript/issues/3254 - test('finish a transaction + child span + sampled:true', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test', op: 'parent', sampled: true }); - const childSpan = transaction.startChild({ op: 'child' }); - childSpan.end(); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(1); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test("finish a child span shouldn't trigger captureEvent", () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpan = transaction.startChild(); - childSpan.end(); - expect(spy).not.toHaveBeenCalled(); - }); - - test("finish a span with another one on the scope shouldn't override contexts.trace", () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpanOne = transaction.startChild(); - childSpanOne.end(); - - hub.getScope().setSpan(childSpanOne); - - const spanTwo = transaction.startChild(); - spanTwo.end(); - transaction.end(); - - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(2); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test('no span recorder created if transaction.sampled is false', () => { - const options = getDefaultBrowserClientOptions({ - tracesSampleRate: 1, - }); - const _hub = new Hub(new BrowserClient(options)); - const spy = jest.spyOn(_hub as any, 'captureEvent') as any; - const transaction = _hub.startTransaction({ name: 'test', sampled: false }); - for (let i = 0; i < 10; i++) { - const child = transaction.startChild(); - child.end(); - } - transaction.end(); - expect((transaction as any).spanRecorder).toBeUndefined(); - expect(spy).not.toHaveBeenCalled(); - }); - - test('tree structure of spans should be correct when mixing it with span on scope', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - - const transaction = hub.startTransaction({ name: 'test' }); - const childSpanOne = transaction.startChild(); - - const childSpanTwo = childSpanOne.startChild(); - childSpanTwo.end(); - - childSpanOne.end(); - - hub.getScope().setSpan(transaction); - - const spanTwo = transaction.startChild({}); - spanTwo.end(); - transaction.end(); - - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(3); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - expect(childSpanOne.toJSON().parent_span_id).toEqual(transaction.toJSON().span_id); - expect(childSpanTwo.toJSON().parent_span_id).toEqual(childSpanOne.toJSON().span_id); - expect(spanTwo.toJSON().parent_span_id).toEqual(transaction.toJSON().span_id); - }); - }); - }); - - describe('end', () => { - test('simple', () => { - const span = new SentrySpan({}); - expect(spanToJSON(span).timestamp).toBeUndefined(); - span.end(); - expect(spanToJSON(span).timestamp).toBeGreaterThan(1); - }); - - test('with endTime in seconds', () => { - const span = new SentrySpan({}); - expect(spanToJSON(span).timestamp).toBeUndefined(); - const endTime = Date.now() / 1000; - span.end(endTime); - expect(spanToJSON(span).timestamp).toBe(endTime); - }); - - test('with endTime in milliseconds', () => { - const span = new SentrySpan({}); - expect(spanToJSON(span).timestamp).toBeUndefined(); - const endTime = Date.now(); - span.end(endTime); - expect(spanToJSON(span).timestamp).toBe(endTime / 1000); - }); - - describe('hub.startTransaction', () => { - let hub: Hub; - - beforeEach(() => { - hub = getCurrentHub() as Hub; - }); - - test('finish a transaction', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(0); - expect(spy.mock.calls[0][0].timestamp).toBeTruthy(); - expect(spy.mock.calls[0][0].start_timestamp).toBeTruthy(); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test('finish a transaction + child span', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpan = transaction.startChild(); - childSpan.end(); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(1); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - // See https://github.com/getsentry/sentry-javascript/issues/3254 - test('finish a transaction + child span + sampled:true', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test', op: 'parent', sampled: true }); - const childSpan = transaction.startChild({ op: 'child' }); - childSpan.end(); - transaction.end(); - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(1); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test("finish a child span shouldn't trigger captureEvent", () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpan = transaction.startChild(); - childSpan.end(); - expect(spy).not.toHaveBeenCalled(); - }); - - test("finish a span with another one on the scope shouldn't override contexts.trace", () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test' }); - const childSpanOne = transaction.startChild(); - childSpanOne.end(); - - hub.getScope().setSpan(childSpanOne); - - const spanTwo = transaction.startChild(); - spanTwo.end(); - transaction.end(); - - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(2); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - }); - - test('no span recorder created if transaction.sampled is false', () => { - const options = getDefaultBrowserClientOptions({ - tracesSampleRate: 1, - }); - const _hub = new Hub(new BrowserClient(options)); - const spy = jest.spyOn(_hub as any, 'captureEvent') as any; - const transaction = _hub.startTransaction({ name: 'test', sampled: false }); - for (let i = 0; i < 10; i++) { - const child = transaction.startChild(); - child.end(); - } - transaction.end(); - expect((transaction as any).spanRecorder).toBeUndefined(); - expect(spy).not.toHaveBeenCalled(); - }); - - test('tree structure of spans should be correct when mixing it with span on scope', () => { - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - - const transaction = hub.startTransaction({ name: 'test' }); - const childSpanOne = transaction.startChild(); - - const childSpanTwo = childSpanOne.startChild(); - childSpanTwo.end(); - - childSpanOne.end(); - - hub.getScope().setSpan(transaction); - - const spanTwo = transaction.startChild({}); - spanTwo.end(); - transaction.end(); - - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls[0][0].spans).toHaveLength(3); - expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext()); - expect(childSpanOne.toJSON().parent_span_id).toEqual(transaction.toJSON().span_id); - expect(childSpanTwo.toJSON().parent_span_id).toEqual(childSpanOne.toJSON().span_id); - expect(spanTwo.toJSON().parent_span_id).toEqual(transaction.toJSON().span_id); - }); - }); - }); - - describe('getTraceContext', () => { - test('should have status attribute undefined if no status tag is available', () => { - const span = new SentrySpan({}); - const context = span.getTraceContext(); - expect((context as any).status).toBeUndefined(); - }); - - test('should have success status extracted from tags', () => { - const span = new SentrySpan({}); - span.setStatus('ok'); - const context = span.getTraceContext(); - expect((context as any).status).toBe('ok'); - }); - - test('should have failure status extracted from tags', () => { - const span = new SentrySpan({}); - span.setStatus('resource_exhausted'); - const context = span.getTraceContext(); - expect((context as any).status).toBe('resource_exhausted'); - }); - - test('should drop all `undefined` values', () => { - const spanB = new SentrySpan({ spanId: 'd', traceId: 'c' }); - const context = spanB.getTraceContext(); - expect(context).toStrictEqual({ - span_id: 'd', - trace_id: 'c', - data: { - 'sentry.origin': 'manual', - }, - origin: 'manual', - }); - }); - }); - - describe('toContext and updateWithContext', () => { - test('toContext should return correct context', () => { - const originalContext = { - traceId: 'a', - spanId: 'b', - sampled: false, - name: 'test', - op: 'op', - }; - const span = new SentrySpan(originalContext); - - const newContext = span.toContext(); - - expect(newContext).toStrictEqual({ - ...originalContext, - spanId: expect.any(String), - startTimestamp: expect.any(Number), - tags: {}, - traceId: expect.any(String), - data: { - 'sentry.op': 'op', - 'sentry.origin': 'manual', - }, - }); - }); - - test('updateWithContext should completely change span properties', () => { - const originalContext = { - traceId: 'a', - spanId: 'b', - sampled: false, - name: 'test', - op: 'op', - tags: { - tag0: 'hello', - }, - }; - const span = new SentrySpan(originalContext); - - span.updateWithContext({ - traceId: 'c', - spanId: 'd', - sampled: true, - }); - - expect(span.spanContext().traceId).toBe('c'); - expect(span.spanContext().spanId).toBe('d'); - expect(span.sampled).toBe(true); - expect(spanToJSON(span).description).toBe(undefined); - expect(spanToJSON(span).op).toBe(undefined); - expect(span.tags).toStrictEqual({}); - }); - - test('using toContext and updateWithContext together should update only changed properties', () => { - const originalContext = { - traceId: 'a', - spanId: 'b', - sampled: false, - name: 'test', - op: 'op', - tags: { tag0: 'hello' }, - data: { data0: 'foo' }, - }; - const span = new SentrySpan(originalContext); - - const newContext = { - ...span.toContext(), - name: 'new', - endTimestamp: 1, - op: 'new-op', - sampled: true, - tags: { - tag1: 'bye', - }, - data: { - ...span.toContext().data, - }, - }; - - if (newContext.data) newContext.data.data1 = 'bar'; - - span.updateWithContext(newContext); - - expect(span.spanContext().traceId).toBe('a'); - expect(span.spanContext().spanId).toBe('b'); - expect(spanToJSON(span).description).toBe('new'); - expect(spanToJSON(span).timestamp).toBe(1); - expect(spanToJSON(span).op).toBe('new-op'); - expect(span.sampled).toBe(true); - expect(span.tags).toStrictEqual({ tag1: 'bye' }); - expect(span.data).toStrictEqual({ - data0: 'foo', - data1: 'bar', - 'sentry.op': 'op', - 'sentry.origin': 'manual', - }); - }); - }); - - describe('getDynamicSamplingContext', () => { - beforeEach(() => { - getClient()!.getOptions = () => { - return { - release: '1.0.1', - environment: 'production', - } as ClientOptions; - }; - }); - - test('should return DSC that was provided during transaction creation, if it was provided', () => { - const transaction = new Transaction( - { - name: 'tx', - metadata: { dynamicSamplingContext: { environment: 'myEnv' } }, - }, - getCurrentHub(), - ); - - const dynamicSamplingContext = transaction.getDynamicSamplingContext(); - - expect(dynamicSamplingContext).toStrictEqual({ environment: 'myEnv' }); - }); - - test('should return new DSC, if no DSC was provided during transaction creation', () => { - const transaction = new Transaction({ - name: 'tx', - metadata: { - sampleRate: 0.56, - }, - sampled: true, - }); - - const getOptionsSpy = jest.spyOn(getClient()!, 'getOptions'); - - const dynamicSamplingContext = transaction.getDynamicSamplingContext(); - - expect(getOptionsSpy).toHaveBeenCalledTimes(1); - expect(dynamicSamplingContext).toStrictEqual({ - release: '1.0.1', - environment: 'production', - sampled: 'true', - sample_rate: '0.56', - trace_id: expect.any(String), - transaction: 'tx', - }); - }); - - describe('Including transaction name in DSC', () => { - test('is not included if transaction source is url', () => { - const transaction = new Transaction( - { - name: 'tx', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }, - getCurrentHub(), - ); - - const dsc = transaction.getDynamicSamplingContext()!; - expect(dsc.transaction).toBeUndefined(); - }); - - test.each([ - ['is included if transaction source is paremeterized route/url', 'route'], - ['is included if transaction source is a custom name', 'custom'], - ] as const)('%s', (_, source) => { - const transaction = new Transaction( - { - name: 'tx', - attributes: { - ...(source && { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source }), - }, - }, - getCurrentHub(), - ); - - const dsc = transaction.getDynamicSamplingContext()!; - - expect(dsc.transaction).toEqual('tx'); - }); - }); - }); - - describe('Transaction source', () => { - test('is included when transaction metadata is set', () => { - const hub = getCurrentHub(); - - const spy = jest.spyOn(hub as any, 'captureEvent') as any; - const transaction = hub.startTransaction({ name: 'test', sampled: true }); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url'); - expect(spy).toHaveBeenCalledTimes(0); - - transaction.end(); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith( - expect.objectContaining({ - transaction_info: { - source: 'url', - }, - }), - ); - }); - }); -}); diff --git a/packages/tracing/test/testutils.ts b/packages/tracing/test/testutils.ts deleted file mode 100644 index 5ce79099d38f..000000000000 --- a/packages/tracing/test/testutils.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { createTransport } from '@sentry/browser'; -import type { Client, ClientOptions } from '@sentry/types'; -import { GLOBAL_OBJ, parseSemver, resolvedSyncPromise } from '@sentry/utils'; -import { JSDOM } from 'jsdom'; - -/** - * Injects DOM properties into node global object. - * - * Useful for running tests where some of the tested code is isomorphic (apply to both node and browser). - * Note that not all properties from the browser `window` object are available. - * - * @param properties The names of the properties to add - */ -export function addDOMPropertiesToGlobal(properties: string[]): void { - // we have to add things into the real global object - // because there are modules which call GLOBAL_OBJ as they load, which is too early for jest to intervene - const { window } = new JSDOM('', { url: 'http://dogs.are.great/' }); - - properties.forEach(prop => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - (GLOBAL_OBJ as any)[prop] = window[prop]; - }); -} - -/** - * Returns the symbol with the given description being used as a key in the given object. - * - * In the case where there are multiple symbols in the object with the same description, it throws an error. - * - * @param obj The object whose symbol-type key you want - * @param description The symbol's descriptor - * @returns The first symbol found in the object with the given description, or undefined if not found. - */ -export function getSymbolObjectKeyByName(obj: Record, description: string): symbol | undefined { - const symbols = Object.getOwnPropertySymbols(obj); - - const matches = symbols.filter(sym => sym.toString() === `Symbol(${description})`); - - if (matches.length > 1) { - throw new Error(`More than one symbol key found with description '${description}'.`); - } - - return matches[0] || undefined; -} - -export const testOnlyIfNodeVersionAtLeast = (minVersion: number): jest.It => { - const currentNodeVersion = process.env.NODE_VERSION; - - try { - if (Number(currentNodeVersion?.split('.')[0]) < minVersion) { - return it.skip; - } - } catch (oO) { - // we can't tell, so err on the side of running the test - } - - return it; -}; - -/** - * Returns`describe` or `describe.skip` depending on allowed major versions of Node. - * - * @param {{ min?: number; max?: number }} allowedVersion - * @return {*} {jest.Describe} - */ -export const conditionalTest = (allowedVersion: { min?: number; max?: number }): jest.Describe => { - const NODE_VERSION = parseSemver(process.versions.node).major; - if (!NODE_VERSION) { - return describe.skip; - } - - return NODE_VERSION < (allowedVersion.min || -Infinity) || NODE_VERSION > (allowedVersion.max || Infinity) - ? describe.skip - : describe; -}; - -export function getDefaultBrowserClientOptions(options: Partial = {}): ClientOptions { - return { - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), - stackParser: () => [], - ...options, - }; -} - -export function getTestClient(options: Partial): Client { - class TestClient { - _options: Partial; - - constructor(options: Partial) { - this._options = options; - } - - getOptions(): Partial { - return this._options; - } - } - - return new TestClient(options) as unknown as Client; -} diff --git a/packages/tracing/test/transaction.test.ts b/packages/tracing/test/transaction.test.ts deleted file mode 100644 index 43f5a4d76544..000000000000 --- a/packages/tracing/test/transaction.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import { BrowserClient, Hub } from '@sentry/browser'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - spanToJSON, -} from '@sentry/core'; - -import { Transaction, addExtensionMethods } from '../src'; -import { getDefaultBrowserClientOptions } from './testutils'; - -describe('`Transaction` class', () => { - beforeAll(() => { - addExtensionMethods(); - }); - - describe('transaction name source', () => { - it('sets source in constructor if provided', () => { - const transaction = new Transaction({ - name: 'dogpark', - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' }, - }); - - expect(spanToJSON(transaction).description).toEqual('dogpark'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - }); - - it("sets source to be `'custom'` in constructor if not provided", () => { - const transaction = new Transaction({ name: 'dogpark' }); - - expect(spanToJSON(transaction).description).toEqual('dogpark'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); - }); - - it("sets source to `'custom'` when assigning to `name` property", () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.updateName('ballpit'); - - expect(spanToJSON(transaction).description).toEqual('ballpit'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); - }); - - it('sets instrumenter to be `sentry` in constructor if not provided', () => { - const transaction = new Transaction({ name: 'dogpark' }); - - expect(transaction.instrumenter).toEqual('sentry'); - }); - - it('allows to set instrumenter', () => { - const transaction = new Transaction({ name: 'dogpark', instrumenter: 'otel' }); - - expect(transaction.instrumenter).toEqual('otel'); - }); - - describe('`updateName` method', () => { - it("sets source to `'custom'` if no source provided", () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.updateName('ballpit'); - - expect(spanToJSON(transaction).description).toEqual('ballpit'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); - }); - - it('uses given `source` value', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.updateName('ballpit'); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - - expect(spanToJSON(transaction).description).toEqual('ballpit'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - }); - }); - }); - - describe('setContext', () => { - it('sets context', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.setContext('foo', { - key: 'val', - key2: 'val2', - }); - - // @ts-expect-error accessing private property - expect(transaction._contexts).toEqual({ - foo: { - key: 'val', - key2: 'val2', - }, - }); - }); - - it('overwrites context', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.setContext('foo', { - key: 'val', - key2: 'val2', - }); - transaction.setContext('foo', { - key3: 'val3', - }); - - // @ts-expect-error accessing private property - expect(transaction._contexts).toEqual({ - foo: { - key3: 'val3', - }, - }); - }); - - it('merges context', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.setContext('foo', { - key: 'val', - key2: 'val2', - }); - transaction.setContext('bar', { - anotherKey: 'anotherVal', - }); - - // @ts-expect-error accessing private property - expect(transaction._contexts).toEqual({ - foo: { - key: 'val', - key2: 'val2', - }, - bar: { - anotherKey: 'anotherVal', - }, - }); - }); - - it('deletes context', () => { - const transaction = new Transaction({ name: 'dogpark' }); - transaction.setContext('foo', { - key: 'val', - key2: 'val2', - }); - transaction.setContext('foo', null); - - // @ts-expect-error accessing private property - expect(transaction._contexts).toEqual({}); - }); - - it('sets contexts on the event', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const client = new BrowserClient(options); - const hub = new Hub(client); - - jest.spyOn(hub, 'captureEvent'); - - const transaction = hub.startTransaction({ name: 'dogpark' }); - transaction.setContext('foo', { key: 'val' }); - transaction.end(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(hub.captureEvent).toHaveBeenCalledTimes(1); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(hub.captureEvent).toHaveBeenLastCalledWith( - expect.objectContaining({ - contexts: { - foo: { key: 'val' }, - trace: { - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - span_id: transaction.spanId, - trace_id: transaction.traceId, - origin: 'manual', - }, - }, - }), - ); - }); - - it('does not override trace context', () => { - const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - const client = new BrowserClient(options); - const hub = new Hub(client); - - jest.spyOn(hub, 'captureEvent'); - - const transaction = hub.startTransaction({ name: 'dogpark' }); - transaction.setContext('trace', { key: 'val' }); - transaction.end(); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(hub.captureEvent).toHaveBeenCalledTimes(1); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(hub.captureEvent).toHaveBeenLastCalledWith( - expect.objectContaining({ - contexts: { - trace: { - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - span_id: transaction.spanId, - trace_id: transaction.traceId, - origin: 'manual', - }, - }, - }), - ); - }); - }); -}); diff --git a/packages/tracing/test/utils.test.ts b/packages/tracing/test/utils.test.ts deleted file mode 100644 index e7b582ab94be..000000000000 --- a/packages/tracing/test/utils.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import { hasTracingEnabled } from '../src'; - -describe('hasTracingEnabled (deprecated)', () => { - const tracesSampler = () => 1; - const tracesSampleRate = 1; - it.each([ - ['No options', undefined, false], - ['No tracesSampler or tracesSampleRate or enableTracing', {}, false], - ['With tracesSampler', { tracesSampler }, true], - ['With tracesSampleRate', { tracesSampleRate }, true], - ['With enableTracing=true', { enableTracing: true }, true], - ['With enableTracing=false', { enableTracing: false }, false], - ['With tracesSampler && enableTracing=false', { tracesSampler, enableTracing: false }, true], - ['With tracesSampleRate && enableTracing=false', { tracesSampler, enableTracing: false }, true], - ['With tracesSampler and tracesSampleRate', { tracesSampler, tracesSampleRate }, true], - [ - 'With tracesSampler and tracesSampleRate and enableTracing=true', - { tracesSampler, tracesSampleRate, enableTracing: true }, - true, - ], - [ - 'With tracesSampler and tracesSampleRate and enableTracing=false', - { tracesSampler, tracesSampleRate, enableTracing: false }, - true, - ], - ])( - '%s', - // eslint-disable-next-line deprecation/deprecation - (_: string, input: Parameters[0], output: ReturnType) => { - // eslint-disable-next-line deprecation/deprecation - expect(hasTracingEnabled(input)).toBe(output); - }, - ); -}); diff --git a/packages/tracing/tsconfig.json b/packages/tracing/tsconfig.json deleted file mode 100644 index bf45a09f2d71..000000000000 --- a/packages/tracing/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - // package-specific options - } -} diff --git a/packages/tracing/tsconfig.test.json b/packages/tracing/tsconfig.test.json deleted file mode 100644 index 87f6afa06b86..000000000000 --- a/packages/tracing/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "include": ["test/**/*"], - - "compilerOptions": { - // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] - - // other package-specific, test-specific options - } -} diff --git a/packages/tracing/tsconfig.types.json b/packages/tracing/tsconfig.types.json deleted file mode 100644 index 374fd9bc9364..000000000000 --- a/packages/tracing/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "outDir": "build/npm/types" - } -} diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 5cfb5abdad1d..7380e8228245 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -111,7 +111,7 @@ export interface SpanContext { /** * Completion status of the Span. - * See: {@sentry/tracing SpanStatus} for possible values + * See: {SpanStatusType} for possible values */ status?: string | undefined; @@ -246,7 +246,7 @@ export interface Span extends Omit Date: Fri, 23 Feb 2024 08:45:42 +0100 Subject: [PATCH 143/173] test(browser-integration): Sync loader script fixture (#10795) --- dev-packages/browser-integration-tests/fixtures/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/fixtures/loader.js b/dev-packages/browser-integration-tests/fixtures/loader.js index d35fd69458a4..2f4705810b2f 100644 --- a/dev-packages/browser-integration-tests/fixtures/loader.js +++ b/dev-packages/browser-integration-tests/fixtures/loader.js @@ -1,4 +1,4 @@ -!function(n,e,t,r,i,o,a,c,u){for(var s=u,f=0;f-1){s&&"no"===document.scripts[f].getAttribute("data-lazy")&&(s=!1);break}var p=[];function d(n){return"e"in n}function l(n){return"p"in n}function _(n){return"f"in n}var v=[];function y(n){s&&(d(n)||l(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&L(),v.push(n)}function h(){y({e:[].slice.call(arguments)})}function E(n){y({p:n})}function O(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(t,h),n.removeEventListener(r,E);var a=c;for(var u in i)Object.prototype.hasOwnProperty.call(i,u)&&(a[u]=i[u]);!function(n,e){var t=n.integrations||[];if(!Array.isArray(t))return;var r=t.map((function(n){return n.name}));n.tracesSampleRate&&-1===r.indexOf("BrowserTracing")&&t.push(new e.BrowserTracing);(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===r.indexOf("Replay")&&t.push(new e.Replay);n.integrations=t}(a,e),o(a)},setTimeout((function(){return function(e){try{"function"==typeof n.sentryOnLoad&&(n.sentryOnLoad(),n.sentryOnLoad=void 0);for(var t=0;t-1){u&&"no"===document.scripts[f].getAttribute("data-lazy")&&(u=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[];function y(n){u&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&m(),v.push(n)}function g(){y({e:[].slice.call(arguments)})}function h(n){y({p:n})}function E(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,g),n.removeEventListener(t,h);var a=c;for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(a[s]=i[s]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&(e.BrowserTracing?r.push(new e.BrowserTracing):e.browserTracingIntegration&&r.push(e.browserTracingIntegration()));(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&(e.Replay?r.push(new e.Replay):e.replayIntegration&&r.push(e.replayIntegration()));n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{"function"==typeof n.sentryOnLoad&&(n.sentryOnLoad(),n.sentryOnLoad=void 0);for(var r=0;r Date: Fri, 23 Feb 2024 11:11:43 +0100 Subject: [PATCH 144/173] ref: Remove `BrowserTracing` (#10653) --- .size-limit.js | 14 +- .../onLoad/customBrowserTracing/test.ts | 2 +- .../browserTracingIntegrationHashShim/init.js | 12 - .../template.html | 9 - .../browserTracingIntegrationHashShim/test.ts | 36 -- .../suites/tracing/browserTracingShim/init.js | 12 - .../tracing/browserTracingShim/template.html | 9 - .../suites/tracing/browserTracingShim/test.ts | 33 - .../backgroundtab-custom/init.js | 9 - .../backgroundtab-custom/subject.js | 16 - .../backgroundtab-custom/template.html | 10 - .../backgroundtab-custom/test.ts | 37 -- .../backgroundtab-pageload/subject.js | 8 - .../backgroundtab-pageload/template.html | 9 - .../backgroundtab-pageload/test.ts | 21 - .../browsertracing/http-timings/init.js | 16 - .../browsertracing/http-timings/subject.js | 1 - .../browsertracing/http-timings/test.ts | 58 -- .../suites/tracing/browsertracing/init.js | 9 - .../interactions/assets/script.js | 17 - .../browsertracing/interactions/init.js | 17 - .../browsertracing/interactions/template.html | 12 - .../browsertracing/interactions/test.ts | 114 ---- .../long-tasks-disabled/assets/script.js | 12 - .../long-tasks-disabled/init.js | 9 - .../long-tasks-disabled/template.html | 10 - .../long-tasks-disabled/test.ts | 22 - .../long-tasks-enabled/assets/script.js | 12 - .../browsertracing/long-tasks-enabled/init.js | 13 - .../long-tasks-enabled/template.html | 10 - .../browsertracing/long-tasks-enabled/test.ts | 37 -- .../tracing/browsertracing/meta/init.js | 10 - .../tracing/browsertracing/meta/template.html | 11 - .../tracing/browsertracing/meta/test.ts | 96 --- .../tracing/browsertracing/navigation/test.ts | 51 -- .../tracing/browsertracing/pageload/init.js | 10 - .../tracing/browsertracing/pageload/test.ts | 24 - .../browsertracing/pageloadDelayed/init.js | 13 - .../browsertracing/pageloadDelayed/test.ts | 26 - .../pageloadWithHeartbeatTimeout/init.js | 14 - .../pageloadWithHeartbeatTimeout/test.ts | 26 - .../customTargets/init.js | 10 - .../customTargets/subject.js | 1 - .../customTargets/test.ts | 33 - .../defaultTargetsMatch/init.js | 9 - .../defaultTargetsMatch/subject.js | 1 - .../defaultTargetsMatch/test.ts | 29 - .../defaultTargetsNoMatch/init.js | 9 - .../defaultTargetsNoMatch/subject.js | 1 - .../defaultTargetsNoMatch/test.ts | 29 - .../envelope-header-transaction-name/init.js | 11 +- .../suites/tracing/envelope-header/test.ts | 2 +- .../suites/tracing/metrics/init.js | 2 +- .../create-remix-app-v2/app/entry.client.tsx | 6 +- .../create-remix-app/app/entry.client.tsx | 7 +- .../test-apps/booking-app/with-replay.html | 2 +- .../test-apps/booking-app/with-sentry.html | 2 +- packages/angular/src/tracing.ts | 2 +- packages/astro/test/client/sdk.test.ts | 8 +- .../astro/test/integration/snippets.test.ts | 2 +- packages/browser/src/helpers.ts | 36 +- packages/browser/src/index.bundle.feedback.ts | 24 +- packages/browser/src/index.bundle.replay.ts | 22 +- .../index.bundle.tracing.replay.feedback.ts | 8 +- .../src/index.bundle.tracing.replay.ts | 14 +- packages/browser/src/index.bundle.tracing.ts | 27 +- packages/browser/src/index.bundle.ts | 32 +- packages/browser/src/index.ts | 2 - .../test/unit/index.bundle.feedback.test.ts | 14 +- .../test/unit/index.bundle.replay.test.ts | 14 +- .../browser/test/unit/index.bundle.test.ts | 17 +- ...dex.bundle.tracing.replay.feedback.test.ts | 10 +- .../unit/index.bundle.tracing.replay.test.ts | 15 +- .../test/unit/index.bundle.tracing.test.ts | 18 +- packages/gatsby/test/sdk.test.ts | 10 +- packages/integration-shims/package.json | 1 + .../integration-shims/src/BrowserTracing.ts | 57 +- packages/integration-shims/src/Feedback.ts | 7 +- packages/integration-shims/src/Replay.ts | 7 +- packages/integration-shims/src/index.ts | 14 +- .../src/client/browserTracingIntegration.ts | 2 +- packages/node/test/integrations/http.test.ts | 2 +- .../test/integration/app_v1/entry.client.tsx | 6 +- .../test/integration/app_v2/entry.client.tsx | 6 +- packages/svelte/README.md | 43 +- .../src/client/browserTracingIntegration.ts | 21 - packages/sveltekit/src/client/router.ts | 133 ---- packages/sveltekit/src/client/sdk.ts | 64 +- packages/sveltekit/test/client/router.test.ts | 193 ------ packages/sveltekit/test/client/sdk.test.ts | 61 +- .../src/browser/browserTracingIntegration.ts | 7 +- .../tracing-internal/src/browser/index.ts | 6 +- packages/tracing-internal/src/index.ts | 2 - .../browser/browserTracingIntegration.test.ts | 482 ++++++++++++++ .../test/browser/browsertracing.test.ts | 589 ------------------ packages/types/src/client.ts | 8 +- packages/vue/src/browserTracingIntegration.ts | 2 +- yarn.lock | 15 +- 98 files changed, 661 insertions(+), 2383 deletions(-) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/assets/script.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/assets/script.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/assets/script.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/navigation/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts delete mode 100644 packages/sveltekit/src/client/router.ts delete mode 100644 packages/sveltekit/test/client/router.test.ts delete mode 100644 packages/tracing-internal/test/browser/browsertracing.test.ts diff --git a/.size-limit.js b/.size-limit.js index 5e94a923e656..739c3f62aae1 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -3,28 +3,28 @@ module.exports = [ { name: '@sentry/browser (incl. Tracing, Replay, Feedback) - Webpack (gzipped)', path: 'packages/browser/build/npm/esm/index.js', - import: '{ init, Replay, BrowserTracing, Feedback }', + import: '{ init, Replay, browserTracingIntegration, Feedback }', gzip: true, limit: '90 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - Webpack (gzipped)', path: 'packages/browser/build/npm/esm/index.js', - import: '{ init, Replay, BrowserTracing }', + import: '{ init, Replay, browserTracingIntegration }', gzip: true, limit: '75 KB', }, { name: '@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped)', path: 'packages/browser/build/npm/esm/index.js', - import: '{ init, Replay, BrowserTracing, ReplayCanvas }', + import: '{ init, Replay, browserTracingIntegration, ReplayCanvas }', gzip: true, limit: '90 KB', }, { name: '@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped)', path: 'packages/browser/build/npm/esm/index.js', - import: '{ init, Replay, BrowserTracing }', + import: '{ init, Replay, browserTracingIntegration }', gzip: true, limit: '75 KB', modifyWebpackConfig: function (config) { @@ -43,7 +43,7 @@ module.exports = [ { name: '@sentry/browser (incl. Tracing) - Webpack (gzipped)', path: 'packages/browser/build/npm/esm/index.js', - import: '{ init, BrowserTracing }', + import: '{ init, browserTracingIntegration }', gzip: true, limit: '35 KB', }, @@ -138,7 +138,7 @@ module.exports = [ { name: '@sentry/react (incl. Tracing, Replay) - Webpack (gzipped)', path: 'packages/react/build/esm/index.js', - import: '{ init, BrowserTracing, Replay }', + import: '{ init, browserTracingIntegration, Replay }', gzip: true, limit: '75 KB', }, @@ -154,7 +154,7 @@ module.exports = [ { name: '@sentry/nextjs Client (incl. Tracing, Replay) - Webpack (gzipped)', path: 'packages/nextjs/build/esm/client/index.js', - import: '{ init, BrowserTracing, Replay }', + import: '{ init, browserTracingIntegration, Replay }', gzip: true, limit: '110 KB', }, diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customBrowserTracing/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customBrowserTracing/test.ts index 64b7fb800533..12cf305fab7d 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customBrowserTracing/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customBrowserTracing/test.ts @@ -7,7 +7,7 @@ import { waitForTransactionRequestOnUrl, } from '../../../../utils/helpers'; -sentryTest('should handle custom added BrowserTracing integration', async ({ getLocalTestUrl, page }) => { +sentryTest('should handle custom added browserTracingIntegration instances', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/init.js deleted file mode 100644 index cd05f29615bb..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/init.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - sampleRate: 1, - integrations: [new Sentry.Integrations.BrowserTracing()], -}); - -// This should not fail -Sentry.addTracingExtensions(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/template.html deleted file mode 100644 index 2b3e2f0b27b4..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/test.ts deleted file mode 100644 index e37181ee815b..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationHashShim/test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../utils/helpers'; - -sentryTest( - 'exports a shim Integrations.BrowserTracing integration for non-tracing bundles', - async ({ getLocalTestPath, page }) => { - // Skip in tracing tests - if (!shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const consoleMessages: string[] = []; - page.on('console', msg => consoleMessages.push(msg.text())); - - let requestCount = 0; - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - requestCount++; - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - - expect(requestCount).toBe(0); - expect(consoleMessages).toEqual([ - 'You are using new BrowserTracing() even though this bundle does not include tracing.', - ]); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/init.js deleted file mode 100644 index 95f654dd4428..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/init.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - sampleRate: 1, - integrations: [new Sentry.BrowserTracing()], -}); - -// This should not fail -Sentry.addTracingExtensions(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/template.html deleted file mode 100644 index 2b3e2f0b27b4..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/test.ts deleted file mode 100644 index 7b6027694734..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingShim/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../utils/helpers'; - -sentryTest('exports a shim BrowserTracing integration for non-tracing bundles', async ({ getLocalTestPath, page }) => { - // Skip in tracing tests - if (!shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const consoleMessages: string[] = []; - page.on('console', msg => consoleMessages.push(msg.text())); - - let requestCount = 0; - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - requestCount++; - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - - expect(requestCount).toBe(0); - expect(consoleMessages).toEqual([ - 'You are using new BrowserTracing() even though this bundle does not include tracing.', - ]); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js deleted file mode 100644 index 62685d4b6d18..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing({ idleTimeout: 9000, startTransactionOnPageLoad: false })], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js deleted file mode 100644 index e40426fdbe26..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js +++ /dev/null @@ -1,16 +0,0 @@ -document.getElementById('go-background').addEventListener('click', () => { - Object.defineProperty(document, 'hidden', { value: true, writable: true }); - const ev = document.createEvent('Event'); - ev.initEvent('visibilitychange'); - document.dispatchEvent(ev); -}); - -document.getElementById('start-span').addEventListener('click', () => { - const span = Sentry.startInactiveSpan({ name: 'test-span' }); - window.span = span; - Sentry.getCurrentScope().setSpan(span); -}); - -window.getSpanJson = () => { - return Sentry.spanToJSON(window.span); -}; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html deleted file mode 100644 index 772158d31f51..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/template.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts deleted file mode 100644 index fad37e85d6b4..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect } from '@playwright/test'; -import type { SpanJSON } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should finish a custom transaction when the page goes background', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - page.goto(url); - - await page.locator('#start-span').click(); - const spanJsonBefore: SpanJSON = await page.evaluate('window.getSpanJson()'); - - const id_before = spanJsonBefore.span_id; - const description_before = spanJsonBefore.description; - const status_before = spanJsonBefore.status; - - expect(description_before).toBe('test-span'); - expect(status_before).toBeUndefined(); - - await page.locator('#go-background').click(); - const spanJsonAfter: SpanJSON = await page.evaluate('window.getSpanJson()'); - - const id_after = spanJsonAfter.span_id; - const description_after = spanJsonAfter.description; - const status_after = spanJsonAfter.status; - const data_after = spanJsonAfter.data; - - expect(id_before).toBe(id_after); - expect(description_after).toBe(description_before); - expect(status_after).toBe('cancelled'); - expect(data_after?.['sentry.cancellation_reason']).toBe('document.hidden'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/subject.js deleted file mode 100644 index b657f38ac009..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/subject.js +++ /dev/null @@ -1,8 +0,0 @@ -document.getElementById('go-background').addEventListener('click', () => { - setTimeout(() => { - Object.defineProperty(document, 'hidden', { value: true, writable: true }); - const ev = document.createEvent('Event'); - ev.initEvent('visibilitychange'); - document.dispatchEvent(ev); - }, 250); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/template.html deleted file mode 100644 index 31cfc73ec3c3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/template.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts deleted file mode 100644 index 1feda2e850e5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should finish pageload transaction when the page goes background', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - await page.locator('#go-background').click(); - - const pageloadTransaction = await getFirstSentryEnvelopeRequest(page); - - expect(pageloadTransaction.contexts?.trace?.op).toBe('pageload'); - expect(pageloadTransaction.contexts?.trace?.status).toBe('cancelled'); - expect(pageloadTransaction.contexts?.trace?.data?.['sentry.cancellation_reason']).toBe('document.hidden'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js deleted file mode 100644 index ec8b0cb08034..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js +++ /dev/null @@ -1,16 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - new Sentry.BrowserTracing({ - idleTimeout: 1000, - _experiments: { - enableHTTPTimings: true, - }, - }), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/test.ts deleted file mode 100644 index b6da7522d82c..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should create fetch spans with http timing @firefox', async ({ browserName, getLocalTestPath, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { - sentryTest.skip(); - } - await page.route('http://example.com/*', async route => { - const request = route.request(); - const postData = await request.postDataJSON(); - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify(Object.assign({ id: 1 }, postData)), - }); - }); - - const url = await getLocalTestPath({ testDir: __dirname }); - - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); - const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers - - // eslint-disable-next-line deprecation/deprecation - const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); - - expect(requestSpans).toHaveLength(3); - - await page.pause(); - requestSpans?.forEach((span, index) => - expect(span).toMatchObject({ - description: `GET http://example.com/${index}`, - parent_span_id: tracingEvent.contexts?.trace?.span_id, - span_id: expect.any(String), - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - trace_id: tracingEvent.contexts?.trace?.trace_id, - data: expect.objectContaining({ - 'http.request.redirect_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - 'network.protocol.version': expect.any(String), - }), - }), - ); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js deleted file mode 100644 index 14e743361fd7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/assets/script.js deleted file mode 100644 index a37a2c70ad27..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/assets/script.js +++ /dev/null @@ -1,17 +0,0 @@ -const delay = e => { - const startTime = Date.now(); - - function getElasped() { - const time = Date.now(); - return time - startTime; - } - - while (getElasped() < 70) { - // - } - - e.target.classList.add('clicked'); -}; - -document.querySelector('[data-test-id=interaction-button]').addEventListener('click', delay); -document.querySelector('[data-test-id=annotated-button]').addEventListener('click', delay); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js deleted file mode 100644 index f8f8cf526a6b..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - new Sentry.BrowserTracing({ - idleTimeout: 1000, - _experiments: { - enableInteractions: true, - enableLongTask: false, - }, - }), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/template.html deleted file mode 100644 index 3357fb20a94e..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/template.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - -
    Rendered Before Long Task
    - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/test.ts deleted file mode 100644 index fa9d2889bae3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { SerializedEvent, Span, SpanContext, Transaction } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { - getFirstSentryEnvelopeRequest, - getMultipleSentryEnvelopeRequests, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; - -type TransactionJSON = ReturnType & { - spans: ReturnType[]; - contexts: SpanContext; - platform: string; - type: string; -}; - -const wait = (time: number) => new Promise(res => setTimeout(res, time)); - -sentryTest('should capture interaction transaction. @firefox', async ({ browserName, getLocalTestPath, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - await getFirstSentryEnvelopeRequest(page); - - await page.locator('[data-test-id=interaction-button]').click(); - await page.locator('.clicked[data-test-id=interaction-button]').isVisible(); - - const envelopes = await getMultipleSentryEnvelopeRequests(page, 1); - expect(envelopes).toHaveLength(1); - - const eventData = envelopes[0]; - - expect(eventData.contexts).toMatchObject({ trace: { op: 'ui.action.click' } }); - expect(eventData.platform).toBe('javascript'); - expect(eventData.type).toBe('transaction'); - expect(eventData.spans).toHaveLength(1); - - const interactionSpan = eventData.spans![0]; - expect(interactionSpan.op).toBe('ui.interaction.click'); - expect(interactionSpan.description).toBe('body > button.clicked'); - expect(interactionSpan.timestamp).toBeDefined(); - - const interactionSpanDuration = (interactionSpan.timestamp! - interactionSpan.start_timestamp) * 1000; - expect(interactionSpanDuration).toBeGreaterThan(65); - expect(interactionSpanDuration).toBeLessThan(200); -}); - -sentryTest( - 'should create only one transaction per interaction @firefox', - async ({ browserName, getLocalTestPath, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); - - const url = await getLocalTestPath({ testDir: __dirname }); - await page.goto(url); - await getFirstSentryEnvelopeRequest(page); - - for (let i = 0; i < 4; i++) { - await wait(100); - await page.locator('[data-test-id=interaction-button]').click(); - const envelope = await getMultipleSentryEnvelopeRequests(page, 1); - expect(envelope[0].spans).toHaveLength(1); - } - }, -); - -sentryTest( - 'should use the component name for a clicked element when it is available', - async ({ browserName, getLocalTestPath, page }) => { - const supportedBrowsers = ['chromium', 'firefox']; - - if (shouldSkipTracingTest() || !supportedBrowsers.includes(browserName)) { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), - ); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - await getFirstSentryEnvelopeRequest(page); - - await page.locator('[data-test-id=annotated-button]').click(); - - const envelopes = await getMultipleSentryEnvelopeRequests(page, 1); - expect(envelopes).toHaveLength(1); - const eventData = envelopes[0]; - - expect(eventData.spans).toHaveLength(1); - - const interactionSpan = eventData.spans![0]; - expect(interactionSpan.op).toBe('ui.interaction.click'); - expect(interactionSpan.description).toBe('body > AnnotatedButton'); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/assets/script.js deleted file mode 100644 index 9ac3d6fb33d2..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/assets/script.js +++ /dev/null @@ -1,12 +0,0 @@ -(() => { - const startTime = Date.now(); - - function getElasped() { - const time = Date.now(); - return time - startTime; - } - - while (getElasped() < 101) { - // - } -})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js deleted file mode 100644 index 8dba00211a01..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing({ enableLongTask: false, idleTimeout: 9000 })], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/template.html deleted file mode 100644 index 5c3a14114991..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/template.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -
    Rendered Before Long Task
    - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts deleted file mode 100644 index d460d2883afd..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should not capture long task when flag is disabled.', async ({ browserName, getLocalTestPath, page }) => { - // Long tasks only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); - - expect(uiSpans?.length).toBe(0); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/assets/script.js deleted file mode 100644 index 5a2aef02028d..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/assets/script.js +++ /dev/null @@ -1,12 +0,0 @@ -(() => { - const startTime = Date.now(); - - function getElasped() { - const time = Date.now(); - return time - startTime; - } - - while (getElasped() < 105) { - // - } -})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js deleted file mode 100644 index 155966847b1c..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - new Sentry.BrowserTracing({ - idleTimeout: 9000, - }), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/template.html deleted file mode 100644 index 5c3a14114991..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/template.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -
    Rendered Before Long Task
    - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts deleted file mode 100644 index 1ed0bcda2a89..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, page }) => { - // Long tasks only work on chrome - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); - - expect(uiSpans?.length).toBeGreaterThan(0); - - const [firstUISpan] = uiSpans || []; - expect(firstUISpan).toEqual( - expect.objectContaining({ - op: 'ui.long-task', - description: 'Main UI thread blocked', - parent_span_id: eventData.contexts?.trace?.span_id, - }), - ); - const start = firstUISpan.start_timestamp ?? 0; - const end = firstUISpan.timestamp ?? 0; - const duration = end - start; - - expect(duration).toBeGreaterThanOrEqual(0.1); - expect(duration).toBeLessThanOrEqual(0.15); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js deleted file mode 100644 index e6f49fa89562..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing()], - tracesSampleRate: 1, - environment: 'staging', -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/template.html b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/template.html deleted file mode 100644 index 09984cb0c488..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/template.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/test.ts deleted file mode 100644 index ae89fd383cbb..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event, EventEnvelopeHeaders } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; - -sentryTest( - 'should create a pageload transaction based on `sentry-trace` ', - async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.contexts?.trace).toMatchObject({ - op: 'pageload', - parent_span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - }); - - expect(eventData.spans?.length).toBeGreaterThan(0); - }, -); - -sentryTest( - 'should pick up `baggage` tag, propagate the content in transaction and not add own data', - async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); - - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ - release: '2.1.12', - sample_rate: '0.3232', - trace_id: '123', - public_key: 'public', - }); - }, -); - -sentryTest( - "should create a navigation that's not influenced by `sentry-trace` ", - async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const pageloadRequest = await getFirstSentryEnvelopeRequest(page, url); - const navigationRequest = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); - - expect(pageloadRequest.contexts?.trace).toMatchObject({ - op: 'pageload', - parent_span_id: '1121201211212012', - trace_id: '12312012123120121231201212312012', - }); - - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - expect(navigationRequest.contexts?.trace?.trace_id).toBeDefined(); - expect(navigationRequest.contexts?.trace?.trace_id).not.toBe(pageloadRequest.contexts?.trace?.trace_id); - - const pageloadSpans = pageloadRequest.spans; - const navigationSpans = navigationRequest.spans; - - const pageloadSpanId = pageloadRequest.contexts?.trace?.span_id; - const navigationSpanId = navigationRequest.contexts?.trace?.span_id; - - expect(pageloadSpanId).toBeDefined(); - expect(navigationSpanId).toBeDefined(); - - pageloadSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: pageloadSpanId, - }), - ); - - navigationSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: navigationSpanId, - }), - ); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/navigation/test.ts deleted file mode 100644 index 5a46a65a4392..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/navigation/test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should create a navigation transaction on page navigation', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const pageloadRequest = await getFirstSentryEnvelopeRequest(page, url); - const navigationRequest = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); - - expect(pageloadRequest.contexts?.trace?.op).toBe('pageload'); - expect(navigationRequest.contexts?.trace?.op).toBe('navigation'); - - expect(navigationRequest.transaction_info?.source).toEqual('url'); - - const pageloadTraceId = pageloadRequest.contexts?.trace?.trace_id; - const navigationTraceId = navigationRequest.contexts?.trace?.trace_id; - - expect(pageloadTraceId).toBeDefined(); - expect(navigationTraceId).toBeDefined(); - expect(pageloadTraceId).not.toEqual(navigationTraceId); - - const pageloadSpans = pageloadRequest.spans; - const navigationSpans = navigationRequest.spans; - - const pageloadSpanId = pageloadRequest.contexts?.trace?.span_id; - const navigationSpanId = navigationRequest.contexts?.trace?.span_id; - - expect(pageloadSpanId).toBeDefined(); - expect(navigationSpanId).toBeDefined(); - - pageloadSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: pageloadSpanId, - }), - ); - - navigationSpans?.forEach(span => - expect(span).toMatchObject({ - parent_span_id: navigationSpanId, - }), - ); - - expect(pageloadSpanId).not.toEqual(navigationSpanId); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js deleted file mode 100644 index a1e77dae58c2..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; -window._testBaseTimestamp = performance.timeOrigin / 1000; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/test.ts deleted file mode 100644 index 6a186b63b02a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should create a pageload transaction', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const timeOrigin = await page.evaluate('window._testBaseTimestamp'); - - const { start_timestamp: startTimestamp } = eventData; - - expect(startTimestamp).toBeCloseTo(timeOrigin, 1); - - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('url'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js deleted file mode 100644 index 6e4774650261..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; -window._testBaseTimestamp = performance.timeOrigin / 1000; - -setTimeout(() => { - window._testTimeoutTimestamp = (performance.timeOrigin + performance.now()) / 1000; - Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing()], - tracesSampleRate: 1, - }); -}, 250); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/test.ts deleted file mode 100644 index 882c08d23c5e..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should create a pageload transaction when initialized delayed', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - const timeOrigin = await page.evaluate('window._testBaseTimestamp'); - const timeoutTimestamp = await page.evaluate('window._testTimeoutTimestamp'); - - const { start_timestamp: startTimestamp } = eventData; - - expect(startTimestamp).toBeCloseTo(timeOrigin, 1); - expect(startTimestamp).toBeLessThan(timeoutTimestamp); - - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect(eventData.spans?.length).toBeGreaterThan(0); - expect(eventData.transaction_info?.source).toEqual('url'); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js deleted file mode 100644 index 2a4797a74a15..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js +++ /dev/null @@ -1,14 +0,0 @@ -import * as Sentry from '@sentry/browser'; -import { startSpanManual } from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing()], - tracesSampleRate: 1, -}); - -setTimeout(() => { - startSpanManual({ name: 'pageload-child-span' }, () => {}); -}, 200); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts deleted file mode 100644 index ead37d6f8662..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -// This tests asserts that the pageload transaction will finish itself after about 15 seconds (3x5s of heartbeats) if it -// has a child span without adding any additional ones or finishing any of them finishing. All of the child spans that -// are still running should have the status "cancelled". -sentryTest( - 'should send a pageload transaction terminated via heartbeat timeout', - async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.contexts?.trace?.op).toBe('pageload'); - expect( - eventData.spans?.find(span => span.description === 'pageload-child-span' && span.status === 'cancelled'), - ).toBeDefined(); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/init.js deleted file mode 100644 index 7cd076a052e5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/init.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], - tracePropagationTargets: ['http://example.com'], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts deleted file mode 100644 index fb6e9e540c46..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - 'should attach `sentry-trace` and `baggage` header to request matching tracePropagationTargets', - async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const requests = ( - await Promise.all([ - page.goto(url), - Promise.all([0, 1, 2].map(idx => page.waitForRequest(`http://example.com/${idx}`))), - ]) - )[1]; - - expect(requests).toHaveLength(3); - - for (const request of requests) { - const requestHeaders = request.headers(); - - expect(requestHeaders).toMatchObject({ - 'sentry-trace': expect.any(String), - baggage: expect.any(String), - }); - } - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/init.js deleted file mode 100644 index 83076460599f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js deleted file mode 100644 index 7e662b55c333..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('/0').then(fetch('/1').then(fetch('/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts deleted file mode 100644 index f9f9af3ddb47..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - 'should attach `sentry-trace` and `baggage` header to same-origin requests when no tracePropagationTargets are defined', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const requests = ( - await Promise.all([page.goto(url), Promise.all([0, 1, 2].map(idx => page.waitForRequest(`**/${idx}`)))]) - )[1]; - - expect(requests).toHaveLength(3); - - for (const request of requests) { - const requestHeaders = request.headers(); - expect(requestHeaders).toMatchObject({ - 'sentry-trace': expect.any(String), - baggage: expect.any(String), - }); - } - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js deleted file mode 100644 index 83076460599f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://example.com/0').then(fetch('http://example.com/1').then(fetch('http://example.com/2'))); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts deleted file mode 100644 index 6739b7ce3621..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - 'should not attach `sentry-trace` and `baggage` header to cross-origin requests when no tracePropagationTargets are defined', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const requests = ( - await Promise.all([page.goto(url), Promise.all([0, 1, 2].map(idx => page.waitForRequest(`**/${idx}`)))]) - )[1]; - - expect(requests).toHaveLength(3); - - for (const request of requests) { - const requestHeaders = request.headers(); - expect(requestHeaders).not.toMatchObject({ - 'sentry-trace': expect.any(String), - baggage: expect.any(String), - }); - } - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index 647e6af5e963..8ac3d814b2bd 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -5,16 +5,17 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.BrowserTracing({ tracingOrigins: [/.*/] })], + integrations: [Sentry.browserTracingIntegration()], environment: 'production', tracesSampleRate: 1, debug: true, }); -const scope = Sentry.getCurrentScope(); -scope.setUser({ id: 'user123' }); -scope.addEventProcessor(event => { +Sentry.setUser({ id: 'user123' }); + +Sentry.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; }); -scope.getTransaction().setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); + +Sentry.getActiveSpan().setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts index 0d7e2b793553..6df352af5fc0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts @@ -21,7 +21,7 @@ sentryTest( // In this test, we don't expect trace.transaction to be present because without a custom routing instrumentation // we for now don't have parameterization. This might change in the future but for now the only way of having - // transaction in DSC with the default BrowserTracing integration is when the transaction name is set manually. + // transaction in DSC with the default browserTracingIntegration is when the transaction name is set manually. // This scenario is covered in another integration test (envelope-header-transaction-name). expect(envHeader.trace).toBeDefined(); expect(envHeader.trace).toEqual({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js index 155966847b1c..ad1d8832b228 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js @@ -5,7 +5,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ - new Sentry.BrowserTracing({ + Sentry.browserTracingIntegration({ idleTimeout: 9000, }), ], diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx index a56d13dddadc..1f1a48d28fbd 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/app/entry.client.tsx @@ -13,8 +13,10 @@ Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: window.ENV.SENTRY_DSN, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches), + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, }), new Sentry.Replay(), ], diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx index a56d13dddadc..d5a8978376a4 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx @@ -12,12 +12,7 @@ import { hydrateRoot } from 'react-dom/client'; Sentry.init({ environment: 'qa', // dynamic sampling bias to keep transactions dsn: window.ENV.SENTRY_DSN, - integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches), - }), - new Sentry.Replay(), - ], + integrations: [Sentry.browserTracingIntegration({ useEffect, useMatches, useLocation }), new Sentry.Replay()], // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! // Session Replay diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html index 7f471cc12c94..21f19f6260be 100644 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html +++ b/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html @@ -223,7 +223,7 @@

    This is a test app.

    replaysOnErrorSampleRate: 1.0, enableTracing: true, integrations: [ - new Sentry.Integrations.BrowserTracing(), + Sentry.browserTracingIntegration(), new Sentry.Integrations.Replay({ useCompression: true, flushMinDelay: 2000, diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html index cd122c172ad2..94c581f184ab 100644 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html +++ b/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html @@ -220,7 +220,7 @@

    This is a test app.

    Sentry.init({ dsn: 'https://d16ae2d36f9249849c7964e9a3a8a608@o447951.ingest.sentry.io/5429213', enableTracing: true, - integrations: [new Sentry.Integrations.BrowserTracing()], + integrations: [Sentry.browserTracingIntegration()], }); diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index e07ee34f6516..4ac977c66f48 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -67,7 +67,7 @@ export function routingInstrumentation( export const instrumentAngularRouting = routingInstrumentation; /** - * A custom BrowserTracing integration for Angular. + * A custom browser tracing integration for Angular. * * Use this integration in combination with `TraceService` */ diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index 311287bfc533..50f4e3c9e354 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -56,7 +56,7 @@ describe('Sentry client SDK', () => { ['tracesSampleRate', { tracesSampleRate: 0 }], ['tracesSampler', { tracesSampler: () => 1.0 }], ['enableTracing', { enableTracing: true }], - ])('adds the BrowserTracing integration if tracing is enabled via %s', (_, tracingOptions) => { + ])('adds browserTracingIntegration if tracing is enabled via %s', (_, tracingOptions) => { init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', ...tracingOptions, @@ -72,7 +72,7 @@ describe('Sentry client SDK', () => { it.each([ ['enableTracing', { enableTracing: false }], ['no tracing option set', {}], - ])("doesn't add the BrowserTracing integration if tracing is disabled via %s", (_, tracingOptions) => { + ])("doesn't add browserTracingIntegration if tracing is disabled via %s", (_, tracingOptions) => { init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', ...tracingOptions, @@ -85,7 +85,7 @@ describe('Sentry client SDK', () => { expect(browserTracing).toBeUndefined(); }); - it("doesn't add the BrowserTracing integration if `__SENTRY_TRACING__` is set to false", () => { + it("doesn't add browserTracingIntegration if `__SENTRY_TRACING__` is set to false", () => { globalThis.__SENTRY_TRACING__ = false; init({ @@ -102,7 +102,7 @@ describe('Sentry client SDK', () => { delete globalThis.__SENTRY_TRACING__; }); - it('Overrides the automatically default BrowserTracing instance with a a user-provided browserTracingIntegration instance', () => { + it('Overrides the automatically default browserTracingIntegration instance with a a user-provided browserTracingIntegration instance', () => { init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ diff --git a/packages/astro/test/integration/snippets.test.ts b/packages/astro/test/integration/snippets.test.ts index ddd7188d9b18..b89522c78c89 100644 --- a/packages/astro/test/integration/snippets.test.ts +++ b/packages/astro/test/integration/snippets.test.ts @@ -50,7 +50,7 @@ describe('buildClientSnippet', () => { `); }); - it('does not include BrowserTracing if tracesSampleRate is 0', () => { + it('does not include browserTracingIntegration if tracesSampleRate is 0', () => { const snippet = buildClientSnippet({ tracesSampleRate: 0 }); expect(snippet).toMatchInlineSnapshot(` "import * as Sentry from \\"@sentry/astro\\"; diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 6b9df98f1751..a232d24044dc 100644 --- a/packages/browser/src/helpers.ts +++ b/packages/browser/src/helpers.ts @@ -1,7 +1,5 @@ -import type { browserTracingIntegration } from '@sentry-internal/tracing'; -import { BrowserTracing } from '@sentry-internal/tracing'; import { captureException, withScope } from '@sentry/core'; -import type { Integration, Mechanism, WrappedFunction } from '@sentry/types'; +import type { Mechanism, WrappedFunction } from '@sentry/types'; import { GLOBAL_OBJ, addExceptionMechanism, @@ -155,35 +153,3 @@ export function wrap( return sentryWrapped; } - -/** - * This is a slim shim of `browserTracingIntegration` for the CDN bundles. - * Since the actual functional integration uses a different code from `BrowserTracing`, - * we want to avoid shipping both of them in the CDN bundles, as that would blow up the size. - * Instead, we provide a functional integration with the same API, but the old implementation. - * This means that it's not possible to register custom routing instrumentation, but that's OK for now. - * We also don't expose the utilities for this anyhow in the CDN bundles. - * For users that need custom routing in CDN bundles, they have to continue using `new BrowserTracing()` until v8. - */ -export function bundleBrowserTracingIntegration( - options: Parameters[0] = {}, -): Integration { - // Migrate some options from the old integration to the new one - // eslint-disable-next-line deprecation/deprecation - const opts: ConstructorParameters[0] = options; - - if (typeof options.markBackgroundSpan === 'boolean') { - opts.markBackgroundTransactions = options.markBackgroundSpan; - } - - if (typeof options.instrumentPageLoad === 'boolean') { - opts.startTransactionOnPageLoad = options.instrumentPageLoad; - } - - if (typeof options.instrumentNavigation === 'boolean') { - opts.startTransactionOnLocationChange = options.instrumentNavigation; - } - - // eslint-disable-next-line deprecation/deprecation - return new BrowserTracing(opts); -} diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index 8e653c2d4757..7cb0501e1a92 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -1,31 +1,25 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { Feedback, feedbackIntegration } from '@sentry-internal/feedback'; import { - BrowserTracing, - Replay, - addTracingExtensions, - browserTracingIntegration, - replayIntegration, + ReplayShim, + addTracingExtensionsShim, + browserTracingIntegrationShim, + replayIntegrationShim, } from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; // TODO (v8): Remove this as it was only needed for backwards compatibility // eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.Replay = Replay; - -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; +Sentry.Integrations.Replay = ReplayShim; export * from './index.bundle.base'; export { + browserTracingIntegrationShim as browserTracingIntegration, + addTracingExtensionsShim as addTracingExtensions, // eslint-disable-next-line deprecation/deprecation - BrowserTracing, - browserTracingIntegration, - addTracingExtensions, - // eslint-disable-next-line deprecation/deprecation - Replay, - replayIntegration, + ReplayShim as Replay, + replayIntegrationShim as replayIntegration, // eslint-disable-next-line deprecation/deprecation Feedback, feedbackIntegration, diff --git a/packages/browser/src/index.bundle.replay.ts b/packages/browser/src/index.bundle.replay.ts index 2e4619ab49ea..71baed234eba 100644 --- a/packages/browser/src/index.bundle.replay.ts +++ b/packages/browser/src/index.bundle.replay.ts @@ -1,10 +1,9 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { - BrowserTracing, - Feedback, - addTracingExtensions, - browserTracingIntegration, - feedbackIntegration, + FeedbackShim, + addTracingExtensionsShim, + browserTracingIntegrationShim, + feedbackIntegrationShim, } from '@sentry-internal/integration-shims'; import { Replay, replayIntegration } from '@sentry/replay'; @@ -14,20 +13,15 @@ import * as Sentry from './index.bundle.base'; // eslint-disable-next-line deprecation/deprecation Sentry.Integrations.Replay = Replay; -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; - export * from './index.bundle.base'; export { - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, - browserTracingIntegration, - addTracingExtensions, + browserTracingIntegrationShim as browserTracingIntegration, + addTracingExtensionsShim as addTracingExtensions, // eslint-disable-next-line deprecation/deprecation Replay, replayIntegration, // eslint-disable-next-line deprecation/deprecation - Feedback, - feedbackIntegration, + FeedbackShim as Feedback, + feedbackIntegrationShim as feedbackIntegration, }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index b103b6f4583d..0e75d5319ee2 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,8 +1,7 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/feedback'; -import { BrowserTracing } from '@sentry-internal/tracing'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import { addTracingExtensions } from '@sentry/core'; import { Replay, replayIntegration } from '@sentry/replay'; -import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; import * as Sentry from './index.bundle.base'; @@ -12,9 +11,6 @@ import * as Sentry from './index.bundle.base'; // eslint-disable-next-line deprecation/deprecation Sentry.Integrations.Replay = Replay; -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; - // We are patching the global object with our hub extension methods addTracingExtensions(); @@ -25,8 +21,6 @@ export { Replay, feedbackIntegration, replayIntegration, - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, browserTracingIntegration, addTracingExtensions, }; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index e817390aa39d..5b5fdef07bf6 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,8 +1,7 @@ -import { Feedback, feedbackIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing } from '@sentry-internal/tracing'; +import { FeedbackShim, feedbackIntegrationShim } from '@sentry-internal/integration-shims'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import { addTracingExtensions } from '@sentry/core'; import { Replay, replayIntegration } from '@sentry/replay'; -import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; import * as Sentry from './index.bundle.base'; @@ -12,21 +11,16 @@ import * as Sentry from './index.bundle.base'; // eslint-disable-next-line deprecation/deprecation Sentry.Integrations.Replay = Replay; -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; - // We are patching the global object with our hub extension methods addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation - Feedback, + FeedbackShim as Feedback, // eslint-disable-next-line deprecation/deprecation Replay, replayIntegration, - feedbackIntegration, - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, + feedbackIntegrationShim as feedbackIntegration, browserTracingIntegration, addTracingExtensions, }; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 704a646dcdb8..e3e75924c10f 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,32 +1,31 @@ // This is exported so the loader does not fail when switching off Replay -import { Feedback, Replay, feedbackIntegration, replayIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing } from '@sentry-internal/tracing'; +import { + FeedbackShim, + ReplayShim, + feedbackIntegrationShim, + replayIntegrationShim, +} from '@sentry-internal/integration-shims'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import { addTracingExtensions } from '@sentry/core'; -import { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; import * as Sentry from './index.bundle.base'; -// TODO (v8): Remove this as it was only needed for backwards compatibility +// TODO(v8): Remove this as it was only needed for backwards compatibility // We want replay to be available under Sentry.Replay, to be consistent // with the NPM package version. // eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.Replay = Replay; - -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; +Sentry.Integrations.Replay = ReplayShim; // We are patching the global object with our hub extension methods addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation - Feedback, - // eslint-disable-next-line deprecation/deprecation - Replay, - feedbackIntegration, - replayIntegration, + FeedbackShim as Feedback, // eslint-disable-next-line deprecation/deprecation - BrowserTracing, + ReplayShim as Replay, + feedbackIntegrationShim as feedbackIntegration, + replayIntegrationShim as replayIntegration, browserTracingIntegration, addTracingExtensions, }; diff --git a/packages/browser/src/index.bundle.ts b/packages/browser/src/index.bundle.ts index 3087d7d317ca..b113bd56d142 100644 --- a/packages/browser/src/index.bundle.ts +++ b/packages/browser/src/index.bundle.ts @@ -1,34 +1,28 @@ // This is exported so the loader does not fail when switching off Replay/Tracing import { - BrowserTracing, - Feedback, - Replay, - addTracingExtensions, - browserTracingIntegration, - feedbackIntegration, - replayIntegration, + FeedbackShim, + ReplayShim, + addTracingExtensionsShim, + browserTracingIntegrationShim, + feedbackIntegrationShim, + replayIntegrationShim, } from '@sentry-internal/integration-shims'; import * as Sentry from './index.bundle.base'; // TODO (v8): Remove this as it was only needed for backwards compatibility // eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.Replay = Replay; - -// eslint-disable-next-line deprecation/deprecation -Sentry.Integrations.BrowserTracing = BrowserTracing; +Sentry.Integrations.Replay = ReplayShim; export * from './index.bundle.base'; export { + addTracingExtensionsShim as addTracingExtensions, // eslint-disable-next-line deprecation/deprecation - BrowserTracing, - addTracingExtensions, - // eslint-disable-next-line deprecation/deprecation - Replay, + ReplayShim as Replay, // eslint-disable-next-line deprecation/deprecation - Feedback, - browserTracingIntegration, - feedbackIntegration, - replayIntegration, + FeedbackShim as Feedback, + browserTracingIntegrationShim as browserTracingIntegration, + feedbackIntegrationShim as feedbackIntegration, + replayIntegrationShim as replayIntegration, }; // Note: We do not export a shim for `Span` here, as that is quite complex and would blow up the bundle diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 9957e290acf5..a7e8bc2d8b0c 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -55,8 +55,6 @@ export { } from '@sentry-internal/feedback'; export { - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, defaultRequestInstrumentationOptions, instrumentOutgoingRequests, browserTracingIntegration, diff --git a/packages/browser/test/unit/index.bundle.feedback.test.ts b/packages/browser/test/unit/index.bundle.feedback.test.ts index 5fe2940d1881..91475a34bb02 100644 --- a/packages/browser/test/unit/index.bundle.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.feedback.test.ts @@ -1,9 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { - BrowserTracing as BrowserTracingShim, - Replay as ReplayShim, - replayIntegration as replayIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { ReplayShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; import { Feedback, feedbackIntegration } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.feedback'; @@ -11,11 +7,6 @@ import * as TracingReplayBundle from '../../src/index.bundle.feedback'; describe('index.bundle.feedback', () => { it('has correct exports', () => { Object.keys(TracingReplayBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingReplayBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -23,9 +14,6 @@ describe('index.bundle.feedback', () => { expect(TracingReplayBundle.Replay).toBe(ReplayShim); expect(TracingReplayBundle.replayIntegration).toBe(replayIntegrationShim); - expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingReplayBundle.Feedback).toBe(Feedback); expect(TracingReplayBundle.feedbackIntegration).toBe(feedbackIntegration); }); diff --git a/packages/browser/test/unit/index.bundle.replay.test.ts b/packages/browser/test/unit/index.bundle.replay.test.ts index e2d5640a2183..a40daa2ea0d6 100644 --- a/packages/browser/test/unit/index.bundle.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.replay.test.ts @@ -1,9 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { - BrowserTracing as BrowserTracingShim, - Feedback as FeedbackShim, - feedbackIntegration as feedbackIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { FeedbackShim, feedbackIntegrationShim } from '@sentry-internal/integration-shims'; import { Replay, replayIntegration } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.replay'; @@ -11,11 +7,6 @@ import * as TracingReplayBundle from '../../src/index.bundle.replay'; describe('index.bundle.replay', () => { it('has correct exports', () => { Object.keys(TracingReplayBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingReplayBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -23,9 +14,6 @@ describe('index.bundle.replay', () => { expect(TracingReplayBundle.Replay).toBe(Replay); expect(TracingReplayBundle.replayIntegration).toBe(replayIntegration); - expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingReplayBundle.Feedback).toBe(FeedbackShim); expect(TracingReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); }); diff --git a/packages/browser/test/unit/index.bundle.test.ts b/packages/browser/test/unit/index.bundle.test.ts index d637c8de1c3d..bf498477c73c 100644 --- a/packages/browser/test/unit/index.bundle.test.ts +++ b/packages/browser/test/unit/index.bundle.test.ts @@ -1,10 +1,9 @@ /* eslint-disable deprecation/deprecation */ import { - BrowserTracing as BrowserTracingShim, - Feedback as FeedbackShim, - Replay as ReplayShim, - feedbackIntegration as feedbackIntegrationShim, - replayIntegration as replayIntegrationShim, + FeedbackShim, + ReplayShim, + feedbackIntegrationShim, + replayIntegrationShim, } from '@sentry-internal/integration-shims'; import * as TracingBundle from '../../src/index.bundle'; @@ -12,11 +11,6 @@ import * as TracingBundle from '../../src/index.bundle'; describe('index.bundle', () => { it('has correct exports', () => { Object.keys(TracingBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -24,9 +18,6 @@ describe('index.bundle', () => { expect(TracingBundle.Replay).toBe(ReplayShim); expect(TracingBundle.replayIntegration).toBe(replayIntegrationShim); - expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingBundle.BrowserTracing).toBe(BrowserTracingShim); - expect(TracingBundle.Feedback).toBe(FeedbackShim); expect(TracingBundle.feedbackIntegration).toBe(feedbackIntegrationShim); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts index 29b700773d92..962934f064f8 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { BrowserTracing } from '@sentry-internal/tracing'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import { Feedback, Replay, feedbackIntegration, replayIntegration } from '@sentry/browser'; import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.replay.feedback'; @@ -7,11 +7,6 @@ import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.rep describe('index.bundle.tracing.replay.feedback', () => { it('has correct exports', () => { Object.keys(TracingReplayFeedbackBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingReplayFeedbackBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -19,8 +14,7 @@ describe('index.bundle.tracing.replay.feedback', () => { expect(TracingReplayFeedbackBundle.Replay).toBe(Replay); expect(TracingReplayFeedbackBundle.replayIntegration).toBe(replayIntegration); - expect(TracingReplayFeedbackBundle.Integrations.BrowserTracing).toBe(BrowserTracing); - expect(TracingReplayFeedbackBundle.BrowserTracing).toBe(BrowserTracing); + expect(TracingReplayFeedbackBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingReplayFeedbackBundle.Feedback).toBe(Feedback); expect(TracingReplayFeedbackBundle.feedbackIntegration).toBe(feedbackIntegration); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts index 4db32003607e..a90eac6cbe60 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts @@ -1,9 +1,6 @@ /* eslint-disable deprecation/deprecation */ -import { - Feedback as FeedbackShim, - feedbackIntegration as feedbackIntegrationShim, -} from '@sentry-internal/integration-shims'; -import { BrowserTracing } from '@sentry-internal/tracing'; +import { FeedbackShim, feedbackIntegrationShim } from '@sentry-internal/integration-shims'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import { Replay, replayIntegration } from '@sentry/browser'; import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; @@ -11,11 +8,6 @@ import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; describe('index.bundle.tracing.replay', () => { it('has correct exports', () => { Object.keys(TracingReplayBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingReplayBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -23,8 +15,7 @@ describe('index.bundle.tracing.replay', () => { expect(TracingReplayBundle.Replay).toBe(Replay); expect(TracingReplayBundle.replayIntegration).toBe(replayIntegration); - expect(TracingReplayBundle.Integrations.BrowserTracing).toBe(BrowserTracing); - expect(TracingReplayBundle.BrowserTracing).toBe(BrowserTracing); + expect(TracingReplayBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingReplayBundle.Feedback).toBe(FeedbackShim); expect(TracingReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); diff --git a/packages/browser/test/unit/index.bundle.tracing.test.ts b/packages/browser/test/unit/index.bundle.tracing.test.ts index cf03a26f7054..0f8257ee4ad0 100644 --- a/packages/browser/test/unit/index.bundle.tracing.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.test.ts @@ -1,22 +1,17 @@ /* eslint-disable deprecation/deprecation */ import { - Feedback as FeedbackShim, - Replay as ReplayShim, - feedbackIntegration as feedbackIntegrationShim, - replayIntegration as replayIntegrationShim, + FeedbackShim, + ReplayShim, + feedbackIntegrationShim, + replayIntegrationShim, } from '@sentry-internal/integration-shims'; -import { BrowserTracing } from '@sentry-internal/tracing'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; import * as TracingBundle from '../../src/index.bundle.tracing'; describe('index.bundle.tracing', () => { it('has correct exports', () => { Object.keys(TracingBundle.Integrations).forEach(key => { - // Skip BrowserTracing because it doesn't have a static id field. - if (key === 'BrowserTracing') { - return; - } - expect((TracingBundle.Integrations[key] as any).id).toStrictEqual(expect.any(String)); }); @@ -24,8 +19,7 @@ describe('index.bundle.tracing', () => { expect(TracingBundle.Replay).toBe(ReplayShim); expect(TracingBundle.replayIntegration).toBe(replayIntegrationShim); - expect(TracingBundle.Integrations.BrowserTracing).toBe(BrowserTracing); - expect(TracingBundle.BrowserTracing).toBe(BrowserTracing); + expect(TracingBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingBundle.Feedback).toBe(FeedbackShim); expect(TracingBundle.feedbackIntegration).toBe(feedbackIntegrationShim); diff --git a/packages/gatsby/test/sdk.test.ts b/packages/gatsby/test/sdk.test.ts index 28206d1ef6c5..7abdc82c6834 100644 --- a/packages/gatsby/test/sdk.test.ts +++ b/packages/gatsby/test/sdk.test.ts @@ -48,7 +48,7 @@ describe('Initialize React SDK', () => { }); }); - test('Has BrowserTracing if tracing enabled', () => { + test('Has browserTracingIntegration if tracing enabled', () => { gatsbyInit({ tracesSampleRate: 1 }); expect(reactInit).toHaveBeenCalledTimes(1); const calledWith = reactInit.mock.calls[0][0]; @@ -66,13 +66,13 @@ describe('Integrations from options', () => { ['tracing disabled, no integrations', [], {}, []], ['tracing enabled, no integrations', [], { tracesSampleRate: 1 }, ['BrowserTracing']], [ - 'tracing disabled, with BrowserTracing as an array', + 'tracing disabled, with browserTracingIntegration as an array', [], { integrations: [browserTracingIntegration()] }, ['BrowserTracing'], ], [ - 'tracing disabled, with BrowserTracing as a function', + 'tracing disabled, with browserTracingIntegration as a function', [], { integrations: () => [browserTracingIntegration()], @@ -80,13 +80,13 @@ describe('Integrations from options', () => { ['BrowserTracing'], ], [ - 'tracing enabled, with BrowserTracing as an array', + 'tracing enabled, with browserTracingIntegration as an array', [], { tracesSampleRate: 1, integrations: [browserTracingIntegration()] }, ['BrowserTracing'], ], [ - 'tracing enabled, with BrowserTracing as a function', + 'tracing enabled, with browserTracingIntegration as a function', [], { tracesSampleRate: 1, integrations: () => [browserTracingIntegration()] }, ['BrowserTracing'], diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index 5bc5f40f039d..3d2c599058c8 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -39,6 +39,7 @@ "url": "https://github.com/getsentry/sentry-javascript/issues" }, "dependencies": { + "@sentry/core": "7.100.0", "@sentry/types": "7.100.0", "@sentry/utils": "7.100.0" }, diff --git a/packages/integration-shims/src/BrowserTracing.ts b/packages/integration-shims/src/BrowserTracing.ts index 1c68faf30469..32841d842718 100644 --- a/packages/integration-shims/src/BrowserTracing.ts +++ b/packages/integration-shims/src/BrowserTracing.ts @@ -1,58 +1,23 @@ -import type { Integration } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; import { consoleSandbox } from '@sentry/utils'; /** * This is a shim for the BrowserTracing integration. * It is needed in order for the CDN bundles to continue working when users add/remove tracing * from it, without changing their config. This is necessary for the loader mechanism. - * - * @deprecated Use `browserTracingIntegration()` instead. */ -class BrowserTracingShim implements Integration { - /** - * @inheritDoc - */ - public static id: string = 'BrowserTracing'; +export const browserTracingIntegrationShim = defineIntegration((_options?: unknown) => { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn('You are using new BrowserTracing() even though this bundle does not include tracing.'); + }); - /** - * @inheritDoc - */ - public name: string; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public constructor(_options: any) { - // eslint-disable-next-line deprecation/deprecation - this.name = BrowserTracingShim.id; - - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn('You are using new BrowserTracing() even though this bundle does not include tracing.'); - }); - } - - /** jsdoc */ - public setupOnce(): void { - // noop - } -} - -/** - * This is a shim for the BrowserTracing integration. - * It is needed in order for the CDN bundles to continue working when users add/remove tracing - * from it, without changing their config. This is necessary for the loader mechanism. - */ -function browserTracingIntegrationShim(_options: unknown): Integration { - // eslint-disable-next-line deprecation/deprecation - return new BrowserTracingShim({}); -} - -export { - // eslint-disable-next-line deprecation/deprecation - BrowserTracingShim as BrowserTracing, - browserTracingIntegrationShim as browserTracingIntegration, -}; + return { + name: 'BrowserTracing', + }; +}); /** Shim function */ -export function addTracingExtensions(): void { +export function addTracingExtensionsShim(): void { // noop } diff --git a/packages/integration-shims/src/Feedback.ts b/packages/integration-shims/src/Feedback.ts index 7b717e3a4e3b..419071d2bd26 100644 --- a/packages/integration-shims/src/Feedback.ts +++ b/packages/integration-shims/src/Feedback.ts @@ -8,7 +8,7 @@ import { consoleSandbox } from '@sentry/utils'; * * @deprecated Use `feedbackIntegration()` instead. */ -class FeedbackShim implements Integration { +export class FeedbackShim implements Integration { /** * @inheritDoc */ @@ -75,10 +75,7 @@ class FeedbackShim implements Integration { * It is needed in order for the CDN bundles to continue working when users add/remove feedback * from it, without changing their config. This is necessary for the loader mechanism. */ -export function feedbackIntegration(_options: unknown): Integration { +export function feedbackIntegrationShim(_options: unknown): Integration { // eslint-disable-next-line deprecation/deprecation return new FeedbackShim({}); } - -// eslint-disable-next-line deprecation/deprecation -export { FeedbackShim as Feedback }; diff --git a/packages/integration-shims/src/Replay.ts b/packages/integration-shims/src/Replay.ts index 3bcc6c3e6563..ddebdbb14402 100644 --- a/packages/integration-shims/src/Replay.ts +++ b/packages/integration-shims/src/Replay.ts @@ -8,7 +8,7 @@ import { consoleSandbox } from '@sentry/utils'; * * @deprecated Use `replayIntegration()` instead. */ -class ReplayShim implements Integration { +export class ReplayShim implements Integration { /** * @inheritDoc */ @@ -56,10 +56,7 @@ class ReplayShim implements Integration { * It is needed in order for the CDN bundles to continue working when users add/remove replay * from it, without changing their config. This is necessary for the loader mechanism. */ -export function replayIntegration(_options: unknown): Integration { +export function replayIntegrationShim(_options: unknown): Integration { // eslint-disable-next-line deprecation/deprecation return new ReplayShim({}); } - -// eslint-disable-next-line deprecation/deprecation -export { ReplayShim as Replay }; diff --git a/packages/integration-shims/src/index.ts b/packages/integration-shims/src/index.ts index bffdf82c99f7..64124f4a93cc 100644 --- a/packages/integration-shims/src/index.ts +++ b/packages/integration-shims/src/index.ts @@ -1,18 +1,16 @@ export { // eslint-disable-next-line deprecation/deprecation - Feedback, - feedbackIntegration, + FeedbackShim, + feedbackIntegrationShim, } from './Feedback'; export { // eslint-disable-next-line deprecation/deprecation - Replay, - replayIntegration, + ReplayShim, + replayIntegrationShim, } from './Replay'; export { - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, - browserTracingIntegration, - addTracingExtensions, + browserTracingIntegrationShim, + addTracingExtensionsShim, } from './BrowserTracing'; diff --git a/packages/nextjs/src/client/browserTracingIntegration.ts b/packages/nextjs/src/client/browserTracingIntegration.ts index 42af9f9a2045..d70eb3da0746 100644 --- a/packages/nextjs/src/client/browserTracingIntegration.ts +++ b/packages/nextjs/src/client/browserTracingIntegration.ts @@ -7,7 +7,7 @@ import type { Integration, StartSpanOptions } from '@sentry/types'; import { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation'; /** - * A custom BrowserTracing integration for Next.js. + * A custom browser tracing integration for Next.js. */ export function browserTracingIntegration( options: Parameters[0] = {}, diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 63559d9102d7..c2e85f8ac170 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,7 +1,7 @@ import * as http from 'http'; import * as https from 'https'; -import type { Hub, SentrySpan } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import { getCurrentHub, getIsolationScope, setCurrentClient } from '@sentry/core'; import { Transaction } from '@sentry/core'; import { getCurrentScope, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; diff --git a/packages/remix/test/integration/app_v1/entry.client.tsx b/packages/remix/test/integration/app_v1/entry.client.tsx index 17da03ff6a70..bdee1860d03f 100644 --- a/packages/remix/test/integration/app_v1/entry.client.tsx +++ b/packages/remix/test/integration/app_v1/entry.client.tsx @@ -7,8 +7,10 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches), + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, }), ], }); diff --git a/packages/remix/test/integration/app_v2/entry.client.tsx b/packages/remix/test/integration/app_v2/entry.client.tsx index 17da03ff6a70..bdee1860d03f 100644 --- a/packages/remix/test/integration/app_v2/entry.client.tsx +++ b/packages/remix/test/integration/app_v2/entry.client.tsx @@ -7,8 +7,10 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches), + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, }), ], }); diff --git a/packages/svelte/README.md b/packages/svelte/README.md index 5ea7bbed0fb8..bccd18a3c744 100644 --- a/packages/svelte/README.md +++ b/packages/svelte/README.md @@ -10,28 +10,29 @@ [![npm dm](https://img.shields.io/npm/dm/@sentry/svelte.svg)](https://www.npmjs.com/package/@sentry/svelte) [![npm dt](https://img.shields.io/npm/dt/@sentry/svelte.svg)](https://www.npmjs.com/package/@sentry/svelte) -This SDK currently only supports [Svelte](https://svelte.dev/) apps in the browser. -If you're using SvelteKit, we recommend using our dedicated [Sentry SvelteKit SDK](https://github.com/getsentry/sentry-javascript/tree/develop/packages/sveltekit). +This SDK currently only supports [Svelte](https://svelte.dev/) apps in the browser. If you're using SvelteKit, we +recommend using our dedicated +[Sentry SvelteKit SDK](https://github.com/getsentry/sentry-javascript/tree/develop/packages/sveltekit). ## General -This package is a wrapper around `@sentry/browser`, providing error monitoring and basic performance monitoring -features for [Svelte](https://svelte.dev/). +This package is a wrapper around `@sentry/browser`, providing error monitoring and basic performance monitoring features +for [Svelte](https://svelte.dev/). To use the SDK, initialize Sentry in your Svelte entry point `main.js` before you bootstrap your Svelte app: ```ts // main.js / main.ts -import App from "./App.svelte"; +import App from './App.svelte'; -import * as Sentry from "@sentry/svelte"; +import * as Sentry from '@sentry/svelte'; // Initialize the Sentry SDK here Sentry.init({ - dsn: "__DSN__", - release: "my-project-name@2.3.12", - integrations: [new Sentry.BrowserTracing()], + dsn: '__DSN__', + release: 'my-project-name@2.3.12', + integrations: [Sentry.browserTracingIntegration()], // Set tracesSampleRate to 1.0 to capture 100% // of transactions for performance monitoring. @@ -41,19 +42,25 @@ Sentry.init({ // Then bootstrap your Svelte app const app = new App({ - target: document.getElementById("app"), + target: document.getElementById('app'), }); export default app; ``` -The Sentry Svelte SDK supports all features from the `@sentry/browser` SDK. Until it becomes more stable, please refer to the Sentry [Browser SDK documentation](https://docs.sentry.io/platforms/javascript/) for more information and usage instructions. +The Sentry Svelte SDK supports all features from the `@sentry/browser` SDK. Until it becomes more stable, please refer +to the Sentry [Browser SDK documentation](https://docs.sentry.io/platforms/javascript/) for more information and usage +instructions. ## Sourcemaps and Releases -To generate source maps of your Svelte app bundle, check our guide [how to configure your bundler](https://docs.sentry.io/platforms/javascript/guides/svelte/sourcemaps/generating/) to emit source maps. +To generate source maps of your Svelte app bundle, check our guide +[how to configure your bundler](https://docs.sentry.io/platforms/javascript/guides/svelte/sourcemaps/generating/) to +emit source maps. -To [create releases and upload source maps](https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/cli/) to Sentry, we recommend using [`sentry-cli`](https://github.com/getsentry/sentry-cli). You can for instance create a bash script to take care of creating a release, uploading source maps and finalizing the release: +To [create releases and upload source maps](https://docs.sentry.io/platforms/javascript/sourcemaps/uploading/cli/) to +Sentry, we recommend using [`sentry-cli`](https://github.com/getsentry/sentry-cli). You can for instance create a bash +script to take care of creating a release, uploading source maps and finalizing the release: ```bash #!/bin/bash @@ -69,6 +76,12 @@ sentry-cli releases files $VERSION upload-sourcemaps $SOURCEMAPS_PATH --org $ORG sentry-cli releases finalize $VERSION --org $ORG --project $PROJECT ``` -Please note that the paths provided in this example work for a typical Svelte project that adheres to the project structure set by [create-vite](https://www.npmjs.com/package/create-vite) with the `svelte(-ts)` template. If your project setup differs from this template, your configuration may need adjustments. Please refer to our documentation of [Advanced `sentry-cli` Sourcemaps Options](https://docs.sentry.io/product/cli/releases/#sentry-cli-sourcemaps) and to our [Sourcemaps Troubleshooting Guide](https://docs.sentry.io/platforms/javascript/sourcemaps/troubleshooting_js/). +Please note that the paths provided in this example work for a typical Svelte project that adheres to the project +structure set by [create-vite](https://www.npmjs.com/package/create-vite) with the `svelte(-ts)` template. If your +project setup differs from this template, your configuration may need adjustments. Please refer to our documentation of +[Advanced `sentry-cli` Sourcemaps Options](https://docs.sentry.io/product/cli/releases/#sentry-cli-sourcemaps) and to +our [Sourcemaps Troubleshooting Guide](https://docs.sentry.io/platforms/javascript/sourcemaps/troubleshooting_js/). -Check out our [Svelte source maps uploading](https://docs.sentry.io/platforms/javascript/guides/svelte/sourcemaps/uploading/) guide for more information. +Check out our +[Svelte source maps uploading](https://docs.sentry.io/platforms/javascript/guides/svelte/sourcemaps/uploading/) guide +for more information. diff --git a/packages/sveltekit/src/client/browserTracingIntegration.ts b/packages/sveltekit/src/client/browserTracingIntegration.ts index ab727e9d76f8..42817d49e22a 100644 --- a/packages/sveltekit/src/client/browserTracingIntegration.ts +++ b/packages/sveltekit/src/client/browserTracingIntegration.ts @@ -1,7 +1,6 @@ import { navigating, page } from '$app/stores'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { - BrowserTracing as OriginalBrowserTracing, WINDOW, browserTracingIntegration as originalBrowserTracingIntegration, startBrowserTracingNavigationSpan, @@ -10,26 +9,6 @@ import { } from '@sentry/svelte'; import type { Client, Integration, Span } from '@sentry/types'; import { dropUndefinedKeys } from '@sentry/utils'; -import { svelteKitRoutingInstrumentation } from './router'; - -/** - * A custom BrowserTracing integration for Sveltekit. - * - * @deprecated use `browserTracingIntegration()` instead. The new `browserTracingIntegration()` - * includes SvelteKit-specific routing instrumentation out of the box. Therefore there's no need - * to pass in `svelteKitRoutingInstrumentation` anymore. - */ -// eslint-disable-next-line deprecation/deprecation -export class BrowserTracing extends OriginalBrowserTracing { - // eslint-disable-next-line deprecation/deprecation - public constructor(options?: ConstructorParameters[0]) { - super({ - // eslint-disable-next-line deprecation/deprecation - routingInstrumentation: svelteKitRoutingInstrumentation, - ...options, - }); - } -} /** * A custom `BrowserTracing` integration for SvelteKit. diff --git a/packages/sveltekit/src/client/router.ts b/packages/sveltekit/src/client/router.ts deleted file mode 100644 index cb38aad2ea3a..000000000000 --- a/packages/sveltekit/src/client/router.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveTransaction } from '@sentry/core'; -import { WINDOW } from '@sentry/svelte'; -import type { Span, Transaction, TransactionContext } from '@sentry/types'; - -import { navigating, page } from '$app/stores'; - -const DEFAULT_TAGS = { - 'routing.instrumentation': '@sentry/sveltekit', -}; - -/** - * Automatically creates pageload and navigation transactions for the client-side SvelteKit router. - * - * This instrumentation makes use of SvelteKit's `page` and `navigating` stores which can be accessed - * anywhere on the client side. - * - * @param startTransactionFn the function used to start (idle) transactions - * @param startTransactionOnPageLoad controls if pageload transactions should be created (defaults to `true`) - * @param startTransactionOnLocationChange controls if navigation transactions should be created (defauls to `true`) - * - * @deprecated use `browserTracingIntegration()` instead which includes SvelteKit-specific routing instrumentation out of the box. - * Therefore, this function will be removed in v8. - */ -export function svelteKitRoutingInstrumentation( - startTransactionFn: (context: TransactionContext) => T | undefined, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, -): void { - if (startTransactionOnPageLoad) { - instrumentPageload(startTransactionFn); - } - - if (startTransactionOnLocationChange) { - instrumentNavigations(startTransactionFn); - } -} - -function instrumentPageload(startTransactionFn: (context: TransactionContext) => Transaction | undefined): void { - const initialPath = WINDOW && WINDOW.location && WINDOW.location.pathname; - - const pageloadTransaction = startTransactionFn({ - name: initialPath, - op: 'pageload', - origin: 'auto.pageload.sveltekit', - tags: { - ...DEFAULT_TAGS, - }, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }); - - page.subscribe(page => { - if (!page) { - return; - } - - const routeId = page.route && page.route.id; - - if (pageloadTransaction && routeId) { - pageloadTransaction.updateName(routeId); - pageloadTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - } - }); -} - -/** - * Use the `navigating` store to start a transaction on navigations. - */ -function instrumentNavigations(startTransactionFn: (context: TransactionContext) => Transaction | undefined): void { - let routingSpan: Span | undefined = undefined; - let activeTransaction: Transaction | undefined; - - navigating.subscribe(navigation => { - if (!navigation) { - // `navigating` emits a 'null' value when the navigation is completed. - // So in this case, we can finish the routing span. If the transaction was an IdleTransaction, - // it will finish automatically and if it was user-created users also need to finish it. - if (routingSpan) { - routingSpan.end(); - routingSpan = undefined; - } - return; - } - - const from = navigation.from; - const to = navigation.to; - - // for the origin we can fall back to window.location.pathname because in this emission, it still is set to the origin path - const rawRouteOrigin = (from && from.url.pathname) || (WINDOW && WINDOW.location && WINDOW.location.pathname); - - const rawRouteDestination = to && to.url.pathname; - - // We don't want to create transactions for navigations of same origin and destination. - // We need to look at the raw URL here because parameterized routes can still differ in their raw parameters. - if (rawRouteOrigin === rawRouteDestination) { - return; - } - - const parameterizedRouteOrigin = from && from.route.id; - const parameterizedRouteDestination = to && to.route.id; - - // eslint-disable-next-line deprecation/deprecation - activeTransaction = getActiveTransaction(); - - if (!activeTransaction) { - activeTransaction = startTransactionFn({ - name: parameterizedRouteDestination || rawRouteDestination || 'unknown', - op: 'navigation', - origin: 'auto.navigation.sveltekit', - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedRouteDestination ? 'route' : 'url' }, - tags: { - ...DEFAULT_TAGS, - }, - }); - } - - if (activeTransaction) { - if (routingSpan) { - // If a routing span is still open from a previous navigation, we finish it. - routingSpan.end(); - } - // eslint-disable-next-line deprecation/deprecation - routingSpan = activeTransaction.startChild({ - op: 'ui.sveltekit.routing', - name: 'SvelteKit Route Change', - origin: 'auto.ui.sveltekit', - }); - // eslint-disable-next-line deprecation/deprecation - activeTransaction.setTag('from', parameterizedRouteOrigin); - } - }); -} diff --git a/packages/sveltekit/src/client/sdk.ts b/packages/sveltekit/src/client/sdk.ts index a3c996457c46..86b869af65d3 100644 --- a/packages/sveltekit/src/client/sdk.ts +++ b/packages/sveltekit/src/client/sdk.ts @@ -1,13 +1,10 @@ import { applySdkMetadata, hasTracingEnabled, setTag } from '@sentry/core'; -import type { BrowserOptions, browserTracingIntegration } from '@sentry/svelte'; +import type { BrowserOptions } from '@sentry/svelte'; import { getDefaultIntegrations as getDefaultSvelteIntegrations } from '@sentry/svelte'; import { WINDOW, init as initSvelteSdk } from '@sentry/svelte'; import type { Integration } from '@sentry/types'; -import { - BrowserTracing, - browserTracingIntegration as svelteKitBrowserTracingIntegration, -} from './browserTracingIntegration'; +import { browserTracingIntegration as svelteKitBrowserTracingIntegration } from './browserTracingIntegration'; type WindowWithSentryFetchProxy = typeof WINDOW & { _sentryFetchProxy?: typeof fetch; @@ -29,8 +26,6 @@ export function init(options: BrowserOptions): void { applySdkMetadata(opts, 'sveltekit', ['sveltekit', 'svelte']); - fixBrowserTracingIntegration(opts); - // 1. Switch window.fetch to our fetch proxy we injected earlier const actualFetch = switchToFetchProxy(); @@ -45,61 +40,6 @@ export function init(options: BrowserOptions): void { setTag('runtime', 'browser'); } -// TODO v8: Remove this again -// We need to handle BrowserTracing passed to `integrations` that comes from `@sentry/tracing`, not `@sentry/sveltekit` :( -function fixBrowserTracingIntegration(options: BrowserOptions): void { - const { integrations } = options; - if (!integrations) { - return; - } - - if (Array.isArray(integrations)) { - options.integrations = maybeUpdateBrowserTracingIntegration(integrations); - } else { - options.integrations = defaultIntegrations => { - const userFinalIntegrations = integrations(defaultIntegrations); - - return maybeUpdateBrowserTracingIntegration(userFinalIntegrations); - }; - } -} - -function isNewBrowserTracingIntegration( - integration: Integration, -): integration is Integration & { options?: Parameters[0] } { - // eslint-disable-next-line deprecation/deprecation - return !!integration.afterAllSetup && !!(integration as BrowserTracing).options; -} - -function maybeUpdateBrowserTracingIntegration(integrations: Integration[]): Integration[] { - const browserTracing = integrations.find(integration => integration.name === 'BrowserTracing'); - - if (!browserTracing) { - return integrations; - } - - // If `browserTracingIntegration()` was added, we need to force-convert it to our custom one - if (isNewBrowserTracingIntegration(browserTracing)) { - const { options } = browserTracing; - // eslint-disable-next-line deprecation/deprecation - integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options); - } - - // If BrowserTracing was added, but it is not our forked version, - // replace it with our forked version with the same options - // eslint-disable-next-line deprecation/deprecation - if (!(browserTracing instanceof BrowserTracing)) { - // eslint-disable-next-line deprecation/deprecation - const options: ConstructorParameters[0] = (browserTracing as BrowserTracing).options; - // This option is overwritten by the custom integration - delete options.routingInstrumentation; - // eslint-disable-next-line deprecation/deprecation - integrations[integrations.indexOf(browserTracing)] = new BrowserTracing(options); - } - - return integrations; -} - function getDefaultIntegrations(options: BrowserOptions): Integration[] | undefined { // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside // will get treeshaken away diff --git a/packages/sveltekit/test/client/router.test.ts b/packages/sveltekit/test/client/router.test.ts deleted file mode 100644 index ab8455c5c4e2..000000000000 --- a/packages/sveltekit/test/client/router.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -/* eslint-disable @typescript-eslint/unbound-method */ -import type { Transaction } from '@sentry/types'; -import { writable } from 'svelte/store'; -import type { SpyInstance } from 'vitest'; -import { vi } from 'vitest'; - -import { navigating, page } from '$app/stores'; - -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { svelteKitRoutingInstrumentation } from '../../src/client/router'; - -// we have to overwrite the global mock from `vitest.setup.ts` here to reset the -// `navigating` store for each test. -vi.mock('$app/stores', async () => { - return { - get navigating() { - return navigatingStore; - }, - page: writable(), - }; -}); - -let navigatingStore = writable(); - -describe('sveltekitRoutingInstrumentation', () => { - let returnedTransaction: (Transaction & { returnedTransaction: SpyInstance }) | undefined; - const mockedStartTransaction = vi.fn().mockImplementation(txnCtx => { - returnedTransaction = { - ...txnCtx, - updateName: vi.fn(), - setAttribute: vi.fn(), - startChild: vi.fn().mockImplementation(ctx => { - return { ...mockedRoutingSpan, ...ctx }; - }), - setTag: vi.fn(), - }; - return returnedTransaction; - }); - - const mockedRoutingSpan = { - end: () => {}, - }; - - const routingSpanFinishSpy = vi.spyOn(mockedRoutingSpan, 'end'); - - beforeEach(() => { - navigatingStore = writable(); - vi.clearAllMocks(); - }); - - it("starts a pageload transaction when it's called with default params", () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction); - - expect(mockedStartTransaction).toHaveBeenCalledTimes(1); - expect(mockedStartTransaction).toHaveBeenCalledWith({ - name: '/', - op: 'pageload', - origin: 'auto.pageload.sveltekit', - tags: { - 'routing.instrumentation': '@sentry/sveltekit', - }, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }); - - // We emit an update to the `page` store to simulate the SvelteKit router lifecycle - page.set({ route: { id: 'testRoute' } }); - - // This should update the transaction name with the parameterized route: - expect(returnedTransaction?.updateName).toHaveBeenCalledTimes(1); - expect(returnedTransaction?.updateName).toHaveBeenCalledWith('testRoute'); - expect(returnedTransaction?.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); - }); - - it("doesn't start a pageload transaction if `startTransactionOnPageLoad` is false", () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false); - expect(mockedStartTransaction).toHaveBeenCalledTimes(0); - }); - - it("doesn't start a navigation transaction when `startTransactionOnLocationChange` is false", () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false, false); - - // We emit an update to the `navigating` store to simulate the SvelteKit navigation lifecycle - navigating.set({ - from: { route: { id: '/users' }, url: { pathname: '/users' } }, - to: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, - }); - - // This should update the transaction name with the parameterized route: - expect(mockedStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('starts a navigation transaction when `startTransactionOnLocationChange` is true', () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false, true); - - // We emit an update to the `navigating` store to simulate the SvelteKit navigation lifecycle - navigating.set({ - from: { route: { id: '/users' }, url: { pathname: '/users' } }, - to: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, - }); - - // This should update the transaction name with the parameterized route: - expect(mockedStartTransaction).toHaveBeenCalledTimes(1); - expect(mockedStartTransaction).toHaveBeenCalledWith({ - name: '/users/[id]', - op: 'navigation', - origin: 'auto.navigation.sveltekit', - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', - }, - }); - - // eslint-disable-next-line deprecation/deprecation - expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ - op: 'ui.sveltekit.routing', - origin: 'auto.ui.sveltekit', - name: 'SvelteKit Route Change', - }); - - // eslint-disable-next-line deprecation/deprecation - expect(returnedTransaction?.setTag).toHaveBeenCalledWith('from', '/users'); - - // We emit `null` here to simulate the end of the navigation lifecycle - navigating.set(null); - - expect(routingSpanFinishSpy).toHaveBeenCalledTimes(1); - }); - - describe('handling same origin and destination navigations', () => { - it("doesn't start a navigation transaction if the raw navigation origin and destination are equal", () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false, true); - - // We emit an update to the `navigating` store to simulate the SvelteKit navigation lifecycle - navigating.set({ - from: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, - to: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, - }); - - expect(mockedStartTransaction).toHaveBeenCalledTimes(0); - }); - - it('starts a navigation transaction if the raw navigation origin and destination are not equal', () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false, true); - - navigating.set({ - from: { route: { id: '/users/[id]' }, url: { pathname: '/users/7762' } }, - to: { route: { id: '/users/[id]' }, url: { pathname: '/users/223412' } }, - }); - - expect(mockedStartTransaction).toHaveBeenCalledTimes(1); - expect(mockedStartTransaction).toHaveBeenCalledWith({ - name: '/users/[id]', - op: 'navigation', - origin: 'auto.navigation.sveltekit', - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route' }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', - }, - }); - - // eslint-disable-next-line deprecation/deprecation - expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ - op: 'ui.sveltekit.routing', - origin: 'auto.ui.sveltekit', - name: 'SvelteKit Route Change', - }); - - // eslint-disable-next-line deprecation/deprecation - expect(returnedTransaction?.setTag).toHaveBeenCalledWith('from', '/users/[id]'); - }); - - it('falls back to `window.location.pathname` to determine the raw origin', () => { - // eslint-disable-next-line deprecation/deprecation - svelteKitRoutingInstrumentation(mockedStartTransaction, false, true); - - // window.location.pathame is "/" in tests - - navigating.set({ - to: { route: {}, url: { pathname: '/' } }, - }); - - expect(mockedStartTransaction).toHaveBeenCalledTimes(0); - }); - }); -}); diff --git a/packages/sveltekit/test/client/sdk.test.ts b/packages/sveltekit/test/client/sdk.test.ts index 9e969c08504d..162ccd72852d 100644 --- a/packages/sveltekit/test/client/sdk.test.ts +++ b/packages/sveltekit/test/client/sdk.test.ts @@ -1,17 +1,9 @@ import type { BrowserClient } from '@sentry/svelte'; import * as SentrySvelte from '@sentry/svelte'; -import { - SDK_VERSION, - browserTracingIntegration, - getClient, - getCurrentScope, - getGlobalScope, - getIsolationScope, -} from '@sentry/svelte'; +import { SDK_VERSION, getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/svelte'; import { vi } from 'vitest'; -import { BrowserTracing, init } from '../../src/client'; -import { svelteKitRoutingInstrumentation } from '../../src/client/router'; +import { init } from '../../src/client'; const svelteInit = vi.spyOn(SentrySvelte, 'init'); @@ -61,7 +53,7 @@ describe('Sentry client SDK', () => { ['tracesSampleRate', { tracesSampleRate: 0 }], ['tracesSampler', { tracesSampler: () => 1.0 }], ['enableTracing', { enableTracing: true }], - ])('adds the BrowserTracing integration if tracing is enabled via %s', (_, tracingOptions) => { + ])('adds a browserTracingIntegration if tracing is enabled via %s', (_, tracingOptions) => { init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', ...tracingOptions, @@ -74,7 +66,7 @@ describe('Sentry client SDK', () => { it.each([ ['enableTracing', { enableTracing: false }], ['no tracing option set', {}], - ])("doesn't add the BrowserTracing integration if tracing is disabled via %s", (_, tracingOptions) => { + ])("doesn't add a browserTracingIntegration integration if tracing is disabled via %s", (_, tracingOptions) => { init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', ...tracingOptions, @@ -84,7 +76,7 @@ describe('Sentry client SDK', () => { expect(browserTracing).toBeUndefined(); }); - it("doesn't add the BrowserTracing integration if `__SENTRY_TRACING__` is set to false", () => { + it("doesn't add a browserTracingIntegration if `__SENTRY_TRACING__` is set to false", () => { // This is the closest we can get to unit-testing the `__SENTRY_TRACING__` tree-shaking guard // IRL, the code to add the integration would most likely be removed by the bundler. @@ -100,49 +92,6 @@ describe('Sentry client SDK', () => { delete globalThis.__SENTRY_TRACING__; }); - - it('Merges a user-provided BrowserTracing integration with the automatically added one', () => { - init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - // eslint-disable-next-line deprecation/deprecation - integrations: [new BrowserTracing({ finalTimeout: 10 })], - enableTracing: true, - }); - - // eslint-disable-next-line deprecation/deprecation - const browserTracing = getClient()?.getIntegrationByName('BrowserTracing') as BrowserTracing; - const options = browserTracing.options; - - expect(browserTracing).toBeDefined(); - - // This shows that the user-configured options are still here - expect(options.finalTimeout).toEqual(10); - - // But we force the routing instrumentation to be ours - // eslint-disable-next-line deprecation/deprecation - expect(options.routingInstrumentation).toEqual(svelteKitRoutingInstrumentation); - }); - - it('Merges a user-provided browserTracingIntegration with the automatically added one', () => { - init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [browserTracingIntegration({ finalTimeout: 10 })], - enableTracing: true, - }); - - // eslint-disable-next-line deprecation/deprecation - const browserTracing = getClient()?.getIntegrationByName('BrowserTracing') as BrowserTracing; - const options = browserTracing.options; - - expect(browserTracing).toBeDefined(); - - // This shows that the user-configured options are still here - expect(options.finalTimeout).toEqual(10); - - // But we force the routing instrumentation to be ours - // eslint-disable-next-line deprecation/deprecation - expect(options.routingInstrumentation).toEqual(svelteKitRoutingInstrumentation); - }); }); }); }); diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index 02ef63d13aa7..fa0b7d514358 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -379,15 +379,12 @@ export const browserTracingIntegration = ((_options: Partial { expect(spanIsSampled(span!)).toBe(false); }); + it("doesn't create a pageload span when instrumentPageLoad is false", () => { + const client = new TestClient( + getDefaultClientOptions({ + integrations: [browserTracingIntegration({ instrumentPageLoad: false })], + }), + ); + setCurrentClient(client); + client.init(); + + const span = getActiveSpan(); + expect(span).not.toBeDefined(); + }); + it('works with tracing enabled but unsampled', () => { const client = new TestClient( getDefaultClientOptions({ @@ -181,6 +201,75 @@ describe('browserTracingIntegration', () => { }); }); + it('extracts window.location/self.location for sampling context in pageload transactions', () => { + // this is what is used to get the span name - JSDOM does not update this on it's own! + const dom = new JSDOM(undefined, { url: 'https://example.com/test' }); + Object.defineProperty(global, 'location', { value: dom.window.document.location, writable: true }); + + const tracesSampler = jest.fn(); + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + tracesSampler, + }), + ); + setCurrentClient(client); + client.init(); + + expect(tracesSampler).toHaveBeenCalledWith( + expect.objectContaining({ + location: dom.window.document.location, + }), + ); + }); + + it('extracts window.location/self.location for sampling context in navigation transactions', () => { + const tracesSampler = jest.fn(); + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration({ instrumentPageLoad: false })], + tracesSampler, + }), + ); + setCurrentClient(client); + client.init(); + + // this is what is used to get the span name - JSDOM does not update this on it's own! + const dom = new JSDOM(undefined, { url: 'https://example.com/test' }); + Object.defineProperty(global, 'location', { value: dom.window.document.location, writable: true }); + + WINDOW.history.pushState({}, '', '/test'); + + expect(tracesSampler).toHaveBeenCalledWith( + expect.objectContaining({ + location: dom.window.document.location, + }), + ); + }); + + it("trims pageload transactions to the max duration of the transaction's children", () => { + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + }), + ); + + setCurrentClient(client); + client.init(); + + const pageloadSpan = getActiveSpan(); + const childSpan = startInactiveSpan({ name: 'pageload-child' }); + const timestamp = timestampInSeconds(); + + childSpan?.end(timestamp); + pageloadSpan?.end(timestamp + 12345); + + expect(spanToJSON(pageloadSpan!).timestamp).toBe(timestamp); + }); + describe('startBrowserTracingPageLoadSpan', () => { it('works without integration setup', () => { const client = new TestClient( @@ -275,6 +364,87 @@ describe('browserTracingIntegration', () => { trace_id: expect.any(String), }); }); + + it('calls before beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => options); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ instrumentPageLoad: false, beforeStartSpan: mockBeforeStartSpan }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingPageLoadSpan(client, { name: 'test span' }); + + expect(mockBeforeStartSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'test span', + op: 'pageload', + }), + ); + }); + + it('uses options overridden with beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => ({ + ...options, + op: 'test op', + })); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ + instrumentPageLoad: false, + instrumentNavigation: false, + beforeStartSpan: mockBeforeStartSpan, + }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingPageLoadSpan(client, { name: 'test span' }); + + const pageloadSpan = getActiveSpan(); + + expect(spanToJSON(pageloadSpan!).op).toBe('test op'); + }); + }); + + it('sets source to "custom" if name is changed in beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => ({ + ...options, + name: 'changed', + })); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ + instrumentPageLoad: false, + instrumentNavigation: false, + beforeStartSpan: mockBeforeStartSpan, + }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingPageLoadSpan(client, { name: 'test span' }); + + const pageloadSpan = getActiveSpan(); + + expect(spanToJSON(pageloadSpan!).description).toBe('changed'); + expect(spanToJSON(pageloadSpan!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); }); describe('startBrowserTracingNavigationSpan', () => { @@ -371,5 +541,317 @@ describe('browserTracingIntegration', () => { trace_id: expect.any(String), }); }); + + it('calls before beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => options); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ + instrumentPageLoad: false, + instrumentNavigation: false, + beforeStartSpan: mockBeforeStartSpan, + }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingNavigationSpan(client, { name: 'test span' }); + + expect(mockBeforeStartSpan).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'test span', + op: 'navigation', + }), + ); + }); + + it('uses options overridden with beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => ({ + ...options, + op: 'test op', + })); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ + instrumentPageLoad: false, + instrumentNavigation: false, + beforeStartSpan: mockBeforeStartSpan, + }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingNavigationSpan(client, { name: 'test span' }); + + const navigationSpan = getActiveSpan(); + + expect(spanToJSON(navigationSpan!).op).toBe('test op'); + }); + + it('sets source to "custom" if name is changed in beforeStartSpan', () => { + const mockBeforeStartSpan = jest.fn((options: StartSpanOptions) => ({ + ...options, + name: 'changed', + })); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 0, + integrations: [ + browserTracingIntegration({ + instrumentPageLoad: false, + instrumentNavigation: false, + beforeStartSpan: mockBeforeStartSpan, + }), + ], + }), + ); + setCurrentClient(client); + client.init(); + + startBrowserTracingNavigationSpan(client, { name: 'test span' }); + + const pageloadSpan = getActiveSpan(); + + expect(spanToJSON(pageloadSpan!).description).toBe('changed'); + expect(spanToJSON(pageloadSpan!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); + }); + }); + + it('sets transaction context from sentry-trace header for pageload transactions', () => { + const name = 'sentry-trace'; + const content = '126de09502ae4e0fb26c6967190756a4-b6e54397b12a2a0f-1'; + document.head.innerHTML = + `` + ''; + const startIdleTransaction = jest.spyOn(hubExtensions, 'startIdleTransaction'); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + client.init(); + + expect(startIdleTransaction).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + traceId: '126de09502ae4e0fb26c6967190756a4', + parentSpanId: 'b6e54397b12a2a0f', + parentSampled: true, + metadata: { + dynamicSamplingContext: { release: '2.1.14' }, + }, + }), + expect.any(Number), + expect.any(Number), + expect.any(Boolean), + expect.any(Object), + expect.any(Number), + true, + ); + }); + + describe('using the tag data', () => { + it('uses the tracing data for pageload transactions', () => { + // make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one + document.head.innerHTML = + '' + + ''; + + const client = new TestClient( + getDefaultClientOptions({ + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + + // pageload transactions are created as part of the browserTracingIntegration's initialization + client.init(); + + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + // eslint-disable-next-line deprecation/deprecation + const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; + + expect(transaction).toBeDefined(); + expect(spanToJSON(transaction).op).toBe('pageload'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.traceId).toEqual('12312012123120121231201212312012'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.parentSpanId).toEqual('1121201211212012'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.sampled).toBe(false); + expect(dynamicSamplingContext).toBeDefined(); + expect(dynamicSamplingContext).toStrictEqual({ release: '2.1.14' }); + }); + + it('puts frozen Dynamic Sampling Context on pageload transactions if sentry-trace data and only 3rd party baggage is present', () => { + // make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one + document.head.innerHTML = + '' + + ''; + + const client = new TestClient( + getDefaultClientOptions({ + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + + // pageload transactions are created as part of the browserTracingIntegration's initialization + client.init(); + + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + // eslint-disable-next-line deprecation/deprecation + const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; + + expect(transaction).toBeDefined(); + expect(spanToJSON(transaction).op).toBe('pageload'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.traceId).toEqual('12312012123120121231201212312012'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.parentSpanId).toEqual('1121201211212012'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.sampled).toBe(false); + expect(dynamicSamplingContext).toStrictEqual({}); + }); + + it('ignores the meta tag data for navigation transactions', () => { + document.head.innerHTML = + '' + + ''; + + const client = new TestClient( + getDefaultClientOptions({ + integrations: [browserTracingIntegration({ instrumentPageLoad: false })], + }), + ); + setCurrentClient(client); + + // pageload transactions are created as part of the browserTracingIntegration's initialization + client.init(); + + // this is what is used to get the span name - JSDOM does not update this on it's own! + const dom = new JSDOM(undefined, { url: 'https://example.com/navigation-test' }); + Object.defineProperty(global, 'location', { value: dom.window.document.location, writable: true }); + + WINDOW.history.pushState({}, '', '/navigation-test'); + + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + // eslint-disable-next-line deprecation/deprecation + const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; + + expect(transaction).toBeDefined(); + expect(spanToJSON(transaction).op).toBe('navigation'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.traceId).not.toEqual('12312012123120121231201212312012'); + // eslint-disable-next-line deprecation/deprecation + expect(transaction.parentSpanId).toBeUndefined(); + expect(dynamicSamplingContext).toMatchObject({ + trace_id: expect.not.stringMatching('12312012123120121231201212312012'), + }); + transaction.end(); + }); + }); + + describe('idleTimeout', () => { + it('is created by default', () => { + jest.useFakeTimers(); + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration()], + }), + ); + setCurrentClient(client); + client.init(); + + const mockFinish = jest.fn(); + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + transaction.sendAutoFinishSignal(); + transaction.end = mockFinish; + + // eslint-disable-next-line deprecation/deprecation + const span = transaction.startChild(); // activities = 1 + span.end(); // activities = 0 + + expect(mockFinish).toHaveBeenCalledTimes(0); + jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); + expect(mockFinish).toHaveBeenCalledTimes(1); + }); + + it('can be a custom value', () => { + jest.useFakeTimers(); + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration({ idleTimeout: 2000 })], + }), + ); + setCurrentClient(client); + client.init(); + + const mockFinish = jest.fn(); + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + transaction.sendAutoFinishSignal(); + transaction.end = mockFinish; + + // eslint-disable-next-line deprecation/deprecation + const span = transaction.startChild(); // activities = 1 + span.end(); // activities = 0 + + expect(mockFinish).toHaveBeenCalledTimes(0); + jest.advanceTimersByTime(2000); + expect(mockFinish).toHaveBeenCalledTimes(1); + }); + }); + + // TODO(lforst): I cannot manage to get this test to pass. + /* + it('heartbeatInterval can be a custom value', () => { + jest.useFakeTimers(); + + const interval = 200; + + const client = new TestClient( + getDefaultClientOptions({ + tracesSampleRate: 1, + integrations: [browserTracingIntegration({ heartbeatInterval: interval })], + }), + ); + + setCurrentClient(client); + client.init(); + + const mockFinish = jest.fn(); + // eslint-disable-next-line deprecation/deprecation + const transaction = getActiveTransaction() as IdleTransaction; + transaction.sendAutoFinishSignal(); + transaction.end = mockFinish; + + const span = startInactiveSpan({ name: 'child-span' }); // activities = 1 + span!.end(); // activities = 0 + + expect(mockFinish).toHaveBeenCalledTimes(0); + jest.advanceTimersByTime(interval * 3); + expect(mockFinish).toHaveBeenCalledTimes(1); }); + */ }); diff --git a/packages/tracing-internal/test/browser/browsertracing.test.ts b/packages/tracing-internal/test/browser/browsertracing.test.ts deleted file mode 100644 index e24e4d166099..000000000000 --- a/packages/tracing-internal/test/browser/browsertracing.test.ts +++ /dev/null @@ -1,589 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -import { - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - TRACING_DEFAULTS, - getActiveTransaction, - getClient, - getCurrentHub, - setCurrentClient, - spanToJSON, -} from '@sentry/core'; -import * as hubExtensions from '@sentry/core'; -import type { BaseTransportOptions, ClientOptions, DsnComponents, HandlerDataHistory } from '@sentry/types'; -import { JSDOM } from 'jsdom'; - -import type { IdleTransaction } from '@sentry/core'; -import { timestampInSeconds } from '@sentry/utils'; -import type { BrowserTracingOptions } from '../../src/browser/browsertracing'; -import { BrowserTracing, getMetaContent } from '../../src/browser/browsertracing'; -import { defaultRequestInstrumentationOptions } from '../../src/browser/request'; -import { instrumentRoutingWithDefaults } from '../../src/browser/router'; -import { WINDOW } from '../../src/browser/types'; -import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; - -let mockChangeHistory: (data: HandlerDataHistory) => void = () => {}; - -jest.mock('@sentry/utils', () => { - const actual = jest.requireActual('@sentry/utils'); - return { - ...actual, - - addHistoryInstrumentationHandler: (callback: (data: HandlerDataHistory) => void): void => { - mockChangeHistory = callback; - }, - }; -}); - -const mockStartTrackingWebVitals = jest.fn().mockReturnValue(() => () => {}); - -jest.mock('../../src/browser/metrics', () => ({ - addPerformanceEntries: jest.fn(), - startTrackingInteractions: jest.fn(), - startTrackingLongTasks: jest.fn(), - startTrackingWebVitals: () => mockStartTrackingWebVitals(), -})); - -const instrumentOutgoingRequestsMock = jest.fn(); -jest.mock('./../../src/browser/request', () => { - const actual = jest.requireActual('./../../src/browser/request'); - return { - ...actual, - instrumentOutgoingRequests: (options: Partial) => instrumentOutgoingRequestsMock(options), - }; -}); - -beforeAll(() => { - const dom = new JSDOM(); - // @ts-expect-error need to override global document - WINDOW.document = dom.window.document; - // @ts-expect-error need to override global document - WINDOW.window = dom.window; - WINDOW.location = dom.window.location; -}); - -describe('BrowserTracing', () => { - beforeEach(() => { - jest.useFakeTimers(); - const options = getDefaultClientOptions({ tracesSampleRate: 1 }); - const client = new TestClient(options); - setCurrentClient(client); - client.init(); - document.head.innerHTML = ''; - - mockStartTrackingWebVitals.mockClear(); - }); - - afterEach(() => { - const activeTransaction = getActiveTransaction(); - if (activeTransaction) { - // Should unset off of scope. - activeTransaction.end(); - } - }); - - function createBrowserTracing(setup?: boolean, _options?: Partial): BrowserTracing { - const instance = new BrowserTracing(_options); - if (setup) { - const processor = () => undefined; - instance.setupOnce(processor, () => getCurrentHub() as hubExtensions.Hub); - } - - return instance; - } - - // These are important enough to check with a test as incorrect defaults could - // break a lot of users' configurations. - it('is created with default settings', () => { - const browserTracing = createBrowserTracing(); - - expect(browserTracing.options).toEqual({ - enableLongTask: true, - _experiments: {}, - ...TRACING_DEFAULTS, - markBackgroundTransactions: true, - routingInstrumentation: instrumentRoutingWithDefaults, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ...defaultRequestInstrumentationOptions, - }); - }); - - it('is allows to disable enableLongTask via _experiments', () => { - const browserTracing = createBrowserTracing(false, { - _experiments: { - enableLongTask: false, - }, - }); - - expect(browserTracing.options).toEqual({ - enableLongTask: false, - ...TRACING_DEFAULTS, - markBackgroundTransactions: true, - routingInstrumentation: instrumentRoutingWithDefaults, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ...defaultRequestInstrumentationOptions, - _experiments: { - enableLongTask: false, - }, - }); - }); - - it('is allows to disable enableLongTask', () => { - const browserTracing = createBrowserTracing(false, { - enableLongTask: false, - }); - - expect(browserTracing.options).toEqual({ - enableLongTask: false, - _experiments: {}, - ...TRACING_DEFAULTS, - markBackgroundTransactions: true, - routingInstrumentation: instrumentRoutingWithDefaults, - startTransactionOnLocationChange: true, - startTransactionOnPageLoad: true, - ...defaultRequestInstrumentationOptions, - }); - }); - - /** - * All of these tests under `describe('route transaction')` are tested with - * `browserTracing.options = { routingInstrumentation: customInstrumentRouting }`, - * so that we can show this functionality works independent of the default routing integration. - */ - describe('route transaction', () => { - const customInstrumentRouting = (customStartTransaction: (obj: any) => void) => { - customStartTransaction({ name: 'a/path', op: 'pageload' }); - }; - - it('_experiements calls onStartRouteTransaction on route instrumentation', () => { - const onStartTranscation = jest.fn(); - createBrowserTracing(true, { - _experiments: { - onStartRouteTransaction: onStartTranscation, - }, - }); - - expect(onStartTranscation).toHaveBeenCalledTimes(1); - }); - - it('calls custom routing instrumenation', () => { - createBrowserTracing(true, { - routingInstrumentation: customInstrumentRouting, - }); - - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).description).toBe('a/path'); - expect(spanToJSON(transaction).op).toBe('pageload'); - }); - - it('trims all transactions', () => { - createBrowserTracing(true, { - routingInstrumentation: customInstrumentRouting, - }); - - const transaction = getActiveTransaction() as IdleTransaction; - const span = transaction.startChild(); - - const timestamp = timestampInSeconds(); - span.end(timestamp); - transaction.end(timestamp + 12345); - - expect(spanToJSON(transaction).timestamp).toBe(timestamp); - }); - - describe('beforeNavigate', () => { - it('is called on transaction creation', () => { - const mockBeforeNavigation = jest.fn().mockReturnValue({ name: 'here/is/my/path' }); - createBrowserTracing(true, { - beforeNavigate: mockBeforeNavigation, - routingInstrumentation: customInstrumentRouting, - }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - - expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); - }); - - it('creates a transaction with sampled = false if beforeNavigate returns undefined', () => { - const mockBeforeNavigation = jest.fn().mockReturnValue(undefined); - createBrowserTracing(true, { - beforeNavigate: mockBeforeNavigation, - routingInstrumentation: customInstrumentRouting, - }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction.sampled).toBe(false); - - expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); - }); - - it('can override default context values', () => { - const mockBeforeNavigation = jest.fn(ctx => ({ - ...ctx, - op: 'not-pageload', - })); - createBrowserTracing(true, { - beforeNavigate: mockBeforeNavigation, - routingInstrumentation: customInstrumentRouting, - }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).op).toBe('not-pageload'); - - expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); - }); - - it("sets transaction name source to `'custom'` if name is changed", () => { - const mockBeforeNavigation = jest.fn(ctx => ({ - ...ctx, - name: 'newName', - })); - createBrowserTracing(true, { - beforeNavigate: mockBeforeNavigation, - routingInstrumentation: customInstrumentRouting, - }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).description).toBe('newName'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('custom'); - - expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); - }); - - it('sets transaction name source to default `url` if name is not changed', () => { - const mockBeforeNavigation = jest.fn(ctx => ({ - ...ctx, - })); - createBrowserTracing(true, { - beforeNavigate: mockBeforeNavigation, - routingInstrumentation: (customStartTransaction: (obj: any) => void) => { - customStartTransaction({ - name: 'a/path', - op: 'pageload', - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' }, - }); - }, - }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).description).toBe('a/path'); - expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); - - expect(mockBeforeNavigation).toHaveBeenCalledTimes(1); - }); - }); - - it('sets transaction context from sentry-trace header', () => { - const name = 'sentry-trace'; - const content = '126de09502ae4e0fb26c6967190756a4-b6e54397b12a2a0f-1'; - document.head.innerHTML = - `` + ''; - const startIdleTransaction = jest.spyOn(hubExtensions, 'startIdleTransaction'); - - createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); - - expect(startIdleTransaction).toHaveBeenCalledWith( - expect.any(Object), - expect.objectContaining({ - traceId: '126de09502ae4e0fb26c6967190756a4', - parentSpanId: 'b6e54397b12a2a0f', - parentSampled: true, - metadata: { - dynamicSamplingContext: { release: '2.1.14' }, - }, - }), - expect.any(Number), - expect.any(Number), - expect.any(Boolean), - expect.any(Object), - expect.any(Number), - true, - ); - }); - - describe('idleTimeout', () => { - it('is created by default', () => { - createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); - const mockFinish = jest.fn(); - const transaction = getActiveTransaction() as IdleTransaction; - transaction.sendAutoFinishSignal(); - transaction.end = mockFinish; - - const span = transaction.startChild(); // activities = 1 - span.end(); // activities = 0 - - expect(mockFinish).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); - expect(mockFinish).toHaveBeenCalledTimes(1); - }); - - it('can be a custom value', () => { - createBrowserTracing(true, { idleTimeout: 2000, routingInstrumentation: customInstrumentRouting }); - const mockFinish = jest.fn(); - const transaction = getActiveTransaction() as IdleTransaction; - transaction.sendAutoFinishSignal(); - transaction.end = mockFinish; - - const span = transaction.startChild(); // activities = 1 - span.end(); // activities = 0 - - expect(mockFinish).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(2000); - expect(mockFinish).toHaveBeenCalledTimes(1); - }); - - it('calls `_collectWebVitals` if enabled', () => { - createBrowserTracing(true, { routingInstrumentation: customInstrumentRouting }); - const transaction = getActiveTransaction() as IdleTransaction; - - const span = transaction.startChild(); // activities = 1 - span.end(); // activities = 0 - - jest.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout); - expect(mockStartTrackingWebVitals).toHaveBeenCalledTimes(1); - }); - }); - - describe('heartbeatInterval', () => { - it('can be a custom value', () => { - const interval = 200; - createBrowserTracing(true, { heartbeatInterval: interval, routingInstrumentation: customInstrumentRouting }); - const mockFinish = jest.fn(); - const transaction = getActiveTransaction() as IdleTransaction; - transaction.sendAutoFinishSignal(); - transaction.end = mockFinish; - - const span = transaction.startChild(); // activities = 1 - span.end(); // activities = 0 - - expect(mockFinish).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(interval * 3); - expect(mockFinish).toHaveBeenCalledTimes(1); - }); - }); - }); - - // Integration tests for the default routing instrumentation - describe('default routing instrumentation', () => { - describe('pageload transaction', () => { - it('is created on setup on scope', () => { - createBrowserTracing(true); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).toBeDefined(); - - expect(spanToJSON(transaction).op).toBe('pageload'); - }); - - it('is not created if the option is false', () => { - createBrowserTracing(true, { startTransactionOnPageLoad: false }); - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).not.toBeDefined(); - }); - }); - - describe('navigation transaction', () => { - beforeEach(() => { - mockChangeHistory = () => undefined; - }); - - it('it is not created automatically at startup', () => { - createBrowserTracing(true); - jest.runAllTimers(); - - const transaction = getActiveTransaction() as IdleTransaction; - expect(transaction).not.toBeDefined(); - }); - - it('is created on location change', () => { - createBrowserTracing(true); - const transaction1 = getActiveTransaction() as IdleTransaction; - expect(spanToJSON(transaction1).op).toBe('pageload'); - expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); - - mockChangeHistory({ to: 'here', from: 'there' }); - const transaction2 = getActiveTransaction() as IdleTransaction; - expect(spanToJSON(transaction2).op).toBe('navigation'); - - expect(spanToJSON(transaction1).timestamp).toBeDefined(); - }); - - it('is not created if startTransactionOnLocationChange is false', () => { - createBrowserTracing(true, { startTransactionOnLocationChange: false }); - const transaction1 = getActiveTransaction() as IdleTransaction; - expect(spanToJSON(transaction1).op).toBe('pageload'); - expect(spanToJSON(transaction1).timestamp).not.toBeDefined(); - - mockChangeHistory({ to: 'here', from: 'there' }); - const transaction2 = getActiveTransaction() as IdleTransaction; - expect(spanToJSON(transaction2).op).toBe('pageload'); - }); - }); - }); - - describe('sentry-trace and baggage elements', () => { - describe('getMetaContent', () => { - it('finds the specified tag and extracts the value', () => { - const name = 'sentry-trace'; - const content = '126de09502ae4e0fb26c6967190756a4-b6e54397b12a2a0f-1'; - document.head.innerHTML = ``; - - const metaTagValue = getMetaContent(name); - expect(metaTagValue).toBe(content); - }); - - it("doesn't return meta tags other than the one specified", () => { - document.head.innerHTML = ''; - - const metaTagValue = getMetaContent('dogpark'); - expect(metaTagValue).toBe(undefined); - }); - - it('can pick the correct tag out of multiple options', () => { - const name = 'sentry-trace'; - const content = '126de09502ae4e0fb26c6967190756a4-b6e54397b12a2a0f-1'; - const sentryTraceMeta = ``; - const otherMeta = ''; - document.head.innerHTML = `${sentryTraceMeta} ${otherMeta}`; - - const metaTagValue = getMetaContent(name); - expect(metaTagValue).toBe(content); - }); - }); - - describe('using the tag data', () => { - beforeEach(() => { - getClient()!.getOptions = () => { - return { - release: '1.0.0', - environment: 'production', - } as ClientOptions; - }; - - getClient()!.getDsn = () => { - return { - publicKey: 'pubKey', - } as DsnComponents; - }; - }); - - it('uses the tracing data for pageload transactions', () => { - // make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one - document.head.innerHTML = - '' + - ''; - - // pageload transactions are created as part of the BrowserTracing integration's initialization - createBrowserTracing(true); - const transaction = getActiveTransaction() as IdleTransaction; - const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; - - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).op).toBe('pageload'); - expect(transaction.traceId).toEqual('12312012123120121231201212312012'); - expect(transaction.parentSpanId).toEqual('1121201211212012'); - expect(transaction.sampled).toBe(false); - expect(dynamicSamplingContext).toBeDefined(); - expect(dynamicSamplingContext).toStrictEqual({ release: '2.1.14' }); - }); - - it('puts frozen Dynamic Sampling Context on pageload transactions if sentry-trace data and only 3rd party baggage is present', () => { - // make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one - document.head.innerHTML = - '' + - ''; - - // pageload transactions are created as part of the BrowserTracing integration's initialization - createBrowserTracing(true); - const transaction = getActiveTransaction() as IdleTransaction; - const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; - - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).op).toBe('pageload'); - expect(transaction.traceId).toEqual('12312012123120121231201212312012'); - expect(transaction.parentSpanId).toEqual('1121201211212012'); - expect(transaction.sampled).toBe(false); - expect(dynamicSamplingContext).toStrictEqual({}); - }); - - it('ignores the meta tag data for navigation transactions', () => { - mockChangeHistory = () => undefined; - document.head.innerHTML = - '' + - ''; - - createBrowserTracing(true); - - mockChangeHistory({ to: 'here', from: 'there' }); - const transaction = getActiveTransaction() as IdleTransaction; - const dynamicSamplingContext = transaction.getDynamicSamplingContext()!; - - expect(transaction).toBeDefined(); - expect(spanToJSON(transaction).op).toBe('navigation'); - expect(transaction.traceId).not.toEqual('12312012123120121231201212312012'); - expect(transaction.parentSpanId).toBeUndefined(); - expect(dynamicSamplingContext).toStrictEqual({ - release: '1.0.0', - environment: 'production', - public_key: 'pubKey', - sampled: 'false', - trace_id: expect.not.stringMatching('12312012123120121231201212312012'), - }); - }); - }); - }); - - describe('sampling', () => { - const dogParkLocation = { - hash: '#next-to-the-fountain', - host: 'the.dog.park', - hostname: 'the.dog.park', - href: 'mutualsniffing://the.dog.park/by/the/trees/?chase=me&please=thankyou#next-to-the-fountain', - origin: "'mutualsniffing://the.dog.park", - pathname: '/by/the/trees/', - port: '', - protocol: 'mutualsniffing:', - search: '?chase=me&please=thankyou', - }; - - it('extracts window.location/self.location for sampling context in pageload transactions', () => { - WINDOW.location = dogParkLocation as any; - - const tracesSampler = jest.fn(); - const options = getDefaultClientOptions({ tracesSampler }); - const client = new TestClient(options); - setCurrentClient(client); - client.init(); - // setting up the BrowserTracing integration automatically starts a pageload transaction - createBrowserTracing(true); - - expect(tracesSampler).toHaveBeenCalledWith( - expect.objectContaining({ - location: dogParkLocation, - transactionContext: expect.objectContaining({ op: 'pageload' }), - }), - ); - }); - - it('extracts window.location/self.location for sampling context in navigation transactions', () => { - WINDOW.location = dogParkLocation as any; - - const tracesSampler = jest.fn(); - const options = getDefaultClientOptions({ tracesSampler }); - const client = new TestClient(options); - setCurrentClient(client); - client.init(); - // setting up the BrowserTracing integration normally automatically starts a pageload transaction, but that's not - // what we're testing here - createBrowserTracing(true, { startTransactionOnPageLoad: false }); - - mockChangeHistory({ to: 'here', from: 'there' }); - expect(tracesSampler).toHaveBeenCalledWith( - expect.objectContaining({ - location: dogParkLocation, - transactionContext: expect.objectContaining({ op: 'navigation' }), - }), - ); - }); - }); -}); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 7c6b562a2f73..4d41b0f216bd 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -245,12 +245,12 @@ export interface Client { ): void; /** - * A hook for BrowserTracing to trigger a span start for a page load. + * A hook for the browser tracing integrations to trigger a span start for a page load. */ on(hook: 'startPageLoadSpan', callback: (options: StartSpanOptions) => void): void; /** - * A hook for BrowserTracing to trigger a span for a navigation. + * A hook for browser tracing integrations to trigger a span for a navigation. */ on(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; @@ -326,12 +326,12 @@ export interface Client { emit(hook: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay?: boolean }): void; /** - * Emit a hook event for BrowserTracing to trigger a span start for a page load. + * Emit a hook event for browser tracing integrations to trigger a span start for a page load. */ emit(hook: 'startPageLoadSpan', options: StartSpanOptions): void; /** - * Emit a hook event for BrowserTracing to trigger a span for a navigation. + * Emit a hook event for browser tracing integrations to trigger a span for a navigation. */ emit(hook: 'startNavigationSpan', options: StartSpanOptions): void; diff --git a/packages/vue/src/browserTracingIntegration.ts b/packages/vue/src/browserTracingIntegration.ts index d78bdd992d6b..e3d178628724 100644 --- a/packages/vue/src/browserTracingIntegration.ts +++ b/packages/vue/src/browserTracingIntegration.ts @@ -44,7 +44,7 @@ type VueBrowserTracingIntegrationOptions = Parameters Date: Fri, 23 Feb 2024 09:44:24 -0500 Subject: [PATCH 145/173] feat(v8/core): Remove span.instrumenter and instrumenter option (#10769) ref https://github.com/getsentry/sentry-javascript/issues/10677 Removes `instrumenter` from the span interface, and removes the `instrumenter` option from client options accordingly. --- packages/bun/test/helpers.ts | 1 - packages/core/src/tracing/hubextensions.ts | 15 ------ packages/core/src/tracing/sentrySpan.ts | 15 ------ .../core/test/lib/serverruntimeclient.test.ts | 1 - .../wrapAppGetInitialPropsWithSentry.ts | 5 +- .../wrapDocumentGetInitialPropsWithSentry.ts | 6 +-- .../wrapErrorGetInitialPropsWithSentry.ts | 5 +- .../common/wrapGetInitialPropsWithSentry.ts | 5 +- .../wrapGetServerSidePropsWithSentry.ts | 47 ++++++++----------- .../common/wrapGetStaticPropsWithSentry.ts | 14 ++---- packages/nextjs/test/config/wrappers.test.ts | 2 +- packages/node-experimental/src/sdk/init.ts | 1 - packages/node/src/handlers.ts | 7 +-- packages/node/src/integrations/http.ts | 6 --- packages/node/src/sdk.ts | 4 -- .../node/test/helper/node-client-options.ts | 1 - packages/node/test/index.test.ts | 19 -------- packages/node/test/integrations/http.test.ts | 29 +----------- packages/opentelemetry-node/README.md | 2 - .../opentelemetry-node/src/spanprocessor.ts | 2 - packages/opentelemetry/README.md | 15 +++--- packages/opentelemetry/src/spanExporter.ts | 2 - .../opentelemetry/test/helpers/mockSdkInit.ts | 7 +-- .../src/node/integrations/apollo.ts | 6 --- .../src/node/integrations/express.ts | 10 +--- .../src/node/integrations/graphql.ts | 6 --- .../src/node/integrations/mongo.ts | 6 --- .../src/node/integrations/mysql.ts | 6 --- .../src/node/integrations/postgres.ts | 6 --- .../src/node/integrations/prisma.ts | 8 +--- .../src/node/integrations/utils/node-utils.ts | 15 ------ packages/types/src/index.ts | 1 - packages/types/src/instrumenter.ts | 1 - packages/types/src/options.ts | 10 ---- packages/types/src/span.ts | 13 ----- packages/types/src/startSpanOptions.ts | 6 --- packages/types/src/transaction.ts | 8 ---- packages/vercel-edge/src/sdk.ts | 4 -- 38 files changed, 44 insertions(+), 273 deletions(-) delete mode 100644 packages/tracing-internal/src/node/integrations/utils/node-utils.ts delete mode 100644 packages/types/src/instrumenter.ts diff --git a/packages/bun/test/helpers.ts b/packages/bun/test/helpers.ts index 32d4e4d716ac..61b4de5e65f1 100644 --- a/packages/bun/test/helpers.ts +++ b/packages/bun/test/helpers.ts @@ -8,7 +8,6 @@ export function getDefaultBunClientOptions(options: Partial = integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), stackParser: () => [], - instrumenter: 'sentry', ...options, }; } diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts index f705f676e660..5f30a94498b9 100644 --- a/packages/core/src/tracing/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -1,8 +1,6 @@ import type { ClientOptions, CustomSamplingContext, Hub, TransactionContext } from '@sentry/types'; -import { logger } from '@sentry/utils'; import { getMainCarrier } from '../asyncContext'; -import { DEBUG_BUILD } from '../debug-build'; import { registerErrorInstrumentation } from './errors'; import { IdleTransaction } from './idletransaction'; import { sampleTransaction } from './sampling'; @@ -32,19 +30,6 @@ function _startTransaction( const client = this.getClient(); const options: Partial = (client && client.getOptions()) || {}; - const configInstrumenter = options.instrumenter || 'sentry'; - const transactionInstrumenter = transactionContext.instrumenter || 'sentry'; - - if (configInstrumenter !== transactionInstrumenter) { - DEBUG_BUILD && - logger.error( - `A transaction was started with instrumenter=\`${transactionInstrumenter}\`, but the SDK is configured with the \`${configInstrumenter}\` instrumenter. -The transaction will not be sampled. Please use the ${configInstrumenter} instrumentation to start transactions.`, - ); - - transactionContext.sampled = false; - } - // eslint-disable-next-line deprecation/deprecation let transaction = new Transaction(transactionContext, this); transaction = sampleTransaction(transaction, options, { diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 2f1fa54c5306..f74050b24223 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -1,5 +1,4 @@ import type { - Instrumenter, Primitive, Span as SpanInterface, SpanAttributeValue, @@ -90,18 +89,6 @@ export class SentrySpan implements SpanInterface { * @deprecated Use top level `Sentry.getRootSpan()` instead */ public transaction?: Transaction; - - /** - * The instrumenter that created this span. - * - * TODO (v8): This can probably be replaced by an `instanceOf` check of the span class. - * the instrumenter can only be sentry or otel so we can check the span instance - * to verify which one it is and remove this field entirely. - * - * @deprecated This field will be removed. - */ - public instrumenter: Instrumenter; - protected _traceId: string; protected _spanId: string; protected _parentSpanId?: string | undefined; @@ -132,8 +119,6 @@ export class SentrySpan implements SpanInterface { this.tags = spanContext.tags ? { ...spanContext.tags } : {}; // eslint-disable-next-line deprecation/deprecation this.data = spanContext.data ? { ...spanContext.data } : {}; - // eslint-disable-next-line deprecation/deprecation - this.instrumenter = spanContext.instrumenter || 'sentry'; this._attributes = {}; this.setAttributes({ diff --git a/packages/core/test/lib/serverruntimeclient.test.ts b/packages/core/test/lib/serverruntimeclient.test.ts index 4d5cc8f33ce4..ee5d96863174 100644 --- a/packages/core/test/lib/serverruntimeclient.test.ts +++ b/packages/core/test/lib/serverruntimeclient.test.ts @@ -11,7 +11,6 @@ function getDefaultClientOptions(options: Partial = integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), stackParser: () => [], - instrumenter: 'sentry', ...options, }; } diff --git a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts index 11730529de23..50bbd346fcd2 100644 --- a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts @@ -1,7 +1,6 @@ import { addTracingExtensions, getActiveSpan, - getClient, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader, @@ -35,13 +34,11 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI const { req, res } = context.ctx; const errorWrappedAppGetInitialProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (req && res && options?.instrumenter === 'sentry') { + if (req && res) { const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedAppGetInitialProps, req, res, { dataFetcherRouteName: '/_app', requestedRouteName: context.ctx.pathname, diff --git a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts index 695111429938..9c903a1fa795 100644 --- a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts @@ -1,4 +1,4 @@ -import { addTracingExtensions, getClient } from '@sentry/core'; +import { addTracingExtensions } from '@sentry/core'; import type Document from 'next/document'; import { isBuild } from './utils/isBuild'; @@ -29,13 +29,11 @@ export function wrapDocumentGetInitialPropsWithSentry( const { req, res } = context; const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (req && res && options?.instrumenter === 'sentry') { + if (req && res) { const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { dataFetcherRouteName: '/_document', requestedRouteName: context.pathname, diff --git a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts index 44121a8c2e4a..aeeccd332213 100644 --- a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts @@ -1,7 +1,6 @@ import { addTracingExtensions, getActiveSpan, - getClient, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader, @@ -38,13 +37,11 @@ export function wrapErrorGetInitialPropsWithSentry( const { req, res } = context; const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (req && res && options?.instrumenter === 'sentry') { + if (req && res) { const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { dataFetcherRouteName: '/_error', requestedRouteName: context.pathname, diff --git a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts index add8af0c0f8f..c78ce497e394 100644 --- a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts @@ -1,7 +1,6 @@ import { addTracingExtensions, getActiveSpan, - getClient, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader, @@ -34,13 +33,11 @@ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialPro const { req, res } = context; const errorWrappedGetInitialProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - // Generally we can assume that `req` and `res` are always defined on the server: // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher // span with each other when there are no req or res objects, we simply do not trace them at all here. - if (req && res && options?.instrumenter === 'sentry') { + if (req && res) { const tracedGetInitialProps = withTracedServerSideDataFetcher(errorWrappedGetInitialProps, req, res, { dataFetcherRouteName: context.pathname, requestedRouteName: context.pathname, diff --git a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts index f854cedd8b5d..32122869b8f4 100644 --- a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts @@ -1,7 +1,6 @@ import { addTracingExtensions, getActiveSpan, - getClient, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader, @@ -35,34 +34,28 @@ export function wrapGetServerSidePropsWithSentry( const { req, res } = context; const errorWrappedGetServerSideProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - - if (options?.instrumenter === 'sentry') { - const tracedGetServerSideProps = withTracedServerSideDataFetcher(errorWrappedGetServerSideProps, req, res, { - dataFetcherRouteName: parameterizedRoute, - requestedRouteName: parameterizedRoute, - dataFetchingMethodName: 'getServerSideProps', - }); - - const serverSideProps = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType< - typeof tracedGetServerSideProps - >); - - if (serverSideProps && 'props' in serverSideProps) { - const activeSpan = getActiveSpan(); - const requestTransaction = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); - if (requestTransaction) { - serverSideProps.props._sentryTraceData = spanToTraceHeader(requestTransaction); - - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestTransaction); - serverSideProps.props._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - } + const tracedGetServerSideProps = withTracedServerSideDataFetcher(errorWrappedGetServerSideProps, req, res, { + dataFetcherRouteName: parameterizedRoute, + requestedRouteName: parameterizedRoute, + dataFetchingMethodName: 'getServerSideProps', + }); + + const serverSideProps = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType< + typeof tracedGetServerSideProps + >); + + if (serverSideProps && 'props' in serverSideProps) { + const activeSpan = getActiveSpan(); + const requestTransaction = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); + if (requestTransaction) { + serverSideProps.props._sentryTraceData = spanToTraceHeader(requestTransaction); + + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestTransaction); + serverSideProps.props._sentryBaggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); } - - return serverSideProps; - } else { - return errorWrappedGetServerSideProps.apply(thisArg, args); } + + return serverSideProps; }, }); } diff --git a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts index 1d5c0b1890de..67ac97daac5c 100644 --- a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts @@ -1,4 +1,4 @@ -import { addTracingExtensions, getClient } from '@sentry/core'; +import { addTracingExtensions } from '@sentry/core'; import type { GetStaticProps } from 'next'; import { isBuild } from './utils/isBuild'; @@ -26,14 +26,10 @@ export function wrapGetStaticPropsWithSentry( addTracingExtensions(); const errorWrappedGetStaticProps = withErrorInstrumentation(wrappingTarget); - const options = getClient()?.getOptions(); - - if (options?.instrumenter === 'sentry') { - return callDataFetcherTraced(errorWrappedGetStaticProps, args, { - parameterizedRoute, - dataFetchingMethodName: 'getStaticProps', - }); - } + return callDataFetcherTraced(errorWrappedGetStaticProps, args, { + parameterizedRoute, + dataFetchingMethodName: 'getStaticProps', + }); return errorWrappedGetStaticProps.apply(thisArg, args); }, diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index b15af158a098..e1791e3996d5 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -23,7 +23,7 @@ describe('data-fetching function wrappers should create spans', () => { jest.spyOn(SentryCore, 'hasTracingEnabled').mockReturnValue(true); jest.spyOn(SentryCore, 'getClient').mockImplementation(() => { return { - getOptions: () => ({ instrumenter: 'sentry' }), + getOptions: () => ({}), getDsn: () => {}, } as Client; }); diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 2b41c235b234..80b69112f261 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -153,7 +153,6 @@ function getClientOptions(options: NodeOptions): NodeClientOptions { ...baseOptions, ...options, ...overwriteOptions, - instrumenter: 'otel', stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser), integrations: getIntegrationsToSetup({ defaultIntegrations: options.defaultIntegrations, diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index b66315afcb73..5c1c0a26e481 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -48,12 +48,7 @@ export function tracingHandler(): ( ): void { const options = getClient()?.getOptions(); - if ( - !options || - options.instrumenter !== 'sentry' || - req.method?.toUpperCase() === 'OPTIONS' || - req.method?.toUpperCase() === 'HEAD' - ) { + if (req.method?.toUpperCase() === 'OPTIONS' || req.method?.toUpperCase() === 'HEAD') { return next(); } diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index b8010b083d5b..22407ca77e91 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -185,12 +185,6 @@ export class Http implements Integration { return; } - // Do not auto-instrument for other instrumenter - if (clientOptions && clientOptions.instrumenter !== 'sentry') { - DEBUG_BUILD && logger.log('HTTP Integration is skipped because of instrumenter configuration.'); - return; - } - const shouldCreateSpanForRequest = _getShouldCreateSpanForRequest(shouldCreateSpans, this._tracing, clientOptions); // eslint-disable-next-line deprecation/deprecation diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index d39d853c7bbc..1225cce83485 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -147,10 +147,6 @@ export function init(options: NodeOptions = {}): void { options.autoSessionTracking = true; } - if (options.instrumenter === undefined) { - options.instrumenter = 'sentry'; - } - // TODO(v7): Refactor this to reduce the logic above const clientOptions: NodeClientOptions = { ...options, diff --git a/packages/node/test/helper/node-client-options.ts b/packages/node/test/helper/node-client-options.ts index 9103174ad813..e9428ea0bb7e 100644 --- a/packages/node/test/helper/node-client-options.ts +++ b/packages/node/test/helper/node-client-options.ts @@ -8,7 +8,6 @@ export function getDefaultNodeClientOptions(options: Partial integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), stackParser: () => [], - instrumenter: 'sentry', ...options, }; } diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 3b9a52272b61..563fa8886420 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -26,7 +26,6 @@ import { import { setNodeAsyncContextStrategy } from '../src/async'; import { ContextLines } from '../src/integrations'; import { defaultStackParser, getDefaultIntegrations } from '../src/sdk'; -import type { NodeClientOptions } from '../src/types'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; jest.mock('@sentry/core', () => { @@ -487,24 +486,6 @@ describe('SentryNode initialization', () => { }); }); - describe('instrumenter', () => { - it('defaults to sentry instrumenter', () => { - init({ dsn }); - - const instrumenter = (getClient()?.getOptions() as NodeClientOptions).instrumenter; - - expect(instrumenter).toEqual('sentry'); - }); - - it('allows to set instrumenter', () => { - init({ dsn, instrumenter: 'otel' }); - - const instrumenter = (getClient()?.getOptions() as NodeClientOptions).instrumenter; - - expect(instrumenter).toEqual('otel'); - }); - }); - describe('propagation context', () => { beforeEach(() => { process.env.SENTRY_TRACE = '12312012123120121231201212312012-1121201211212012-0'; diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index c2e85f8ac170..0a0f8f16645b 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -7,7 +7,7 @@ import { Transaction } from '@sentry/core'; import { getCurrentScope, setUser, spanToJSON, startInactiveSpan } from '@sentry/core'; import { addTracingExtensions } from '@sentry/core'; import type { TransactionContext } from '@sentry/types'; -import { TRACEPARENT_REGEXP, logger } from '@sentry/utils'; +import { TRACEPARENT_REGEXP } from '@sentry/utils'; import * as nock from 'nock'; import { HttpsProxyAgent } from '../../src/proxy'; @@ -259,33 +259,6 @@ describe('tracing', () => { expect(baggage).not.toBeDefined(); }); - it("doesn't attach when using otel instrumenter", () => { - const loggerLogSpy = jest.spyOn(logger, 'log'); - - const options = getDefaultNodeClientOptions({ - dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012', - tracesSampleRate: 1.0, - // eslint-disable-next-line deprecation/deprecation - integrations: [new HttpIntegration({ tracing: true })], - release: '1.0.0', - environment: 'production', - instrumenter: 'otel', - }); - const client = new NodeClient(options); - setCurrentClient(client); - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - - // eslint-disable-next-line deprecation/deprecation - const integration = new HttpIntegration(); - integration.setupOnce( - () => {}, - () => hub as Hub, - ); - - expect(loggerLogSpy).toBeCalledWith('HTTP Integration is skipped because of instrumenter configuration.'); - }); - it('omits query and fragment from description and adds to span data instead', () => { nock('http://dogs.are.great').get('/spaniel?tail=wag&cute=true#learn-more').reply(200); diff --git a/packages/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md index b75ca0ab0ef8..5d09f0edb182 100644 --- a/packages/opentelemetry-node/README.md +++ b/packages/opentelemetry-node/README.md @@ -54,8 +54,6 @@ const { Sentry.init({ dsn: '__DSN__', tracesSampleRate: 1.0, - // set the instrumenter to use OpenTelemetry instead of Sentry - instrumenter: 'otel', // ... }); diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 71a643a5b90b..4ef437a6e88e 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -67,7 +67,6 @@ export class SentrySpanProcessor implements OtelSpanProcessor { // eslint-disable-next-line deprecation/deprecation const sentryChildSpan = sentryParentSpan.startChild({ name: otelSpan.name, - instrumenter: 'otel', startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), spanId: otelSpanId, }); @@ -80,7 +79,6 @@ export class SentrySpanProcessor implements OtelSpanProcessor { name: otelSpan.name, ...traceCtx, attributes: otelSpan.attributes, - instrumenter: 'otel', startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), spanId: otelSpanId, }); diff --git a/packages/opentelemetry/README.md b/packages/opentelemetry/README.md index 593c5a280868..18ae5e9572d0 100644 --- a/packages/opentelemetry/README.md +++ b/packages/opentelemetry/README.md @@ -38,13 +38,13 @@ This package exposes a few building blocks you can add to your OpenTelemetry set This is how you can use this in your app: -1. Initialize Sentry, e.g. `@sentry/node` - make sure to set `instrumenter: 'otel'` in the SDK `init({})`! -1. Call `setupEventContextTrace(client)` -1. Add `SentrySampler` as sampler -1. Add `SentrySpanProcessor` as span processor -1. Add a context manager wrapped via `wrapContextManagerClass` -1. Add `SentryPropagator` as propagator -1. Setup OTEL-powered async context strategy for Sentry via `setOpenTelemetryContextAsyncContextStrategy()` +1. Initialize Sentry, e.g. `@sentry/node`! +2. Call `setupEventContextTrace(client)` +3. Add `SentrySampler` as sampler +4. Add `SentrySpanProcessor` as span processor +5. Add a context manager wrapped via `wrapContextManagerClass` +6. Add `SentryPropagator` as propagator +7. Setup OTEL-powered async context strategy for Sentry via `setOpenTelemetryContextAsyncContextStrategy()` For example, you could set this up as follows: @@ -63,7 +63,6 @@ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-ho function setupSentry() { Sentry.init({ dsn: 'xxx', - instrumenter: 'otel' }); const client = Sentry.getClient(); diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 2c3a4c677b39..a0488ac22f64 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -156,7 +156,6 @@ function createTransactionForOtelSpan(span: ReadableSpan): Transaction { parentSampled, name: description, op, - instrumenter: 'otel', status: mapStatus(span), startTimestamp: convertOtelTimeToSeconds(span.startTime), metadata: { @@ -213,7 +212,6 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, sentryParentSpan: Sentry op, data: allData, status: mapStatus(span), - instrumenter: 'otel', startTimestamp: convertOtelTimeToSeconds(span.startTime), spanId, origin, diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index f77c37ce4034..d4a41f90959e 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -13,13 +13,8 @@ const PUBLIC_DSN = 'https://username@domain/123'; * Initialize Sentry for Node. */ function init(options: Partial | undefined = {}): void { - const fullOptions: Partial = { - instrumenter: 'otel', - ...options, - }; - setOpenTelemetryContextAsyncContextStrategy(); - initTestClient(fullOptions); + initTestClient(options); initOtel(); } diff --git a/packages/tracing-internal/src/node/integrations/apollo.ts b/packages/tracing-internal/src/node/integrations/apollo.ts index 85029943cbaf..d2a7f0ff73ee 100644 --- a/packages/tracing-internal/src/node/integrations/apollo.ts +++ b/packages/tracing-internal/src/node/integrations/apollo.ts @@ -4,7 +4,6 @@ import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; import type { LazyLoadedIntegration } from './lazy'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; interface ApolloOptions { useNestjs?: boolean; @@ -77,11 +76,6 @@ export class Apollo implements LazyLoadedIntegration void, getCurrentHub: () => Hub): void { - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('Apollo Integration is skipped because of instrumenter configuration.'); - return; - } - if (this._useNest) { const pkg = this.loadDependency(); diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index 04f3e55dec68..e1417c0f1773 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; -import type { Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types'; +import type { Integration, PolymorphicRequest, Transaction } from '@sentry/types'; import { GLOBAL_OBJ, extractPathForTransaction, @@ -11,7 +11,6 @@ import { } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; type Method = | 'all' @@ -119,17 +118,12 @@ export class Express implements Integration { /** * @inheritDoc */ - public setupOnce(_: unknown, getCurrentHub: () => Hub): void { + public setupOnce(_: unknown): void { if (!this._router) { DEBUG_BUILD && logger.error('ExpressIntegration is missing an Express instance'); return; } - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('Express Integration is skipped because of instrumenter configuration.'); - return; - } - instrumentMiddlewares(this._router, this._methods); instrumentRouter(this._router as ExpressRouter); } diff --git a/packages/tracing-internal/src/node/integrations/graphql.ts b/packages/tracing-internal/src/node/integrations/graphql.ts index 4ce7476a45ac..16256e0dccab 100644 --- a/packages/tracing-internal/src/node/integrations/graphql.ts +++ b/packages/tracing-internal/src/node/integrations/graphql.ts @@ -4,7 +4,6 @@ import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; import type { LazyLoadedIntegration } from './lazy'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; type GraphQLModule = { [method: string]: (...args: unknown[]) => unknown; @@ -37,11 +36,6 @@ export class GraphQL implements LazyLoadedIntegration { * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('GraphQL Integration is skipped because of instrumenter configuration.'); - return; - } - const pkg = this.loadDependency(); if (!pkg) { diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index 75e03649dba2..8496ed422821 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -4,7 +4,6 @@ import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; import type { LazyLoadedIntegration } from './lazy'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; // This allows us to use the same array for both defaults options and the type itself. // (note `as const` at the end to make it a union of string literal types (i.e. "a" | "b" | ... ) @@ -140,11 +139,6 @@ export class Mongo implements LazyLoadedIntegration { * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('Mongo Integration is skipped because of instrumenter configuration.'); - return; - } - const pkg = this.loadDependency(); if (!pkg) { diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index 09d3f45932eb..d7349f804aae 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -4,7 +4,6 @@ import { fill, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; import type { LazyLoadedIntegration } from './lazy'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; interface MysqlConnection { prototype: { @@ -46,11 +45,6 @@ export class Mysql implements LazyLoadedIntegration { * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('Mysql Integration is skipped because of instrumenter configuration.'); - return; - } - const pkg = this.loadDependency(); if (!pkg) { diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts index 06b5eb7a76e9..9b6cb9fd77dc 100644 --- a/packages/tracing-internal/src/node/integrations/postgres.ts +++ b/packages/tracing-internal/src/node/integrations/postgres.ts @@ -4,7 +4,6 @@ import { fill, isThenable, loadModule, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; import type { LazyLoadedIntegration } from './lazy'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; type PgClientQuery = ( config: unknown, @@ -76,11 +75,6 @@ export class Postgres implements LazyLoadedIntegration { * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - DEBUG_BUILD && logger.log('Postgres Integration is skipped because of instrumenter configuration.'); - return; - } - const pkg = this.loadDependency(); if (!pkg) { diff --git a/packages/tracing-internal/src/node/integrations/prisma.ts b/packages/tracing-internal/src/node/integrations/prisma.ts index 2e2aa7dab17a..8f50cb18b3aa 100644 --- a/packages/tracing-internal/src/node/integrations/prisma.ts +++ b/packages/tracing-internal/src/node/integrations/prisma.ts @@ -1,9 +1,8 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getCurrentHub, startSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; import type { Integration } from '@sentry/types'; import { addNonEnumerableProperty, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../../common/debug-build'; -import { shouldDisableAutoInstrumentation } from './utils/node-utils'; type PrismaAction = | 'findUnique' @@ -92,11 +91,6 @@ export class Prisma implements Integration { } options.client.$use((params, next: (params: PrismaMiddlewareParams) => Promise) => { - // eslint-disable-next-line deprecation/deprecation - if (shouldDisableAutoInstrumentation(getCurrentHub)) { - return next(params); - } - const action = params.action; const model = params.model; diff --git a/packages/tracing-internal/src/node/integrations/utils/node-utils.ts b/packages/tracing-internal/src/node/integrations/utils/node-utils.ts deleted file mode 100644 index cc40f2946406..000000000000 --- a/packages/tracing-internal/src/node/integrations/utils/node-utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Hub } from '@sentry/types'; - -/** - * Check if Sentry auto-instrumentation should be disabled. - * - * @param getCurrentHub A method to fetch the current hub - * @returns boolean - */ -export function shouldDisableAutoInstrumentation(getCurrentHub: () => Hub): boolean { - // eslint-disable-next-line deprecation/deprecation - const clientOptions = getCurrentHub().getClient()?.getOptions(); - const instrumenter = clientOptions?.instrumenter || 'sentry'; - - return instrumenter !== 'sentry'; -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6a5f14d7b03c..57c565a86d6e 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -133,7 +133,6 @@ export type { export type { User, UserFeedback } from './user'; export type { WebFetchHeaders, WebFetchRequest } from './webfetchapi'; export type { WrappedFunction } from './wrappedfunction'; -export type { Instrumenter } from './instrumenter'; export type { HandlerDataFetch, HandlerDataXhr, diff --git a/packages/types/src/instrumenter.ts b/packages/types/src/instrumenter.ts deleted file mode 100644 index 4212d53b11f0..000000000000 --- a/packages/types/src/instrumenter.ts +++ /dev/null @@ -1 +0,0 @@ -export type Instrumenter = 'sentry' | 'otel'; diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index bcc3498a1e59..3f8c55e84949 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -1,6 +1,5 @@ import type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; import type { ErrorEvent, Event, EventHint, TransactionEvent } from './event'; -import type { Instrumenter } from './instrumenter'; import type { Integration } from './integration'; import type { CaptureContext } from './scope'; import type { SdkMetadata } from './sdkmetadata'; @@ -60,15 +59,6 @@ export interface ClientOptions, Sp */ metadata: TransactionMetadata; - /** - * The instrumenter that created this transaction. - * - * @deprecated This field will be removed in v8. - */ - instrumenter: Instrumenter; - /** * Set the context of a transaction event. * @deprecated Use either `.setAttribute()`, or set the context on the scope before creating the transaction. diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 5a0c00208b73..3a50156162ae 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -69,10 +69,6 @@ export function init(options: VercelEdgeOptions = {}): void { options.autoSessionTracking = true; } - if (options.instrumenter === undefined) { - options.instrumenter = 'sentry'; - } - const clientOptions: VercelEdgeClientOptions = { ...options, stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), From b194be4e59357f3a57aebc6a8d1fe0d558b1bb36 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Fri, 23 Feb 2024 09:44:40 -0500 Subject: [PATCH 146/173] feat(v8/core): Remove deprecated setHttpStatus (#10774) --- packages/core/src/tracing/sentrySpan.ts | 10 ---------- packages/types/src/span.ts | 7 ------- 2 files changed, 17 deletions(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index f74050b24223..b837b97285ed 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -25,7 +25,6 @@ import { spanToTraceContext, } from '../utils/spanUtils'; import type { SpanStatusType } from './spanstatus'; -import { setHttpStatus } from './spanstatus'; import { addChildSpanToSpan } from './trace'; /** @@ -390,15 +389,6 @@ export class SentrySpan implements SpanInterface { return this; } - /** - * @inheritDoc - * @deprecated Use top-level `setHttpStatus()` instead. - */ - public setHttpStatus(httpStatus: number): this { - setHttpStatus(this, httpStatus); - return this; - } - /** * @inheritDoc */ diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index a23c3feababa..03a45c6017fc 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -297,13 +297,6 @@ export interface Span extends Omit Date: Fri, 23 Feb 2024 12:54:56 -0500 Subject: [PATCH 147/173] feat(v8/core): Remove span.finish call (#10773) --- packages/core/src/tracing/sentrySpan.ts | 9 ----- packages/node-experimental/README.md | 2 +- packages/profiling-node/README.md | 2 +- packages/profiling-node/src/hubextensions.ts | 12 +++--- .../test/hubextensions.hub.test.ts | 39 +++++++------------ .../profiling-node/test/hubextensions.test.ts | 24 ++++++------ packages/profiling-node/test/index.test.ts | 23 ++++------- packages/types/src/span.ts | 9 ----- 8 files changed, 40 insertions(+), 80 deletions(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index b837b97285ed..e81675e1b89c 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -397,15 +397,6 @@ export class SentrySpan implements SpanInterface { return this; } - /** - * @inheritDoc - * - * @deprecated Use `.end()` instead. - */ - public finish(endTimestamp?: number): void { - return this.end(endTimestamp); - } - /** @inheritdoc */ public end(endTimestamp?: SpanTimeInput): void { // If already ended, skip diff --git a/packages/node-experimental/README.md b/packages/node-experimental/README.md index 36a522d78248..fbb1dfc232cc 100644 --- a/packages/node-experimental/README.md +++ b/packages/node-experimental/README.md @@ -82,7 +82,7 @@ const span = Sentry.startInactiveSpan({ description: 'non-active span' }); doSomethingSlow(); -span.finish(); +span.end(); ``` Finally you can also get the currently active span, if you need to do more with it: diff --git a/packages/profiling-node/README.md b/packages/profiling-node/README.md index 82d7ea97b4c6..3facb6cd7541 100644 --- a/packages/profiling-node/README.md +++ b/packages/profiling-node/README.md @@ -46,7 +46,7 @@ const transaction = Sentry.startTransaction({ name: 'some workflow' }); // The code between startTransaction and transaction.finish will be profiled -transaction.finish(); +transaction.end(); ``` ### Building the package from source diff --git a/packages/profiling-node/src/hubextensions.ts b/packages/profiling-node/src/hubextensions.ts index 19fe86777bff..2f615eb1324e 100644 --- a/packages/profiling-node/src/hubextensions.ts +++ b/packages/profiling-node/src/hubextensions.ts @@ -183,13 +183,12 @@ export function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction: S }, maxProfileDurationMs); // We need to reference the original finish call to avoid creating an infinite loop - // eslint-disable-next-line deprecation/deprecation - const originalFinish = transaction.finish.bind(transaction); + const originalEnd = transaction.end.bind(transaction); // Wrap the transaction finish method to stop profiling and set the profile on the transaction. - function profilingWrappedTransactionFinish(): void { + function profilingWrappedTransactionEnd(): void { if (!profile_id) { - return originalFinish(); + return originalEnd(); } // We stop the handler first to ensure that the timeout is cleared and the profile is stopped. @@ -207,11 +206,10 @@ export function __PRIVATE__wrapStartTransactionWithProfiling(startTransaction: S // @ts-expect-error profile is not part of metadata // eslint-disable-next-line deprecation/deprecation transaction.setMetadata({ profile }); - return originalFinish(); + return originalEnd(); } - // eslint-disable-next-line deprecation/deprecation - transaction.finish = profilingWrappedTransactionFinish; + transaction.end = profilingWrappedTransactionEnd; return transaction; }; } diff --git a/packages/profiling-node/test/hubextensions.hub.test.ts b/packages/profiling-node/test/hubextensions.hub.test.ts index 87b2154deeaf..b73592323044 100644 --- a/packages/profiling-node/test/hubextensions.hub.test.ts +++ b/packages/profiling-node/test/hubextensions.hub.test.ts @@ -101,8 +101,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); expect(transportSpy.mock.calls?.[0]?.[0]?.[1]?.[0]?.[1]).toMatchObject({ environment: 'test-environment' }); @@ -138,8 +137,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub' }); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -188,8 +186,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub', traceId: 'boop' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); expect(logSpy.mock?.calls?.[6]?.[0]).toBe('[Profiling] Invalid traceId: ' + 'boop' + ' on profiled event'); @@ -211,8 +208,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -232,8 +228,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -285,8 +280,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -308,8 +302,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -336,8 +329,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -360,8 +352,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); @@ -392,8 +383,7 @@ describe('hubextensions', () => { expect(stopProfilingSpy).toHaveBeenCalledTimes(1); expect((stopProfilingSpy.mock.calls[startProfilingSpy.mock.calls.length - 1]?.[0] as string).length).toBe(32); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); expect(stopProfilingSpy).toHaveBeenCalledTimes(1); }); }); @@ -409,10 +399,8 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'txn' }); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); + transaction.end(); expect(stopProfilingSpy).toHaveBeenCalledTimes(1); }); @@ -456,8 +444,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(1000); diff --git a/packages/profiling-node/test/hubextensions.test.ts b/packages/profiling-node/test/hubextensions.test.ts index ffa4131b96ab..362803e4b48d 100644 --- a/packages/profiling-node/test/hubextensions.test.ts +++ b/packages/profiling-node/test/hubextensions.test.ts @@ -18,8 +18,8 @@ function makeTransactionMock(options = {}): Transaction { tags: {}, sampled: true, contexts: {}, - startChild: () => ({ finish: () => void 0 }), - finish() { + startChild: () => ({ end: () => void 0 }), + end() { return; }, toContext: () => { @@ -74,7 +74,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, {}); - transaction.finish(); + transaction.end(); expect(startTransaction).toHaveBeenCalledTimes(1); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -87,7 +87,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, {}); - transaction.finish(); + transaction.end(); expect(startTransaction).toHaveBeenCalledTimes(1); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -102,7 +102,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, {}); - transaction.finish(); + transaction.end(); expect(startTransaction).toHaveBeenCalledTimes(1); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -118,7 +118,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, {}); - transaction.finish(); + transaction.end(); expect(startTransaction).toHaveBeenCalledTimes(1); expect(startProfilingSpy).toHaveBeenCalledTimes(1); @@ -136,7 +136,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, {}); - transaction.finish(); + transaction.end(); expect(startTransaction).toHaveBeenCalledTimes(1); expect(startProfilingSpy).not.toHaveBeenCalledTimes(1); @@ -150,7 +150,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const samplingContext = { beep: 'boop' }; const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, samplingContext); - transaction.finish(); + transaction.end(); const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -171,7 +171,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const samplingContext = { beep: 'boop' }; const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, samplingContext); - transaction.finish(); + transaction.end(); expect(options.profilesSampler).toHaveBeenCalled(); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -192,7 +192,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const samplingContext = { beep: 'boop' }; const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, samplingContext); - transaction.finish(); + transaction.end(); expect(options.profilesSampler).toHaveBeenCalled(); expect(startProfilingSpy).not.toHaveBeenCalled(); @@ -212,7 +212,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const samplingContext = { beep: 'boop' }; const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, samplingContext); - transaction.finish(); + transaction.end(); expect(options.profilesSampler).toHaveBeenCalledWith({ ...samplingContext, @@ -235,7 +235,7 @@ describe('hubextensions', () => { const maybeStartTransactionWithProfiling = __PRIVATE__wrapStartTransactionWithProfiling(startTransaction); const samplingContext = { beep: 'boop' }; const transaction = maybeStartTransactionWithProfiling.call(hub, { name: '' }, samplingContext); - transaction.finish(); + transaction.end(); expect(startProfilingSpy).toHaveBeenCalled(); }); diff --git a/packages/profiling-node/test/index.test.ts b/packages/profiling-node/test/index.test.ts index d0ac92ad65d6..b29c21bb1f23 100644 --- a/packages/profiling-node/test/index.test.ts +++ b/packages/profiling-node/test/index.test.ts @@ -88,8 +88,7 @@ describe('Sentry - Profiling', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'title' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); await Sentry.flush(500); expect(findProfile(transport)).not.toBe(null); @@ -108,10 +107,8 @@ describe('Sentry - Profiling', () => { const t2 = Sentry.startTransaction({ name: 'inner' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - t2.finish(); - // eslint-disable-next-line deprecation/deprecation - t1.finish(); + t2.end(); + t1.end(); await Sentry.flush(500); @@ -133,17 +130,15 @@ describe('Sentry - Profiling', () => { // eslint-disable-next-line deprecation/deprecation const t2 = Sentry.startTransaction({ name: 'same-title' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - t2.finish(); - // eslint-disable-next-line deprecation/deprecation - t1.finish(); + t2.end(); + t1.end(); await Sentry.flush(500); expect(findAllProfiles(transport)).toHaveLength(2); expect(findProfile(transport)).not.toBe(null); }); - it('does not crash if finish is called multiple times', async () => { + it('does not crash if end is called multiple times', async () => { const [client, transport] = makeClientWithoutHooks(); // eslint-disable-next-line deprecation/deprecation const hub = Sentry.getCurrentHub(); @@ -153,10 +148,8 @@ describe('Sentry - Profiling', () => { // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'title' }); await wait(500); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); - // eslint-disable-next-line deprecation/deprecation - transaction.finish(); + transaction.end(); + transaction.end(); await Sentry.flush(500); expect(findAllProfiles(transport)).toHaveLength(1); diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 03a45c6017fc..1a0625fee214 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -245,15 +245,6 @@ export interface Span extends Omit Date: Mon, 26 Feb 2024 13:13:22 +0100 Subject: [PATCH 148/173] feat(core): Allow to pass `forceTransaction` to `startSpan()` APIs (#10749) This will ensure a span is sent as a transaction to Sentry. This only implements this option for the core implementation, not yet for OTEL - that is a follow up here: https://github.com/getsentry/sentry-javascript/pull/10807 --- packages/core/src/tracing/trace.ts | 126 ++++--- packages/core/src/tracing/transaction.ts | 4 +- packages/core/test/lib/tracing/trace.test.ts | 321 +++++++++++++++++- .../node/test/integrations/undici.test.ts | 2 +- packages/types/src/startSpanOptions.ts | 7 + 5 files changed, 402 insertions(+), 58 deletions(-) diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index dff11499285b..c227dda5057a 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,13 +1,14 @@ import type { Hub, Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types'; import { addNonEnumerableProperty, dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; +import { getDynamicSamplingContextFromSpan } from '.'; import { getCurrentScope, getIsolationScope, withScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; import { getCurrentHub } from '../hub'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; -import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; +import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; /** * Wraps a function with a transaction/span and finishes the span after the function is done. @@ -21,7 +22,7 @@ import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; * and the `span` returned from the callback will be undefined. */ export function startSpan(context: StartSpanOptions, callback: (span: Span | undefined) => T): T { - const ctx = normalizeContext(context); + const spanContext = normalizeContext(context); return withScope(context.scope, scope => { // eslint-disable-next-line deprecation/deprecation @@ -30,10 +31,14 @@ export function startSpan(context: StartSpanOptions, callback: (span: Span | const parentSpan = scope.getSpan(); const shouldSkipSpan = context.onlyIfParent && !parentSpan; - const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); - - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(activeSpan); + const activeSpan = shouldSkipSpan + ? undefined + : createChildSpanOrTransaction(hub, { + parentSpan, + spanContext, + forceTransaction: context.forceTransaction, + scope, + }); return handleCallbackErrors( () => callback(activeSpan), @@ -66,7 +71,7 @@ export function startSpanManual( context: StartSpanOptions, callback: (span: Span | undefined, finish: () => void) => T, ): T { - const ctx = normalizeContext(context); + const spanContext = normalizeContext(context); return withScope(context.scope, scope => { // eslint-disable-next-line deprecation/deprecation @@ -75,10 +80,14 @@ export function startSpanManual( const parentSpan = scope.getSpan(); const shouldSkipSpan = context.onlyIfParent && !parentSpan; - const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); - - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(activeSpan); + const activeSpan = shouldSkipSpan + ? undefined + : createChildSpanOrTransaction(hub, { + parentSpan, + spanContext, + forceTransaction: context.forceTransaction, + scope, + }); function finishAndSetSpan(): void { activeSpan && activeSpan.end(); @@ -114,7 +123,7 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { return undefined; } - const ctx = normalizeContext(context); + const spanContext = normalizeContext(context); // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); const parentSpan = context.scope @@ -128,41 +137,19 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { return undefined; } - const isolationScope = getIsolationScope(); - const scope = getCurrentScope(); - - let span: Span | undefined; - - if (parentSpan) { - // eslint-disable-next-line deprecation/deprecation - span = parentSpan.startChild(ctx); - } else { - const { traceId, dsc, parentSpanId, sampled } = { - ...isolationScope.getPropagationContext(), - ...scope.getPropagationContext(), - }; - - // eslint-disable-next-line deprecation/deprecation - span = hub.startTransaction({ - traceId, - parentSpanId, - parentSampled: sampled, - ...ctx, - metadata: { - dynamicSamplingContext: dsc, - // eslint-disable-next-line deprecation/deprecation - ...ctx.metadata, - }, - }); - } - - if (parentSpan) { - addChildSpanToSpan(parentSpan, span); - } + const scope = context.scope || getCurrentScope(); - setCapturedScopesOnSpan(span, scope, isolationScope); + // Even though we don't actually want to make this span active on the current scope, + // we need to make it active on a temporary scope that we use for event processing + // as otherwise, it won't pick the correct span for the event when processing it + const temporaryScope = scope.clone(); - return span; + return createChildSpanOrTransaction(hub, { + parentSpan, + spanContext, + forceTransaction: context.forceTransaction, + scope: temporaryScope, + }); } /** @@ -277,20 +264,47 @@ export const continueTrace: ContinueTrace = ( function createChildSpanOrTransaction( hub: Hub, - parentSpan: Span | undefined, - ctx: TransactionContext, + { + parentSpan, + spanContext, + forceTransaction, + scope, + }: { + parentSpan: Span | undefined; + spanContext: TransactionContext; + forceTransaction?: boolean; + scope: Scope; + }, ): Span | undefined { if (!hasTracingEnabled()) { return undefined; } const isolationScope = getIsolationScope(); - const scope = getCurrentScope(); let span: Span | undefined; - if (parentSpan) { + if (parentSpan && !forceTransaction) { + // eslint-disable-next-line deprecation/deprecation + span = parentSpan.startChild(spanContext); + addChildSpanToSpan(parentSpan, span); + } else if (parentSpan) { + // If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope + const dsc = getDynamicSamplingContextFromSpan(parentSpan); + const { traceId, spanId: parentSpanId } = parentSpan.spanContext(); + const sampled = spanIsSampled(parentSpan); + // eslint-disable-next-line deprecation/deprecation - span = parentSpan.startChild(ctx); + span = hub.startTransaction({ + traceId, + parentSpanId, + parentSampled: sampled, + ...spanContext, + metadata: { + dynamicSamplingContext: dsc, + // eslint-disable-next-line deprecation/deprecation + ...spanContext.metadata, + }, + }); } else { const { traceId, dsc, parentSpanId, sampled } = { ...isolationScope.getPropagationContext(), @@ -302,18 +316,20 @@ function createChildSpanOrTransaction( traceId, parentSpanId, parentSampled: sampled, - ...ctx, + ...spanContext, metadata: { dynamicSamplingContext: dsc, // eslint-disable-next-line deprecation/deprecation - ...ctx.metadata, + ...spanContext.metadata, }, }); } - if (parentSpan) { - addChildSpanToSpan(parentSpan, span); - } + // We always set this as active span on the scope + // In the case of this being an inactive span, we ensure to pass a detached scope in here in the first place + // But by having this here, we can ensure that the lookup through `getCapturedScopesOnSpan` results in the correct scope & span combo + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(span); setCapturedScopesOnSpan(span, scope, isolationScope); diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index d3d0c19718a0..bd71918cfc4c 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -301,7 +301,9 @@ export class Transaction extends SentrySpan implements TransactionInterface { ...metadata, capturedSpanScope, capturedSpanIsolationScope, - dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), + ...dropUndefinedKeys({ + dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), + }), }, _metrics_summary: getMetricSummaryJsonForSpan(this), ...(source && { diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 8c18bea9afe9..b02a4d2bb0fc 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,4 +1,4 @@ -import type { Span as SpanType } from '@sentry/types'; +import type { Event, Span as SpanType } from '@sentry/types'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, addTracingExtensions, @@ -288,6 +288,112 @@ describe('startSpan', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to force a transaction with forceTransaction=true', async () => { + const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const transactionEvents: Event[] = []; + + client.addEventProcessor(event => { + if (event.type === 'transaction') { + transactionEvents.push(event); + } + return event; + }); + + startSpan({ name: 'outer transaction' }, () => { + startSpan({ name: 'inner span' }, () => { + startSpan({ name: 'inner transaction', forceTransaction: true }, () => { + startSpan({ name: 'inner span 2' }, () => { + // all good + }); + }); + }); + }); + + await client.flush(); + + const normalizedTransactionEvents = transactionEvents.map(event => { + return { + ...event, + spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + sdkProcessingMetadata: { + dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, + }, + }; + }); + + expect(normalizedTransactionEvents).toHaveLength(2); + + const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); + const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); + + const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; + // The inner transaction should be a child of the last span of the outer transaction + const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerSpanId = innerTransaction?.contexts?.trace?.span_id; + + expect(outerTraceId).toBeDefined(); + expect(innerParentSpanId).toBeDefined(); + expect(innerSpanId).toBeDefined(); + // inner span ID should _not_ be the parent span ID, but the id of the new span + expect(innerSpanId).not.toEqual(innerParentSpanId); + + expect(outerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.sample_rate': 1, + 'sentry.origin': 'manual', + }, + span_id: expect.any(String), + trace_id: expect.any(String), + origin: 'manual', + }, + }); + expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); + expect(outerTransaction?.tags).toEqual({ + transaction: 'outer transaction', + }); + expect(outerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + + expect(innerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.origin': 'manual', + }, + parent_span_id: innerParentSpanId, + span_id: expect.any(String), + trace_id: outerTraceId, + origin: 'manual', + }, + }); + expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); + expect(innerTransaction?.tags).toEqual({ + transaction: 'inner transaction', + }); + expect(innerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + }); + it("picks up the trace id off the parent scope's propagation context", () => { expect.assertions(1); withScope(scope => { @@ -463,6 +569,116 @@ describe('startSpanManual', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to force a transaction with forceTransaction=true', async () => { + const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const transactionEvents: Event[] = []; + + client.addEventProcessor(event => { + if (event.type === 'transaction') { + transactionEvents.push(event); + } + return event; + }); + + startSpanManual({ name: 'outer transaction' }, span => { + startSpanManual({ name: 'inner span' }, span => { + startSpanManual({ name: 'inner transaction', forceTransaction: true }, span => { + startSpanManual({ name: 'inner span 2' }, span => { + // all good + span?.end(); + }); + span?.end(); + }); + span?.end(); + }); + span?.end(); + }); + + await client.flush(); + + const normalizedTransactionEvents = transactionEvents.map(event => { + return { + ...event, + spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + sdkProcessingMetadata: { + dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, + }, + }; + }); + + expect(normalizedTransactionEvents).toHaveLength(2); + + const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); + const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); + + const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; + // The inner transaction should be a child of the last span of the outer transaction + const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerSpanId = innerTransaction?.contexts?.trace?.span_id; + + expect(outerTraceId).toBeDefined(); + expect(innerParentSpanId).toBeDefined(); + expect(innerSpanId).toBeDefined(); + // inner span ID should _not_ be the parent span ID, but the id of the new span + expect(innerSpanId).not.toEqual(innerParentSpanId); + + expect(outerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.sample_rate': 1, + 'sentry.origin': 'manual', + }, + span_id: expect.any(String), + trace_id: expect.any(String), + origin: 'manual', + }, + }); + expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); + expect(outerTransaction?.tags).toEqual({ + transaction: 'outer transaction', + }); + expect(outerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + + expect(innerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.origin': 'manual', + }, + parent_span_id: innerParentSpanId, + span_id: expect.any(String), + trace_id: outerTraceId, + origin: 'manual', + }, + }); + expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); + expect(innerTransaction?.tags).toEqual({ + transaction: 'inner transaction', + }); + expect(innerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + }); + it('allows to pass a `startTime`', () => { const start = startSpanManual({ name: 'outer', startTime: [1234, 0] }, span => { span?.end(); @@ -573,6 +789,109 @@ describe('startInactiveSpan', () => { expect(getActiveSpan()).toBeUndefined(); }); + it('allows to force a transaction with forceTransaction=true', async () => { + const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); + client = new TestClient(options); + setCurrentClient(client); + client.init(); + + const transactionEvents: Event[] = []; + + client.addEventProcessor(event => { + if (event.type === 'transaction') { + transactionEvents.push(event); + } + return event; + }); + + startSpan({ name: 'outer transaction' }, () => { + startSpan({ name: 'inner span' }, () => { + const innerTransaction = startInactiveSpan({ name: 'inner transaction', forceTransaction: true }); + innerTransaction?.end(); + }); + }); + + await client.flush(); + + const normalizedTransactionEvents = transactionEvents.map(event => { + return { + ...event, + spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + sdkProcessingMetadata: { + dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, + }, + }; + }); + + expect(normalizedTransactionEvents).toHaveLength(2); + + const outerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'outer transaction'); + const innerTransaction = normalizedTransactionEvents.find(event => event.transaction === 'inner transaction'); + + const outerTraceId = outerTransaction?.contexts?.trace?.trace_id; + // The inner transaction should be a child of the last span of the outer transaction + const innerParentSpanId = outerTransaction?.spans?.[0].id; + const innerSpanId = innerTransaction?.contexts?.trace?.span_id; + + expect(outerTraceId).toBeDefined(); + expect(innerParentSpanId).toBeDefined(); + expect(innerSpanId).toBeDefined(); + // inner span ID should _not_ be the parent span ID, but the id of the new span + expect(innerSpanId).not.toEqual(innerParentSpanId); + + expect(outerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.sample_rate': 1, + 'sentry.origin': 'manual', + }, + span_id: expect.any(String), + trace_id: expect.any(String), + origin: 'manual', + }, + }); + expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); + expect(outerTransaction?.tags).toEqual({ + transaction: 'outer transaction', + }); + expect(outerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + + expect(innerTransaction?.contexts).toEqual({ + trace: { + data: { + 'sentry.source': 'custom', + 'sentry.origin': 'manual', + }, + parent_span_id: innerParentSpanId, + span_id: expect.any(String), + trace_id: outerTraceId, + origin: 'manual', + }, + }); + expect(innerTransaction?.spans).toEqual([]); + expect(innerTransaction?.tags).toEqual({ + transaction: 'inner transaction', + }); + expect(innerTransaction?.sdkProcessingMetadata).toEqual({ + dynamicSamplingContext: { + environment: 'production', + trace_id: outerTraceId, + sample_rate: '1', + transaction: 'outer transaction', + sampled: 'true', + }, + }); + }); + it('allows to pass a `startTime`', () => { const span = startInactiveSpan({ name: 'outer', startTime: [1234, 0] }); expect(spanToJSON(span!).start_timestamp).toEqual(1234); diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 3284e13436d8..2409515c08cb 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -154,7 +154,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { }); }); - it('creates a span for invalid looking urls xxx', async () => { + it('creates a span for invalid looking urls', async () => { await startSpan({ name: 'outer-span' }, async outerSpan => { try { // Intentionally add // to the url diff --git a/packages/types/src/startSpanOptions.ts b/packages/types/src/startSpanOptions.ts index 44be5bb1c699..01293e129c74 100644 --- a/packages/types/src/startSpanOptions.ts +++ b/packages/types/src/startSpanOptions.ts @@ -19,6 +19,13 @@ export interface StartSpanOptions extends TransactionContext { /** An op for the span. This is a categorization for spans. */ op?: string; + /** + * If set to true, this span will be forced to be treated as a transaction in the Sentry UI, if possible and applicable. + * Note that it is up to the SDK to decide how exactly the span will be sent, which may change in future SDK versions. + * It is not guaranteed that a span started with this flag set to `true` will be sent as a transaction. + */ + forceTransaction?: boolean; + /** * The origin of the span - if it comes from auto instrumentation or manual instrumentation. * From 8ed3598e62479bb3bc000eeda5b77cd1012ab4af Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 26 Feb 2024 09:02:02 -0500 Subject: [PATCH 149/173] feat(v8/core): Remove deprecated updateWithContext method (#10800) ref https://github.com/getsentry/sentry-javascript/issues/10677 --- packages/core/src/tracing/sentrySpan.ts | 23 ----------------------- packages/core/src/tracing/transaction.ts | 13 ------------- packages/types/src/span.ts | 6 ------ packages/types/src/transaction.ts | 6 ------ 4 files changed, 48 deletions(-) diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index e81675e1b89c..11ac96f2f266 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -441,29 +441,6 @@ export class SentrySpan implements SpanInterface { }); } - /** - * @inheritDoc - * - * @deprecated Update the fields directly instead. - */ - public updateWithContext(spanContext: SpanContext): this { - // eslint-disable-next-line deprecation/deprecation - this.data = spanContext.data || {}; - this._name = spanContext.name; - this._endTime = spanContext.endTimestamp; - this._attributes = { ...this._attributes, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: spanContext.op }; - this._parentSpanId = spanContext.parentSpanId; - this._sampled = spanContext.sampled; - this._spanId = spanContext.spanId || this._spanId; - this._startTime = spanContext.startTimestamp || this._startTime; - this._status = spanContext.status; - // eslint-disable-next-line deprecation/deprecation - this.tags = spanContext.tags || {}; - this._traceId = spanContext.traceId || this._traceId; - - return this; - } - /** * @inheritDoc * diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index bd71918cfc4c..c1c567b68710 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -198,19 +198,6 @@ export class Transaction extends SentrySpan implements TransactionInterface { }); } - /** - * @inheritDoc - */ - public updateWithContext(transactionContext: TransactionContext): this { - // eslint-disable-next-line deprecation/deprecation - super.updateWithContext(transactionContext); - - this._name = transactionContext.name || ''; - this._trimEnd = transactionContext.trimEnd; - - return this; - } - /** * @inheritdoc * diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 1a0625fee214..dfc5ad5f9e6f 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -307,12 +307,6 @@ export interface Span extends Omit, Sp */ toContext(): TransactionContext; - /** - * Updates the current transaction with a new `TransactionContext`. - * @deprecated Update the fields directly instead. - */ - updateWithContext(transactionContext: TransactionContext): this; - /** * Set metadata for this transaction. * @deprecated Use attributes or store data on the scope instead. From d5705949ebd5cc279616d88f8026b4135a298846 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 26 Feb 2024 14:45:46 +0000 Subject: [PATCH 150/173] feat(remix): Add Vite dev-mode support to Express instrumentation. (#10784) Resolves #10724 Related: https://github.com/getsentry/sentry-javascript/issues/9500 Adds dev-mode support to Remix setups with Vite and Express. We currently accept Remix server `build` as an object to instrument. But Remix allows `build` as a synchronous or asynchronous function that returns the build object. Currently, it seems that functions are only used in development servers, and not in production. So, while this update slightly reduces `requestHandler` performance on dev servers, it does not on production builds. We need `build` in 2 places: 1- We instrument the loaders / actions on build, then we pass them down to the original implementations. 2- We use the `routes` inside them to create parameterised transactions. This update adds new internal wrappers around them to make sure that we don't miss out on the returned / resolved values in case `build` is a function, for both cases. This PR also adds a new E2E test application using the latest Remix version and Vite, and it runs the tests on `dev` mode. We also need a documentation update to reflect this, if it gets merged. --- .github/workflows/build.yml | 1 + .../.eslintrc.js | 79 + .../.gitignore | 5 + .../create-remix-app-express-vite-dev/.npmrc | 2 + .../README.md | 34 + .../app/entry.client.tsx | 46 + .../app/entry.server.tsx | 147 + .../app/root.tsx | 80 + .../app/routes/_index.tsx | 21 + .../app/routes/client-error.tsx | 13 + .../app/routes/navigate.tsx | 20 + .../app/routes/user.$id.tsx | 3 + .../env.d.ts | 2 + .../package.json | 51 + .../playwright.config.ts | 58 + .../server.mjs | 53 + .../tests/behaviour-client.test.ts | 200 + .../tsconfig.json | 24 + .../vite.config.ts | 18 + .../yarn.lock | 6230 +++++++++++++++++ .../create-remix-app-v2/package.json | 12 +- .../remix/src/utils/serverAdapters/express.ts | 102 +- 22 files changed, 7179 insertions(+), 22 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.eslintrc.js create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/README.md create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/root.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/_index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/client-error.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/navigate.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/routes/user.$id.tsx create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/server.mjs create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tests/behaviour-client.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/vite.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/yarn.lock diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a488a285816f..f852c9f465b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1058,6 +1058,7 @@ jobs: 'create-next-app', 'create-remix-app', 'create-remix-app-v2', + 'create-remix-app-express-vite-dev', 'debug-id-sourcemaps', 'nextjs-app-dir', 'nextjs-14', diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.eslintrc.js b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.eslintrc.js new file mode 100644 index 000000000000..e0a82f1826e3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.eslintrc.js @@ -0,0 +1,79 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ['eslint:recommended'], + + overrides: [ + // React + { + files: ['**/*.{js,jsx,ts,tsx}'], + plugins: ['react', 'jsx-a11y'], + extends: [ + 'plugin:react/recommended', + 'plugin:react/jsx-runtime', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + ], + settings: { + react: { + version: 'detect', + }, + formComponents: ['Form'], + linkComponents: [ + { name: 'Link', linkAttribute: 'to' }, + { name: 'NavLink', linkAttribute: 'to' }, + ], + 'import/resolver': { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ['**/*.{ts,tsx}'], + plugins: ['@typescript-eslint', 'import'], + parser: '@typescript-eslint/parser', + settings: { + 'import/internal-regex': '^~/', + 'import/resolver': { + node: { + extensions: ['.ts', '.tsx'], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript'], + }, + + // Node + { + files: ['.eslintrc.js', 'server.mjs'], + env: { + node: true, + }, + }, + ], +}; diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.gitignore b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.gitignore new file mode 100644 index 000000000000..80ec311f4ff5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.gitignore @@ -0,0 +1,5 @@ +node_modules + +/.cache +/build +.env diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.npmrc b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/.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/create-remix-app-express-vite-dev/README.md b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/README.md new file mode 100644 index 000000000000..ec619a8eb455 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/README.md @@ -0,0 +1,34 @@ +# Welcome to Remix + Vite! + +📖 See the [Remix docs](https://remix.run/docs) and the [Remix Vite docs](https://remix.run/docs/en/main/future/vite) for details on supported features. + +## Development + +Run the Express server with Vite dev middleware: + +```shellscript +npm run dev +``` + +## Deployment + +First, build your app for production: + +```sh +npm run build +``` + +Then run the app in production mode: + +```sh +npm start +``` + +Now you'll need to pick a host to deploy it to. + +### DIY + +If you're familiar with deploying Express applications you should be right at home. Just make sure to deploy the output of `npm run build` + +- `build/server` +- `build/client` diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx new file mode 100644 index 000000000000..d71aaa5cd286 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.client.tsx @@ -0,0 +1,46 @@ +import { RemixBrowser, useLocation, useMatches } from '@remix-run/react'; +import * as Sentry from '@sentry/remix'; +import { StrictMode, startTransition, useEffect } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: window.ENV.SENTRY_DSN, + integrations: [ + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, + }), + new Sentry.Replay(), + ], + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! + // Session Replay + replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. +}); + +Sentry.addEventProcessor(event => { + if ( + event.type === 'transaction' && + (event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation') + ) { + const eventId = event.event_id; + if (eventId) { + window.recordedTransactions = window.recordedTransactions || []; + window.recordedTransactions.push(eventId); + } + } + + return event; +}); + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx new file mode 100644 index 000000000000..c3deb6369af3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/entry.server.tsx @@ -0,0 +1,147 @@ +import { PassThrough } from 'node:stream'; + +import type { AppLoadContext, EntryContext } from '@remix-run/node'; +import { createReadableStreamFromReadable } from '@remix-run/node'; +import { installGlobals } from '@remix-run/node'; +import { RemixServer } from '@remix-run/react'; +import * as Sentry from '@sentry/remix'; +import * as isbotModule from 'isbot'; +import { renderToPipeableStream } from 'react-dom/server'; + +installGlobals(); + +const ABORT_DELAY = 5_000; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production! +}); + +export const handleError = Sentry.wrapRemixHandleError; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext, +) { + return isBotRequest(request.headers.get('user-agent')) + ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) + : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); +} + +// We have some Remix apps in the wild already running with isbot@3 so we need +// to maintain backwards compatibility even though we want new apps to use +// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev. +function isBotRequest(userAgent: string | null) { + if (!userAgent) { + return false; + } + + // isbot >= 3.8.0, >4 + if ('isbot' in isbotModule && typeof isbotModule.isbot === 'function') { + return isbotModule.isbot(userAgent); + } + + // isbot < 3.8.0 + if ('default' in isbotModule && typeof isbotModule.default === 'function') { + return isbotModule.default(userAgent); + } + + return false; +} + +function handleBotRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} + +function handleBrowserRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); + + setTimeout(abort, ABORT_DELAY); + }); +} diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/root.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/root.tsx new file mode 100644 index 000000000000..517a37a9d76b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/app/root.tsx @@ -0,0 +1,80 @@ +import { cssBundleHref } from '@remix-run/css-bundle'; +import { LinksFunction, MetaFunction, json } from '@remix-run/node'; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, + useRouteError, +} from '@remix-run/react'; +import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix'; +import type { SentryMetaArgs } from '@sentry/remix'; + +export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])]; + +export const loader = () => { + return json({ + ENV: { + SENTRY_DSN: process.env.E2E_TEST_DSN, + }, + }); +}; + +export const meta = ({ data }: SentryMetaArgs>) => { + return [ + { + env: data.ENV, + }, + { + name: 'sentry-trace', + content: data.sentryTrace, + }, + { + name: 'baggage', + content: data.sentryBaggage, + }, + ]; +}; + +export function ErrorBoundary() { + const error = useRouteError(); + const eventId = captureRemixErrorBoundaryError(error); + + return ( +
    + ErrorBoundary Error + {eventId} +
    + ); +} + +function App() { + const { ENV } = useLoaderData(); + + return ( + + + + +