diff --git a/.craft.yml b/.craft.yml index 99efaf3d095e..9259fc979356 100644 --- a/.craft.yml +++ b/.craft.yml @@ -70,9 +70,6 @@ targets: - name: npm id: '@sentry/wasm' includeNames: /^sentry-wasm-\d.*\.tgz$/ - - name: npm - id: '@sentry/integrations' - includeNames: /^sentry-integrations-\d.*\.tgz$/ ## 4. WinterCG Packages - name: npm @@ -131,15 +128,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/hub' - includeNames: /^sentry-hub-\d.*\.tgz$/ - - 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/.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'], diff --git a/.github/CANARY_FAILURE_TEMPLATE.md b/.github/CANARY_FAILURE_TEMPLATE.md index d52932b93fc3..a99ec1e844f7 100644 --- a/.github/CANARY_FAILURE_TEMPLATE.md +++ b/.github/CANARY_FAILURE_TEMPLATE.md @@ -1,5 +1,5 @@ --- title: '{{ env.TITLE }}' -labels: 'Type: Tests' +labels: 'Type: Tests, Waiting for: Product Owner' --- Canary tests failed: {{ env.RUN_LINK }} 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 b30882983ddb..f48cd5b3597a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,7 @@ env: ${{ github.workspace }}/packages/utils/cjs ${{ github.workspace }}/packages/utils/esm - BUILD_CACHE_KEY: ${{ github.event.inputs.commit || github.sha }} + BUILD_CACHE_KEY: build-cache-${{ github.event.inputs.commit || github.sha }} BUILD_PROFILING_NODE_CACHE_TARBALL_KEY: profiling-node-tarball-${{ github.event.inputs.commit || github.sha }} # GH will use the first restore-key it finds that matches @@ -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/**' @@ -99,7 +101,6 @@ jobs: - 'packages/tracing-internal/**' - 'packages/utils/**' - 'packages/types/**' - - 'packages/integrations/**' browser: &browser - *shared - 'packages/browser/**' @@ -115,6 +116,11 @@ jobs: - *shared - *browser - 'packages/ember/**' + node: + - *shared + - 'packages/node/**' + - 'packages/node-experimental/**' + - 'dev-packages/node-integration-tests/**' nextjs: - *shared - *browser @@ -127,18 +133,16 @@ 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: - - 'packages/profiling-node/bindings/**' + - *workflow + - 'packages/profiling-node/**' + - 'dev-packages/e2e-tests/test-applications/node-profiling/**' deno: - *shared - *browser @@ -263,17 +267,12 @@ jobs: needs.job_get_metadata.outputs.force_skip_cache == 'false' with: path: .nxcache - key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} + key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT || github.sha }} # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it restore-keys: ${{needs.job_get_metadata.outputs.is_develop == 'false' && env.NX_CACHE_RESTORE_KEYS || 'nx-never-restore'}} - name: Build packages - # Under normal circumstances, using the git SHA as a cache key, there shouldn't ever be a cache hit on the built - # packages, and so `yarn build` should always run. This `if` check is therefore only there for testing CI issues - # where the built packages are beside the point. In that case, you can change `BUILD_CACHE_KEY` (at the top of - # this file) to a constant and skip rebuilding all of the packages each time CI runs. - if: steps.cache_built_packages.outputs.cache-hit == '' run: yarn build outputs: # this needs to be passed on, because the `needs` context only looks at direct ancestors (so steps which depend on @@ -419,7 +418,6 @@ jobs: name: ${{ github.sha }} path: | ${{ github.workspace }}/packages/browser/build/bundles/** - ${{ github.workspace }}/packages/integrations/build/bundles/** ${{ github.workspace }}/packages/replay/build/bundles/** ${{ github.workspace }}/packages/replay-canvas/build/bundles/** ${{ github.workspace }}/packages/**/*.tgz @@ -477,10 +475,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,21 +506,16 @@ 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 - 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 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 @@ -543,9 +532,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: @@ -554,7 +541,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: @@ -588,7 +575,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 @@ -615,12 +602,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: @@ -721,6 +708,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 @@ -778,6 +772,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 @@ -877,7 +877,7 @@ jobs: strategy: fail-fast: false matrix: - node: [10, 12, 14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 21] typescript: - false include: @@ -905,6 +905,7 @@ jobs: - name: Run integration tests env: NODE_VERSION: ${{ matrix.node }} + TS_VERSION: ${{ matrix.typescript }} run: | cd dev-packages/node-integration-tests yarn test @@ -1048,9 +1049,9 @@ jobs: 'node-express-app', 'create-react-app', 'create-next-app', - # disabling remix e2e tests because of flakes - # 'create-remix-app', - # 'create-remix-app-v2', + 'create-remix-app', + 'create-remix-app-v2', + 'create-remix-app-express-vite-dev', 'debug-id-sourcemaps', 'nextjs-app-dir', 'nextjs-14', @@ -1062,9 +1063,9 @@ 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', - 'node-profiling', 'vue-3' ] build-command: @@ -1086,7 +1087,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 @@ -1107,34 +1107,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 - - - 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 uses: actions/cache/restore@v4 with: @@ -1176,6 +1148,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: @@ -1195,6 +1254,7 @@ jobs: job_browser_loader_tests, job_remix_integration_tests, job_e2e_tests, + job_profiling_e2e_tests, job_artifacts, job_lint, job_check_format, diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index d499c12d661b..b123afdc141b 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -76,7 +76,7 @@ jobs: with: list-files: json filters: | - browser_integration: dev-packages/browser-integration-tests/suites/** + browser_integration: dev-packages/browser-integration-tests/suites/**/test.ts - name: Detect flaky tests run: yarn test:detect-flaky 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/CHANGELOG.md b/CHANGELOG.md index 75a70e414df3..237a8f79099f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,283 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.0.0-alpha.1 + +This is the first Alpha release of the v8 cycle, which includes a variety of breaking changes. + +Read the [in-depth migration guide](./MIGRATION.md) to find out how to address any breaking changes in your code. + +### Important Changes + +**- feat(node): Make `@sentry/node` powered by OpenTelemetry (#10762)** + +The biggest change is the switch to use OpenTelemetry under the hood in `@sentry/node`. This brings with it a variety of +changes: + +- There is now automated performance instrumentation for Express, Fastify, Nest.js and Koa. You can remove any + performance and request isolation code you previously wrote manually for these frameworks. +- All performance instrumenttion is enabled by default, and will only take effect if the instrumented package is used. + You don't need to use `autoDiscoverNodePerformanceMonitoringIntegrations()` anymore. +- You need to ensure to call `Sentry.init()` _before_ you import any other packages. Otherwise, the packages cannot be + instrumented: + +```js +const Sentry = require('@sentry/node'); +Sentry.init({ + dsn: '...', + // ... other config here +}); +// now require other things below this! +const http = require('http'); +const express = require('express'); +// .... +``` + +- Currently, we only support CJS-based Node application out of the box. There is experimental ESM support, see + [the instructions](./packages/node-experimental/README.md#esm-support). +- `startTransaction` and `span.startChild()` are no longer supported. This is due to the underlying change to + OpenTelemetry powered performance instrumentation. See + [docs on the new performance APIs](./docs/v8-new-performance-apis.md) for details. + +Related changes: + +- feat(node-experimental): Add missing re-exports (#10679) +- feat(node-experimental): Move `defaultStackParser` & `getSentryRelease` (#10722) +- feat(node-experimental): Move `errorHandler` (#10728) +- feat(node-experimental): Move cron code over (#10742) +- feat(node-experimental): Move integrations from node (#10743) +- feat(node-experimental): Properly set request & session on http requests (#10676) +- feat(opentelemetry): Support `forceTransaction` in OTEL (#10807) +- feat(opentelemetry): Align span options with core span options (#10761) +- feat(opentelemetry): Do not capture span events as breadcrumbs (#10612) +- feat(opentelemetry): Ensure DSC & attributes are correctly set (#10806) +- feat(opentelemetry): Fix & align isolation scope usage in node-experimental (#10570) +- feat(opentelemetry): Merge node-experimental changes into opentelemetry (#10689) +- ref(node-experimental): Cleanup re-exports (#10741) +- ref(node-experimental): Cleanup tracing intergations (#10730) +- ref(node-experimental): Copy transport & client to node-experimental (#10720) +- ref(node-experimental): Remove custom `isInitialized` (#10607) +- ref(node-experimental): Remove custom hub & scope (#10616) +- ref(node-experimental): Remove deprecated class integrations (#10675) +- ref(node-experimental): Rename `errorHandler` to `expressErrorHandler` (#10746) +- ref(node-integration-tests): Migrate to new Http integration (#10765) +- ref(node): Align semantic attribute handling (#10827) + +**- feat: Remove `@sentry/integrations` package (#10799)** + +This package is no longer published. You can instead import these pluggable integrations directly from your SDK package +(e.g. `@sentry/browser` or `@sentry/react`). + +**- feat: Remove `@sentry/hub` package (#10783)** + +This package is no longer published. You can instead import directly from your SDK package (e.g. `@sentry/react` or +`@sentry/node`). + +**- feat(v8): Remove @sentry/tracing (#10625)** + +This package is no longer published. You can instead import directly from your SDK package (e.g. `@sentry/react` or +`@sentry/node`). + +**- feat: Set required node version to >=14.8.0 for all packages (#10834)** + +The minimum required node version is now 14.8+. If you need support for older node versions, you can stay on the v7 +branch. + +**- Removed class-based integrations** + +We have removed most of the deprecated class-based integrations. Instead, you can use the functional styles: + +```js +import * as Sentry from '@sentry/browser'; +// v7 +Sentry.init({ + integrations: [new Sentry.BrowserTracing()], +}); +// v8 +Sentry.init({ + integrations: [new Sentry.browserTracingIntegration()], +}); +``` + +- ref: Remove `BrowserTracing` (#10653) +- feat(v8/node): Remove LocalVariables class integration (#10558) +- feat(v8/react): Delete react router exports (#10532) +- feat(v8/vue): Remove all deprecated exports from vue (#10533) +- feat(v8/wasm): Remove deprecated exports (#10552) + +**- feat(v8/browser): Remove XHR transport (#10703)** + +We have removed the XHR transport, and are instead using the fetch-based transport now by default. This means that if +you are using Sentry in a browser environment without fetch, you'll need to either provide a fetch polyfill, or provide +a custom transport to Sentry. + +**- feat(sveltekit): Update `@sentry/vite-plugin` to 2.x and adjust options API (#10813)** + +We have updated `@sentry/sveltekit` to use the latest version of `@sentry/vite-plugin`, which lead to changes in +configuration options. + +### Other Changes + +- feat: Allow passing `null` to `withActiveSpan` (#10717) +- feat: Implement new Async Context Strategy (#10647) +- feat: Remove `hub` from global, `hub.run` & hub utilities (#10718) +- feat: Update default trace propagation targets logic in the browser (#10621) +- feat(browser): Export `getIsolationScope` and `getGlobalScope` (#10658) +- feat(core): Add metric summaries to spans (#10554) +- feat(core): Decouple metrics aggregation from client (#10628) +- feat(core): Lookup client on current scope, not hub (#10635) +- feat(core): Make `setXXX` methods set on isolation scope (#10678) +- feat(core): Make custom tracing methods return spans & set default op (#10633) +- feat(core): Make global `addBreadcrumb` write to the isolation scope instead of current scope (#10586) +- feat(core): Remove health check transaction filters (#10818) +- feat(core): Streamline custom hub creation for node-experimental (#10555) +- feat(core): Update `addEventProcessor` to add to isolation scope (#10606) +- feat(core): Update `Sentry.addBreadcrumb` to skip hub (#10601) +- feat(core): Use global `TextEncoder` and `TextDecoder` (#10701) +- feat(deps): bump @sentry/cli from 2.26.0 to 2.28.0 (#10496) +- feat(deps): bump @sentry/cli from 2.28.0 to 2.28.5 (#10620) +- feat(deps): bump @sentry/cli from 2.28.5 to 2.28.6 (#10727) +- feat(integrations): Capture error arguments as exception regardless of level in `captureConsoleIntegration` (#10744) +- feat(metrics): Remove metrics method from `BaseClient` (#10789) +- feat(node): Remove unnecessary URL imports (#10860) +- feat(react): Drop support for React 15 (#10115) +- feat(remix): Add Vite dev-mode support to Express instrumentation. (#10784) +- fix: Export session API (#10711) +- fix(angular-ivy): Add `exports` field to `package.json` (#10569) +- fix(angular): Ensure navigations always create a transaction (#10646) +- fix(core): Add lost scope tests & fix update case (#10738) +- fix(core): Fix scope capturing via `captureContext` function (#10735) +- fix(feedback): Replay breadcrumb for feedback events was incorrect (#10536) +- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10642) +- fix(node): import `worker_threads` and fix node v14 types (#10791) +- fix(node): Record local variables with falsy values, `null` and `undefined` (#10821) +- fix(stacktrace): Always use `?` for anonymous function name (#10732) +- fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) +- fix(sveltekit): Ensure navigations and redirects always create a new transaction (#10656) +- fix(sveltekit): Properly await sourcemaps flattening (#10602) +- fix(types): Improve attachment type (#10832) +- fx(node): Fix anr worker check (#10719) +- ref: Cleanup browser profiling integration (#10766) +- ref: Collect child spans references via non-enumerable on Span object (#10715) +- ref: Make scope setters on hub only write to isolation scope (#10572) +- ref: Store runtime on isolation scope (#10657) +- ref(astro): Put request as SDK processing metadata instead of span data (#10840) +- ref(core): Always use a (default) ACS (#10644) +- ref(core): Make `on` and `emit` required on client (#10603) +- ref(core): Make remaining client methods required (#10605) +- ref(core): Rename `Span` class to `SentrySpan` (#10687) +- ref(core): Restructure hub exports (#10639) +- ref(core): Skip hub in top level `captureXXX` methods (#10688) +- ref(core): Allow `number` as span `traceFlag` (#10855) +- ref(core): Remove `status` field from Span (#10856) +- ref(remix): Make `@remix-run/router` a dependency. (#10479) +- ref(replay): Use `beforeAddBreadcrumb` hook instead of scope listener (#10600) +- ref(sveltekit): Hard-pin Vite plugin version (#10843) + +### Other Deprecation Removals/Changes + +We have also removed or updated a variety of deprecated APIs. + +- feat(v8): Remove `extractTraceparentData` export (#10559) +- feat(v8): Remove defaultIntegrations deprecated export (#10691) +- feat(v8): Remove deprecated `span.isSuccess` method (#10699) +- feat(v8): Remove deprecated `traceHeaders` method (#10776) +- feat(v8): Remove deprecated addInstrumentationHandler (#10693) +- feat(v8): Remove deprecated configureScope call (#10565) +- feat(v8): Remove deprecated runWithAsyncContext API (#10780) +- feat(v8): Remove deprecated spanStatusfromHttpCode export (#10563) +- feat(v8): Remove deprecated trace and startActiveSpan methods (#10593) +- feat(v8): Remove requestData deprecations (#10626) +- feat(v8): Remove Severity enum (#10551) +- feat(v8): Remove span.origin (#10753) +- feat(v8): Remove span.toTraceparent method (#10698) +- feat(v8): Remove usage of span.description and span.name (#10697) +- feat(v8): Update eventFromUnknownInput to only use client (#10692) +- feat(v8/astro): Remove deprecated exports from Astro SDK (#10611) +- feat(v8/browser): Remove `_eventFromIncompleteOnError` usage (#10553) +- feat(v8/browser): Remove XHR transport (#10703) +- feat(v8/browser): Rename TryCatch integration to `browserApiErrorsIntegration` (#10755) +- feat(v8/core): Remove deprecated setHttpStatus (#10774) +- feat(v8/core): Remove deprecated updateWithContext method (#10800) +- feat(v8/core): Remove getters for span.op (#10767) +- feat(v8/core): Remove span.finish call (#10773) +- feat(v8/core): Remove span.instrumenter and instrumenter option (#10769) +- feat(v8/ember): Remove deprecated exports (#10535) +- feat(v8/integrations): Remove deprecated exports (#10556) +- feat(v8/node): Remove deepReadDirSync export (#10564) +- feat(v8/node): Remove deprecated anr methods (#10562) +- feat(v8/node): Remove getModuleFromFilename export (#10754) +- ref: Make `setupOnce` optional in integrations (#10729) +- ref: Migrate transaction source from metadata to attributes (#10674) +- ref: Refactor remaining `makeMain` usage (#10713) +- ref(astro): Remove deprecated Replay and BrowserTracing (#10768) +- feat(core): Remove deprecated `scope.applyToEvent()` method (#10842) +- ref(integrations): Remove offline integration (#9456) +- ref(nextjs): Remove all deprecated API (#10549) +- ref: Remove `lastEventId` (#10585) +- ref: Remove `reuseExisting` option for ACS (#10645) +- ref: Remove `tracingOrigins` options (#10614) +- ref: Remove deprecated `showReportDialog` APIs (#10609) +- ref: Remove usage of span tags (#10808) +- ref: Remove user segment (#10575) + +## 7.103.0 + +### Important Changes + +- **feat(core): Allow to pass `forceTransaction` to `startSpan()` APIs (#10819)** + +You can now pass `forceTransaction: true` to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`. This allows +you to start a span that you want to be a transaction, if possible. Under the hood, the SDK will connect this span to +the running active span (if there is one), but still send the new span as a transaction to the Sentry backend, if +possible, ensuring it shows up as a transaction throughout the system. + +Please note that setting this to `true` does not _guarantee_ that this will be sent as a transaction, but that the SDK +will try to do so. You can enable this flag if this span is important to you and you want to ensure that you can see it +in the Sentry UI. + +### Other Changes + +- fix: Make breadcrumbs option optional in WinterCGFetch integration (#10792) + +## 7.102.1 + +- fix(performance): Fixes latest route name and source for interactions not updating properly on navigation (#10702) +- fix(tracing): Guard against missing `window.location` (#10659) +- ref: Make span types more robust (#10660) +- ref(remix): Make `@remix-run/router` a dependency (v7) (#10779) + +## 7.102.0 + +- fix: Export session API (#10712) +- fix(core): Fix scope capturing via `captureContext` function (#10737) + +## 7.101.1 + +In version 7.101.0 the `@sentry/hub` package was missing due to a publishing issue. This release contains the package +again. + +- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10641) + +## 7.101.0 + +- feat: Export semantic attribute keys from SDK packages (#10637) +- feat(core): Add metric summaries to spans (#10554) +- feat(core): Deprecate the `Hub` constructor (#10584) +- feat(core): Make custom tracing methods return spans & set default op (#10633) +- feat(replay): Add `getReplay` utility function (#10510) +- fix(angular-ivy): Add `exports` field to `package.json` (#10569) +- fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) +- fix(sveltekit): Properly await sourcemaps flattening (#10602) + +## 7.100.1 + +This release contains build fixes for profiling-node. + +- build(profiling-node): make sure debug build plugin is used #10534 +- build: Only run profiling e2e test if bindings have changed #10542 +- fix(feedback): Replay breadcrumb for feedback events was incorrect #10536 + ## 7.100.0 ### Important Changes diff --git a/CODEOWNERS b/CODEOWNERS index 6a09d1924613..6ae2679deafa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,5 @@ -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 +packages/replay-canvas @getsentry/replay-sdk-web +packages/feedback @getsentry/replay-sdk-web +dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web diff --git a/MIGRATION.md b/MIGRATION.md index e315bf77ccfd..0ab76f759ab7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,3 +1,160 @@ +# Upgrading from 7.x to 8.x + +## Removal of `trackHeaders` option for Astro middleware + +Instead of opting-in via the middleware config, you can configure if headers should be captured via +`requestDataIntegration` options, which defaults to `true` but can be disabled like this: + +``` +Sentry.init({ + integrations: [ + Sentry.requestDataIntegration({ + include: { + headers: false + }, + }), + ], +}); +``` + +## Removal of Client-Side health check transaction filters + +The SDK no longer filters out health check transactions by default. Instead, they are sent to Sentry but still dropped +by the Sentry backend by default. You can disable dropping them in your Sentry project settings. If you still want to +drop specific transactions within the SDK you can either use the `ignoreTransactions` SDK option. + +## 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 +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. +The `tracePropagationTargets` option should be set in the `Sentry.init()` options, or in your custom `Client`s option if +you create them. The `tracePropagationTargets` option can no longer be set in the `browserTracingIntegration()` options. + +## Dropping Support for React 15 + +Sentry will no longer officially support React 15 in version 8. This means that React 15.x will be removed +from`@sentry/react`'s peer dependencies. + +## 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 `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` +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 + +## 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. + +## 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/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) + +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) +- Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) +- Remove deprecated `deepReadDirSync` export from `@sentry/node` (#10564) +- Remove `_eventFromIncompleteOnError` usage (#10553) +- The `Transaction` integration in `@sentry/integrations` has been removed. There is no replacement API. (#10556) +- `extraErrorDataIntegration` now looks at + [`error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) by + default. + +## Integrations + +We moved pluggable integrations from their own package (`@sentry/integrations`) to `@sentry/browser` and `@sentry/node`. + +Integrations that are now exported from `@sentry/browser` (or framework-specific packages like `@sentry/react`): + +- httpClientIntegration +- contextLinesIntegration +- reportingObserverIntegration + +Integrations that are now exported from `@sentry/node` and `@sentry/browser` (or framework-specific packages like +`@sentry/react`): + +- captureConsoleIntegration +- debugIntegration +- extraErrorDataIntegration +- rewriteFramesIntegration +- sessionTimingIntegration +- dedupeIntegration (enabled by default, not pluggable) + # Deprecations in 7.x You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update @@ -272,6 +429,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/README.md b/README.md index 97ec4dadb89a..807c4b6988e2 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,6 @@ package. Please refer to the README and instructions of those SDKs for more deta - [`@sentry/gatsby`](https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby): SDK for Gatsby - [`@sentry/nextjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs): SDK for Next.js - [`@sentry/remix`](https://github.com/getsentry/sentry-javascript/tree/master/packages/remix): SDK for Remix -- [`@sentry/integrations`](https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations): Pluggable - integrations that can be used to enhance JS SDKs - [`@sentry/serverless`](https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless): SDK for Serverless Platforms (AWS, GCP) - [`@sentry/electron`](https://github.com/getsentry/sentry-electron): SDK for Electron with support for native crashes @@ -95,8 +93,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/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 { +sentryTest('should handle custom added browserTracingIntegration instances', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } @@ -25,11 +25,4 @@ sentryTest('should handle custom added BrowserTracing integration', async ({ get expect(eventData.contexts?.trace?.op).toBe('pageload'); expect(eventData.spans?.length).toBeGreaterThan(0); expect(eventData.transaction_info?.source).toEqual('url'); - - const tracePropagationTargets = await page.evaluate(() => { - const browserTracing = (window as any).Sentry.getClient().getIntegrationByName('BrowserTracing'); - return browserTracing.options.tracePropagationTargets; - }); - - expect(tracePropagationTargets).toEqual(['http://localhost:1234']); }); diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 7f12a2c7d954..93d794c5a84f 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -1,10 +1,10 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "main": "index.js", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14.8" }, "private": true, "scripts": { @@ -47,9 +47,8 @@ "@babel/preset-typescript": "^7.16.7", "@playwright/test": "^1.40.1", "@sentry-internal/rrweb": "2.11.0", - "@sentry/browser": "7.100.0", - "@sentry/tracing": "7.100.0", - "axios": "1.6.0", + "@sentry/browser": "8.0.0-alpha.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/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', 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/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/ContextLines/init.js b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js index 4461826e0214..cdcb68f2b48f 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js @@ -1,9 +1,9 @@ import * as Sentry from '@sentry/browser'; -import { ContextLines } from '@sentry/integrations'; +import { contextLinesIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new ContextLines()], + integrations: [contextLinesIntegration()], }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js index 5d43b49e75fb..8540ab176c38 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { HttpClient } from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new HttpClient()], + integrations: [httpClientIntegration()], tracesSampleRate: 1, sendDefaultPii: true, }); 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/integrations/httpclient/httpClientIntegration/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js index 07bc4a5b351e..8540ab176c38 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { httpClientIntegration } from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js index 5d43b49e75fb..8540ab176c38 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/browser'; -import { HttpClient } from '@sentry/integrations'; +import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new HttpClient()], + integrations: [httpClientIntegration()], tracesSampleRate: 1, sendDefaultPii: true, }); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js index d176abb81e23..cf70853184cd 100644 --- a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js +++ b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js @@ -1,12 +1,12 @@ import { Breadcrumbs, BrowserClient, - Dedupe, FunctionToString, HttpContext, Hub, InboundFilters, LinkedErrors, + dedupeIntegration, defaultStackParser, makeFetchTransport, } from '@sentry/browser'; @@ -14,7 +14,7 @@ import { const integrations = [ new Breadcrumbs(), new FunctionToString(), - new Dedupe(), + dedupeIntegration(), new HttpContext(), new InboundFilters(), new LinkedErrors(), 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/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/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/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/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/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/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/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/suites/replay/dsc/init.js b/dev-packages/browser-integration-tests/suites/replay/dsc/init.js index c3c2f62f2c14..a686fdc78205 100644 --- a/dev-packages/browser-integration-tests/suites/replay/dsc/init.js +++ b/dev-packages/browser-integration-tests/suites/replay/dsc/init.js @@ -1,5 +1,4 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; window.Sentry = Sentry; window.Replay = new Sentry.Replay({ @@ -11,7 +10,8 @@ window.Replay = new Sentry.Replay({ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] }), window.Replay], + integrations: [Sentry.browserTracingIntegration(), window.Replay], + tracePropagationTargets: [/.*/], environment: 'production', tracesSampleRate: 1, // Needs manual start! 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/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); 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/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts index d33d8a64f1c1..50709dc37705 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts @@ -36,23 +36,25 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); - void page.evaluate(() => { - /* eslint-disable */ - const xhr = new XMLHttpRequest(); - - xhr.open('POST', 'http://localhost:7654/foo'); - xhr.send('{"foo":"bar"}'); - - xhr.addEventListener('readystatechange', function () { - if (xhr.readyState === 4) { - // @ts-expect-error Sentry is a global - setTimeout(() => Sentry.captureException('test error', 0)); - } - }); - /* eslint-enable */ - }); + const [, request] = await Promise.all([ + page.evaluate(() => { + /* eslint-disable */ + const xhr = new XMLHttpRequest(); + + xhr.open('POST', 'http://localhost:7654/foo'); + xhr.send('{"foo":"bar"}'); + + xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + // @ts-expect-error Sentry is a global + setTimeout(() => Sentry.captureException('test error', 0)); + } + }); + /* eslint-enable */ + }), + requestPromise, + ]); - const request = await requestPromise; const eventData = envelopeRequestParser(request); expect(eventData.exception?.values).toHaveLength(1); 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); 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 5355521f1655..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.getCurrentHub().configureScope(scope => scope.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 de1cd552ccab..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 name_before = await getPropertyValue(transactionHandle, 'name'); - 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(name_before).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 name_after = await getPropertyValue(transactionHandle, 'name'); - 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(name_after).toBe(name_before); + 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/browserTracingIntegration/http-timings/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts index b6da7522d82c..467507fa6f75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts @@ -26,7 +26,6 @@ sentryTest('should create fetch spans with http timing @firefox', async ({ brows 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/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/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/browserTracingIntegration/tracePropagationTargets/customTargets/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js index ad48a291386e..7cd076a052e5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargets/init.js @@ -4,6 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration({ tracePropagationTargets: ['http://example.com'] })], + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/init.js deleted file mode 100644 index 572b8c69d4dc..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/init.js +++ /dev/null @@ -1,11 +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: [], tracingOrigins: ['http://example.com'] }), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/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/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/test.ts deleted file mode 100644 index a6cc58ca46ff..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTargetsAndOrigins/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - '[pre-v8] should prefer custom tracePropagationTargets over tracingOrigins', - 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).not.toMatchObject({ - 'sentry-trace': expect.any(String), - baggage: expect.any(String), - }); - } - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/init.js deleted file mode 100644 index 45e5237e4c24..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/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({ tracingOrigins: ['http://example.com'] })], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/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/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/test.ts deleted file mode 100644 index 9f32b7b1ad28..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/tracePropagationTargets/customTracingOrigins/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - '[pre-v8] should attach `sentry-trace` and `baggage` header to request matching tracingOrigins', - 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/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/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/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/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 a4bddcff1b21..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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 })], - 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 5355521f1655..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/subject.js +++ /dev/null @@ -1,11 +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-transaction').addEventListener('click', () => { - window.transaction = Sentry.startTransaction({ name: 'test-transaction' }); - Sentry.getCurrentHub().configureScope(scope => scope.setSpan(window.transaction)); -}); 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 fac45ecebfaf..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 de1cd552ccab..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-custom/test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { JSHandle } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { Event } 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(); -} - -sentryTest('should finish a custom transaction when the page goes background', async ({ getLocalTestPath, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestPath({ testDir: __dirname }); - - const pageloadTransaction = await getFirstSentryEnvelopeRequest(page, url); - expect(pageloadTransaction).toBeDefined(); - - await page.locator('#start-transaction').click(); - const transactionHandle = await page.evaluateHandle('window.transaction'); - - const id_before = await getPropertyValue(transactionHandle, 'span_id'); - const name_before = await getPropertyValue(transactionHandle, 'name'); - const status_before = await getPropertyValue(transactionHandle, 'status'); - const tags_before = await getPropertyValue(transactionHandle, 'tags'); - - expect(name_before).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 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(status_after).toBe('cancelled'); - expect(tags_after).toStrictEqual({ visibilitychange: '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 8432245f9c9b..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/backgroundtab-pageload/test.ts +++ /dev/null @@ -1,23 +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?.tags).toMatchObject({ - visibilitychange: '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 efe1e2ef9778..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/http-timings/init.js +++ /dev/null @@ -1,17 +0,0 @@ -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: 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 ce4e0c4ad7f7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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()], - 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 d30222b7f47e..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/interactions/init.js +++ /dev/null @@ -1,18 +0,0 @@ -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: 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 c1d5e8a8a7ae..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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 })], - 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 1f7bb54bb36a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-disabled/test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { Event } 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); - // eslint-disable-next-line deprecation/deprecation - 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 037e2dc88517..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/init.js +++ /dev/null @@ -1,14 +0,0 @@ -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, - }), - ], - 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 32819fd784e0..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/long-tasks-enabled/test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Route } from '@playwright/test'; -import { expect } from '@playwright/test'; -import type { Event } 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); - // eslint-disable-next-line deprecation/deprecation - 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 as Event)['start_timestamp'] ?? 0; - const end = (firstUISpan as Event)['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 50f4a898e251..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/meta/init.js +++ /dev/null @@ -1,11 +0,0 @@ -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()], - 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 2340df528aa7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageload/init.js +++ /dev/null @@ -1,11 +0,0 @@ -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()], - 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 ff6345dec8f2..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadDelayed/init.js +++ /dev/null @@ -1,14 +0,0 @@ -import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; - -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 Integrations.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 ce0d16f0f0db..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/init.js +++ /dev/null @@ -1,15 +0,0 @@ -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()], - 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 dbb284aecb3b..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/pageloadWithHeartbeatTimeout/test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } 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( - // 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/tracePropagationTargets/customTargets/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargets/init.js deleted file mode 100644 index eaa53ad8b0a8..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'; -import { Integrations } from '@sentry/tracing'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ 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/customTargetsAndOrigins/init.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js deleted file mode 100644 index f98812f56e8f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/init.js +++ /dev/null @@ -1,12 +0,0 @@ -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({ tracePropagationTargets: [], tracingOrigins: ['http://example.com'] }), - ], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/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/customTargetsAndOrigins/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts deleted file mode 100644 index a6cc58ca46ff..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTargetsAndOrigins/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - '[pre-v8] should prefer custom tracePropagationTargets over tracingOrigins', - 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).not.toMatchObject({ - 'sentry-trace': expect.any(String), - baggage: expect.any(String), - }); - } - }, -); 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 deleted file mode 100644 index 505fab06b330..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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({ tracingOrigins: ['http://example.com'] })], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/subject.js deleted file mode 100644 index f62499b1e9c5..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/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/customTracingOrigins/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts deleted file mode 100644 index 9f32b7b1ad28..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/customTracingOrigins/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../../utils/helpers'; - -sentryTest( - '[pre-v8] should attach `sentry-trace` and `baggage` header to request matching tracingOrigins', - 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 ce4e0c4ad7f7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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()], - 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 4e9cf0d01004..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://localhost:4200/0').then(fetch('http://localhost:4200/1').then(fetch('http://localhost:4200/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 120b36ec88db..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsMatch/test.ts +++ /dev/null @@ -1,32 +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 default 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://localhost:4200/${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 ce4e0c4ad7f7..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/init.js +++ /dev/null @@ -1,10 +0,0 @@ -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()], - 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 116319259101..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browsertracing/tracePropagationTargets/defaultTargetsNoMatch/test.ts +++ /dev/null @@ -1,32 +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 request not matching default 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).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 c2fcbb33a24c..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 @@ -1,20 +1,21 @@ import * as Sentry from '@sentry/browser'; -import { Integrations } from '@sentry/tracing'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Integrations.BrowserTracing({ tracingOrigins: [/.*/] })], + integrations: [Sentry.browserTracingIntegration()], environment: 'production', tracesSampleRate: 1, debug: true, }); -const scope = Sentry.getCurrentScope(); -scope.setUser({ id: 'user123', segment: 'segmentB' }); -scope.addEventProcessor(event => { +Sentry.setUser({ id: 'user123' }); + +Sentry.addEventProcessor(event => { event.transaction = 'testTransactionDSC'; return event; }); -scope.getTransaction().setMetadata({ source: 'custom' }); + +Sentry.getActiveSpan().setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); 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..51f27f9122df 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 @@ -1,18 +1,18 @@ 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({ tracingOrigins: [/.*/] })], + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: [/.*/], environment: 'production', tracesSampleRate: 1, debug: true, }); 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..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,12 +21,11 @@ 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({ 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/metrics/init.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/init.js index 037e2dc88517..ad1d8832b228 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({ + Sentry.browserTracingIntegration({ 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-with-no-active-span/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-no-active-span/init.js index 92152554ea57..a8d8c03d1f25 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-no-active-span/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-no-active-span/init.js @@ -5,6 +5,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', // disable pageload transaction - integrations: [Sentry.BrowserTracing({ tracingOrigins: ['http://example.com'], startTransactionOnPageLoad: false })], + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false })], + tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-request/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-request/init.js index 505fab06b330..7cd076a052e5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-request/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-with-request/init.js @@ -1,10 +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({ tracingOrigins: ['http://example.com'] })], + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); 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/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/init.js index 505fab06b330..7cd076a052e5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/request/init.js @@ -1,10 +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({ tracingOrigins: ['http://example.com'] })], + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-with-no-active-span/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-with-no-active-span/init.js index 92152554ea57..a8d8c03d1f25 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-with-no-active-span/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-with-no-active-span/init.js @@ -5,6 +5,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', // disable pageload transaction - integrations: [Sentry.BrowserTracing({ tracingOrigins: ['http://example.com'], startTransactionOnPageLoad: false })], + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false })], + tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); 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/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/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index cf2816ab0033..738771aedced 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 @@ -168,14 +168,12 @@ 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 @@ -243,7 +241,7 @@ class SentryScenarioGenerationPlugin { this.localOutPath, path.resolve( PACKAGES_DIR, - 'integrations', + 'browser', BUNDLE_PATHS['integrations'][integrationBundleKey].replace('[INTEGRATION_NAME]', integration), ), fileName, diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 6027695746e9..0e54eea0fb31 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); + + 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/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/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 9e0808b52693..a9ab6635f3e4 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "license": "MIT", "private": true, "scripts": { @@ -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/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/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/angular-17/package.json b/dev-packages/e2e-tests/test-applications/angular-17/package.json index c81a2d19c8c4..6ab5f410e6cc 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-17/package.json @@ -4,7 +4,8 @@ "scripts": { "ng": "ng", "dev": "ng serve", - "preview": "http-server dist/angular-17/browser", + "proxy": "ts-node-script start-event-proxy.ts", + "preview": "http-server dist/angular-17/browser --port 8080", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "playwright test", @@ -22,6 +23,7 @@ "@angular/platform-browser": "^17.1.0", "@angular/platform-browser-dynamic": "^17.1.0", "@angular/router": "^17.1.0", + "@sentry/angular-ivy": "* || latest", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -31,7 +33,6 @@ "@angular/cli": "^17.1.1", "@angular/compiler-cli": "^17.1.0", "@playwright/test": "^1.41.1", - "@sentry/angular-ivy": "latest || *", "@types/jasmine": "~5.1.0", "http-server": "^14.1.1", "jasmine-core": "~5.1.0", @@ -40,8 +41,8 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "typescript": "~5.3.2", "ts-node": "10.9.1", + "typescript": "~5.3.2", "wait-port": "1.0.4" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts b/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts index 967aad98df5e..3b0d25b80d09 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/playwright.config.ts @@ -29,8 +29,8 @@ const config: PlaywrightTestConfig = { */ timeout: 10000, }, - /* Run tests in files in parallel */ - fullyParallel: true, + fullyParallel: false, + workers: 1, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* `next dev` is incredibly buggy with the app dir */ diff --git a/dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts b/dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts index 0b44bc341a9b..d4d9a04f6c99 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/src/app/app.routes.ts @@ -11,6 +11,18 @@ export const routes: Routes = [ path: 'home', component: HomeComponent, }, + { + path: 'redirect1', + redirectTo: '/redirect2', + }, + { + path: 'redirect2', + redirectTo: '/redirect3', + }, + { + path: 'redirect3', + redirectTo: '/users/456', + }, { path: '**', redirectTo: 'home', diff --git a/dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts b/dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts index 58a375be1a2d..9179f5d79638 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/src/app/home/home.component.ts @@ -10,6 +10,7 @@ import { RouterLink } from '@angular/router';

Welcome to Sentry's Angular 17 E2E test app

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/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/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/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/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 ( + + + + +

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/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/sveltekit/package.json b/dev-packages/e2e-tests/test-applications/sveltekit/package.json index ea21787939c3..c1ed602844c1 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit/package.json @@ -30,11 +30,5 @@ "vite": "^4.2.0", "wait-port": "1.0.4" }, - "pnpm": { - "overrides": { - "@sentry/node": "latest || *", - "@sentry/tracing": "latest || *" - } - }, "type": "module" } diff --git a/dev-packages/e2e-tests/test-applications/sveltekit/test/performance.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit/test/performance.test.ts index e0d3d16df1ab..9982678da122 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/test/performance.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit/test/performance.test.ts @@ -22,9 +22,6 @@ test('sends a pageload transaction', async ({ page }) => { origin: 'auto.pageload.sveltekit', }, }, - tags: { - 'routing.instrumentation': '@sentry/sveltekit', - }, }); }); 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/e2e-tests/test-applications/vue-3/package.json b/dev-packages/e2e-tests/test-applications/vue-3/package.json index 1fa4cbcf3882..5de1fdc7d2e9 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/package.json +++ b/dev-packages/e2e-tests/test-applications/vue-3/package.json @@ -21,8 +21,8 @@ }, "devDependencies": { "@playwright/test": "^1.41.1", - "@sentry/types": "^7.99.0", - "@sentry/utils": "^7.99.0", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", "@tsconfig/node20": "^20.1.2", "@types/node": "^20.11.10", "@vitejs/plugin-vue": "^5.0.3", 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 2ed138f1cdcc..042f93162934 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -86,18 +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 - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/nextjs': access: $all publish: $all @@ -170,12 +158,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/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/package.json b/dev-packages/node-integration-tests/package.json index d9679c29dc27..64bbdf9da164 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -1,9 +1,9 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14.8" }, "private": true, "main": "build/cjs/index.js", @@ -14,29 +14,30 @@ "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", - "prisma:init": "(cd suites/tracing/prisma-orm && ts-node ./setup.ts)", - "prisma:init:new": "(cd suites/tracing-new/prisma-orm && ts-node ./setup.ts)", + "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 prisma:init:new", + "pretest": "run-s --silent prisma:init", "test": "ts-node ./utils/run-tests.ts", "jest": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" }, "dependencies": { "@hapi/hapi": "^20.3.0", - "@prisma/client": "3.15.2", - "@sentry/node": "7.100.0", - "@sentry/tracing": "7.100.0", - "@sentry/types": "7.100.0", + "@nestjs/core": "^10.3.3", + "@nestjs/common": "^10.3.3", + "@nestjs/platform-express": "^10.3.3", + "@prisma/client": "5.9.1", + "@sentry/node": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", "@types/mongodb": "^3.6.20", "@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", @@ -50,6 +51,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/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/node-integration-tests/suites/anr/basic-session.js b/dev-packages/node-integration-tests/suites/anr/basic-session.js index fe4190c8cc46..153acf83f16f 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -11,13 +11,13 @@ 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 })], + autoSessionTracking: true, }); 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 097dec6c925c..712e0e26a3f8 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -12,13 +12,12 @@ 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() { 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 43a8d02a41ac..0184ca9583f7 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -12,13 +12,12 @@ 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() { 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 097dec6c925c..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, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 100 })], + 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/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..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,9 +4,9 @@ function configureSentry() { Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true })], + 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 7d0ba8db4484..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,9 +4,9 @@ function configureSentry() { Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', release: '1.0', - debug: true, autoSessionTracking: false, - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true })], + debug: 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/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 fe2d32009ded..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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 8df1b9c3d988..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,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 * as Sentry from '@sentry/node-experimental'; 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/multiple-routers/middle-layer-parameterized/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts index 7b92c2b6508d..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,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 * as Sentry from '@sentry/node-experimental'; 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 7e9ee87a1a2a..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,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 * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; @@ -14,13 +13,12 @@ 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, }); -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-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..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,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 * as Sentry from '@sentry/node-experimental'; 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..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,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 * as Sentry from '@sentry/node-experimental'; 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 153b7351b594..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,8 +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 Tracing from '@sentry/tracing'; +import * as Sentry from '@sentry/node-experimental'; import cors from 'cors'; import express from 'express'; @@ -16,15 +15,14 @@ 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, 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/server.ts b/dev-packages/node-integration-tests/suites/express/sentry-trace/server.ts index 77de1a188afa..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,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 * as Sentry from '@sentry/node-experimental'; 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-experimental/server.js b/dev-packages/node-integration-tests/suites/express/tracing-experimental/server.js index 843c8f47b3f2..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({ @@ -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()); +Sentry.setupExpressErrorHandler(app); startExpressServerAndSendPortToRunner(app); 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..337a1166ee64 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(); }); @@ -22,9 +21,6 @@ conditionalTest({ min: 14 })('express tracing experimental', () => { }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, spans: expect.arrayContaining([ @@ -62,9 +58,6 @@ conditionalTest({ min: 14 })('express tracing experimental', () => { }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, @@ -94,9 +87,6 @@ conditionalTest({ min: 14 })('express tracing experimental', () => { }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, @@ -134,9 +124,6 @@ conditionalTest({ min: 14 })('express tracing experimental', () => { }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, 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..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'; @@ -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/express/tracing/test.ts b/dev-packages/node-integration-tests/suites/express/tracing/test.ts index b0779e21cec0..5c9e7743636f 100644 --- a/dev-packages/node-integration-tests/suites/express/tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/express/tracing/test.ts @@ -19,9 +19,6 @@ test('should create and send transactions for Express routes and spans for middl }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, spans: [ @@ -55,9 +52,6 @@ test('should set a correct transaction name for routes specified in RegEx', done }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, @@ -87,9 +81,6 @@ test.each([['array1'], ['array5']])( }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, @@ -127,9 +118,6 @@ test.each([ }, op: 'http.server', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, }, }, 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/public-api/LocalVariables/local-variables-caught.js b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js index 7c86004da43b..8ea8dd93fb4d 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js @@ -20,6 +20,11 @@ function one(name) { name, num: 5, }; + const bool = false; + const num = 0; + const str = ''; + const something = undefined; + const somethingElse = null; const ty = new Some(); diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs index 37e5966bc575..a7427ac60157 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs @@ -22,6 +22,11 @@ function one(name) { functionsShouldNotBeIncluded: () => {}, functionsShouldNotBeIncluded2() {}, }; + const bool = false; + const num = 0; + const str = ''; + const something = undefined; + const somethingElse = null; const ty = new Some(); 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..35c71a3fc6e8 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 { @@ -22,6 +22,11 @@ function one(name) { name, num: 5, }; + const bool = false; + const num = 0; + const str = ''; + const something = undefined; + const somethingElse = null; const ty = new Some(); diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js index 4a16ad89b5aa..8b64c80cbc5f 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables.js @@ -24,6 +24,11 @@ function one(name) { name, num: 5, }; + const bool = false; + const num = 0; + const str = ''; + const something = undefined; + const somethingElse = null; const ty = new Some(); diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js index f01e33a9cafa..2857fc0e3ce9 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/no-local-variables.js @@ -23,6 +23,11 @@ function one(name) { name, num: 5, }; + const bool = false; + const num = 0; + const str = ''; + const something = undefined; + const somethingElse = null; const ty = new Some(); 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..3a8d904de3c9 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 @@ -16,6 +16,11 @@ const EXPECTED_LOCAL_VARIABLES_EVENT = { arr: [1, '2', null], obj: { name: 'some name', num: 5 }, ty: '', + bool: false, + num: 0, + str: '', + something: '', + somethingElse: '', }, }), expect.objectContaining({ @@ -80,8 +85,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/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/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts new file mode 100644 index 000000000000..a6889bb46a9a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/scenario.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, +}); + +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/startSpan/basic-usage/test.ts similarity index 61% 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 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/startSpan/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/startSpan/with-nested-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts new file mode 100644 index 000000000000..0d33cc197575 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/with-nested-spans/scenario.ts @@ -0,0 +1,31 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, +}); + +Sentry.startSpan({ name: 'root_span' }, () => { + Sentry.startSpan( + { + name: 'span_1', + attributes: { + 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', 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 87% 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 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/startSpan/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/public-api/startTransaction/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts deleted file mode 100644 index 70596da19716..000000000000 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts +++ /dev/null @@ -1,15 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import '@sentry/tracing'; - -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, -}); - -// eslint-disable-next-line deprecation/deprecation -const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); - -transaction.end(); 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 deleted file mode 100644 index f82fe81d969a..000000000000 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts +++ /dev/null @@ -1,46 +0,0 @@ -import '@sentry/tracing'; - -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - 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], - }, -}); -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/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/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/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/hapi/scenario.js index 5f2c898fad60..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,10 +1,9 @@ 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', release: '1.0', - debug: true, tracesSampleRate: 1.0, transport: loggingTransport, }); @@ -27,6 +26,7 @@ const init = async () => { }, }); + await Sentry.setupHapiErrorHandler(server); await server.start(); sendPortToRunner(port); 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/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/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/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js index 47a67e4aaf78..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,10 +1,9 @@ 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', release: '1.0', - debug: true, tracesSampleRate: 1.0, transport: loggingTransport, }); 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/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/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/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/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/nestjs/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/nestjs/scenario.ts new file mode 100644 index 000000000000..f0a97953b2d9 --- /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'; + +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/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-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/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/docker-compose.yml rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/docker-compose.yml diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json similarity index 84% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json index f8b24d7d0465..b9a5e7998269 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "engines": { - "node": ">=12" + "node": ">=16" }, "scripts": { "db-up": "docker-compose up -d", @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "3.12.0", - "prisma": "^3.12.0" + "@prisma/client": "5.9.1", + "prisma": "^5.9.1" } } 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-experimental/prisma-orm/prisma/migrations/migration_lock.toml similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/migration_lock.toml rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/migration_lock.toml 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-experimental/prisma-orm/prisma/migrations/sentry_test/migration.sql similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/migrations/sentry_test/migration.sql rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/migrations/sentry_test/migration.sql diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma similarity index 90% rename from dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma index 4363c97738ee..52682f1b6cf5 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/prisma/schema.prisma +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/prisma/schema.prisma @@ -5,6 +5,7 @@ datasource db { generator client { provider = "prisma-client-js" + previewFeatures = ["tracing"] } model User { diff --git a/dev-packages/node-integration-tests/suites/tracing-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-new/prisma-orm/setup.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts similarity index 62% rename from dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts rename to dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts index d5c4e552b397..a71bec82f893 100755 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/setup.ts +++ b/dev-packages/node-integration-tests/suites/tracing-experimental/prisma-orm/setup.ts @@ -3,7 +3,9 @@ import { parseSemver } from '@sentry/utils'; const NODE_VERSION = parseSemver(process.versions.node); -if (NODE_VERSION.major && NODE_VERSION.major < 12) { +// 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); 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" 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 1584274bce7d..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts +++ /dev/null @@ -1,44 +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 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 () => { - // 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 deleted file mode 100644 index bcddfd588447..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } 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', () => { - 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_transaction', - 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 67d8e13750de..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts +++ /dev/null @@ -1,47 +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, -}); - -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(); 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 d2ce56f314ee..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, conditionalTest } from '../../../../utils'; - -// This test can take longer. -jest.setTimeout(15000); - -conditionalTest({ min: 12 })('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 Transaction', - 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/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 deleted file mode 100644 index 47fb37e054f7..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts +++ /dev/null @@ -1,25 +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()], -}); - -// 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()); - }), -); 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 6efeb6281c05..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 Transaction', - 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/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/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/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts deleted file mode 100644 index 6191dbf31d75..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { randomBytes } from 'crypto'; -/* 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 })], -}); - -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`, - }, - }); - - await client.user.findMany(); - - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', - }, - }, - }); - } finally { - if (transaction) transaction.end(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); 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 4a76f328dd34..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; - -conditionalTest({ min: 12 })('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 Transaction', - 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/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts deleted file mode 100644 index 600b5ef71038..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as http from 'http'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -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: [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(); 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 6a699fa07af7..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts +++ /dev/null @@ -1,46 +0,0 @@ -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()], -}); - -const typeDefs = gql` - type Query { - hello: String - } -`; - -const resolvers = { - Query: { - hello: () => { - return 'Hello world!'; - }, - }, -}; - -const server = new ApolloServer({ - typeDefs, - 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 () => { - // 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 deleted file mode 100644 index bcddfd588447..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } 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', () => { - 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_transaction', - 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 51359ac726da..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts +++ /dev/null @@ -1,48 +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, -}); - -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(); 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 d2ce56f314ee..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, conditionalTest } from '../../../../utils'; - -// This test can take longer. -jest.setTimeout(15000); - -conditionalTest({ min: 12 })('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 Transaction', - 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/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 deleted file mode 100644 index f9bfa0de0294..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts +++ /dev/null @@ -1,26 +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, -}); - -// 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()); - }), -); 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 559f41fcb26a..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 Transaction', - 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 new file mode 100644 index 000000000000..4a0a3ec792e5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -0,0 +1,53 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node-experimental'); + +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); + +Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + () => { + Sentry.metrics.increment('root-counter', 1, { + tags: { + email: 'jon.doe@example.com', + }, + }); + Sentry.metrics.increment('root-counter', 1, { + tags: { + email: 'jane.doe@example.com', + }, + }); + + Sentry.startSpan( + { + name: 'Some other span', + op: 'transaction', + }, + () => { + Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter', 2); + + Sentry.metrics.set('root-set', 'some-value'); + Sentry.metrics.set('root-set', 'another-value'); + Sentry.metrics.set('root-set', 'another-value'); + + Sentry.metrics.gauge('root-gauge', 42); + Sentry.metrics.gauge('root-gauge', 20); + + Sentry.metrics.distribution('root-distribution', 42); + Sentry.metrics.distribution('root-distribution', 20); + }, + ); + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts new file mode 100644 index 000000000000..94f5fdc30c70 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts @@ -0,0 +1,91 @@ +import { createRunner } from '../../../utils/runner'; + +const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + _metrics_summary: { + 'c:root-counter@none': [ + { + min: 1, + max: 1, + count: 1, + sum: 1, + tags: { + release: '1.0', + transaction: 'Test Transaction', + email: 'jon.doe@example.com', + }, + }, + { + min: 1, + max: 1, + count: 1, + sum: 1, + tags: { + release: '1.0', + transaction: 'Test Transaction', + email: 'jane.doe@example.com', + }, + }, + ], + }, + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'Some other span', + op: 'transaction', + _metrics_summary: { + 'c:root-counter@none': [ + { + min: 1, + max: 2, + count: 3, + sum: 4, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + ], + 's:root-set@none': [ + { + min: 0, + max: 1, + count: 3, + sum: 2, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + ], + 'g:root-gauge@none': [ + { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + ], + 'd:root-distribution@none': [ + { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + ], + }, + }), + ]), +}; + +test('Should add metric summaries to spans', done => { + createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done); +}); 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/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 b5003141caec..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { randomBytes } from 'crypto'; -/* 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(); - -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 })], -}); - -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`, - }, - }); - - await client.user.findMany(); - - await client.user.deleteMany({ - where: { - email: { - contains: 'sentry.io', - }, - }, - }); - } finally { - if (transaction) transaction.end(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); 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 4a76f328dd34..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TestEnv, assertSentryTransaction, conditionalTest } from '../../../utils'; - -conditionalTest({ min: 12 })('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 Transaction', - 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 78% 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 9b1abf466db1..1a3faae3805c 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,14 +1,10 @@ -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({})], - debug: true, }); // 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 82% 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..0d882dd84b31 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); @@ -22,18 +22,12 @@ test('should capture spans for outgoing http requests', async () => { op: 'http.client', origin: 'auto.http.node.http', status: 'ok', - tags: { - 'http.status_code': '200', - }, }, { description: 'GET http://match-this-url.com/api/v1', op: 'http.client', origin: 'auto.http.node.http', status: 'ok', - tags: { - 'http.status_code': '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 8ecb7ed3cc61..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,26 +1,17 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -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, tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [new Sentry.Integrations.Http({ tracing: true })], + integrations: [Sentry.httpIntegration({ 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-new/httpIntegration/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts similarity index 71% rename from dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts index 7794b20911f9..cfad7894d2b8 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/httpIntegration/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargetsDisabled/scenario.ts @@ -1,15 +1,12 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -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, tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [Sentry.httpIntegration({})], + integrations: [Sentry.httpIntegration({ tracing: true })], }); Sentry.startSpan({ name: 'test_transaction' }, () => { 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/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; } 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/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json index 4e99daa10705..33fb0397c333 100644 --- a/dev-packages/overhead-metrics/package.json +++ b/dev-packages/overhead-metrics/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "7.100.0", + "version": "8.0.0-alpha.0", "name": "@sentry-internal/overhead-metrics", "main": "index.js", "author": "Sentry", @@ -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/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/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs index 66bded3b62de..6043207ee97c 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', @@ -59,7 +59,7 @@ export function makeBaseBundleConfig(options) { plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin], }; - // used by `@sentry/integrations` and `@sentry/wasm` (bundles which need to be combined with a stand-alone SDK bundle) + // used by `@sentry/wasm` & pluggable integrations from core/browser (bundles which need to be combined with a stand-alone SDK bundle) const addOnBundleConfig = { // These output settings are designed to mimic an IIFE. We don't use Rollup's `iife` format because we don't want to // attach this code to a new global variable, but rather inject it into the existing SDK's `Integrations` object. diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 600c8cc19a58..61f01feaacb0 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/rollup-utils", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils", diff --git a/dev-packages/rollup-utils/plugins/bundlePlugins.mjs b/dev-packages/rollup-utils/plugins/bundlePlugins.mjs index b0a1c806ef98..1df1e2ac917d 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', @@ -163,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/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/docs/v8-new-performance-apis.md b/docs/v8-new-performance-apis.md index 4b3c10f55a2a..cdf7bc6f9d0c 100644 --- a/docs/v8-new-performance-apis.md +++ b/docs/v8-new-performance-apis.md @@ -1,7 +1,5 @@ # New Performance APIs in v8 -> [!WARNING] This document is WIP. We are working on this while we are preparing v8. - In v8.0.0, we moved to new performance APIs. These APIs have been introduced in v7, so they can already be used there. However, in v8 we have removed the old performance APIs, so you have to update your manual instrumentation usage to the new APIs before updating to v8 of the JavaScript SDKs. @@ -36,72 +34,6 @@ In the new model, transactions are conceptually gone. Instead, you will _always_ the tree you are. Note that in the background, spans _may_ still be grouped into a transaction for the Sentry UI. However, this happens transparently, and from an SDK perspective, all you have to think about are spans. -## The Span schema - -Previously, spans & transactions had a bunch of properties and methods to be used. Most of these have been removed in -favor of a slimmer, more straightforward API, which is also aligned with OpenTelemetry Spans. You can refer to the table -below to see which things used to exist, and how they can/should be mapped going forward: - -| Old name | Replace with | -| --------------------- | ---------------------------------------------------- | -| `traceId` | `spanContext().traceId` | -| `spanId` | `spanContext().spanId` | -| `parentSpanId` | `spanToJSON(span).parent_span_id` | -| `status` | use utility method TODO | -| `sampled` | `spanIsSampled(span)` | -| `startTimestamp` | `startTime` - note that this has a different format! | -| `tags` | use attributes, or set tags on the scope | -| `data` | `spanToJSON(span).data` | -| `transaction` | ??? Removed | -| `instrumenter` | Removed | -| `finish()` | `end()` | -| `end()` | Same | -| `setTag()` | `setAttribute()`, or set tags on the scope | -| `setData()` | `setAttribute()` | -| `setStatus()` | TODO: new signature | -| `setHttpStatus()` | ??? TODO | -| `setName()` | `updateName()` | -| `startChild()` | Call `Sentry.startSpan()` independently | -| `isSuccess()` | `spanToJSON(span).status === 'ok'` | -| `toTraceparent()` | `spanToTraceHeader(span)` | -| `toContext()` | Removed | -| `updateWithContext()` | Removed | -| `getTraceContext()` | `spanToTraceContext(span)` | - -In addition, a transaction has this API: - -| Old name | Replace with | -| --------------------------- | ------------------------------------------------ | -| `name` | `spanToJSON(span).description` | -| `trimEnd` | Removed | -| `parentSampled` | `spanIsSampled(span)` & `spanContext().isRemote` | -| `metadata` | Use attributes instead or set on scope | -| `setContext()` | Set context on scope instead | -| `setMeasurement()` | ??? TODO | -| `setMetadata()` | Use attributes instead or set on scope | -| `getDynamicSamplingContext` | ??? TODO | - -### Attributes vs. Data vs. Tags vs. Context - -In the old model, you had the concepts of **Data**, **Tags** and **Context** which could be used for different things. -However, this has two main downsides: One, it is not always clear which of these should be used when. And two, not all -of these are displayed the same way for transactions or spans. - -Because of this, in the new model, there are only **Attributes** to be set on spans anymore. Broadly speaking, they map -to what Data used to be. - -If you still really _need_ to set tags or context, you can do so on the scope before starting a span: - -```js -Sentry.withScope(scope => { - scope.setTag('my-tag', 'tag-value'); - Sentry.startSpan({ name: 'my-span' }, span => { - // do something here - // span will have the tags from the containing scope - }); -}); -``` - ## Creating Spans Instead of manually starting & ending transactions and spans, the new model does not differentiate between these two. @@ -220,6 +152,95 @@ Sentry.startSpan({ name: 'outer' }, () => { No span will ever be created as a child span of an inactive span. +### Creating a child span of a specific span + +You can use the `withActiveSpan` helper to create a span as a child of a specific span: + +```js +Sentry.withActiveSpan(parentSpan, () => { + Sentry.startSpan({ name: 'my-span' }, span => { + // span will be a direct child of parentSpan + }); +}); +``` + +### Creating a transaction + +While in most cases, you shouldn't have to think about creating a span vs. a transaction (just call `startSpan()` and +we'll do the appropriate thing under the hood), there may still be times where you _need_ to ensure you create a +transaction (for example, if you need to see it as a transaction in the Sentry UI). For these cases, you can pass +`forceTransaction: true` to the start-span APIs, e.g.: + +```js +const transaction = Sentry.startInactiveSpan({ name: 'transaction', forceTransaction: true }); +``` + +## The Span schema + +Previously, spans & transactions had a bunch of properties and methods to be used. Most of these have been removed in +favor of a slimmer, more straightforward API, which is also aligned with OpenTelemetry Spans. You can refer to the table +below to see which things used to exist, and how they can/should be mapped going forward: + +| Old name | Replace with | +| --------------------- | ----------------------------------------------------------- | +| `traceId` | `spanContext().traceId` | +| `spanId` | `spanContext().spanId` | +| `parentSpanId` | `spanToJSON(span).parent_span_id` | +| `status` | `spanToJSON(span).status` | +| `sampled` | `spanIsSampled(span)` | +| `startTimestamp` | `startTime` - note that this has a different format! | +| `tags` | use attributes, or set tags on the scope | +| `data` | `spanToJSON(span).data` | +| `transaction` | `getRootSpan(span)` | +| `instrumenter` | Removed | +| `finish()` | `end()` | +| `end()` | Same | +| `setTag()` | `setAttribute()`, or set tags on the scope | +| `setData()` | `setAttribute()` | +| `setStatus()` | The signature of this will change in a coming alpha release | +| `setHttpStatus()` | `setHttpStatus(span, status)` | +| `setName()` | `updateName()` | +| `startChild()` | Call `Sentry.startSpan()` independently | +| `isSuccess()` | `spanToJSON(span).status === 'ok'` | +| `toTraceparent()` | `spanToTraceHeader(span)` | +| `toContext()` | Removed | +| `updateWithContext()` | Removed | +| `getTraceContext()` | `spanToTraceContext(span)` | + +In addition, a transaction has this API: + +| Old name | Replace with | +| --------------------------- | ------------------------------------------------ | +| `name` | `spanToJSON(span).description` | +| `trimEnd` | Removed | +| `parentSampled` | `spanIsSampled(span)` & `spanContext().isRemote` | +| `metadata` | Use attributes instead or set on scope | +| `setContext()` | Set context on scope instead | +| `setMeasurement()` | `Sentry.setMeasurement()` | +| `setMetadata()` | Use attributes instead or set on scope | +| `getDynamicSamplingContext` | `getDynamicSamplingContextFromSpan(span)` | + +### Attributes vs. Data vs. Tags vs. Context + +In the old model, you had the concepts of **Data**, **Tags** and **Context** which could be used for different things. +However, this has two main downsides: One, it is not always clear which of these should be used when. And two, not all +of these are displayed the same way for transactions or spans. + +Because of this, in the new model, there are only **Attributes** to be set on spans anymore. Broadly speaking, they map +to what Data used to be. + +If you still really _need_ to set tags or context, you can do so on the scope before starting a span: + +```js +Sentry.withScope(scope => { + scope.setTag('my-tag', 'tag-value'); + Sentry.startSpan({ name: 'my-span' }, span => { + // do something here + // span will have the tags from the containing scope + }); +}); +``` + ## Other Notable Changes In addition to generally changing the performance APIs, there are also some smaller changes that this brings with it. 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/nx.json b/nx.json index a8b8f213eab2..1fe8820b9110 100644 --- a/nx.json +++ b/nx.json @@ -10,7 +10,7 @@ }, "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], - "sharedGlobals": ["{workspaceRoot}/*.js", "{workspaceRoot}/*.json"], + "sharedGlobals": ["{workspaceRoot}/*.js", "{workspaceRoot}/*.json", "{workspaceRoot}/yarn.lock"], "production": ["default", "!{projectRoot}/test/**/*", "!{projectRoot}/**/*.md", "!{projectRoot}/*.tgz"] }, "targetDefaults": { @@ -27,12 +27,22 @@ "build:transpile": { "inputs": ["production", "^production"], "dependsOn": ["^build:transpile"], - "outputs": ["{projectRoot}/build"] + "outputs": [ + "{projectRoot}/build/esm", + "{projectRoot}/build/cjs", + "{projectRoot}/build/npm/esm", + "{projectRoot}/build/npm/cjs" + ] }, "build:types": { "inputs": ["production", "^production"], "dependsOn": ["^build:types"], - "outputs": ["{projectRoot}/build/**/*.d.ts"] + "outputs": [ + "{projectRoot}/build/types", + "{projectRoot}/build/types-ts3.8", + "{projectRoot}/build/npm/types", + "{projectRoot}/build/npm/types-ts3.8" + ] }, "lint": { "inputs": ["default"], diff --git a/package.json b/package.json index 9ecacd34c252..b44b18e87a7a 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,6 @@ "packages/eslint-plugin-sdk", "packages/feedback", "packages/gatsby", - "packages/hub", - "packages/integrations", "packages/integration-shims", "packages/nextjs", "packages/node", @@ -74,7 +72,6 @@ "packages/serverless", "packages/svelte", "packages/sveltekit", - "packages/tracing", "packages/tracing-internal", "packages/types", "packages/typescript", diff --git a/packages/angular-ivy/package.json b/packages/angular-ivy/package.json index b79371b7a4f6..96578c72d210 100644 --- a/packages/angular-ivy/package.json +++ b/packages/angular-ivy/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/angular-ivy", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Angular with full Ivy Support", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular-ivy", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.8" }, "main": "build/bundles/sentry-angular.umd.js", "module": "build/fesm2015/sentry-angular.js", @@ -21,10 +21,10 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", + "@sentry/browser": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", "tslib": "^2.4.1" }, "devDependencies": { @@ -65,6 +65,12 @@ "dependsOn": [ "^build:transpile", "^build:types" + ], + "outputs": [ + "{projectRoot}/build/esm2015", + "{projectRoot}/build/fesm2015", + "{projectRoot}/build/*.{md,json}", + "{projectRoot}/build/LICENCE" ] } } 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; 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/package.json b/packages/angular/package.json index d6668d7c2af0..cc109cdef282 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/angular", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.8" }, "main": "build/bundles/sentry-angular.umd.js", "module": "build/fesm2015/sentry-angular.js", @@ -21,10 +21,10 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", + "@sentry/browser": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", "tslib": "^2.4.1" }, "devDependencies": { @@ -66,9 +66,12 @@ "nx": { "targets": { "build:transpile": { - "dependsOn": [ - "^build:transpile", - "^build:types" + "dependsOn": ["^build:transpile", "^build:types"], + "outputs": [ + "{projectRoot}/build/esm2015", + "{projectRoot}/build/fesm2015", + "{projectRoot}/build/*.{md,json}", + "{projectRoot}/build/LICENCE" ] } } diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index ec7a735ab559..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 @@ -118,17 +118,16 @@ 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 + if (!event.type && event.event_id) { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id }); } }); // 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/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/src/tracing.ts b/packages/angular/src/tracing.ts index 5b2f74615c45..6ad3ba46776d 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'; @@ -72,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` */ @@ -126,25 +121,29 @@ 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, - op: 'navigation', - 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', - tags: { - 'routing.instrumentation': '@sentry/angular', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', url: strippedUrl, ...(navigationEvent.navigationTrigger && { navigationTrigger: navigationEvent.navigationTrigger, @@ -172,11 +171,10 @@ 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: { - 'routing.instrumentation': '@sentry/angular', + attributes: { url: strippedUrl, ...(navigationEvent.navigationTrigger && { navigationTrigger: navigationEvent.navigationTrigger, @@ -233,8 +231,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()); @@ -249,6 +254,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'; @@ -276,7 +324,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', }); @@ -320,7 +368,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', }); @@ -359,7 +407,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/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/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/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/astro/package.json b/packages/astro/package.json index b26f7561d644..d7593bab3ffe 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/astro", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Astro", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro", @@ -49,12 +49,12 @@ "astro": ">=3.x || >=4.0.0-beta" }, "dependencies": { - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", - "@sentry/vite-plugin": "^2.8.0" + "@sentry/browser": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/node-experimental": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", + "@sentry/vite-plugin": "^2.14.2" }, "devDependencies": { "astro": "^3.5.0", diff --git a/packages/astro/src/client/sdk.ts b/packages/astro/src/client/sdk.ts index a289296c2ab2..b23edc27f54a 100644 --- a/packages/astro/src/client/sdk.ts +++ b/packages/astro/src/client/sdk.ts @@ -8,7 +8,7 @@ import { import { applySdkMetadata, hasTracingEnabled } from '@sentry/core'; import type { Integration } from '@sentry/types'; -// Treeshakable guard to remove all code related to tracing +// Tree-shakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; /** diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 98e5486894db..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. @@ -8,8 +8,6 @@ import { handleRequest } from './server/middleware'; // Hence, we export everything from the Node SDK explicitly: export { - // eslint-disable-next-line deprecation/deprecation - addGlobalEventProcessor, addEventProcessor, addBreadcrumb, captureException, @@ -17,15 +15,8 @@ export { captureMessage, captureCheckIn, withMonitor, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, // eslint-disable-next-line deprecation/deprecation - extractTraceparentData, - // eslint-disable-next-line deprecation/deprecation - getActiveTransaction, - getHubFromCarrier, - // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, isInitialized, @@ -33,12 +24,8 @@ export { getGlobalScope, getIsolationScope, Hub, - // eslint-disable-next-line deprecation/deprecation - makeMain, setCurrentClient, Scope, - // eslint-disable-next-line deprecation/deprecation - startTransaction, SDK_VERSION, setContext, setExtra, @@ -46,30 +33,20 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - trace, withScope, withIsolationScope, autoDiscoverNodePerformanceMonitoringIntegrations, makeNodeTransport, - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, defaultStackParser, - // eslint-disable-next-line deprecation/deprecation - lastEventId, flush, close, getSentryRelease, addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData, - // eslint-disable-next-line deprecation/deprecation - deepReadDirSync, Integrations, consoleIntegration, onUncaughtExceptionIntegration, @@ -86,17 +63,19 @@ export { setMeasurement, getActiveSpan, startSpan, - // eslint-disable-next-line deprecation/deprecation - startActiveSpan, startInactiveSpan, startSpanManual, continueTrace, cron, parameterize, -} from '@sentry/node'; + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, +} 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/index.types.ts b/packages/astro/src/index.types.ts index 026321e8ab3d..11ea06bae7c1 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -1,5 +1,5 @@ // We export everything from both the client part of the SDK and from the server part. -// Some of the exports collide, which is not allowed, unless we redifine the colliding +// Some of the exports collide, which is not allowed, unless we redefine the colliding // exports in this file - which we do below. export * from './index.client'; export * from './index.server'; @@ -13,22 +13,18 @@ import sentryAstro from './index.server'; /** Initializes Sentry Astro SDK */ export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): 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; +// We only export server Integrations for now, until the exports are removed from Svelte and Node SDKs. +// Necessary to avoid type collision. +export declare const Integrations: typeof serverSdk.Integrations; export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration; +export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; -export declare const defaultIntegrations: Integration[]; export declare const getDefaultIntegrations: (options: Options) => Integration[]; 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 declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; export default sentryAstro; 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/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/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/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index cd59d2f73344..b5c0e599754a 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -5,10 +5,10 @@ import { getActiveSpan, getClient, getCurrentScope, - runWithAsyncContext, startSpan, -} from '@sentry/node'; -import type { Client, Scope, Span } from '@sentry/types'; + withIsolationScope, +} from '@sentry/node-experimental'; +import type { Client, Scope, Span, SpanAttributes } from '@sentry/types'; import { addNonEnumerableProperty, objectify, stripUrlQueryAndFragment } from '@sentry/utils'; import type { APIContext, MiddlewareResponseHandler } from 'astro'; @@ -27,15 +27,6 @@ type MiddlewareOptions = { * @default false (recommended) */ trackClientIp?: boolean; - - /** - * If true, the headers from the request will be attached to the event by calling `setExtra`. - * - * Only set this to `true` if you're fine with collecting potentially personally identifiable information (PII). - * - * @default false (recommended) - */ - trackHeaders?: boolean; }; function sendErrorToSentry(e: unknown): unknown { @@ -63,7 +54,6 @@ type AstroLocalsWithSentry = Record & { export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseHandler = options => { const handlerOptions = { trackClientIp: false, - trackHeaders: false, ...options, }; @@ -74,7 +64,7 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseH if (getActiveSpan()) { return instrumentRequest(ctx, next, handlerOptions); } - return runWithAsyncContext(() => { + return withIsolationScope(() => { return instrumentRequest(ctx, next, handlerOptions); }); }; @@ -100,13 +90,9 @@ async function instrumentRequest( baggage: headers.get('baggage'), }, async () => { - const allHeaders: Record = {}; - - if (options.trackHeaders) { - headers.forEach((value, key) => { - allHeaders[key] = value; - }); - } + // We store this on the current scope, not isolation scope, + // because we may have multiple requests nested inside each other + getCurrentScope().setSDKProcessingMetadata({ request: ctx.request }); if (options.trackClientIp) { getCurrentScope().setUser({ ip_address: ctx.clientAddress }); @@ -117,22 +103,27 @@ async function instrumentRequest( const source = interpolatedRoute ? 'route' : 'url'; // storing res in a variable instead of directly returning is necessary to // invoke the catch block if next() throws + + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.astro', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, + method, + url: stripUrlQueryAndFragment(ctx.url.href), + }; + + if (ctx.url.search) { + attributes['http.query'] = ctx.url.search; + } + + if (ctx.url.hash) { + attributes['http.fragment'] = ctx.url.hash; + } + const res = await startSpan( { - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.astro', - }, + attributes, name: `${method} ${interpolatedRoute || ctx.url.pathname}`, op: 'http.server', - status: 'ok', - data: { - method, - url: stripUrlQueryAndFragment(ctx.url.href), - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, - ...(ctx.url.search && { 'http.query': ctx.url.search }), - ...(ctx.url.hash && { 'http.fragment': ctx.url.hash }), - ...(options.trackHeaders && { headers: allHeaders }), - }, }, async span => { const originalResponse = await next(); 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/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index d6f22dc9ed7a..50f4e3c9e354 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 { SDK_VERSION, getClient } from '@sentry/browser'; import { vi } from 'vitest'; +import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { init } from '../../../astro/src/client/sdk'; const browserInit = vi.spyOn(SentryBrowser, 'init'); @@ -13,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', () => { @@ -38,16 +43,12 @@ describe('Sentry client SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = getCurrentScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + it('sets the runtime tag on the isolation scope', () => { + expect(getIsolationScope().getScopeData().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(getIsolationScope().getScopeData().tags).toEqual({ runtime: 'browser' }); }); describe('automatically adds integrations', () => { @@ -55,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, @@ -71,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, @@ -84,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({ @@ -101,28 +102,7 @@ describe('Sentry client SDK', () => { delete globalThis.__SENTRY_TRACING__; }); - it('Overrides the automatically default BrowserTracing instance with a a user-provided BrowserTracing instance', () => { - init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - // eslint-disable-next-line deprecation/deprecation - integrations: [new BrowserTracing({ finalTimeout: 10, startTransactionOnLocationChange: false })], - enableTracing: true, - }); - - const integrationsToInit = browserInit.mock.calls[0][0]?.defaultIntegrations; - - // eslint-disable-next-line deprecation/deprecation - const browserTracing = getClient()?.getIntegrationByName('BrowserTracing') as BrowserTracing; - const options = browserTracing.options; - - expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' })); - expect(browserTracing).toBeDefined(); - - // This shows that the user-configured options are still here - expect(options.finalTimeout).toEqual(10); - }); - - 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 172756847a5c..b89522c78c89 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,14 +43,14 @@ 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, });" `); }); - 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\\"; @@ -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, });" diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index a83de3cb0eb2..cd5caec43c48 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'; @@ -19,6 +19,7 @@ describe('sentryMiddleware', () => { return {} as Span | undefined; }); const setUserMock = vi.fn(); + const setSDKProcessingMetadataMock = vi.fn(); beforeEach(() => { vi.spyOn(SentryNode, 'getCurrentScope').mockImplementation(() => { @@ -26,6 +27,7 @@ describe('sentryMiddleware', () => { setUser: setUserMock, setPropagationContext: vi.fn(), getSpan: getSpanMock, + setSDKProcessingMetadata: setSDKProcessingMetadataMock, } as any; }); vi.spyOn(SentryNode, 'getActiveSpan').mockImplementation(getSpanMock); @@ -60,15 +62,12 @@ describe('sentryMiddleware', () => { { attributes: { 'sentry.origin': 'auto.http.astro', - }, - data: { method: 'GET', url: 'https://mydomain.io/users/123/details', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, name: 'GET /users/[id]/details', op: 'http.server', - status: 'ok', }, expect.any(Function), // the `next` function ); @@ -97,15 +96,12 @@ describe('sentryMiddleware', () => { { attributes: { 'sentry.origin': 'auto.http.astro', - }, - data: { method: 'GET', url: 'http://localhost:1234/a%xx', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, name: 'GET a%xx', op: 'http.server', - status: 'ok', }, expect.any(Function), // the `next` function ); @@ -142,8 +138,8 @@ describe('sentryMiddleware', () => { }); }); - it('attaches client IP and request headers if options are set', async () => { - const middleware = handleRequest({ trackClientIp: true, trackHeaders: true }); + it('attaches client IP if `trackClientIp=true`', async () => { + const middleware = handleRequest({ trackClientIp: true }); const ctx = { request: { method: 'GET', @@ -162,17 +158,36 @@ describe('sentryMiddleware', () => { await middleware(ctx, next); expect(setUserMock).toHaveBeenCalledWith({ ip_address: '192.168.0.1' }); + }); - expect(startSpanSpy).toHaveBeenCalledWith( - expect.objectContaining({ - data: expect.objectContaining({ - headers: { - 'some-header': 'some-value', - }, + it('attaches request as SDK processing metadata', async () => { + const middleware = handleRequest({}); + const ctx = { + request: { + method: 'GET', + url: '/users', + headers: new Headers({ + 'some-header': 'some-value', }), - }), - expect.any(Function), // the `next` function - ); + }, + clientAddress: '192.168.0.1', + params: {}, + url: new URL('https://myDomain.io/users/'), + }; + const next = vi.fn(() => nextResult); + + // @ts-expect-error, a partial ctx object is fine here + await middleware(ctx, next); + + expect(setSDKProcessingMetadataMock).toHaveBeenCalledWith({ + request: { + method: 'GET', + url: '/users', + headers: new Headers({ + 'some-header': 'some-value', + }), + }, + }); }); it('injects tracing tags into the HTML of a pageload response', async () => { @@ -260,10 +275,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 +294,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 +312,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 b132c32a03c9..b1dc3821c88f 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 * as SentryNode from '@sentry/node-experimental'; +import { SDK_VERSION } from '@sentry/node-experimental'; import { vi } from 'vitest'; import { init } from '../../src/server/sdk'; @@ -11,7 +10,11 @@ describe('Sentry server SDK', () => { describe('init', () => { afterEach(() => { vi.clearAllMocks(); - GLOBAL_OBJ.__SENTRY__.hub = undefined; + + SentryNode.getGlobalScope().clear(); + SentryNode.getIsolationScope().clear(); + SentryNode.getCurrentScope().clear(); + SentryNode.getCurrentScope().setClient(undefined); }); it('adds Astro metadata to the SDK options', () => { @@ -36,16 +39,12 @@ describe('Sentry server SDK', () => { ); }); - it('sets the runtime tag on the scope', () => { - const currentScope = SentryNode.getCurrentScope(); - - // @ts-expect-error need access to protected _tags attribute - expect(currentScope._tags).toEqual({}); + it('sets the runtime tag on the isolation scope', () => { + 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(currentScope._tags).toEqual({ runtime: 'node' }); + expect(SentryNode.getIsolationScope().getScopeData().tags).toEqual({ runtime: 'node' }); }); }); }); 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/package.json b/packages/browser/package.json index db4a7315a346..3d2cb0a25e37 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/browser", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "cjs", @@ -29,16 +29,16 @@ "access": "public" }, "dependencies": { - "@sentry-internal/feedback": "7.100.0", - "@sentry-internal/replay-canvas": "7.100.0", - "@sentry-internal/tracing": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/replay": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry-internal/feedback": "8.0.0-alpha.0", + "@sentry-internal/replay-canvas": "8.0.0-alpha.0", + "@sentry-internal/tracing": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/replay": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "devDependencies": { - "@sentry-internal/integration-shims": "7.100.0", + "@sentry-internal/integration-shims": "8.0.0-alpha.0", "@types/md5": "2.1.33", "btoa": "^1.2.1", "chai": "^4.1.2", diff --git a/packages/browser/rollup.bundle.config.mjs b/packages/browser/rollup.bundle.config.mjs index c64a88931a33..d262f1a1bc51 100644 --- a/packages/browser/rollup.bundle.config.mjs +++ b/packages/browser/rollup.bundle.config.mjs @@ -8,6 +8,17 @@ if (targets.some(target => target !== 'es5' && target !== 'es6')) { throw new Error('JS_VERSION must be either "es5" or "es6"'); } +const browserPluggableIntegrationFiles = ['contextlines', 'httpclient', 'reportingobserver']; + +const corePluggableIntegrationFiles = [ + 'captureconsole', + 'debug', + 'dedupe', + 'extraerrordata', + 'rewriteframes', + 'sessiontiming', +]; + targets.forEach(jsVersion => { const baseBundleConfig = makeBaseBundleConfig({ bundleType: 'standalone', @@ -21,10 +32,34 @@ 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' : ''}`, }); + browserPluggableIntegrationFiles.forEach(integrationName => { + const integrationsBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: [`src/integrations/${integrationName}.ts`], + jsVersion, + licenseTitle: `@sentry/browser - ${integrationName}`, + outputFileBase: () => `bundles/${integrationName}${jsVersion === 'es5' ? '.es5' : ''}`, + }); + + builds.push(...makeBundleConfigVariants(integrationsBundleConfig)); + }); + + corePluggableIntegrationFiles.forEach(integrationName => { + const integrationsBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: [`src/integrations-bundle/index.${integrationName}.ts`], + jsVersion, + licenseTitle: `@sentry/browser - ${integrationName}`, + outputFileBase: () => `bundles/${integrationName}${jsVersion === 'es5' ? '.es5' : ''}`, + }); + + builds.push(...makeBundleConfigVariants(integrationsBundleConfig)); + }); + builds.push(...makeBundleConfigVariants(baseBundleConfig), ...makeBundleConfigVariants(tracingBaseBundleConfig)); }); @@ -50,7 +85,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 +93,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/client.ts b/packages/browser/src/client.ts index 2f1c5ba2fdbb..869d28c16c50 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); @@ -98,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); } /** @@ -132,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/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..b066ebe3d9a4 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, @@ -19,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 @@ -32,21 +29,17 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, flush, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, isInitialized, getCurrentScope, + getIsolationScope, + getGlobalScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, Scope, @@ -66,19 +59,33 @@ export { setUser, withScope, withIsolationScope, + withActiveSpan, // eslint-disable-next-line deprecation/deprecation FunctionToString, // eslint-disable-next-line deprecation/deprecation InboundFilters, - metrics, functionToStringIntegration, inboundFiltersIntegration, + dedupeIntegration, parameterize, + startSession, + captureSession, + endSession, + spanToJSON, } from '@sentry/core'; +export { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, +} from '@sentry/core'; + +export * from './metrics'; + export { WINDOW } from './helpers'; export { BrowserClient } from './client'; -export { makeFetchTransport, makeXHRTransport } from './transports'; +export { makeFetchTransport } from './transports/fetch'; export { defaultStackParser, defaultStackLineParsers, @@ -91,8 +98,6 @@ export { export { eventFromException, eventFromMessage, exceptionFromError } from './eventbuilder'; export { createUserFeedbackEnvelope } from './userfeedback'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, forceLoad, init, @@ -104,11 +109,10 @@ export { } from './sdk'; export { breadcrumbsIntegration } from './integrations/breadcrumbs'; -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 { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations'; +export { Breadcrumbs, LinkedErrors, HttpContext } from './integrations'; diff --git a/packages/browser/src/helpers.ts b/packages/browser/src/helpers.ts index 35930167672d..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 { DsnLike, Integration, Mechanism, WrappedFunction } from '@sentry/types'; +import type { Mechanism, WrappedFunction } from '@sentry/types'; import { GLOBAL_OBJ, addExceptionMechanism, @@ -155,67 +153,3 @@ 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`, - * 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.base.ts b/packages/browser/src/index.bundle.base.ts index 304d1c4f16d1..20bea46dca8b 100644 --- a/packages/browser/src/index.bundle.base.ts +++ b/packages/browser/src/index.bundle.base.ts @@ -1,3 +1,5 @@ +import type { IntegrationFn } from '@sentry/types/src'; + export * from './exports'; import { Integrations as CoreIntegrations } from '@sentry/core'; @@ -14,7 +16,7 @@ if (WINDOW.Sentry && WINDOW.Sentry.Integrations) { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -const INTEGRATIONS: Record Integration> = { +const INTEGRATIONS: Record Integration) | IntegrationFn> = { ...windowIntegrations, // eslint-disable-next-line deprecation/deprecation ...CoreIntegrations, 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 4a015f0dd9fe..0e75d5319ee2 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -1,7 +1,7 @@ import { Feedback, feedbackIntegration } from '@sentry-internal/feedback'; -import { BrowserTracing, Span, addExtensionMethods } 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'; @@ -11,11 +11,8 @@ 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 -addExtensionMethods(); +addTracingExtensions(); export { // eslint-disable-next-line deprecation/deprecation @@ -24,10 +21,7 @@ export { Replay, feedbackIntegration, replayIntegration, - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, browserTracingIntegration, - Span, - 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 c880f97bd84f..5b5fdef07bf6 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,7 +1,7 @@ -import { Feedback, feedbackIntegration } from '@sentry-internal/integration-shims'; -import { BrowserTracing, Span, addExtensionMethods } 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'; @@ -11,23 +11,17 @@ 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 -addExtensionMethods(); +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, - Span, - 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 e645de683dd5..e3e75924c10f 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,33 +1,32 @@ // 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 { bundleBrowserTracingIntegration as browserTracingIntegration } from './helpers'; +import { + FeedbackShim, + ReplayShim, + feedbackIntegrationShim, + replayIntegrationShim, +} from '@sentry-internal/integration-shims'; +import { browserTracingIntegration } from '@sentry-internal/tracing'; +import { addTracingExtensions } from '@sentry/core'; 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 -addExtensionMethods(); +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, - Span, - addExtensionMethods, + addTracingExtensions, }; export * from './index.bundle.base'; 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 2be5c71c4518..d4c47e6f91eb 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -23,10 +23,23 @@ const INTEGRATIONS = { // eslint-disable-next-line deprecation/deprecation export { INTEGRATIONS as Integrations }; +export { reportingObserverIntegration } from './integrations/reportingobserver'; +export { httpClientIntegration } from './integrations/httpclient'; +export { contextLinesIntegration } from './integrations/contextlines'; + +export { + captureConsoleIntegration, + debugIntegration, + extraErrorDataIntegration, + rewriteFramesIntegration, + sessionTimingIntegration, +} from '@sentry/core'; + export { // eslint-disable-next-line deprecation/deprecation Replay, replayIntegration, + getReplay, } from '@sentry/replay'; export type { ReplayEventType, @@ -54,8 +67,6 @@ export { } from '@sentry-internal/feedback'; export { - // eslint-disable-next-line deprecation/deprecation - BrowserTracing, defaultRequestInstrumentationOptions, instrumentOutgoingRequests, browserTracingIntegration, @@ -67,15 +78,9 @@ export { addTracingExtensions, setMeasurement, // eslint-disable-next-line deprecation/deprecation - extractTraceparentData, - // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - trace, makeMultiplexedTransport, // eslint-disable-next-line deprecation/deprecation ModuleMetadata, @@ -84,9 +89,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/integrations-bundle/index.captureconsole.ts b/packages/browser/src/integrations-bundle/index.captureconsole.ts new file mode 100644 index 000000000000..a2187ae98798 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.captureconsole.ts @@ -0,0 +1 @@ +export { captureConsoleIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations-bundle/index.debug.ts b/packages/browser/src/integrations-bundle/index.debug.ts new file mode 100644 index 000000000000..39e8920e381f --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.debug.ts @@ -0,0 +1 @@ +export { debugIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations-bundle/index.dedupe.ts b/packages/browser/src/integrations-bundle/index.dedupe.ts new file mode 100644 index 000000000000..776d967c31a9 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.dedupe.ts @@ -0,0 +1 @@ +export { dedupeIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations-bundle/index.extraerrordata.ts b/packages/browser/src/integrations-bundle/index.extraerrordata.ts new file mode 100644 index 000000000000..4306f9694902 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.extraerrordata.ts @@ -0,0 +1 @@ +export { extraErrorDataIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations-bundle/index.rewriteframes.ts b/packages/browser/src/integrations-bundle/index.rewriteframes.ts new file mode 100644 index 000000000000..07ebaf6666f5 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.rewriteframes.ts @@ -0,0 +1 @@ +export { rewriteFramesIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations-bundle/index.sessiontiming.ts b/packages/browser/src/integrations-bundle/index.sessiontiming.ts new file mode 100644 index 000000000000..ae495a70cc49 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.sessiontiming.ts @@ -0,0 +1 @@ +export { sessionTimingIntegration } from '@sentry/core'; diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index 1cbd8321346e..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)); @@ -88,7 +86,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/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/integrations/src/contextlines.ts b/packages/browser/src/integrations/contextlines.ts similarity index 80% rename from packages/integrations/src/contextlines.ts rename to packages/browser/src/integrations/contextlines.ts index f9835ad2f0f5..8e77ea6f8a08 100644 --- a/packages/integrations/src/contextlines.ts +++ b/packages/browser/src/integrations/contextlines.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 { GLOBAL_OBJ, addContextToFrame, stripUrlQueryAndFragment } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -23,16 +23,12 @@ 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); }, }; }) satisfies IntegrationFn; -export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); - /** * Collects source context lines around the lines of stackframes pointing to JS embedded in * the current page's HTML. @@ -43,13 +39,8 @@ export const contextLinesIntegration = defineIntegration(_contextLinesIntegratio * * Use this integration if you have inline JS code in HTML pages that can't be accessed * by our backend (e.g. due to a login-protected page). - * - * @deprecated Use `contextLinesIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } -> & { new (options?: { frameContextLines?: number }): Integration }; +export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); /** * Processes an event and adds context lines. diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts deleted file mode 100644 index f4ace6011b19..000000000000 --- a/packages/browser/src/integrations/dedupe.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; - -const INTEGRATION_NAME = 'Dedupe'; - -const _dedupeIntegration = (() => { - let previousEvent: Event | undefined; - - 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. - if (currentEvent.type) { - return currentEvent; - } - - // Juuust in case something goes wrong - try { - if (_shouldDropEvent(currentEvent, previousEvent)) { - DEBUG_BUILD && logger.warn('Event dropped due to being a duplicate of previously captured event.'); - return null; - } - } catch (_oO) {} // eslint-disable-line no-empty - - return (previousEvent = currentEvent); - }, - }; -}) satisfies IntegrationFn; - -export const dedupeIntegration = defineIntegration(_dedupeIntegration); - -/** - * Deduplication filter. - * @deprecated Use `dedupeIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; - -function _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean { - if (!previousEvent) { - return false; - } - - if (_isSameMessageEvent(currentEvent, previousEvent)) { - return true; - } - - if (_isSameExceptionEvent(currentEvent, previousEvent)) { - return true; - } - - return false; -} - -function _isSameMessageEvent(currentEvent: Event, previousEvent: Event): boolean { - const currentMessage = currentEvent.message; - const previousMessage = previousEvent.message; - - // If neither event has a message property, they were both exceptions, so bail out - if (!currentMessage && !previousMessage) { - return false; - } - - // If only one event has a stacktrace, but not the other one, they are not the same - if ((currentMessage && !previousMessage) || (!currentMessage && previousMessage)) { - return false; - } - - if (currentMessage !== previousMessage) { - return false; - } - - if (!_isSameFingerprint(currentEvent, previousEvent)) { - return false; - } - - if (!_isSameStacktrace(currentEvent, previousEvent)) { - return false; - } - - return true; -} - -function _isSameExceptionEvent(currentEvent: Event, previousEvent: Event): boolean { - const previousException = _getExceptionFromEvent(previousEvent); - const currentException = _getExceptionFromEvent(currentEvent); - - if (!previousException || !currentException) { - return false; - } - - if (previousException.type !== currentException.type || previousException.value !== currentException.value) { - return false; - } - - if (!_isSameFingerprint(currentEvent, previousEvent)) { - return false; - } - - if (!_isSameStacktrace(currentEvent, previousEvent)) { - return false; - } - - return true; -} - -function _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean { - let currentFrames = _getFramesFromEvent(currentEvent); - let previousFrames = _getFramesFromEvent(previousEvent); - - // If neither event has a stacktrace, they are assumed to be the same - if (!currentFrames && !previousFrames) { - return true; - } - - // If only one event has a stacktrace, but not the other one, they are not the same - if ((currentFrames && !previousFrames) || (!currentFrames && previousFrames)) { - return false; - } - - currentFrames = currentFrames as StackFrame[]; - previousFrames = previousFrames as StackFrame[]; - - // If number of frames differ, they are not the same - if (previousFrames.length !== currentFrames.length) { - return false; - } - - // Otherwise, compare the two - for (let i = 0; i < previousFrames.length; i++) { - const frameA = previousFrames[i]; - const frameB = currentFrames[i]; - - if ( - frameA.filename !== frameB.filename || - frameA.lineno !== frameB.lineno || - frameA.colno !== frameB.colno || - frameA.function !== frameB.function - ) { - return false; - } - } - - return true; -} - -function _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean { - let currentFingerprint = currentEvent.fingerprint; - let previousFingerprint = previousEvent.fingerprint; - - // If neither event has a fingerprint, they are assumed to be the same - if (!currentFingerprint && !previousFingerprint) { - return true; - } - - // If only one event has a fingerprint, but not the other one, they are not the same - if ((currentFingerprint && !previousFingerprint) || (!currentFingerprint && previousFingerprint)) { - return false; - } - - currentFingerprint = currentFingerprint as string[]; - previousFingerprint = previousFingerprint as string[]; - - // Otherwise, compare the two - try { - return !!(currentFingerprint.join('') === previousFingerprint.join('')); - } catch (_oO) { - return false; - } -} - -function _getExceptionFromEvent(event: Event): Exception | undefined { - return event.exception && event.exception.values && event.exception.values[0]; -} - -function _getFramesFromEvent(event: Event): StackFrame[] | undefined { - const exception = event.exception; - - if (exception) { - try { - // @ts-expect-error Object could be undefined - return exception.values[0].stacktrace.frames; - } catch (_oO) { - return undefined; - } - } - return undefined; -} diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index c1096e7c2132..b57fd8383155 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -1,19 +1,10 @@ -/* 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 { + UNKNOWN_FUNCTION, addGlobalErrorInstrumentationHandler, addGlobalUnhandledRejectionInstrumentationHandler, getLocationHref, - isErrorEvent, isPrimitive, isString, logger, @@ -57,18 +48,6 @@ const _globalHandlersIntegration = ((options: Partial void }> & { - new (options?: Partial): Integration; -}; - function _installGlobalOnErrorHandler(client: Client): void { addGlobalErrorInstrumentationHandler(data => { const { stackParser, attachStacktrace } = getOptions(); @@ -79,15 +58,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 +108,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 +151,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 @@ -230,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/integrations/src/httpclient.ts b/packages/browser/src/integrations/httpclient.ts similarity index 92% rename from packages/integrations/src/httpclient.ts rename to packages/browser/src/integrations/httpclient.ts index 0ae0b2d41184..e8d5d596d975 100644 --- a/packages/integrations/src/httpclient.ts +++ b/packages/browser/src/integrations/httpclient.ts @@ -1,18 +1,5 @@ -import { - captureEvent, - convertIntegrationFnToClass, - defineIntegration, - getClient, - isSentryRequestUrl, -} from '@sentry/core'; -import type { - Client, - Event as SentryEvent, - Integration, - IntegrationClass, - IntegrationFn, - SentryWrappedXMLHttpRequest, -} from '@sentry/types'; +import { captureEvent, defineIntegration, getClient, isSentryRequestUrl } from '@sentry/core'; +import type { Client, Event as SentryEvent, IntegrationFn, SentryWrappedXMLHttpRequest } from '@sentry/types'; import { GLOBAL_OBJ, SENTRY_XHR_DATA_KEY, @@ -23,7 +10,7 @@ import { supportsNativeFetch, } from '@sentry/utils'; -import { DEBUG_BUILD } from './debug-build'; +import { DEBUG_BUILD } from '../debug-build'; export type HttpStatusCodeRange = [number, number] | number; export type HttpRequestTarget = string | RegExp; @@ -60,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); @@ -69,21 +54,10 @@ const _httpClientIntegration = ((options: Partial = {}) => { }; }) satisfies IntegrationFn; -export const httpClientIntegration = defineIntegration(_httpClientIntegration); - /** * Create events for failed client side HTTP requests. - * @deprecated Use `httpClientIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const HttpClient = convertIntegrationFnToClass(INTEGRATION_NAME, httpClientIntegration) as IntegrationClass< - Integration & { setup: (client: Client) => void } -> & { - new (options?: { - failedRequestStatusCodes: HttpStatusCodeRange[]; - failedRequestTargets: HttpRequestTarget[]; - }): Integration; -}; +export const httpClientIntegration = defineIntegration(_httpClientIntegration); /** * Interceptor function for fetch requests 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/index.ts b/packages/browser/src/integrations/index.ts index 17ac85b31232..66aa1d8f11a5 100644 --- a/packages/browser/src/integrations/index.ts +++ b/packages/browser/src/integrations/index.ts @@ -1,7 +1,4 @@ /* eslint-disable deprecation/deprecation */ -export { GlobalHandlers } from './globalhandlers'; -export { TryCatch } from './trycatch'; export { Breadcrumbs } from './breadcrumbs'; export { LinkedErrors } from './linkederrors'; export { HttpContext } from './httpcontext'; -export { Dedupe } from './dedupe'; 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/integrations/src/reportingobserver.ts b/packages/browser/src/integrations/reportingobserver.ts similarity index 84% rename from packages/integrations/src/reportingobserver.ts rename to packages/browser/src/integrations/reportingobserver.ts index b22133d771fe..12a4039542b9 100644 --- a/packages/integrations/src/reportingobserver.ts +++ b/packages/browser/src/integrations/reportingobserver.ts @@ -1,5 +1,5 @@ -import { captureMessage, convertIntegrationFnToClass, defineIntegration, getClient, withScope } from '@sentry/core'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { captureMessage, defineIntegration, getClient, withScope } from '@sentry/core'; +import type { Client, IntegrationFn } from '@sentry/types'; import { GLOBAL_OBJ, supportsReportingObserver } from '@sentry/utils'; const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; @@ -115,18 +115,7 @@ const _reportingObserverIntegration = ((options: ReportingObserverOptions = {}) }; }) satisfies IntegrationFn; -export const reportingObserverIntegration = defineIntegration(_reportingObserverIntegration); - /** * Reporting API integration - https://w3c.github.io/reporting/ - * @deprecated Use `reportingObserverIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const ReportingObserver = convertIntegrationFnToClass( - INTEGRATION_NAME, - reportingObserverIntegration, -) as IntegrationClass void }> & { - new (options?: { - types?: ReportTypes[]; - }): Integration; -}; +export const reportingObserverIntegration = defineIntegration(_reportingObserverIntegration); 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/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/browser/src/profiling/integration.ts b/packages/browser/src/profiling/integration.ts index bf8e56a626b5..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, @@ -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(); @@ -35,11 +34,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); @@ -103,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/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/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 1c57f534867c..254276af335c 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, @@ -17,38 +16,30 @@ import { supportsFetch, } from '@sentry/utils'; +import { dedupeIntegration } from '@sentry/core'; 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'; +import { browserApiErrorsIntegration } from './integrations/browserapierrors'; 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, makeXHRTransport } from './transports'; - -/** @deprecated Use `getDefaultIntegrations(options)` instead. */ -export const defaultIntegrations = [ - inboundFiltersIntegration(), - functionToStringIntegration(), - browserApiErrorsIntegration(), - breadcrumbsIntegration(), - globalHandlersIntegration(), - linkedErrorsIntegration(), - dedupeIntegration(), - httpContextIntegration(), -]; +import { makeFetchTransport } from './transports/fetch'; /** 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(), ]; } @@ -79,17 +70,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', @@ -136,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); @@ -150,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; @@ -198,13 +195,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'; @@ -234,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/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/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/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/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/integration/common/init.js b/packages/browser/test/integration/common/init.js index 8215232aadcc..8f126bf175aa 100644 --- a/packages/browser/test/integration/common/init.js +++ b/packages/browser/test/integration/common/init.js @@ -22,7 +22,6 @@ var dsn = function initSDK() { Sentry.init({ dsn: dsn, - integrations: [new Sentry.Integrations.Dedupe()], attachStacktrace: true, ignoreErrors: ['ignoreErrorTest'], denyUrls: ['foo.js'], diff --git a/packages/browser/test/integration/run.js b/packages/browser/test/integration/run.js index abca77887ddf..2eac65132877 100755 --- a/packages/browser/test/integration/run.js +++ b/packages/browser/test/integration/run.js @@ -73,7 +73,7 @@ function build() { writeFile( 'artifacts/dedupe.js', - readFile('../../../integrations/build/bundles/dedupe.js').replace('//# sourceMappingURL=dedupe.js.map', ''), + readFile('../../../browser/build/bundles/dedupe.js').replace('//# sourceMappingURL=dedupe.js.map', ''), ); concatFiles('artifacts/setup.js', ['artifacts/dedupe.js', 'common/utils.js', 'common/triggers.js', 'common/init.js']); rmdir('artifacts/dedupe.js'); diff --git a/packages/browser/test/package/test-code.js b/packages/browser/test/package/test-code.js index 3a3811eebb89..72d0a494caa8 100644 --- a/packages/browser/test/package/test-code.js +++ b/packages/browser/test/package/test-code.js @@ -1,11 +1,9 @@ /* eslint-disable no-console */ const Sentry = require('../../build/npm/cjs/index.js'); -const Integrations = require('../../../integrations/build/npm/cjs/dedupe.js'); // Init Sentry.init({ dsn: 'https://completelyrandom@dsn.asdf/42', - integrations: [new Integrations.Dedupe()], beforeSend(_event) { console.log('Got an event'); return null; @@ -17,13 +15,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/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..a081c21a1e59 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,21 +11,13 @@ 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)); + expect((TracingBundle.Integrations[key] as any).name).toStrictEqual(expect.any(String)); }); expect(TracingBundle.Integrations.Replay).toBe(ReplayShim); 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/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index fe884ccc5438..a7e4cf6afd2a 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, @@ -54,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', }); }); @@ -87,8 +91,7 @@ describe('SentryBrowser', () => { getCurrentScope().setUser(EX_USER); setCurrentClient(client); - // eslint-disable-next-line deprecation/deprecation - showReportDialog(); + showReportDialog({ eventId: 'foobar' }); expect(getReportDialogEndpoint).toHaveBeenCalledTimes(1); expect(getReportDialogEndpoint).toHaveBeenCalledWith( @@ -102,8 +105,7 @@ describe('SentryBrowser', () => { setCurrentClient(client); 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( @@ -136,8 +138,8 @@ 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); @@ -151,8 +153,8 @@ describe('SentryBrowser', () => { const onClose = jest.fn(() => { throw new Error(); }); - // eslint-disable-next-line deprecation/deprecation - showReportDialog({ onClose }); + + showReportDialog({ eventId: 'foobar', onClose }); await waitForPostMessage('__sentry_reportdialog_closed__'); expect(onClose).toHaveBeenCalledTimes(1); @@ -164,8 +166,8 @@ 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(); @@ -290,20 +292,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', () => { @@ -314,13 +316,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/integrations/test/contextlines.test.ts b/packages/browser/test/unit/integrations/contextlines.test.ts similarity index 97% rename from packages/integrations/test/contextlines.test.ts rename to packages/browser/test/unit/integrations/contextlines.test.ts index 00365d7ab910..5f19bd2b41a7 100644 --- a/packages/integrations/test/contextlines.test.ts +++ b/packages/browser/test/unit/integrations/contextlines.test.ts @@ -1,6 +1,6 @@ import type { StackFrame } from '@sentry/types'; -import { applySourceContextToFrame } from '../src/contextlines'; +import { applySourceContextToFrame } from '../../../src/integrations/contextlines'; const lines = ['line1', 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8', 'line9']; describe('ContextLines', () => { diff --git a/packages/integrations/test/reportingobserver.test.ts b/packages/browser/test/unit/integrations/reportingobserver.test.ts similarity index 67% rename from packages/integrations/test/reportingobserver.test.ts rename to packages/browser/test/unit/integrations/reportingobserver.test.ts index c699b0d7f8dc..aba669286f44 100644 --- a/packages/integrations/test/reportingobserver.test.ts +++ b/packages/browser/test/unit/integrations/reportingobserver.test.ts @@ -1,8 +1,7 @@ import * as SentryCore from '@sentry/core'; -import type { Client, Hub } from '@sentry/types'; +import type { Client } from '@sentry/types'; -import type { reportingObserverIntegration } from '../src/reportingobserver'; -import { ReportingObserver } from '../src/reportingobserver'; +import { reportingObserverIntegration } from '../../../src/integrations/reportingobserver'; const mockScope = { setExtra: jest.fn(), @@ -14,8 +13,6 @@ const withScope = jest.fn(callback => { const captureMessage = jest.fn(); -const mockHub = {} as unknown as Hub; - const mockReportingObserverConstructor = jest.fn(); const mockObserve = jest.fn(); @@ -27,11 +24,6 @@ class MockReportingObserver { } } -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new ReportingObserver(...args); -} - describe('ReportingObserver', () => { let mockClient: Client; @@ -55,13 +47,10 @@ describe('ReportingObserver', () => { // Act like ReportingObserver is unavailable delete (global as any).ReportingObserver; - const reportingObserverIntegration = getIntegration(); + const reportingObserver = reportingObserverIntegration(); expect(() => { - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); + reportingObserver.setupOnce!(); }).not.toThrow(); expect(mockReportingObserverConstructor).not.toHaveBeenCalled(); @@ -69,12 +58,9 @@ describe('ReportingObserver', () => { }); it('should use default report types', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -84,12 +70,9 @@ describe('ReportingObserver', () => { }); it('should use user-provided report types', () => { - const reportingObserverIntegration = getIntegration({ types: ['crash'] }); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration({ types: ['crash'] }); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -99,12 +82,9 @@ describe('ReportingObserver', () => { }); it('should use `buffered` option', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); expect(mockReportingObserverConstructor).toHaveBeenCalledTimes(1); expect(mockReportingObserverConstructor).toHaveBeenCalledWith( @@ -114,12 +94,9 @@ describe('ReportingObserver', () => { }); it('should call `observe` function', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); expect(mockObserve).toHaveBeenCalledTimes(1); }); @@ -127,11 +104,8 @@ describe('ReportingObserver', () => { describe('handler', () => { it('should abort gracefully and not do anything when integration is not installed', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); // without calling setup, the integration is not registered const handler = mockReportingObserverConstructor.mock.calls[0][0]; @@ -144,12 +118,9 @@ describe('ReportingObserver', () => { }); it('should capture messages', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -161,12 +132,9 @@ describe('ReportingObserver', () => { }); it('should set extra including the url of a report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([ @@ -179,12 +147,9 @@ describe('ReportingObserver', () => { }); it('should set extra including the report body if available', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report1 = { type: 'crash', url: 'some url 1', body: { crashId: 'id1' } } as const; @@ -197,12 +162,9 @@ describe('ReportingObserver', () => { }); it('should not set extra report body extra when no body is set', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; handler([{ type: 'crash', url: 'some url' }]); @@ -211,12 +173,9 @@ describe('ReportingObserver', () => { }); it('should capture report details from body on crash report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -232,12 +191,9 @@ describe('ReportingObserver', () => { }); it('should capture report message from body on deprecation report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -252,12 +208,9 @@ describe('ReportingObserver', () => { }); it('should capture report message from body on intervention report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -272,12 +225,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body is available', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -291,12 +241,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body details are available for crash report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { type: 'crash', url: 'some url', body: { crashId: '', reason: '' } } as const; @@ -307,12 +254,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body message is available for deprecation report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { @@ -327,12 +271,9 @@ describe('ReportingObserver', () => { }); it('should use fallback message when no body message is available for intervention report', () => { - const reportingObserverIntegration = getIntegration(); - reportingObserverIntegration.setupOnce( - () => undefined, - () => mockHub, - ); - reportingObserverIntegration.setup(mockClient); + const reportingObserver = reportingObserverIntegration(); + reportingObserver.setupOnce!(); + reportingObserver.setup?.(mockClient); const handler = mockReportingObserverConstructor.mock.calls[0][0]; const report = { 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); 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/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 deleted file mode 100644 index 9e5290d224c2..000000000000 --- a/packages/browser/test/unit/transports/xhr.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { TextEncoder } from 'util'; -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, - textEncoder: new TextEncoder(), -}; - -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, new TextEncoder())); - }); - - 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/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/package.json b/packages/bun/package.json index 74ccdc24611e..f73ad938beed 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/bun", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for bun", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "cjs", @@ -29,10 +29,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.100.0", - "@sentry/node": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/core": "8.0.0-alpha.0", + "@sentry/node-experimental": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "devDependencies": { "bun-types": "latest" diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index b51083052c8b..fe00c1eef4b9 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, @@ -20,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 { @@ -33,15 +31,10 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, - // eslint-disable-next-line deprecation/deprecation - extractTraceparentData, flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, @@ -51,11 +44,8 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, @@ -66,12 +56,8 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - trace, withScope, withIsolationScope, captureCheckIn, @@ -82,21 +68,25 @@ export { startInactiveSpan, startSpanManual, continueTrace, - metrics, + metricsDefault as metrics, functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration, + captureConsoleIntegration, + debugIntegration, + dedupeIntegration, + extraErrorDataIntegration, + rewriteFramesIntegration, + sessionTimingIntegration, parameterize, + startSession, + captureSession, + endSession, + withActiveSpan, } 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 - getModuleFromFilename, DEFAULT_USER_INCLUDES, autoDiscoverNodePerformanceMonitoringIntegrations, cron, @@ -117,25 +107,26 @@ export { onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, spotlightIntegration, -} from '@sentry/node'; + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, +} from '@sentry/node-experimental'; export { BunClient } from './client'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, } 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'; const INTEGRATIONS = { // eslint-disable-next-line deprecation/deprecation ...CoreIntegrations, - // eslint-disable-next-line deprecation/deprecation ...NodeIntegrations, BunServer, }; 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/bun/src/sdk.ts b/packages/bun/src/sdk.ts index f8dbcb99c0df..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'; @@ -21,35 +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(), - // new NodeIntegrations.LocalVariables(), # does't work with Bun - 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(), ]; } @@ -74,18 +67,7 @@ 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'); + * const { addBreadcrumb } = require('@sentry/node-experimental'); * addBreadcrumb({ * message: 'My Breadcrumb', * // ... @@ -95,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/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/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index bd62881b8ccf..35aac412717f 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,19 +18,15 @@ describe('Bun Serve Integration', () => { beforeEach(() => { const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true }); client = new BunClient(options); - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); test('generates a transaction around a request', async () => { client.on('finishTransaction', transaction => { expect(transaction.status).toBe('ok'); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.tags).toEqual({ - 'http.status_code': '200', - }); - expect(transaction.op).toEqual('http.server'); + expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('GET /'); }); @@ -50,11 +45,8 @@ describe('Bun Serve Integration', () => { test('generates a post transaction', async () => { client.on('finishTransaction', transaction => { expect(transaction.status).toBe('ok'); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.tags).toEqual({ - 'http.status_code': '200', - }); - expect(transaction.op).toEqual('http.server'); + expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('POST /'); }); diff --git a/packages/core/package.json b/packages/core/package.json index 24b1157174e6..b971b1e00beb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/core", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "cjs", @@ -29,8 +29,8 @@ "access": "public" }, "dependencies": { - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/core/src/asyncContext.ts b/packages/core/src/asyncContext.ts new file mode 100644 index 000000000000..fa47ce8aa020 --- /dev/null +++ b/packages/core/src/asyncContext.ts @@ -0,0 +1,101 @@ +import type { Hub, Integration } from '@sentry/types'; +import type { Scope } from '@sentry/types'; +import { GLOBAL_OBJ } from '@sentry/utils'; + +/** + * @private Private API with no semver guarantees! + * + * Strategy used to track async context. + */ +export interface AsyncContextStrategy { + /** + * Gets the currently active hub. + */ + getCurrentHub: () => Hub; + + /** + * Fork the isolation scope inside of the provided callback. + */ + 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; +} + +/** + * An object that contains a hub and maintains a scope stack. + * @hidden + */ +export interface Carrier { + __SENTRY__?: SentryCarrier; +} + +interface SentryCarrier { + 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; + }; +} + +/** + * 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. + **/ +export function getMainCarrier(): Carrier { + // This ensures a Sentry carrier exists + getSentryCarrier(GLOBAL_OBJ); + return GLOBAL_OBJ; +} + +/** + * @private Private API with no semver guarantees! + * + * Sets the global async context strategy + */ +export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void { + // Get main carrier (global for every environment) + const registry = getMainCarrier(); + const sentry = getSentryCarrier(registry); + sentry.acs = strategy; +} + +/** Will either get the existing sentry carrier, or create a new one. */ +export function getSentryCarrier(carrier: Carrier): SentryCarrier { + if (!carrier.__SENTRY__) { + carrier.__SENTRY__ = { + extensions: {}, + }; + } + return carrier.__SENTRY__; +} diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index b7f00e14baef..21341bef1e42 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -16,14 +16,11 @@ import type { FeedbackEvent, Integration, IntegrationClass, - MetricBucketItem, - MetricsAggregator, Outcome, ParameterizedString, SdkMetadata, Session, SessionAggregates, - Severity, SeverityLevel, StartSpanOptions, Transaction, @@ -48,14 +45,12 @@ import { } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; +import { getIsolationScope } from './currentScopes'; 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'; import { setupIntegration, setupIntegrations } from './integration'; -import { createMetricEnvelope } from './metrics/envelope'; import type { Scope } from './scope'; import { updateSession } from './session'; import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; @@ -95,13 +90,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; @@ -160,7 +148,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)) { @@ -186,8 +174,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 { @@ -283,9 +270,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); }); @@ -300,9 +285,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; }); } @@ -389,16 +372,10 @@ 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); + const promise = this.sendEnvelope(env); if (promise) { promise.then(sendResponse => this.emit('afterSendEvent', event, sendResponse), null); } @@ -410,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); } /** @@ -436,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 */ @@ -498,6 +458,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]) { @@ -544,6 +508,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]) { @@ -551,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. */ @@ -681,7 +666,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, @@ -834,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. */ @@ -868,7 +838,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; /** @@ -876,8 +846,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; } @@ -936,17 +905,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/breadcrumbs.ts b/packages/core/src/breadcrumbs.ts new file mode 100644 index 000000000000..1cfad5d08fea --- /dev/null +++ b/packages/core/src/breadcrumbs.ts @@ -0,0 +1,40 @@ +import type { Breadcrumb, BreadcrumbHint } from '@sentry/types'; +import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils'; +import { getClient, getIsolationScope } from './currentScopes'; + +/** + * 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/currentScopes.ts b/packages/core/src/currentScopes.ts new file mode 100644 index 000000000000..07e26d9a96b6 --- /dev/null +++ b/packages/core/src/currentScopes.ts @@ -0,0 +1,111 @@ +import type { Scope } from '@sentry/types'; +import type { Client } from '@sentry/types'; +import { getMainCarrier } from './asyncContext'; +import { getAsyncContextStrategy } from './hub'; +import { Scope as ScopeClass } from './scope'; + +/** + * The global scope is kept in this module. + * When accessing it, we'll make sure to set one if none is currently present. + */ +let globalScope: Scope | undefined; + +/** + * Get the currently active scope. + */ +export function getCurrentScope(): Scope { + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + return acs.getCurrentScope(); +} + +/** + * Get the currently active isolation scope. + * The isolation scope is active for the current exection context. + */ +export function getIsolationScope(): Scope { + const carrier = getMainCarrier(); + const acs = getAsyncContextStrategy(carrier); + return acs.getIsolationScope(); +} + +/** + * Get the global scope. + * This scope is applied to _all_ events. + */ +export function getGlobalScope(): Scope { + if (!globalScope) { + globalScope = new ScopeClass(); + } + + return globalScope; +} + +/** + * This is mainly needed for tests. + * DO NOT USE this, as this is an internal API and subject to change. + * @hidden + */ +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); +} + +/** + * 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 e1fc9a6102ac..eb9a95652792 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -1,12 +1,10 @@ import type { - Breadcrumb, - BreadcrumbHint, CaptureContext, CheckIn, - Client, CustomSamplingContext, Event, EventHint, + EventProcessor, Extra, Extras, FinishedCheckIn, @@ -15,7 +13,6 @@ import type { Scope as ScopeInterface, Session, SessionContext, - Severity, SeverityLevel, Span, TransactionContext, @@ -24,11 +21,10 @@ import type { import { GLOBAL_OBJ, isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from './constants'; +import { getClient, getCurrentScope, getIsolationScope, withScope } from './currentScopes'; import { DEBUG_BUILD } from './debug-build'; import type { Hub } from './hub'; -import { runWithAsyncContext } from './hub'; -import { getCurrentHub, getIsolationScope } 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'; @@ -45,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)); } /** @@ -56,17 +51,12 @@ 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; 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); } /** @@ -77,32 +67,7 @@ export function captureMessage( * @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); -} - -/** - * 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. - * - * 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); + return getCurrentScope().captureEvent(event, hint); } /** @@ -112,8 +77,7 @@ export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): Re */ // 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); } /** @@ -121,8 +85,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); } /** @@ -131,8 +94,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); } /** @@ -140,8 +102,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); } /** @@ -153,8 +114,7 @@ export function setTags(tags: { [key: string]: Primitive }): ReturnType { - // eslint-disable-next-line deprecation/deprecation - getCurrentHub().setTag(key, value); + getIsolationScope().setTag(key, value); } /** @@ -163,86 +123,22 @@ 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); -} - -/** - * 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: ScopeInterface | 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: ScopeInterface | undefined, callback: (scope: Scope) => T] -): T { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - - // 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]); + getIsolationScope().setUser(user); } /** - * 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. + * 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. * - * 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 { - return runWithAsyncContext(() => { - return callback(getIsolationScope()); - }); -} - -/** - * 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 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 { +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); }); } @@ -375,25 +271,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. - */ -export function getClient(): C | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getClient(); -} - /** * Returns true if Sentry has been properly initialized. */ @@ -402,11 +279,12 @@ export function isInitialized(): boolean { } /** - * Get the currently active scope. + * 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 getCurrentScope(): Scope { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getScope(); +export function addEventProcessor(callback: EventProcessor): void { + getIsolationScope().addEventProcessor(callback); } /** @@ -483,7 +361,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/hub.ts b/packages/core/src/hub.ts index d0f7c1cf4430..abfb22b612b3 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -12,24 +12,18 @@ import type { Integration, IntegrationClass, Primitive, + Scope as ScopeInterface, Session, SessionContext, - Severity, SeverityLevel, Transaction, 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 type { AsyncContextStrategy, Carrier } from './asyncContext'; +import { getMainCarrier, getSentryCarrier } from './asyncContext'; import { DEFAULT_ENVIRONMENT } from './constants'; import { DEBUG_BUILD } from './debug-build'; import { Scope } from './scope'; @@ -52,54 +46,13 @@ export const API_VERSION = parseFloat(SDK_VERSION); */ const DEFAULT_BREADCRUMBS = 100; -export interface RunWithAsyncContextOptions { - /** Whether to reuse an existing async context if one exists. Defaults to false. */ - reuseExisting?: boolean; -} - -/** - * @private Private API with no semver guarantees! - * - * Strategy used to track async context. - */ -export interface AsyncContextStrategy { - /** - * Gets the current async context. Returns undefined if there is no current async context. - */ - getCurrentHub: () => Hub | undefined; - /** - * Runs the supplied callback in its own async context. - */ - runWithAsyncContext(callback: () => T, options: RunWithAsyncContextOptions): T; -} - /** * A layer in the process stack. * @hidden */ export interface Layer { client?: Client; - scope: Scope; -} - -/** - * An object that contains a hub and maintains a scope stack. - * @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; - }; - }; + scope: ScopeInterface; } /** @@ -109,10 +62,7 @@ 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; + private _isolationScope: ScopeInterface; /** * Creates a new instance of the hub, will push one {@link Layer} into the @@ -121,11 +71,51 @@ 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, - scope?: Scope, - isolationScope?: Scope, + scope?: ScopeInterface, + isolationScope?: ScopeInterface, private readonly _version: number = API_VERSION, ) { let assignedScope; @@ -189,7 +179,7 @@ export class Hub implements HubInterface { * * @deprecated Use `withScope` instead. */ - public pushScope(): Scope { + public pushScope(): ScopeInterface { // We want to clone the content of prev scope // eslint-disable-next-line deprecation/deprecation const scope = this.getScope().clone(); @@ -219,7 +209,7 @@ export class Hub implements HubInterface { * * @deprecated Use `Sentry.withScope()` instead. */ - public withScope(callback: (scope: Scope) => T): T { + public withScope(callback: (scope: ScopeInterface) => T): T { // eslint-disable-next-line deprecation/deprecation const scope = this.pushScope(); @@ -268,7 +258,7 @@ export class Hub implements HubInterface { * * @deprecated Use `Sentry.getCurrentScope()` instead. */ - public getScope(): Scope { + public getScope(): ScopeInterface { // eslint-disable-next-line deprecation/deprecation return this.getStackTop().scope; } @@ -276,7 +266,7 @@ export class Hub implements HubInterface { /** * @deprecated Use `Sentry.getIsolationScope()` instead. */ - public getIsolationScope(): Scope { + public getIsolationScope(): ScopeInterface { return this._isolationScope; } @@ -302,7 +292,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, { @@ -320,13 +310,8 @@ 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 { - const eventId = (this._lastEventId = hint && hint.event_id ? hint.event_id : uuid4()); + public captureMessage(message: string, level?: SeverityLevel, hint?: EventHint): string { + 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, { @@ -346,23 +331,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 * @@ -370,7 +343,7 @@ export class Hub implements HubInterface { */ public addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void { // eslint-disable-next-line deprecation/deprecation - const { scope, client } = this.getStackTop(); + const { client } = this.getStackTop(); if (!client) return; @@ -387,19 +360,10 @@ export class Hub implements HubInterface { if (finalBreadcrumb === null) return; - if (client.emit) { - 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 - // duplicate all of the breadcrumbs. There was the possibility of adding breadcrumbs to both, the isolation scope - // and the normal scope, and deduplicating it down the line in the event processing pipeline. However, that would - // have been very fragile, because the breadcrumb objects would have needed to keep their identity all throughout - // the event processing pipeline. - // In the new implementation, the top level `Sentry.addBreadcrumb()` should ONLY write to the isolation scope. + client.emit('beforeAddBreadcrumb', finalBreadcrumb, hint); - scope.addBreadcrumb(finalBreadcrumb, maxBreadcrumbs); + // eslint-disable-next-line deprecation/deprecation + this.getIsolationScope().addBreadcrumb(finalBreadcrumb, maxBreadcrumbs); } /** @@ -407,9 +371,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); } @@ -419,9 +380,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); } @@ -431,9 +389,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); } @@ -443,9 +398,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); } @@ -455,9 +407,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); } @@ -468,40 +417,10 @@ 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); } - /** - * @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 - */ - 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. @@ -559,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 * @@ -669,29 +580,14 @@ 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.`); } } -/** - * 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. - **/ -export function getMainCarrier(): Carrier { - GLOBAL_OBJ.__SENTRY__ = GLOBAL_OBJ.__SENTRY__ || { - extensions: {}, - hub: undefined, - }; - return GLOBAL_OBJ; -} - /** * Replaces the current main hub with the passed one on the global object * @@ -699,11 +595,9 @@ export function getMainCarrier(): Carrier { * * @deprecated Use `setCurrentClient()` instead. */ -export function makeMain(hub: Hub): Hub { - const registry = getMainCarrier(); - const oldHub = getHubFromCarrier(registry); - setHubOnCarrier(registry, hub); - return oldHub; +export function makeMain(hub: HubInterface): HubInterface { + // noop! + return hub; } /** @@ -715,126 +609,103 @@ export function makeMain(hub: Hub): Hub { * * @deprecated Use the respective replacement method directly instead. */ -export function getCurrentHub(): Hub { +export function getCurrentHub(): HubInterface { // 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; - } - } + const carrier = getMainCarrier(); - // Return hub that lives on a global object - return getGlobalHub(registry); + const acs = getAsyncContextStrategy(carrier); + return acs.getCurrentHub() || getGlobalHub(); } -/** - * Get the currently active isolation scope. - * The isolation scope is active for the current exection context, - * meaning that it will remain stable for the same Hub. - */ -export function getIsolationScope(): Scope { - // eslint-disable-next-line deprecation/deprecation - return getCurrentHub().getIsolationScope(); -} - -function getGlobalHub(registry: Carrier = getMainCarrier()): Hub { - // If there's no hub, or its an old API, assign a new one +let defaultCurrentScope: Scope | undefined; +let defaultIsolationScope: Scope | undefined; - if ( - !hasHubOnCarrier(registry) || - // eslint-disable-next-line deprecation/deprecation - getHubFromCarrier(registry).isOlderThan(API_VERSION) - ) { - setHubOnCarrier(registry, new Hub()); +/** Get the default current scope. */ +export function getDefaultCurrentScope(): Scope { + if (!defaultCurrentScope) { + defaultCurrentScope = new Scope(); } - // Return hub that lives on a global object - return getHubFromCarrier(registry); + return defaultCurrentScope; } -/** - * @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 client = parent.getClient(); - // eslint-disable-next-line deprecation/deprecation - const scope = parent.getScope(); - // eslint-disable-next-line deprecation/deprecation - const isolationScope = parent.getIsolationScope(); - setHubOnCarrier(carrier, new Hub(client, scope.clone(), isolationScope.clone())); +/** Get the default isolation scope. */ +export function getDefaultIsolationScope(): Scope { + if (!defaultIsolationScope) { + defaultIsolationScope = new Scope(); } + + return defaultIsolationScope; } /** - * @private Private API with no semver guarantees! - * - * Sets the global async context strategy + * Get the global hub. + * This will be removed during the v8 cycle and is only here to make migration easier. */ -export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void { - // Get main carrier (global for every environment) +export function getGlobalHub(): HubInterface { const registry = getMainCarrier(); - registry.__SENTRY__ = registry.__SENTRY__ || {}; - registry.__SENTRY__.acs = strategy; + const sentry = getSentryCarrier(registry) as { hub?: HubInterface }; + + // If there's no hub, or its an old API, assign a new one + if (sentry.hub) { + return sentry.hub; + } + + // eslint-disable-next-line deprecation/deprecation + sentry.hub = new Hub(undefined, getDefaultCurrentScope(), getDefaultIsolationScope()); + return sentry.hub; } /** - * 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 + * Get the current async context strategy. + * If none has been setup, the default will be used. */ -export function runWithAsyncContext(callback: () => T, options: RunWithAsyncContextOptions = {}): T { - const registry = getMainCarrier(); +export function getAsyncContextStrategy(carrier: Carrier): AsyncContextStrategy { + const sentry = getSentryCarrier(carrier); - if (registry.__SENTRY__ && registry.__SENTRY__.acs) { - return registry.__SENTRY__.acs.runWithAsyncContext(callback, options); + if (sentry.acs) { + return sentry.acs; } - // if there was no strategy, fallback to just calling the callback - return callback(); + // Otherwise, use the default one + return getHubStackAsyncContextStrategy(); } -/** - * 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); +function withScope(callback: (scope: ScopeInterface) => T): T { + // eslint-disable-next-line deprecation/deprecation + return getGlobalHub().withScope(callback); } -/** - * 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 Hub(), carrier); +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); + }); } -/** - * 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: Hub): boolean { - if (!carrier) return false; - const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {}); - __SENTRY__.hub = hub; - return true; +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, + 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 849e34f6c92b..b5bee258dae6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,6 @@ export type { ClientClass } from './sdk'; -export type { AsyncContextStrategy, Carrier, Layer, RunWithAsyncContextOptions } from './hub'; +export type { Layer } from './hub'; +export type { AsyncContextStrategy, Carrier } from './asyncContext'; export type { OfflineStore, OfflineTransportOptions } from './transports/offline'; export type { ServerRuntimeClientOptions } from './server-runtime-client'; export type { RequestDataIntegrationOptions } from './integrations/requestdata'; @@ -9,19 +10,14 @@ export * from './tracing'; export * from './semanticAttributes'; export { createEventEnvelope, createSessionEnvelope } from './envelope'; export { - addBreadcrumb, captureCheckIn, withMonitor, captureException, captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, flush, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation startTransaction, setContext, setExtra, @@ -29,40 +25,46 @@ export { setTag, setTags, setUser, - withScope, - withIsolationScope, - getClient, isInitialized, - getCurrentScope, startSession, endSession, captureSession, withActiveSpan, + addEventProcessor, } from './exports'; export { // eslint-disable-next-line deprecation/deprecation getCurrentHub, - getIsolationScope, - getHubFromCarrier, Hub, // eslint-disable-next-line deprecation/deprecation makeMain, + getGlobalHub, + getDefaultCurrentScope, + getDefaultIsolationScope, +} from './hub'; +export { + getCurrentScope, + getIsolationScope, + getGlobalScope, + setGlobalScope, + withScope, + withIsolationScope, + getClient, +} from './currentScopes'; +export { getMainCarrier, - runWithAsyncContext, - setHubOnCarrier, - ensureHubOnCarrier, setAsyncContextStrategy, -} from './hub'; +} from './asyncContext'; export { makeSession, closeSession, updateSession } from './session'; export { SessionFlusher } from './sessionflusher'; -export { Scope, getGlobalScope, setGlobalScope } from './scope'; +export { Scope } from './scope'; export { notifyEventProcessors, // eslint-disable-next-line deprecation/deprecation 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'; @@ -98,6 +100,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'; @@ -105,7 +108,16 @@ export { inboundFiltersIntegration } from './integrations/inboundfilters'; export { linkedErrorsIntegration } from './integrations/linkederrors'; export { moduleMetadataIntegration } from './integrations/metadata'; export { requestDataIntegration } from './integrations/requestdata'; +export { captureConsoleIntegration } from './integrations/captureconsole'; +export { debugIntegration } from './integrations/debug'; +export { dedupeIntegration } from './integrations/dedupe'; +export { extraErrorDataIntegration } from './integrations/extraerrordata'; +export { rewriteFramesIntegration } from './integrations/rewriteframes'; +export { sessionTimingIntegration } from './integrations/sessiontiming'; 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/integration.ts b/packages/core/src/integration.ts index 8a91fa10e303..ccf1f86cff18 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' { @@ -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); @@ -140,12 +140,12 @@ 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)); } - 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/integrations/src/captureconsole.ts b/packages/core/src/integrations/captureconsole.ts similarity index 70% rename from packages/integrations/src/captureconsole.ts rename to packages/core/src/integrations/captureconsole.ts index 9bd6c45c8851..8e6dfc646419 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/core/src/integrations/captureconsole.ts @@ -1,12 +1,4 @@ -import { - captureException, - captureMessage, - convertIntegrationFnToClass, - defineIntegration, - getClient, - withScope, -} from '@sentry/core'; -import type { CaptureContext, Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import type { CaptureContext, IntegrationFn } from '@sentry/types'; import { CONSOLE_LEVELS, GLOBAL_OBJ, @@ -15,6 +7,9 @@ import { safeJoin, severityLevelFromString, } from '@sentry/utils'; +import { getClient, withScope } from '../currentScopes'; +import { captureException, captureMessage } from '../exports'; +import { defineIntegration } from '../integration'; interface CaptureConsoleOptions { levels?: string[]; @@ -27,8 +22,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; @@ -45,19 +38,10 @@ const _captureConsoleIntegration = ((options: CaptureConsoleOptions = {}) => { }; }) satisfies IntegrationFn; -export const captureConsoleIntegration = defineIntegration(_captureConsoleIntegration); - /** * Send Console API calls as Sentry Events. - * @deprecated Use `captureConsoleIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const CaptureConsole = convertIntegrationFnToClass( - INTEGRATION_NAME, - captureConsoleIntegration, -) as IntegrationClass void }> & { - new (options?: { levels?: string[] }): Integration; -}; +export const captureConsoleIntegration = defineIntegration(_captureConsoleIntegration); function consoleHandler(args: unknown[], level: string): void { const captureContext: CaptureContext = { @@ -87,7 +71,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; } diff --git a/packages/integrations/src/debug.ts b/packages/core/src/integrations/debug.ts similarity index 67% rename from packages/integrations/src/debug.ts rename to packages/core/src/integrations/debug.ts index 3e76353688d3..83ac43de97b9 100644 --- a/packages/integrations/src/debug.ts +++ b/packages/core/src/integrations/debug.ts @@ -1,6 +1,6 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import type { Event, EventHint, IntegrationFn } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; +import { defineIntegration } from '../integration'; const INTEGRATION_NAME = 'Debug'; @@ -11,6 +11,10 @@ interface DebugOptions { debugger?: boolean; } +/** + * Integration to debug sent Sentry events. + * This integration should not be used in production. + */ const _debugIntegration = ((options: DebugOptions = {}) => { const _options = { debugger: false, @@ -20,13 +24,7 @@ const _debugIntegration = ((options: DebugOptions = {}) => { return { name: INTEGRATION_NAME, - // 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 @@ -54,19 +52,3 @@ const _debugIntegration = ((options: DebugOptions = {}) => { }) satisfies IntegrationFn; export const debugIntegration = defineIntegration(_debugIntegration); - -/** - * Integration to debug sent Sentry events. - * This integration should not be used in production. - * - * @deprecated Use `debugIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const Debug = convertIntegrationFnToClass(INTEGRATION_NAME, debugIntegration) as IntegrationClass< - Integration & { setup: (client: Client) => void } -> & { - new (options?: { - stringify?: boolean; - debugger?: boolean; - }): Integration; -}; diff --git a/packages/integrations/src/dedupe.ts b/packages/core/src/integrations/dedupe.ts similarity index 89% rename from packages/integrations/src/dedupe.ts rename to packages/core/src/integrations/dedupe.ts index f230bc4f0621..13d92fe3d56b 100644 --- a/packages/integrations/src/dedupe.ts +++ b/packages/core/src/integrations/dedupe.ts @@ -1,8 +1,8 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types'; import { logger } from '@sentry/utils'; +import { defineIntegration } from '../integration'; -import { DEBUG_BUILD } from './debug-build'; +import { DEBUG_BUILD } from '../debug-build'; const INTEGRATION_NAME = 'Dedupe'; @@ -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. @@ -33,16 +31,10 @@ const _dedupeIntegration = (() => { }; }) satisfies IntegrationFn; -export const dedupeIntegration = defineIntegration(_dedupeIntegration); - /** * Deduplication filter. - * @deprecated Use `dedupeIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; +export const dedupeIntegration = defineIntegration(_dedupeIntegration); /** only exported for tests. */ export function _shouldDropEvent(currentEvent: Event, previousEvent?: Event): boolean { diff --git a/packages/integrations/src/extraerrordata.ts b/packages/core/src/integrations/extraerrordata.ts similarity index 78% rename from packages/integrations/src/extraerrordata.ts rename to packages/core/src/integrations/extraerrordata.ts index d1d6ae5f0b5d..4218dd1ff8e4 100644 --- a/packages/integrations/src/extraerrordata.ts +++ b/packages/core/src/integrations/extraerrordata.ts @@ -1,16 +1,8 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { - Contexts, - Event, - EventHint, - ExtendedError, - Integration, - IntegrationClass, - IntegrationFn, -} from '@sentry/types'; +import type { Contexts, Event, EventHint, ExtendedError, IntegrationFn } from '@sentry/types'; import { addNonEnumerableProperty, isError, isPlainObject, logger, normalize } from '@sentry/utils'; +import { defineIntegration } from '../integration'; -import { DEBUG_BUILD } from './debug-build'; +import { DEBUG_BUILD } from '../debug-build'; const INTEGRATION_NAME = 'ExtraErrorData'; @@ -21,23 +13,20 @@ interface ExtraErrorDataOptions { depth: number; /** - * Whether to capture error causes. + * Whether to capture error causes. Defaults to true. * * More information: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause */ captureErrorCause: boolean; } +/** + * Extract additional data for from original exceptions. + */ const _extraErrorDataIntegration = ((options: Partial = {}) => { - const depth = options.depth || 3; - - // TODO(v8): Flip the default for this option to true - const captureErrorCause = options.captureErrorCause || false; - + 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); }, @@ -46,23 +35,6 @@ const _extraErrorDataIntegration = ((options: Partial = { export const extraErrorDataIntegration = defineIntegration(_extraErrorDataIntegration); -/** - * Extract additional data for from original exceptions. - * @deprecated Use `extraErrorDataIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const ExtraErrorData = convertIntegrationFnToClass( - INTEGRATION_NAME, - extraErrorDataIntegration, -) as IntegrationClass Event }> & { - new ( - options?: Partial<{ - depth: number; - captureErrorCause: boolean; - }>, - ): Integration; -}; - function _enhanceEventWithErrorData( event: Event, hint: EventHint = {}, 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/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 6ed891c253fa..7d42d57f81ed 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -8,16 +8,6 @@ import { convertIntegrationFnToClass, defineIntegration } from '../integration'; // this is the result of a script being pulled in from an external domain and CORS. const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/]; -const DEFAULT_IGNORE_TRANSACTIONS = [ - /^.*\/healthcheck$/, - /^.*\/healthy$/, - /^.*\/live$/, - /^.*\/ready$/, - /^.*\/heartbeat$/, - /^.*\/health$/, - /^.*\/healthz$/, -]; - /** Options for the InboundFilters integration */ export interface InboundFiltersOptions { allowUrls: Array; @@ -26,15 +16,12 @@ export interface InboundFiltersOptions { ignoreTransactions: Array; ignoreInternal: boolean; disableErrorDefaults: boolean; - disableTransactionDefaults: boolean; } 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); @@ -79,11 +66,7 @@ function _mergeOptions( ...(clientOptions.ignoreErrors || []), ...(internalOptions.disableErrorDefaults ? [] : DEFAULT_IGNORE_ERRORS), ], - ignoreTransactions: [ - ...(internalOptions.ignoreTransactions || []), - ...(clientOptions.ignoreTransactions || []), - ...(internalOptions.disableTransactionDefaults ? [] : DEFAULT_IGNORE_TRANSACTIONS), - ], + ignoreTransactions: [...(internalOptions.ignoreTransactions || []), ...(clientOptions.ignoreTransactions || [])], ignoreInternal: internalOptions.ignoreInternal !== undefined ? internalOptions.ignoreInternal : true, }; } @@ -175,7 +158,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 @@ -200,7 +182,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/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 d4e48620d726..99fc4834b060 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -9,13 +9,7 @@ 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) { - 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/integrations/requestdata.ts b/packages/core/src/integrations/requestdata.ts index d3aa6bb7b850..bd893f2099a6 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: @@ -79,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. @@ -95,15 +89,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') { @@ -142,7 +130,7 @@ const _requestDataIntegration = ((options: RequestDataIntegrationOptions = {}) = export const requestDataIntegration = defineIntegration(_requestDataIntegration); /** - * Add data about a request to an event. Primarily for use in Node-based SDKs, but included in `@sentry/integrations` + * Add data about a request to an event. Primarily for use in Node-based SDKs, but included in `@sentry/core` * so it can be used in cross-platform SDKs like `@sentry/nextjs`. * @deprecated Use `requestDataIntegration()` instead. */ @@ -185,7 +173,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/integrations/src/rewriteframes.ts b/packages/core/src/integrations/rewriteframes.ts similarity index 79% rename from packages/integrations/src/rewriteframes.ts rename to packages/core/src/integrations/rewriteframes.ts index 8d6441649e3d..ee842e86942f 100644 --- a/packages/integrations/src/rewriteframes.ts +++ b/packages/core/src/integrations/rewriteframes.ts @@ -1,6 +1,6 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; +import type { Event, IntegrationFn, StackFrame, Stacktrace } from '@sentry/types'; import { basename, relative } from '@sentry/utils'; +import { defineIntegration } from '../integration'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -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; @@ -85,16 +83,7 @@ const _rewriteFramesIntegration = ((options: RewriteFramesOptions = {}) => { }; }) satisfies IntegrationFn; -export const rewriteFramesIntegration = defineIntegration(_rewriteFramesIntegration); - /** * Rewrite event frames paths. - * @deprecated Use `rewriteFramesIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const RewriteFrames = convertIntegrationFnToClass( - INTEGRATION_NAME, - rewriteFramesIntegration, -) as IntegrationClass Event }> & { - new (options?: { root?: string; prefix?: string; iteratee?: StackFrameIteratee }): Integration; -}; +export const rewriteFramesIntegration = defineIntegration(_rewriteFramesIntegration); diff --git a/packages/core/src/integrations/sessiontiming.ts b/packages/core/src/integrations/sessiontiming.ts new file mode 100644 index 000000000000..6f01e5d48541 --- /dev/null +++ b/packages/core/src/integrations/sessiontiming.ts @@ -0,0 +1,31 @@ +import type { IntegrationFn } from '@sentry/types'; +import { defineIntegration } from '../integration'; + +const INTEGRATION_NAME = 'SessionTiming'; + +const _sessionTimingIntegration = (() => { + const startTime = Date.now(); + + return { + name: INTEGRATION_NAME, + processEvent(event) { + const now = Date.now(); + + return { + ...event, + extra: { + ...event.extra, + ['session:start']: startTime, + ['session:duration']: now - startTime, + ['session:end']: now, + }, + }; + }, + }; +}) satisfies IntegrationFn; + +/** + * This function adds duration since the sessionTimingIntegration was initialized + * till the time event was sent. + */ +export const sessionTimingIntegration = defineIntegration(_sessionTimingIntegration); diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 6a49fda5918b..5f0337e804f3 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -1,13 +1,15 @@ import type { - Client, ClientOptions, MeasurementUnit, MetricsAggregator as MetricsAggregatorBase, Primitive, } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; -import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX } from './constants'; +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'; import { getBucketKey, sanitizeTags } from './utils'; @@ -38,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); @@ -62,7 +64,11 @@ export class MetricsAggregator implements MetricsAggregatorBase { const tags = sanitizeTags(unsanitizedTags); const bucketKey = getBucketKey(metricType, name, unit, tags); + let bucketItem = this._buckets.get(bucketKey); + // If this is a set metric, we need to calculate the delta from the previous weight. + const previousWeight = bucketItem && metricType === SET_METRIC_TYPE ? bucketItem.metric.weight : 0; + if (bucketItem) { bucketItem.metric.add(value); // TODO(abhi): Do we need this check? @@ -82,6 +88,10 @@ export class MetricsAggregator implements MetricsAggregatorBase { this._buckets.set(bucketKey, bucketItem); } + // If value is a string, it's a set metric so calculate the delta from the previous weight. + const val = typeof value === 'string' ? bucketItem.metric.weight - previousWeight : value; + updateMetricSummaryOnActiveSpan(metricType, name, val, unit, unsanitizedTags, bucketKey); + // We need to keep track of the total weight of the buckets so that we can // flush them when we exceed the max weight. this._bucketsTotalWeight += bucketItem.metric.weight; @@ -153,11 +163,11 @@ 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); - 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 5b5c81353024..d19aa441aef3 100644 --- a/packages/core/src/metrics/browser-aggregator.ts +++ b/packages/core/src/metrics/browser-aggregator.ts @@ -1,14 +1,10 @@ -import type { - Client, - ClientOptions, - MeasurementUnit, - MetricBucketItem, - MetricsAggregator, - Primitive, -} from '@sentry/types'; +import type { ClientOptions, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; -import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX } from './constants'; +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'; import { getBucketKey, sanitizeTags } from './utils'; @@ -25,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); } @@ -46,7 +42,11 @@ export class BrowserMetricsAggregator implements MetricsAggregator { const tags = sanitizeTags(unsanitizedTags); const bucketKey = getBucketKey(metricType, name, unit, tags); - const bucketItem: MetricBucketItem | undefined = this._buckets.get(bucketKey); + + let bucketItem = this._buckets.get(bucketKey); + // If this is a set metric, we need to calculate the delta from the previous weight. + const previousWeight = bucketItem && metricType === SET_METRIC_TYPE ? bucketItem.metric.weight : 0; + if (bucketItem) { bucketItem.metric.add(value); // TODO(abhi): Do we need this check? @@ -54,7 +54,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator { bucketItem.timestamp = timestamp; } } else { - this._buckets.set(bucketKey, { + bucketItem = { // @ts-expect-error we don't need to narrow down the type of value here, saves bundle size. metric: new METRIC_MAP[metricType](value), timestamp, @@ -62,8 +62,13 @@ export class BrowserMetricsAggregator implements MetricsAggregator { name, unit, tags, - }); + }; + this._buckets.set(bucketKey, bucketItem); } + + // If value is a string, it's a set metric so calculate the delta from the previous weight. + const val = typeof value === 'string' ? bucketItem.metric.weight - previousWeight : value; + updateMetricSummaryOnActiveSpan(metricType, name, val, unit, unsanitizedTags, bucketKey); } /** @@ -74,11 +79,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); + captureAggregateMetrics(this._client, metricBuckets); + this._buckets.clear(); } diff --git a/packages/core/src/metrics/constants.ts b/packages/core/src/metrics/constants.ts index e89e0fd1562b..a5f3a87f57d5 100644 --- a/packages/core/src/metrics/constants.ts +++ b/packages/core/src/metrics/constants.ts @@ -21,7 +21,7 @@ export const NAME_AND_TAG_KEY_NORMALIZATION_REGEX = /[^a-zA-Z0-9_/.-]+/g; * * See: https://develop.sentry.dev/sdk/metrics/#normalization */ -export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d_:/@.{}[\]$-]+/g; +export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d\s_:/@.{}[\]$-]+/g; /** * This does not match spec in https://develop.sentry.dev/sdk/metrics 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/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 20d63a2ca119..4587c26a8510 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -1,33 +1,56 @@ -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'; +import { getClient } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import { getClient, getCurrentScope } 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'; 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 @@ -44,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); } } @@ -53,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); } /** @@ -62,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); } /** @@ -71,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); } /** @@ -80,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 = { @@ -89,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/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts new file mode 100644 index 000000000000..2be991297296 --- /dev/null +++ b/packages/core/src/metrics/metric-summary.ts @@ -0,0 +1,91 @@ +import type { MeasurementUnit, Span } from '@sentry/types'; +import type { MetricSummary } from '@sentry/types'; +import type { Primitive } from '@sentry/types'; +import { dropUndefinedKeys } from '@sentry/utils'; +import { getActiveSpan } from '../tracing/utils'; +import type { MetricType } from './types'; + +/** + * key: bucketKey + * value: [exportKey, MetricSummary] + */ +type MetricSummaryStorage = Map; + +let SPAN_METRIC_SUMMARY: WeakMap | undefined; + +function getMetricStorageForSpan(span: Span): MetricSummaryStorage | undefined { + return SPAN_METRIC_SUMMARY ? SPAN_METRIC_SUMMARY.get(span) : undefined; +} + +/** + * Fetches the metric summary if it exists for the passed span + */ +export function getMetricSummaryJsonForSpan(span: Span): Record> | undefined { + const storage = getMetricStorageForSpan(span); + + if (!storage) { + return undefined; + } + const output: Record> = {}; + + for (const [, [exportKey, summary]] of storage) { + if (!output[exportKey]) { + output[exportKey] = []; + } + + output[exportKey].push(dropUndefinedKeys(summary)); + } + + return output; +} + +/** + * Updates the metric summary on the currently active span + */ +export function updateMetricSummaryOnActiveSpan( + metricType: MetricType, + sanitizedName: string, + value: number, + unit: MeasurementUnit, + tags: Record, + bucketKey: string, +): void { + const span = getActiveSpan(); + if (span) { + const storage = getMetricStorageForSpan(span) || new Map(); + + const exportKey = `${metricType}:${sanitizedName}@${unit}`; + const bucketItem = storage.get(bucketKey); + + if (bucketItem) { + const [, summary] = bucketItem; + storage.set(bucketKey, [ + exportKey, + { + min: Math.min(summary.min, value), + max: Math.max(summary.max, value), + count: (summary.count += 1), + sum: (summary.sum += value), + tags: summary.tags, + }, + ]); + } else { + storage.set(bucketKey, [ + exportKey, + { + min: value, + max: value, + count: 1, + sum: value, + tags, + }, + ]); + } + + if (!SPAN_METRIC_SUMMARY) { + SPAN_METRIC_SUMMARY = new WeakMap(); + } + + SPAN_METRIC_SUMMARY.set(span, storage); + } +} diff --git a/packages/core/src/metrics/utils.ts b/packages/core/src/metrics/utils.ts index a6674bcf30e1..7b1cf96a8462 100644 --- a/packages/core/src/metrics/utils.ts +++ b/packages/core/src/metrics/utils.ts @@ -62,7 +62,7 @@ export function sanitizeTags(unsanitizedTags: Record): Record for (const key in unsanitizedTags) { if (Object.prototype.hasOwnProperty.call(unsanitizedTags, key)) { const sanitizedKey = key.replace(NAME_AND_TAG_KEY_NORMALIZATION_REGEX, '_'); - tags[sanitizedKey] = String(unsanitizedTags[key]).replace(TAG_VALUE_NORMALIZATION_REGEX, '_'); + tags[sanitizedKey] = String(unsanitizedTags[key]).replace(TAG_VALUE_NORMALIZATION_REGEX, ''); } } return tags; diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 01546a84be25..4646e5e5b015 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, @@ -26,9 +25,7 @@ import type { } from '@sentry/types'; import { dateTimestampInSeconds, isPlainObject, logger, uuid4 } from '@sentry/utils'; -import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors'; import { updateSession } from './session'; -import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent'; /** * Default value for maximum number of breadcrumbs added to an event. @@ -36,14 +33,7 @@ import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent'; const DEFAULT_MAX_BREADCRUMBS = 100; /** - * The global scope is kept in this module. - * When accessing this via `getGlobalScope()` we'll make sure to set one if none is currently present. - */ -let globalScope: ScopeInterface | undefined; - -/** - * Holds additional event information. {@link Scope.applyToEvent} will be - * called by the client before an event will be sent. + * Holds additional event information. */ export class Scope implements ScopeInterface { /** Flag if notifying is happening. */ @@ -52,7 +42,7 @@ export class Scope implements ScopeInterface { /** Callback for client to receive scope changes. */ protected _scopeListeners: Array<(scope: Scope) => void>; - /** Callback list that will be called after {@link applyToEvent}. */ + /** Callback list that will be called during event processing. */ protected _eventProcessors: EventProcessor[]; /** Array of breadcrumbs. */ @@ -86,8 +76,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 @@ -165,8 +154,8 @@ export class Scope implements ScopeInterface { * * It is generally recommended to use the global function `Sentry.getClient()` instead, unless you know what you are doing. */ - public getClient(): Client | undefined { - return this._client; + public getClient(): C | undefined { + return this._client as C | undefined; } /** @@ -195,7 +184,6 @@ export class Scope implements ScopeInterface { email: undefined, id: undefined, ip_address: undefined, - segment: undefined, username: undefined, }; @@ -283,10 +271,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; @@ -378,50 +363,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.length) { + 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; } } @@ -432,6 +415,7 @@ export class Scope implements ScopeInterface { * @inheritDoc */ public clear(): this { + // client is not cleared here on purpose! this._breadcrumbs = []; this._tags = {}; this._extra = {}; @@ -551,32 +535,6 @@ export class Scope implements ScopeInterface { }; } - /** - * Applies data from the scope to the event and runs all event processors on it. - * - * @param event Event - * @param hint Object containing additional information about the original exception, for use by the event processors. - * @hidden - * @deprecated Use `applyScopeDataToEvent()` directly - */ - public applyToEvent( - event: Event, - hint: EventHint = {}, - additionalEventProcessors: EventProcessor[] = [], - ): PromiseLike { - applyScopeDataToEvent(event, this.getScopeData()); - - // TODO (v8): Update this order to be: Global > Client > Scope - const eventProcessors: EventProcessor[] = [ - ...additionalEventProcessors, - // eslint-disable-next-line deprecation/deprecation - ...getGlobalEventProcessors(), - ...this._eventProcessors, - ]; - - return notifyEventProcessors(eventProcessors, event, hint); - } - /** * Add data which will be accessible during event processing but won't get sent to Sentry */ @@ -702,27 +660,6 @@ export class Scope implements ScopeInterface { } } -/** - * Get the global scope. - * This scope is applied to _all_ events. - */ -export function getGlobalScope(): ScopeInterface { - if (!globalScope) { - globalScope = new Scope(); - } - - return globalScope; -} - -/** - * This is mainly needed for tests. - * DO NOT USE this, as this is an internal API and subject to change. - * @hidden - */ -export function setGlobalScope(scope: ScopeInterface | undefined): void { - globalScope = scope; -} - function generatePropagationContext(): PropagationContext { return { traceId: uuid4(), diff --git a/packages/core/src/sdk.ts b/packages/core/src/sdk.ts index f15f6c976e56..3356774e14da 100644 --- a/packages/core/src/sdk.ts +++ b/packages/core/src/sdk.ts @@ -1,8 +1,9 @@ -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'; import { DEBUG_BUILD } from './debug-build'; -import { getCurrentScope } from './exports'; +import type { Hub } from './hub'; import { getCurrentHub } from './hub'; /** A class object that can instantiate Client objects. */ @@ -35,32 +36,26 @@ export function initAndBind( const client = new clientClass(options); setCurrentClient(client); - initializeClient(client); + client.init(); } /** * 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(); - // eslint-disable-next-line deprecation/deprecation - const top = hub.getStackTop(); - 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) { + if (isHubClass(hub)) { // eslint-disable-next-line deprecation/deprecation - client.setupIntegrations(); + const top = hub.getStackTop(); + top.client = client; } } + +function isHubClass(hub: HubInterface): hub is Hub { + // eslint-disable-next-line deprecation/deprecation + return !!(hub as Hub).getStackTop; +} diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index aed69369fc5d..682e58e80355 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'; @@ -16,9 +15,8 @@ import { eventFromMessage, eventFromUnknownInput, logger, resolvedSyncPromise, u import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; +import { 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'; import { @@ -52,17 +50,13 @@ export class ServerRuntimeClient< addTracingExtensions(); super(options); - - if (options._experiments && options._experiments['metricsAggregator']) { - this.metricsAggregator = new MetricsAggregator(this); - } } /** * @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)); } /** @@ -70,8 +64,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( @@ -82,13 +75,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 @@ -107,14 +100,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 @@ -206,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; } @@ -277,6 +270,6 @@ export class ServerRuntimeClient< return [dsc, traceContext]; } - return [getDynamicSamplingContextFromClient(traceId, this, scope), traceContext]; + return [getDynamicSamplingContextFromClient(traceId, this), traceContext]; } } diff --git a/packages/core/src/sessionflusher.ts b/packages/core/src/sessionflusher.ts index dac81b82336d..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 './exports'; +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 a51c2362c437..e2dd9af12b0b 100644 --- a/packages/core/src/tracing/dynamicSamplingContext.ts +++ b/packages/core/src/tracing/dynamicSamplingContext.ts @@ -1,8 +1,9 @@ -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 '../currentScopes'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { getRootSpan } from '../utils/getRootSpan'; import { spanIsSampled, spanToJSON } from '../utils/spanUtils'; @@ -11,27 +12,19 @@ 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; - client.emit && client.emit('createDsc', dsc); + client.emit('createDsc', dsc); return dsc; } @@ -54,8 +47,7 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly = (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.`, - ); - - // eslint-disable-next-line deprecation/deprecation - transactionContext.sampled = false; - } - // eslint-disable-next-line deprecation/deprecation let transaction = new Transaction(transactionContext, this); transaction = sampleTransaction(transaction, options, { @@ -76,9 +44,9 @@ 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) { + if (client) { client.emit('startTransaction', transaction); } return transaction; @@ -123,9 +91,9 @@ export function startIdleTransaction( ...customSamplingContext, }); if (transaction.isRecording()) { - transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans as number)); + transaction.initSpanRecorder(); } - if (client && client.emit) { + if (client) { client.emit('startTransaction', transaction); } return transaction; @@ -143,9 +111,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/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index fed962898d71..8d0db5d92762 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -1,12 +1,10 @@ -/* eslint-disable max-lines */ -import type { SpanTimeInput, TransactionContext } from '@sentry/types'; +import type { Hub, SpanTimeInput, TransactionContext } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import type { Hub } from '../hub'; 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 = { @@ -42,7 +40,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) { @@ -163,23 +161,23 @@ 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); } // 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 d1e1c7f65b44..f8284fe16e29 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -1,27 +1,20 @@ 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 { extractTraceparentData, getActiveTransaction } from './utils'; +export { getActiveTransaction, getActiveSpan } from './utils'; // eslint-disable-next-line deprecation/deprecation export { SpanStatus } from './spanstatus'; export { setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, } from './spanstatus'; export type { SpanStatusType } from './spanstatus'; export { - // eslint-disable-next-line deprecation/deprecation - trace, - getActiveSpan, startSpan, startInactiveSpan, - // eslint-disable-next-line deprecation/deprecation - startActiveSpan, startSpanManual, continueTrace, } from './trace'; diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 427a7076d6d0..1670d38b740d 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; } @@ -105,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/span.ts b/packages/core/src/tracing/sentrySpan.ts similarity index 72% rename from packages/core/src/tracing/span.ts rename to packages/core/src/tracing/sentrySpan.ts index 165677455d7f..b484e9c964ca 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -1,6 +1,4 @@ -/* eslint-disable max-lines */ import type { - Instrumenter, Primitive, Span as SpanInterface, SpanAttributeValue, @@ -16,6 +14,7 @@ import type { import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; +import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; import { getRootSpan } from '../utils/getRootSpan'; import { @@ -24,10 +23,9 @@ import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext, - spanToTraceHeader, } from '../utils/spanUtils'; import type { SpanStatusType } from './spanstatus'; -import { setHttpStatus } from './spanstatus'; +import { addChildSpanToSpan } from './utils'; /** * Keeps track of finished spans for a given transaction @@ -36,7 +34,7 @@ import { setHttpStatus } from './spanstatus'; * @hidden */ export class SpanRecorder { - public spans: Span[]; + public spans: SentrySpan[]; private readonly _maxlen: number; @@ -51,7 +49,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; @@ -64,16 +62,16 @@ 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 `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 }; @@ -90,30 +88,18 @@ export class Span 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; + 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; @@ -132,8 +118,6 @@ export class Span 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({ @@ -142,8 +126,7 @@ export class Span 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; @@ -163,38 +146,6 @@ export class Span 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. @@ -263,7 +214,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; @@ -327,43 +278,6 @@ export class Span 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); - } - - /** - * 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 */ @@ -384,8 +298,8 @@ export class Span implements SpanInterface { */ public startChild( spanContext?: Pick>, - ): Span { - const childSpan = new Span({ + ): SpanInterface { + const childSpan = new SentrySpan({ ...spanContext, parentSpanId: this._spanId, sampled: this._sampled, @@ -400,6 +314,11 @@ export class Span 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 @@ -470,24 +389,6 @@ export class Span implements SpanInterface { return this; } - /** - * @inheritDoc - * @deprecated Use top-level `setHttpStatus()` instead. - */ - public setHttpStatus(httpStatus: number): this { - setHttpStatus(this, httpStatus); - return this; - } - - /** - * @inheritdoc - * - * @deprecated Use `.updateName()` instead. - */ - public setName(name: string): void { - this.updateName(name); - } - /** * @inheritDoc */ @@ -496,24 +397,6 @@ export class Span implements SpanInterface { return this; } - /** - * @inheritDoc - * - * @deprecated Use `spanToJSON(span).status === 'ok'` instead. - */ - public isSuccess(): boolean { - return this._status === 'ok'; - } - - /** - * @inheritDoc - * - * @deprecated Use `.end()` instead. - */ - public finish(endTimestamp?: number): void { - return this.end(endTimestamp); - } - /** @inheritdoc */ public end(endTimestamp?: SpanTimeInput): void { // If already ended, skip @@ -536,15 +419,6 @@ export class Span implements SpanInterface { this._endTime = spanTimeInputToSeconds(endTimestamp); } - /** - * @inheritDoc - * - * @deprecated Use `spanToTraceHeader()` instead. - */ - public toTraceparent(): string { - return spanToTraceHeader(this); - } - /** * @inheritDoc * @@ -553,10 +427,9 @@ export class Span 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, + op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP], parentSpanId: this._parentSpanId, sampled: this._sampled, spanId: this._spanId, @@ -568,31 +441,6 @@ export class Span implements SpanInterface { }); } - /** - * @inheritDoc - * - * @deprecated Update the fields directly instead. - */ - 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._endTime = spanContext.endTimestamp; - // eslint-disable-next-line deprecation/deprecation - this.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 * @@ -614,7 +462,7 @@ export class Span 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, @@ -624,6 +472,7 @@ export class Span implements SpanInterface { timestamp: this._endTime, trace_id: this._traceId, origin: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined, + _metrics_summary: getMetricSummaryJsonForSpan(this), }); } diff --git a/packages/core/src/tracing/spanstatus.ts b/packages/core/src/tracing/spanstatus.ts index aa0d1639a70c..3855fda0307e 100644 --- a/packages/core/src/tracing/spanstatus.ts +++ b/packages/core/src/tracing/spanstatus.ts @@ -123,33 +123,12 @@ 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. */ export function setHttpStatus(span: Span, httpStatus: number): void { - // TODO (v8): Remove these calls - // Relay does not require us to send the status code as a tag - // For now, just because users might expect it to land as a tag we keep sending it. - // Same with data. - // In v8, we replace both, simply with - // span.setAttribute('http.response.status_code', httpStatus); - - // eslint-disable-next-line deprecation/deprecation - span.setTag('http.status_code', String(httpStatus)); - // eslint-disable-next-line deprecation/deprecation - span.setData('http.response.status_code', httpStatus); + span.setAttribute('http.response.status_code', httpStatus); const spanStatus = getSpanStatusFromHttpCode(httpStatus); if (spanStatus !== 'unknown_error') { diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 832180ef3c72..a2030986616d 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,65 +1,16 @@ -import type { Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types'; +import type { Hub, Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types'; -import { addNonEnumerableProperty, dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; +import { dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; + +import { getCurrentScope, getIsolationScope, withScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; -import { getCurrentScope, withScope } from '../exports'; -import type { Hub } from '../hub'; -import { runWithAsyncContext } from '../hub'; -import { getIsolationScope } from '../hub'; import { getCurrentHub } from '../hub'; import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; -import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; - -/** - * Wraps a function with a transaction/span and finishes the span after the function is done. - * - * Note that if you have not enabled tracing extensions via `addTracingExtensions` - * or you didn't set `tracesSampleRate`, this function will not generate spans - * and the `span` returned from the callback will be undefined. - * - * This function is meant to be used internally and may break at any time. Use at your own risk. - * - * @internal - * @private - * - * @deprecated Use `startSpan` instead. - */ -export function trace( - context: TransactionContext, - callback: (span?: Span) => T, - // eslint-disable-next-line @typescript-eslint/no-empty-function - onError: (error: unknown, span?: Span) => void = () => {}, - // eslint-disable-next-line @typescript-eslint/no-empty-function - afterFinish: () => void = () => {}, -): T { - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - const scope = getCurrentScope(); - // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); - - const ctx = normalizeContext(context); - const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx); - - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(activeSpan); - - return handleCallbackErrors( - () => callback(activeSpan), - error => { - activeSpan && activeSpan.setStatus('internal_error'); - onError(error, activeSpan); - }, - () => { - activeSpan && activeSpan.end(); - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(parentSpan); - afterFinish(); - }, - ); -} +import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; +import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; +import { addChildSpanToSpan, getActiveSpan, setCapturedScopesOnSpan } from './utils'; /** * Wraps a function with a transaction/span and finishes the span after the function is done. @@ -73,43 +24,40 @@ export function trace( * 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 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(); - - 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'); - } + 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, + spanContext, + forceTransaction: context.forceTransaction, + scope, + }); + + 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(), + ); }); } -/** - * @deprecated Use {@link startSpan} instead. - */ -export const startActiveSpan = startSpan; - /** * Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span * after the function is done automatically. You'll have to call `span.end()` manually. @@ -125,38 +73,40 @@ export function startSpanManual( context: StartSpanOptions, callback: (span: Span | undefined, finish: () => void) => 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(); - - const shouldSkipSpan = context.onlyIfParent && !parentSpan; - const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx); + const spanContext = normalizeContext(context); - // 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'); - } + 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, + spanContext, + forceTransaction: context.forceTransaction, + scope, + }); + + 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'); } - }, - ); - }); + } + }, + ); }); } @@ -175,7 +125,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 @@ -189,45 +139,19 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { return undefined; } - const isolationScope = getIsolationScope(); - const scope = getCurrentScope(); + const scope = context.scope || getCurrentScope(); - let span: Span | undefined; + // 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(); - 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, - }, - }); - } - - setCapturedScopesOnSpan(span, scope, isolationScope); - - return span; -} - -/** - * Returns the currently active span. - */ -export function getActiveSpan(): Span | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentScope().getSpan(); + return createChildSpanOrTransaction(hub, { + parentSpan, + spanContext, + forceTransaction: context.forceTransaction, + scope: temporaryScope, + }); } interface ContinueTrace { @@ -327,27 +251,54 @@ export const continueTrace: ContinueTrace = ( return transactionContext; } - return runWithAsyncContext(() => { + return withScope(() => { return callback(transactionContext); }); }; 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(), @@ -359,15 +310,21 @@ function createChildSpanOrTransaction( traceId, parentSpanId, parentSampled: sampled, - ...ctx, + ...spanContext, metadata: { dynamicSamplingContext: dsc, // eslint-disable-next-line deprecation/deprecation - ...ctx.metadata, + ...spanContext.metadata, }, }); } + // 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); return span; @@ -390,28 +347,3 @@ function normalizeContext(context: StartSpanOptions): TransactionContext { return context; } - -const SCOPE_ON_START_SPAN_FIELD = '_sentryScope'; -const ISOLATION_SCOPE_ON_START_SPAN_FIELD = '_sentryIsolationScope'; - -type SpanWithScopes = Span & { - [SCOPE_ON_START_SPAN_FIELD]?: Scope; - [ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope; -}; - -function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { - if (span) { - addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope); - addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope); - } -} - -/** - * Grabs the scope and isolation scope off a span that were active when the span was started. - */ -export function getCapturedScopesOnSpan(span: Span): { scope?: Scope; isolationScope?: Scope } { - return { - scope: (span as SpanWithScopes)[SCOPE_ON_START_SPAN_FIELD], - isolationScope: (span as SpanWithScopes)[ISOLATION_SCOPE_ON_START_SPAN_FIELD], - }; -} diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 026723929471..3c0c114f1061 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -2,6 +2,7 @@ import type { Context, Contexts, DynamicSamplingContext, + Hub, MeasurementUnit, Measurements, SpanTimeInput, @@ -9,20 +10,21 @@ import type { TransactionContext, TransactionEvent, TransactionMetadata, + TransactionSource, } from '@sentry/types'; import { dropUndefinedKeys, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; +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 { getCapturedScopesOnSpan } from './trace'; +import { SentrySpan, SpanRecorder } from './sentrySpan'; +import { getCapturedScopesOnSpan, getSpanTree } from './utils'; /** JSDoc */ -export class Transaction extends SpanClass implements TransactionInterface { +export class Transaction extends SentrySpan implements TransactionInterface { /** * The reference to the current hub. */ @@ -34,7 +36,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; @@ -67,6 +69,11 @@ export class Transaction extends SpanClass implements TransactionInterface { this._trimEnd = transactionContext.trimEnd; + this._attributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', + ...this._attributes, + }; + // this is because transactions are also spans, and spans have a transaction pointer // TODO (v8): Replace this with another way to set the root span // eslint-disable-next-line deprecation/deprecation @@ -82,24 +89,6 @@ export class Transaction extends SpanClass 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. @@ -109,17 +98,12 @@ export class Transaction extends SpanClass implements TransactionInterface { // We merge attributes in for backwards compatibility return { // Defaults - // eslint-disable-next-line deprecation/deprecation - source: 'custom', spanMetadata: {}, // Legacy metadata ...this._metadata, // From attributes - ...(this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] && { - source: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionMetadata['source'], - }), ...(this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] && { sampleRate: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] as TransactionMetadata['sampleRate'], }), @@ -136,19 +120,10 @@ export class Transaction extends SpanClass 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: TransactionMetadata['source'] = '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; } @@ -223,19 +198,6 @@ export class Transaction extends SpanClass 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 * @@ -276,7 +238,7 @@ export class Transaction extends SpanClass implements TransactionInterface { // eslint-disable-next-line deprecation/deprecation const client = this._hub.getClient(); - if (client && client.emit) { + if (client) { client.emit('finishTransaction', this); } @@ -291,11 +253,8 @@ export class Transaction extends SpanClass 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[]; @@ -308,8 +267,8 @@ export class Transaction extends SpanClass implements TransactionInterface { // eslint-disable-next-line deprecation/deprecation const { metadata } = this; - // eslint-disable-next-line deprecation/deprecation - const { source } = metadata; + + const source = this._attributes['sentry.source'] as TransactionSource | undefined; const transaction: TransactionEvent = { contexts: { @@ -329,8 +288,11 @@ export class Transaction extends SpanClass implements TransactionInterface { ...metadata, capturedSpanScope, capturedSpanIsolationScope, - dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), + ...dropUndefinedKeys({ + dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), + }), }, + _metrics_summary: getMetricSummaryJsonForSpan(this), ...(source && { transaction_info: { source, @@ -349,9 +311,7 @@ export class Transaction extends SpanClass 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/src/tracing/utils.ts b/packages/core/src/tracing/utils.ts index cab0eead61f2..e01f92a028ba 100644 --- a/packages/core/src/tracing/utils.ts +++ b/packages/core/src/tracing/utils.ts @@ -1,5 +1,7 @@ -import type { Transaction } from '@sentry/types'; -import { extractTraceparentData as _extractTraceparentData } from '@sentry/utils'; +import type { Span, Transaction } from '@sentry/types'; +import type { Scope } from '@sentry/types'; +import { addNonEnumerableProperty } from '@sentry/utils'; +import { getCurrentScope } from '../currentScopes'; import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; @@ -22,15 +24,76 @@ export function getActiveTransaction(maybeHub?: Hub): T | export { stripUrlQueryAndFragment } from '@sentry/utils'; /** - * The `extractTraceparentData` function and `TRACEPARENT_REGEXP` constant used - * to be declared in this file. It was later moved into `@sentry/utils` as part of a - * move to remove `@sentry/tracing` dependencies from `@sentry/node` (`extractTraceparentData` - * is the only tracing function used by `@sentry/node`). - * - * These exports are kept here for backwards compatability's sake. - * - * See https://github.com/getsentry/sentry-javascript/issues/4642 for more details. - * - * @deprecated Import this function from `@sentry/utils` instead + * Returns the currently active span. + */ +export function getActiveSpan(): Span | undefined { + // eslint-disable-next-line deprecation/deprecation + return getCurrentScope().getSpan(); +} + +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 const extractTraceparentData = _extractTraceparentData; +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'; + +type SpanWithScopes = Span & { + [SCOPE_ON_START_SPAN_FIELD]?: Scope; + [ISOLATION_SCOPE_ON_START_SPAN_FIELD]?: Scope; +}; + +/** Store the scope & isolation scope for a span, which can the be used when it is finished. */ +export function setCapturedScopesOnSpan(span: Span | undefined, scope: Scope, isolationScope: Scope): void { + if (span) { + addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope); + addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope); + } +} + +/** + * Grabs the scope and isolation scope off a span that were active when the span was started. + */ +export function getCapturedScopesOnSpan(span: Span): { scope?: Scope; isolationScope?: Scope } { + return { + scope: (span as SpanWithScopes)[SCOPE_ON_START_SPAN_FIELD], + isolationScope: (span as SpanWithScopes)[ISOLATION_SCOPE_ON_START_SPAN_FIELD], + }; +} 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/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/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 17be8c5354de..8b0cc90df2fb 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -12,8 +12,9 @@ import type { import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; +import { getGlobalScope } from '../currentScopes'; import { getGlobalEventProcessors, notifyEventProcessors } from '../eventProcessors'; -import { Scope, getGlobalScope } from '../scope'; +import { Scope } from '../scope'; import { applyScopeDataToEvent, mergeScopeData } from './applyScopeDataToEvent'; import { spanToJSON } from './spanUtils'; @@ -47,9 +48,9 @@ export function prepareEvent( options: ClientOptions, event: Event, hint: EventHint, - scope?: Scope, + scope?: ScopeInterface, client?: Client, - isolationScope?: Scope, + isolationScope?: ScopeInterface, ): PromiseLike { const { normalizeDepth = 3, normalizeMaxBreadth = 1_000 } = options; const prepared: Event = { @@ -75,7 +76,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. @@ -342,7 +343,10 @@ function normalizeEvent(event: Event | null, depth: number, maxBreadth: number): return normalized; } -function getFinalScope(scope: Scope | undefined, captureContext: CaptureContext | undefined): Scope | undefined { +function getFinalScope( + scope: ScopeInterface | undefined, + captureContext: CaptureContext | undefined, +): ScopeInterface | undefined { if (!captureContext) { return scope; } 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/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/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/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/base.test.ts b/packages/core/test/lib/base.test.ts index 197ffa189779..b48d3b0e5eec 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,9 @@ describe('BaseClient', () => { TestClient.sendEventCalled = undefined; TestClient.instance = undefined; setGlobalScope(undefined); + getCurrentScope().clear(); + getCurrentScope().setClient(undefined); + getIsolationScope().clear(); }); afterEach(() => { @@ -114,125 +125,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(); - 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(); - 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 }); + + const breadcrumbs = scope.getScopeData().breadcrumbs; + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; - expect((scope as any)._breadcrumbs[1].timestamp).toBeGreaterThan(1); + 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(); - 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'); - }); + setCurrentClient(client); + client.init(); - test('allows concurrent updates', () => { - expect.assertions(1); + addBreadcrumb({ message: 'hello1' }); + addBreadcrumb({ message: 'hello2' }); + addBreadcrumb({ message: 'hello3' }); - const options = getDefaultTestClientOptions({}); - const client = new TestClient(options); - const scope = new Scope(); - const hub = new Hub(client, scope); + const isolationScopeBreadcrumbs = getIsolationScope().getScopeData().breadcrumbs; - scope.addBreadcrumb({ message: 'hello' }); - // eslint-disable-next-line deprecation/deprecation - hub.addBreadcrumb({ message: 'world' }); - - 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(); - 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(); - 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(); - 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(); - 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) }, + ]); }); }); @@ -619,12 +613,12 @@ describe('BaseClient', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, maxBreadcrumbs: 1 }); const client = new TestClient(options); + setCurrentClient(client); + client.init(); const scope = new Scope(); - 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); @@ -1948,11 +1942,11 @@ describe('BaseClient', () => { 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', () => { @@ -1965,11 +1959,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/core/test/lib/envelope.test.ts b/packages/core/test/lib/envelope.test.ts index 4c0c45585d60..0d6684233cbb 100644 --- a/packages/core/test/lib/envelope.test.ts +++ b/packages/core/test/lib/envelope.test.ts @@ -41,7 +41,6 @@ describe('createEventEnvelope', () => { 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/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/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index e819f9413aec..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,9 +618,7 @@ describe('addIntegration', () => { } const client = getTestClient(); - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); const integration = new CustomIntegration(); addIntegration(integration); @@ -635,9 +634,7 @@ describe('addIntegration', () => { setupOnce = jest.fn(); } - const hub = new Hub(); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + getCurrentScope().setClient(undefined); const integration = new CustomIntegration(); addIntegration(integration); @@ -660,9 +657,8 @@ describe('addIntegration', () => { } const client = getTestClient(); - const hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); const integration = new CustomIntegration(); addIntegration(integration); @@ -683,9 +679,8 @@ describe('addIntegration', () => { } const client = getTestClient(); - 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/integrations/test/captureconsole.test.ts b/packages/core/test/lib/integrations/captureconsole.test.ts similarity index 76% rename from packages/integrations/test/captureconsole.test.ts rename to packages/core/test/lib/integrations/captureconsole.test.ts index 8452f2fe6e27..6ed79d586c9c 100644 --- a/packages/integrations/test/captureconsole.test.ts +++ b/packages/core/test/lib/integrations/captureconsole.test.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/unbound-method */ -import * as SentryCore from '@sentry/core'; import type { Client, ConsoleLevel, Event } from '@sentry/types'; import { CONSOLE_LEVELS, @@ -9,9 +8,10 @@ import { originalConsoleMethods, resetInstrumentationHandlers, } from '@sentry/utils'; +import * as CurrentScopes from '../../../src/currentScopes'; +import * as SentryCore from '../../../src/exports'; -import type { captureConsoleIntegration } from '../src/captureconsole'; -import { CaptureConsole } from '../src/captureconsole'; +import { captureConsoleIntegration } from '../../../src/integrations/captureconsole'; const mockConsole: { [key in ConsoleLevel]: jest.Mock } = { debug: jest.fn(), @@ -23,11 +23,6 @@ const mockConsole: { [key in ConsoleLevel]: jest.Mock } = { trace: jest.fn(), }; -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new CaptureConsole(...args); -} - describe('CaptureConsole setup', () => { // Ensure we've initialized the instrumentation so we can get the original one addConsoleInstrumentationHandler(() => {}); @@ -51,8 +46,8 @@ describe('CaptureConsole setup', () => { jest.spyOn(SentryCore, 'captureMessage').mockImplementation(captureMessage); jest.spyOn(SentryCore, 'captureException').mockImplementation(captureException); - jest.spyOn(SentryCore, 'getClient').mockImplementation(() => mockClient); - jest.spyOn(SentryCore, 'withScope').mockImplementation(withScope); + jest.spyOn(CurrentScopes, 'getClient').mockImplementation(() => mockClient); + jest.spyOn(CurrentScopes, 'withScope').mockImplementation(withScope); CONSOLE_LEVELS.forEach(key => { originalConsoleMethods[key] = mockConsole[key]; @@ -71,8 +66,8 @@ describe('CaptureConsole setup', () => { describe('monkeypatching', () => { it('should patch user-configured console levels', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'warn'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'warn'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('msg 1'); GLOBAL_OBJ.console.log('msg 2'); @@ -82,8 +77,8 @@ describe('CaptureConsole setup', () => { }); it('should fall back to default console levels if none are provided', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); // Assert has a special handling (['debug', 'info', 'warn', 'error', 'log', 'trace'] as const).forEach(key => { @@ -96,8 +91,8 @@ describe('CaptureConsole setup', () => { }); it('should not wrap any functions with an empty levels option', () => { - const captureConsoleIntegration = getIntegration({ levels: [] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: [] }); + captureConsole.setup?.(mockClient); CONSOLE_LEVELS.forEach(key => { GLOBAL_OBJ.console[key]('msg'); @@ -112,9 +107,9 @@ describe('CaptureConsole setup', () => { // @ts-expect-error remove console delete GLOBAL_OBJ.console; - const captureConsoleIntegration = getIntegration(); + const captureConsole = captureConsoleIntegration(); expect(() => { - captureConsoleIntegration.setup(mockClient); + captureConsole.setup?.(mockClient); }).not.toThrow(); // reinstate initial console @@ -122,8 +117,8 @@ describe('CaptureConsole setup', () => { }); it('should send empty arguments as extra data', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.log(); @@ -132,8 +127,8 @@ describe('CaptureConsole setup', () => { }); it('should add an event processor that sets the `logger` field of events', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); // call a wrapped function GLOBAL_OBJ.console.log('some message'); @@ -148,8 +143,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message on a failed assertion', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3); @@ -162,8 +157,8 @@ describe('CaptureConsole setup', () => { }); it('should capture correct message on a failed assertion with message', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 3, 'expression is false'); @@ -176,15 +171,15 @@ describe('CaptureConsole setup', () => { }); it('should not capture message on a successful assertion', () => { - const captureConsoleIntegration = getIntegration({ levels: ['assert'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['assert'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.assert(1 + 1 === 2); }); it('should capture exception when console logs an error object with level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -197,8 +192,8 @@ describe('CaptureConsole setup', () => { }); it('should capture exception on `console.error` when no levels are provided in constructor', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); @@ -211,8 +206,8 @@ describe('CaptureConsole setup', () => { }); it('should capture exception when console logs an error object in any of the args when level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error('Something went wrong', someError); @@ -225,8 +220,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message on `console.log` when no levels are provided in constructor', () => { - const captureConsoleIntegration = getIntegration(); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration(); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('some message'); @@ -238,8 +233,8 @@ describe('CaptureConsole setup', () => { }); it('should capture message when console logs a non-error object with level set to "error"', () => { - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.error('some non-error message'); @@ -252,8 +247,8 @@ describe('CaptureConsole setup', () => { }); it('should capture a message for non-error log levels', () => { - const captureConsoleIntegration = getIntegration({ levels: ['info'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['info'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.info('some message'); @@ -270,8 +265,8 @@ describe('CaptureConsole setup', () => { const mockConsoleLog = jest.fn(); GLOBAL_OBJ.console.log = mockConsoleLog; - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); GLOBAL_OBJ.console.log('some message 1', 'some message 2'); @@ -283,17 +278,17 @@ describe('CaptureConsole setup', () => { }); it('should not wrap any levels that are not members of console', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'someNonExistingLevel', 'error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'someNonExistingLevel', 'error'] }); + captureConsole.setup?.(mockClient); // The provided level should not be created expect((GLOBAL_OBJ.console as any)['someNonExistingLevel']).toBeUndefined(); }); it('should wrap the console when the client does not have a registered captureconsole integration, but not capture any messages', () => { - const captureConsoleIntegration = getIntegration({ levels: ['log', 'error'] }); + const captureConsole = captureConsoleIntegration({ levels: ['log', 'error'] }); // when `setup` is not called on the current client, it will not trigger - captureConsoleIntegration.setup({} as Client); + captureConsole.setup?.({} as Client); // Should not capture messages GLOBAL_OBJ.console.log('some message'); @@ -303,8 +298,8 @@ describe('CaptureConsole setup', () => { it("should not crash when the original console methods don't exist at time of invocation", () => { originalConsoleMethods.log = undefined; - const captureConsoleIntegration = getIntegration({ levels: ['log'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['log'] }); + captureConsole.setup?.(mockClient); expect(() => { GLOBAL_OBJ.console.log('some message'); @@ -314,8 +309,8 @@ describe('CaptureConsole setup', () => { it("marks captured exception's mechanism as unhandled", () => { // const addExceptionMechanismSpy = jest.spyOn(utils, 'addExceptionMechanism'); - const captureConsoleIntegration = getIntegration({ levels: ['error'] }); - captureConsoleIntegration.setup(mockClient); + const captureConsole = captureConsoleIntegration({ levels: ['error'] }); + captureConsole.setup?.(mockClient); const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); diff --git a/packages/integrations/test/debug.test.ts b/packages/core/test/lib/integrations/debug.test.ts similarity index 66% rename from packages/integrations/test/debug.test.ts rename to packages/core/test/lib/integrations/debug.test.ts index b0d922b6de9f..49ed0d2d341a 100644 --- a/packages/integrations/test/debug.test.ts +++ b/packages/core/test/lib/integrations/debug.test.ts @@ -1,18 +1,12 @@ -import type { Client, Event, EventHint, Integration } from '@sentry/types'; +import type { Client, Event, EventHint } from '@sentry/types'; -import type { debugIntegration } from '../src/debug'; -import { Debug } from '../src/debug'; +import { debugIntegration } from '../../../src/integrations/debug'; -interface IntegrationWithSetup extends Integration { - setup: (client: Client) => void; -} - -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new Debug(...args); -} - -function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, testEventHint?: EventHint) { +function testEventLogged( + integration: ReturnType, + testEvent?: Event, + testEventHint?: EventHint, +) { const callbacks: ((event: Event, hint?: EventHint) => void)[] = []; const client: Client = { @@ -22,7 +16,7 @@ function testEventLogged(integration: IntegrationWithSetup, testEvent?: Event, t }, } as Client; - integration.setup(client); + integration.setup?.(client); expect(callbacks.length).toEqual(1); @@ -48,22 +42,22 @@ describe('Debug integration setup should register an event processor that', () = }); it('logs an event', () => { - const debugIntegration = getIntegration(); + const debug = debugIntegration(); const testEvent = { event_id: 'some event' }; - testEventLogged(debugIntegration, testEvent); + testEventLogged(debug, testEvent); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toBeCalledWith(testEvent); }); it('logs an event hint if available', () => { - const debugIntegration = getIntegration(); + const debug = debugIntegration(); const testEvent = { event_id: 'some event' }; const testEventHint = { event_id: 'some event hint' }; - testEventLogged(debugIntegration, testEvent, testEventHint); + testEventLogged(debug, testEvent, testEventHint); expect(mockConsoleLog).toHaveBeenCalledTimes(2); expect(mockConsoleLog).toBeCalledWith(testEvent); @@ -71,22 +65,22 @@ describe('Debug integration setup should register an event processor that', () = }); it('logs events in stringified format when `stringify` option was set', () => { - const debugIntegration = getIntegration({ stringify: true }); + const debug = debugIntegration({ stringify: true }); const testEvent = { event_id: 'some event' }; - testEventLogged(debugIntegration, testEvent); + testEventLogged(debug, testEvent); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toBeCalledWith(JSON.stringify(testEvent, null, 2)); }); it('logs event hints in stringified format when `stringify` option was set', () => { - const debugIntegration = getIntegration({ stringify: true }); + const debug = debugIntegration({ stringify: true }); const testEvent = { event_id: 'some event' }; const testEventHint = { event_id: 'some event hint' }; - testEventLogged(debugIntegration, testEvent, testEventHint); + testEventLogged(debug, testEvent, testEventHint); expect(mockConsoleLog).toHaveBeenCalledTimes(2); expect(mockConsoleLog).toBeCalledWith(JSON.stringify(testEventHint, null, 2)); diff --git a/packages/integrations/test/dedupe.test.ts b/packages/core/test/lib/integrations/dedupe.test.ts similarity index 85% rename from packages/integrations/test/dedupe.test.ts rename to packages/core/test/lib/integrations/dedupe.test.ts index 65bf7a5bbfb9..540b07c53b79 100644 --- a/packages/integrations/test/dedupe.test.ts +++ b/packages/core/test/lib/integrations/dedupe.test.ts @@ -1,7 +1,6 @@ import type { Event as SentryEvent, Exception, StackFrame, Stacktrace } from '@sentry/types'; -import type { dedupeIntegration } from '../src/dedupe'; -import { Dedupe, _shouldDropEvent } from '../src/dedupe'; +import { _shouldDropEvent, dedupeIntegration } from '../../../src/integrations/dedupe'; type EventWithException = SentryEvent & { exception: { @@ -15,11 +14,6 @@ function clone(data: T): T { return JSON.parse(JSON.stringify(data)); } -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new Dedupe(...args); -} - const messageEvent: EventWithException = { fingerprint: ['MrSnuffles'], message: 'PickleRick', @@ -183,27 +177,31 @@ describe('Dedupe', () => { describe('processEvent', () => { it('ignores consecutive errors', () => { - const integration = getIntegration(); + const integration = dedupeIntegration(); - expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).not.toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); }); it('ignores transactions between errors', () => { - const integration = getIntegration(); + const integration = dedupeIntegration(); - expect(integration.processEvent(clone(exceptionEvent))).not.toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).not.toBeNull(); expect( - integration.processEvent({ - event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', - message: 'someMessage', - transaction: 'wat', - type: 'transaction', - }), + integration.processEvent?.( + { + event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', + message: 'someMessage', + transaction: 'wat', + type: 'transaction', + }, + {}, + {} as any, + ), ).not.toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); - expect(integration.processEvent(clone(exceptionEvent))).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); + expect(integration.processEvent?.(clone(exceptionEvent), {}, {} as any)).toBeNull(); }); }); }); diff --git a/packages/integrations/test/extraerrordata.test.ts b/packages/core/test/lib/integrations/extraerrordata.test.ts similarity index 66% rename from packages/integrations/test/extraerrordata.test.ts rename to packages/core/test/lib/integrations/extraerrordata.test.ts index b53b98885357..c5ffd68c1405 100644 --- a/packages/integrations/test/extraerrordata.test.ts +++ b/packages/core/test/lib/integrations/extraerrordata.test.ts @@ -1,14 +1,8 @@ import type { Event as SentryEvent, ExtendedError } from '@sentry/types'; -import type { extraErrorDataIntegration } from '../src/extraerrordata'; -import { ExtraErrorData } from '../src/extraerrordata'; +import { extraErrorDataIntegration } from '../../../src/integrations/extraerrordata'; -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new ExtraErrorData(...args); -} - -const extraErrorData = getIntegration(); +const extraErrorData = extraErrorDataIntegration(); let event: SentryEvent; describe('ExtraErrorData()', () => { @@ -21,9 +15,13 @@ describe('ExtraErrorData()', () => { error.baz = 42; error.foo = 'bar'; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -37,9 +35,13 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.cause = new SyntaxError('bar'); - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -58,9 +60,13 @@ describe('ExtraErrorData()', () => { }, }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -82,9 +88,13 @@ describe('ExtraErrorData()', () => { const error = new TypeError('foo') as ExtendedError; error.baz = 42; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -97,24 +107,32 @@ describe('ExtraErrorData()', () => { it('should return event if originalException is not an Error object', () => { const error = 'error message, not object'; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent).toEqual(event); }); it('should return event if there is no SentryEventHint', () => { - const enhancedEvent = extraErrorData.processEvent(event, {}); + const enhancedEvent = extraErrorData.processEvent?.(event, {}, {} as any); expect(enhancedEvent).toEqual(event); }); it('should return event if there is no originalException', () => { - const enhancedEvent = extraErrorData.processEvent(event, { - // @ts-expect-error Allow event to have extra properties - notOriginalException: 'fooled you', - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + // @ts-expect-error Allow event to have extra properties + notOriginalException: 'fooled you', + }, + {} as any, + ); expect(enhancedEvent).toEqual(event); }); @@ -130,9 +148,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -153,9 +175,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -173,9 +199,13 @@ describe('ExtraErrorData()', () => { }; }; - const enhancedEvent = extraErrorData.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorData.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ TypeError: { @@ -185,21 +215,25 @@ describe('ExtraErrorData()', () => { }); }); - it('captures Error causes when captureErrorCause = true', () => { + it('captures Error causes when captureErrorCause = true (default)', () => { // Error.cause is only available from node 16 upwards const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]); if (nodeMajorVersion < 16) { return; } - const extraErrorDataWithCauseCapture = getIntegration({ captureErrorCause: true }); + const extraErrorDataWithCauseCapture = extraErrorDataIntegration(); // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithCauseCapture.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorDataWithCauseCapture.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).toEqual({ Error: { @@ -217,14 +251,18 @@ describe('ExtraErrorData()', () => { return; } - const extraErrorDataWithoutCauseCapture = getIntegration(); + const extraErrorDataWithoutCauseCapture = extraErrorDataIntegration({ captureErrorCause: false }); // @ts-expect-error The typing .d.ts library we have installed isn't aware of Error.cause yet const error = new Error('foo', { cause: { woot: 'foo' } }) as ExtendedError; - const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent(event, { - originalException: error, - }); + const enhancedEvent = extraErrorDataWithoutCauseCapture.processEvent?.( + event, + { + originalException: error, + }, + {} as any, + ) as SentryEvent; expect(enhancedEvent.contexts).not.toEqual({ Error: { diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index 24783b8eeb68..012c3f5f5f0d 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -216,21 +216,6 @@ const TRANSACTION_EVENT_3: Event = { type: 'transaction', }; -const TRANSACTION_EVENT_HEALTH: Event = { - transaction: 'GET /health', - type: 'transaction', -}; - -const TRANSACTION_EVENT_HEALTH_2: Event = { - transaction: 'GET /healthy', - type: 'transaction', -}; - -const TRANSACTION_EVENT_HEALTH_3: Event = { - transaction: 'GET /live', - type: 'transaction', -}; - describe('InboundFilters', () => { describe('_isSentryError', () => { it('should work as expected', () => { @@ -408,39 +393,12 @@ describe('InboundFilters', () => { const eventProcessor = createInboundFiltersEventProcessor(); expect(eventProcessor(SCRIPT_ERROR_EVENT, {})).toBe(null); expect(eventProcessor(TRANSACTION_EVENT, {})).toBe(TRANSACTION_EVENT); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH, {})).toBe(null); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_2, {})).toBe(null); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_3, {})).toBe(null); }); it('disable default error filters', () => { const eventProcessor = createInboundFiltersEventProcessor({ disableErrorDefaults: true }); expect(eventProcessor(SCRIPT_ERROR_EVENT, {})).toBe(SCRIPT_ERROR_EVENT); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH, {})).toBe(null); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_2, {})).toBe(null); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_3, {})).toBe(null); }); - - it('disable default transaction filters', () => { - const eventProcessor = createInboundFiltersEventProcessor({ disableTransactionDefaults: true }); - expect(eventProcessor(SCRIPT_ERROR_EVENT, {})).toBe(null); - expect(eventProcessor(TRANSACTION_EVENT, {})).toBe(TRANSACTION_EVENT); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH, {})).toBe(TRANSACTION_EVENT_HEALTH); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_2, {})).toBe(TRANSACTION_EVENT_HEALTH_2); - expect(eventProcessor(TRANSACTION_EVENT_HEALTH_3, {})).toBe(TRANSACTION_EVENT_HEALTH_3); - }); - - it.each(['/delivery', '/already', '/healthysnacks'])( - "doesn't filter out transactions that have similar names to health check ones (%s)", - transaction => { - const eventProcessor = createInboundFiltersEventProcessor(); - const evt: Event = { - transaction, - type: 'transaction', - }; - expect(eventProcessor(evt, {})).toBe(evt); - }, - ); }); describe('denyUrls/allowUrls', () => { 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/integrations/test/rewriteframes.test.ts b/packages/core/test/lib/integrations/rewriteframes.test.ts similarity index 81% rename from packages/integrations/test/rewriteframes.test.ts rename to packages/core/test/lib/integrations/rewriteframes.test.ts index 7dc051c478cb..17d6ed4b9c5b 100644 --- a/packages/integrations/test/rewriteframes.test.ts +++ b/packages/core/test/lib/integrations/rewriteframes.test.ts @@ -1,18 +1,8 @@ -import type { Event, Integration, StackFrame } from '@sentry/types'; +import type { Event, StackFrame } from '@sentry/types'; -import type { rewriteFramesIntegration } from '../src/rewriteframes'; -import { RewriteFrames } from '../src/rewriteframes'; +import { rewriteFramesIntegration } from '../../../src/integrations/rewriteframes'; -interface IntegrationWithProcessEvent extends Integration { - processEvent(event: Event): Event; -} - -function getIntegration(...args: Parameters) { - // eslint-disable-next-line deprecation/deprecation - return new RewriteFrames(...args); -} - -let rewriteFrames: IntegrationWithProcessEvent; +let rewriteFrames: ReturnType; let exceptionEvent: Event; let exceptionWithoutStackTrace: Event; let windowsExceptionEvent: Event; @@ -108,11 +98,11 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with `/`', () => { beforeEach(() => { - rewriteFrames = getIntegration(); + rewriteFrames = rewriteFramesIntegration(); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); @@ -120,20 +110,20 @@ describe('RewriteFrames', () => { it('ignore exception without StackTrace', () => { // @ts-expect-error Validates that the Stacktrace does not exist before validating the test. expect(exceptionWithoutStackTrace.exception?.values[0].stacktrace).toEqual(undefined); - const event = rewriteFrames.processEvent(exceptionWithoutStackTrace); + const event = rewriteFrames.processEvent?.(exceptionWithoutStackTrace, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace).toEqual(undefined); }); }); describe('default iteratee prepends custom prefix to basename if frame starts with `/`', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ prefix: 'foobar/', }); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('foobar/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('foobar/file2.js'); }); @@ -141,29 +131,29 @@ describe('RewriteFrames', () => { describe('default iteratee appends basename to `app:///` if frame starts with Windows path prefix', () => { beforeEach(() => { - rewriteFrames = getIntegration(); + rewriteFrames = rewriteFramesIntegration(); }); it('transforms windowsExceptionEvent frames (C:\\)', () => { - const event = rewriteFrames.processEvent(windowsExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with lower-case prefix (c:\\)', () => { - const event = rewriteFrames.processEvent(windowsLowerCaseExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithoutPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithBackslashPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); }); @@ -171,37 +161,37 @@ describe('RewriteFrames', () => { describe('can use custom root to perform `relative` on filepaths', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ root: '/www', }); }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); }); it('transforms windowsExceptionEvent frames', () => { - const event = rewriteFrames.processEvent(windowsExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent lower-case prefix frames', () => { - const event = rewriteFrames.processEvent(windowsLowerCaseExceptionEvent); + const event = rewriteFrames.processEvent?.(windowsLowerCaseExceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with no prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithoutPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithoutPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); it('transforms windowsExceptionEvent frames with backslash prefix', () => { - const event = rewriteFrames.processEvent(windowsExceptionEventWithBackslashPrefix); + const event = rewriteFrames.processEvent?.(windowsExceptionEventWithBackslashPrefix, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/file2.js'); }); @@ -209,7 +199,7 @@ describe('RewriteFrames', () => { describe('can use custom iteratee', () => { beforeEach(() => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ iteratee: (frame: StackFrame) => ({ ...frame, function: 'whoops', @@ -218,7 +208,7 @@ describe('RewriteFrames', () => { }); it('transforms exceptionEvent frames', () => { - const event = rewriteFrames.processEvent(exceptionEvent); + const event = rewriteFrames.processEvent?.(exceptionEvent, {}, {} as any) as Event; expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('/www/src/app/mo\\dule/file2.js'); @@ -228,8 +218,8 @@ describe('RewriteFrames', () => { describe('can process events that contain multiple stacktraces', () => { it('with defaults', () => { - rewriteFrames = getIntegration(); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + rewriteFrames = rewriteFramesIntegration(); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///file2.js'); @@ -242,10 +232,10 @@ describe('RewriteFrames', () => { }); it('with custom root', () => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ root: '/www', }); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('app:///src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![1].filename).toEqual('app:///src/app/mo\\dule/file2.js'); @@ -258,13 +248,13 @@ describe('RewriteFrames', () => { }); it('with custom iteratee', () => { - rewriteFrames = getIntegration({ + rewriteFrames = rewriteFramesIntegration({ iteratee: (frame: StackFrame) => ({ ...frame, function: 'whoops', }), }); - const event = rewriteFrames.processEvent(multipleStacktracesEvent); + const event = rewriteFrames.processEvent?.(multipleStacktracesEvent, {}, {} as any) as Event; // first stacktrace expect(event.exception!.values![0].stacktrace!.frames![0].filename).toEqual('/www/src/app/file1.js'); expect(event.exception!.values![0].stacktrace!.frames![0].function).toEqual('whoops'); @@ -285,17 +275,17 @@ describe('RewriteFrames', () => { describe('bails when unable to extract frames', () => { it('no exception values', () => { - rewriteFrames = getIntegration({}); + rewriteFrames = rewriteFramesIntegration({}); const brokenEvent = { exception: { values: undefined, }, }; - expect(rewriteFrames.processEvent(brokenEvent)).toEqual(brokenEvent); + expect(rewriteFrames.processEvent?.(brokenEvent, {}, {} as any)).toEqual(brokenEvent); }); it('no frames', () => { - rewriteFrames = getIntegration({}); + rewriteFrames = rewriteFramesIntegration({}); const brokenEvent = { exception: { values: [ @@ -305,7 +295,7 @@ describe('RewriteFrames', () => { ], }, }; - expect(rewriteFrames.processEvent(brokenEvent)).toEqual(brokenEvent); + expect(rewriteFrames.processEvent?.(brokenEvent, {}, {} as any)).toEqual(brokenEvent); }); }); }); diff --git a/packages/integrations/test/sessiontiming.test.ts b/packages/core/test/lib/integrations/sessiontiming.test.ts similarity index 50% rename from packages/integrations/test/sessiontiming.test.ts rename to packages/core/test/lib/integrations/sessiontiming.test.ts index 85855324a6dc..6213d7cae9c1 100644 --- a/packages/integrations/test/sessiontiming.test.ts +++ b/packages/core/test/lib/integrations/sessiontiming.test.ts @@ -1,15 +1,19 @@ -import { SessionTiming } from '../src/sessiontiming'; +import type { Event } from '@sentry/types'; +import { sessionTimingIntegration } from '../../../src/integrations/sessiontiming'; -// eslint-disable-next-line deprecation/deprecation -const sessionTiming = new SessionTiming(); +const sessionTiming = sessionTimingIntegration(); describe('SessionTiming', () => { it('should work as expected', () => { - const event = sessionTiming.processEvent({ - extra: { - some: 'value', + const event = sessionTiming.processEvent?.( + { + extra: { + some: 'value', + }, }, - }); + {}, + {} as any, + ) as Event; expect(typeof event.extra?.['session:start']).toBe('number'); expect(typeof event.extra?.['session:duration']).toBe('number'); 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/prepareEvent.test.ts b/packages/core/test/lib/prepareEvent.test.ts index cb3c616b7390..1ad80fb5ce68 100644 --- a/packages/core/test/lib/prepareEvent.test.ts +++ b/packages/core/test/lib/prepareEvent.test.ts @@ -9,9 +9,9 @@ import type { ScopeContext, } from '@sentry/types'; import { GLOBAL_OBJ, createStackParser } from '@sentry/utils'; -import { getIsolationScope, setGlobalScope } from '../../src'; +import { getGlobalScope, getIsolationScope, setGlobalScope } from '../../src'; -import { Scope, getGlobalScope } from '../../src/scope'; +import { Scope } from '../../src/scope'; import { applyDebugIds, applyDebugMeta, @@ -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' }, + }); + }); + }); }); diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 4b4242ce7dc6..30697275db2f 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -1,12 +1,13 @@ -import type { Attachment, Breadcrumb, Client, Event } from '@sentry/types'; +import type { Attachment, Breadcrumb, Client, Event, RequestSessionStatus } from '@sentry/types'; import { - Hub, addTracingExtensions, applyScopeDataToEvent, getActiveSpan, getCurrentScope, + getGlobalScope, getIsolationScope, - makeMain, + setCurrentClient, + setGlobalScope, spanToJSON, startInactiveSpan, startSpan, @@ -14,7 +15,7 @@ import { } from '../../src'; import { withActiveSpan } from '../../src/exports'; -import { Scope, getGlobalScope, setGlobalScope } from '../../src/scope'; +import { Scope } from '../../src/scope'; import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; describe('Scope', () => { @@ -103,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); @@ -473,6 +866,38 @@ describe('Scope', () => { ); }); }); + + describe('addBreadcrumb()', () => { + test('adds a breadcrumb', () => { + const scope = new Scope(); + + scope.addBreadcrumb({ message: 'hello world' }, 100); + + expect((scope as any)._breadcrumbs[0].message).toEqual('hello world'); + }); + + test('adds a timestamp to new breadcrumbs', () => { + const scope = new Scope(); + + scope.addBreadcrumb({ message: 'hello world' }, 100); + + expect((scope as any)._breadcrumbs[0].timestamp).toEqual(expect.any(Number)); + }); + + test('overrides the `maxBreadcrumbs` defined in client options', () => { + const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); + const client = new TestClient(options); + const scope = new Scope(); + + scope.setClient(client); + + scope.addBreadcrumb({ message: 'hello' }, 100); + scope.addBreadcrumb({ message: 'world' }, 100); + scope.addBreadcrumb({ message: '!' }, 100); + + expect((scope as any)._breadcrumbs).toHaveLength(3); + }); + }); }); describe('isolation scope', () => { @@ -521,9 +946,8 @@ describe('withActiveSpan()', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ enableTracing: true }); const client = new TestClient(options); - const scope = new Scope(); - 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', () => { @@ -550,4 +974,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/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index c9d18c02c78e..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,12 +86,6 @@ describe('SDK', () => { }); describe('captureCheckIn', () => { - afterEach(function () { - 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', @@ -102,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/serverruntimeclient.test.ts b/packages/core/test/lib/serverruntimeclient.test.ts index 4ffed6c68f81..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, }; } @@ -78,8 +77,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 +143,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/hub/test/session.test.ts b/packages/core/test/lib/session.test.ts similarity index 97% rename from packages/hub/test/session.test.ts rename to packages/core/test/lib/session.test.ts index fd8a1a58c359..41eb0c234d13 100644 --- a/packages/hub/test/session.test.ts +++ b/packages/core/test/lib/session.test.ts @@ -1,9 +1,7 @@ -/* eslint-disable deprecation/deprecation */ - 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..808ba6308069 100644 --- a/packages/hub/test/sessionflusher.test.ts +++ b/packages/core/test/lib/sessionflusher.test.ts @@ -1,8 +1,6 @@ -/* eslint-disable deprecation/deprecation */ - 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/core/test/lib/tracing/dynamicSamplingContext.test.ts b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts index 01c99e3b87fd..9c531907eac3 100644 --- a/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts +++ b/packages/core/test/lib/tracing/dynamicSamplingContext.test.ts @@ -1,17 +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); - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); addTracingExtensions(); }); @@ -73,7 +75,9 @@ describe('getDynamicSamplingContextFromSpan', () => { name: 'tx', metadata: { sampleRate: 0.56, - source: 'route', + }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, sampled: true, }); @@ -96,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); @@ -108,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/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index b83820865ede..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 { 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'; import { registerErrorInstrumentation } from '../../../src/tracing/errors'; +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; const mockAddGlobalErrorInstrumentationHandler = jest.fn(); const mockAddGlobalUnhandledRejectionInstrumentationHandler = jest.fn(); @@ -33,10 +32,10 @@ describe('registerErrorHandlers()', () => { beforeEach(() => { mockAddGlobalErrorInstrumentationHandler.mockClear(); mockAddGlobalUnhandledRejectionInstrumentationHandler.mockClear(); - const options = getDefaultBrowserClientOptions({ enableTracing: true }); - const hub = new Hub(new BrowserClient(options)); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + const options = getDefaultTestClientOptions({ enableTracing: true }); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); }); it('registers error instrumentation', () => { diff --git a/packages/tracing/test/idletransaction.test.ts b/packages/core/test/lib/tracing/idletransaction.test.ts similarity index 88% rename from packages/tracing/test/idletransaction.test.ts rename to packages/core/test/lib/tracing/idletransaction.test.ts index c6380691b1b3..8f61b43164c3 100644 --- a/packages/tracing/test/idletransaction.test.ts +++ b/packages/core/test/lib/tracing/idletransaction.test.ts @@ -1,25 +1,36 @@ -/* eslint-disable deprecation/deprecation */ -import { BrowserClient } from '@sentry/browser'; import { TRACING_DEFAULTS, Transaction, + getCurrentHub, getCurrentScope, + getGlobalScope, + getIsolationScope, + setCurrentClient, spanToJSON, startInactiveSpan, startSpan, startSpanManual, } from '@sentry/core'; +/* eslint-disable deprecation/deprecation */ +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; -import { Hub, IdleTransaction, Span, getClient, makeMain } 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'; -let hub: Hub; beforeEach(() => { - const options = getDefaultBrowserClientOptions({ dsn, tracesSampleRate: 1 }); - hub = new Hub(new BrowserClient(options)); - makeMain(hub); + getCurrentScope().clear(); + getIsolationScope().clear(); + getGlobalScope().clear(); + + const options = getDefaultTestClientOptions({ dsn, tracesSampleRate: 1 }); + const client = new TestClient(options); + setCurrentClient(client); + client.init(); +}); + +afterEach(() => { + jest.clearAllMocks(); }); describe('IdleTransaction', () => { @@ -27,7 +38,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 +52,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 +63,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 +82,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 +100,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,9 +108,7 @@ describe('IdleTransaction', () => { ); transaction.initSpanRecorder(10); - // @ts-expect-error need to pass in hub - // eslint-disable-next-line deprecation/deprecation - const otherTransaction = new Transaction({ name: 'bar' }, hub); + const otherTransaction = new Transaction({ name: 'bar' }, getCurrentHub()); // eslint-disable-next-line deprecation/deprecation getCurrentScope().setSpan(otherTransaction); @@ -117,7 +126,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({}); @@ -137,7 +146,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 @@ -148,7 +157,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({}); @@ -172,7 +181,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); @@ -192,7 +201,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); @@ -221,11 +230,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); @@ -233,7 +242,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); @@ -248,7 +257,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()!; @@ -262,7 +275,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); @@ -270,7 +283,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); @@ -283,7 +296,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); @@ -301,7 +314,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); @@ -321,7 +334,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); @@ -337,7 +350,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); @@ -359,7 +372,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); @@ -375,7 +388,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); @@ -399,7 +412,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'); @@ -422,7 +435,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 @@ -445,7 +458,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 @@ -508,7 +521,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); @@ -529,7 +542,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); @@ -539,7 +552,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/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index b3c08987d4b2..0a89b5acc5ff 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -1,67 +1,128 @@ import { timestampInSeconds } from '@sentry/utils'; -import { Span } from '../../../src'; -import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON } from '../../../src/utils/spanUtils'; +import { SentrySpan } from '../../../src'; +import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils'; describe('span', () => { describe('name', () => { - /* eslint-disable deprecation/deprecation */ it('works with name', () => { - const span = new Span({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + const span = new SentrySpan({ name: 'span name' }); + expect(spanToJSON(span).description).toEqual('span name'); }); - it('works with description', () => { - const span = new Span({ description: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + it('allows to update the name via updateName', () => { + const span = new SentrySpan({ name: 'span name' }); + expect(spanToJSON(span).description).toEqual('span name'); + + span.updateName('new name'); + + expect(spanToJSON(span).description).toEqual('new name'); }); + }); - it('works without name', () => { - const span = new Span({}); - expect(span.name).toEqual(''); - expect(span.description).toEqual(undefined); + 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); }); + }); - it('allows to update the name via setter', () => { - const span = new Span({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + describe('setters', () => { + test('setName', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).description).toBeUndefined(); + span.updateName('foo'); + expect(spanToJSON(span).description).toBe('foo'); + }); + }); - span.name = 'new name'; + describe('status', () => { + test('setStatus', () => { + const span = new SentrySpan({}); + span.setStatus('permission_denied'); + expect(spanToTraceContext(span).status).toBe('permission_denied'); + }); + }); - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); + 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'); }); - it('allows to update the name via setName', () => { - const span = new Span({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + 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'); + }); - // eslint-disable-next-line deprecation/deprecation - span.setName('new name'); + 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', + }, + }); + }); + }); - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); + describe('finish', () => { + test('simple', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + span.end(); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); }); + }); - it('allows to update the name via updateName', () => { - const span = new Span({ name: 'span name' }); - expect(span.name).toEqual('span name'); - expect(span.description).toEqual('span name'); + describe('end', () => { + test('simple', () => { + const span = new SentrySpan({}); + expect(spanToJSON(span).timestamp).toBeUndefined(); + span.end(); + expect(spanToJSON(span).timestamp).toBeGreaterThan(1); + }); - span.updateName('new name'); + 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); + }); - expect(span.name).toEqual('new name'); - expect(span.description).toEqual('new name'); + 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); }); }); - /* eslint-enable deprecation/deprecation */ 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 +145,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 +164,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 +179,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 +237,7 @@ describe('span', () => { }); it('deletes attributes when setting to `undefined`', () => { - const span = new Span(); + const span = new SentrySpan(); span.setAttribute('str', 'bar'); @@ -191,7 +252,7 @@ describe('span', () => { describe('end', () => { it('works without endTimestamp', () => { - const span = new Span(); + const span = new SentrySpan(); const now = timestampInSeconds(); span.end(); @@ -199,7 +260,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 +268,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 +276,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 +286,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 +296,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 +322,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 +331,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 +343,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 +369,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 +382,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 +394,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..3c72406209ca 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,26 +16,24 @@ 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); - const { status: spanStatus, data, tags } = spanToJSON(span!); + const { status: spanStatus, data } = spanToJSON(span!); expect(spanStatus).toBe(status); expect(data).toMatchObject({ 'http.response.status_code': code }); - expect(tags).toMatchObject({ 'http.status_code': String(code) }); }); 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); - const { status: spanStatus, data, tags } = spanToJSON(span!); + const { status: spanStatus, data } = spanToJSON(span!); expect(spanStatus).toBeUndefined(); expect(data).toMatchObject({ 'http.response.status_code': 600 }); - expect(tags).toMatchObject({ 'http.status_code': '600' }); }); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 4c9190e56b6a..b02a4d2bb0fc 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,17 +1,17 @@ -import type { Span as SpanType } from '@sentry/types'; +import type { Event, Span as SpanType } from '@sentry/types'; import { - Hub, SEMANTIC_ATTRIBUTE_SENTRY_OP, addTracingExtensions, + getCurrentHub, getCurrentScope, - makeMain, + getGlobalScope, + getIsolationScope, setCurrentClient, spanToJSON, withScope, } from '../../../src'; -import { Scope } from '../../../src/scope'; import { - Span, + SentrySpan, continueTrace, getActiveSpan, startInactiveSpan, @@ -29,16 +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); - 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(); @@ -95,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); }); @@ -126,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 => { @@ -163,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 () => { @@ -182,35 +171,11 @@ 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); }); - // 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 => { @@ -256,6 +221,7 @@ describe('startSpan', () => { data: { 'sentry.origin': 'auto.http.browser', 'sentry.sample_rate': 0, + 'sentry.source': 'custom', }, origin: 'auto.http.browser', description: 'GET users/[id]', @@ -269,11 +235,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(); @@ -303,8 +269,8 @@ describe('startSpan', () => { it('allows to pass a scope', () => { const initialScope = getCurrentScope(); - const manualScope = new Scope(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const manualScope = initialScope.clone(); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); @@ -322,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 => { @@ -368,6 +440,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 +463,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,10 +485,26 @@ 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(), + ); + }); + + 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); + }); }); }); }); @@ -427,9 +513,8 @@ describe('startSpanManual', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('creates & finishes span', async () => { @@ -461,8 +546,8 @@ describe('startSpanManual', () => { it('allows to pass a scope', () => { const initialScope = getCurrentScope(); - const manualScope = new Scope(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const manualScope = initialScope.clone(); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); @@ -484,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(); @@ -531,15 +726,24 @@ 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', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1 }); client = new TestClient(options); - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('creates & finishes span', async () => { @@ -565,8 +769,10 @@ describe('startInactiveSpan', () => { }); it('allows to pass a scope', () => { - const manualScope = new Scope(); - const parentSpan = new Span({ spanId: 'parent-span-id' }); + const initialScope = getCurrentScope(); + + const manualScope = initialScope.clone(); + const parentSpan = new SentrySpan({ spanId: 'parent-span-id' }); // eslint-disable-next-line deprecation/deprecation manualScope.setSpan(parentSpan); @@ -583,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); @@ -623,20 +932,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; @@ -650,10 +956,25 @@ 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(), + ); + }); + + 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); }); }); }); @@ -662,9 +983,8 @@ describe('continueTrace', () => { beforeEach(() => { const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 }); client = new TestClient(options); - hub = new Hub(client); - // eslint-disable-next-line deprecation/deprecation - makeMain(hub); + setCurrentClient(client); + client.init(); }); it('works without trace & baggage data', () => { @@ -687,7 +1007,7 @@ describe('continueTrace', () => { traceId: expect.any(String), }); - expect(scope['_sdkProcessingMetadata']).toEqual({}); + expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('works with trace data', () => { @@ -723,7 +1043,7 @@ describe('continueTrace', () => { traceId: '12312012123120121231201212312012', }); - expect(scope['_sdkProcessingMetadata']).toEqual({}); + expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('works with trace & baggage data', () => { @@ -765,7 +1085,7 @@ describe('continueTrace', () => { traceId: '12312012123120121231201212312012', }); - expect(scope['_sdkProcessingMetadata']).toEqual({}); + expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('works with trace & 3rd party baggage data', () => { @@ -807,7 +1127,7 @@ describe('continueTrace', () => { traceId: '12312012123120121231201212312012', }); - expect(scope['_sdkProcessingMetadata']).toEqual({}); + expect(scope.getScopeData().sdkProcessingMetadata).toEqual({}); }); it('returns response of callback', () => { diff --git a/packages/core/test/lib/tracing/transaction.test.ts b/packages/core/test/lib/tracing/transaction.test.ts index 415c0448f78e..781b9bdc1472 100644 --- a/packages/core/test/lib/tracing/transaction.test.ts +++ b/packages/core/test/lib/tracing/transaction.test.ts @@ -1,44 +1,29 @@ -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, + spanToJSON, +} from '../../../src'; describe('transaction', () => { describe('name', () => { /* 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.metadata.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.metadata.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.metadata.source).toEqual('route'); + expect(spanToJSON(transaction).description).toEqual('new name'); + expect(spanToJSON(transaction).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('custom'); }); /* eslint-enable deprecation/deprecation */ }); @@ -48,15 +33,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 +54,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/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/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..bf89b99bc733 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,15 +62,12 @@ describe('spanToJSON', () => { }); it('works with a full span', () => { - const span = new Span({ + const span = new SentrySpan({ name: 'test name', op: 'test op', parentSpanId: '1234', spanId: '5678', status: 'ok', - tags: { - foo: 'bar', - }, traceId: 'abcd', origin: 'auto', startTimestamp: 123, @@ -83,9 +80,6 @@ describe('spanToJSON', () => { parent_span_id: '1234', span_id: '5678', status: 'ok', - tags: { - foo: 'bar', - }, trace_id: 'abcd', origin: 'auto', start_timestamp: 123, @@ -107,7 +101,7 @@ describe('spanToJSON', () => { start_timestamp: 123, }; }, - } as unknown as Span; + } as unknown as SentrySpan; expect(spanToJSON(span)).toEqual({ span_id: 'span_id', @@ -119,20 +113,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/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 7cb1e08cecba..473028ea4b12 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, @@ -7,7 +6,6 @@ import type { Outcome, ParameterizedString, Session, - Severity, SeverityLevel, } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; @@ -20,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({}), ), @@ -53,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 */ @@ -76,11 +70,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 }); } @@ -94,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/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/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/package.json b/packages/deno/package.json index b9ef8fec8bc2..6ba7a746dad7 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/deno", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Deno", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno", @@ -11,16 +11,12 @@ "publishConfig": { "access": "public" }, - "files": [ - "index.mjs", - "index.mjs.map", - "index.d.ts" - ], + "files": ["index.mjs", "index.mjs.map", "index.d.ts"], "dependencies": { - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/browser": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.5", @@ -61,5 +57,15 @@ "skipTypeImports": true } } + }, + "nx": { + "targets": { + "build:transpile": { + "outputs": ["{projectRoot}/build"] + }, + "build:types": { + "outputs": ["{projectRoot}/build-types"] + } + } } } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index d42ad97fedb8..067cfd8a1599 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, @@ -31,16 +29,11 @@ export { captureEvent, captureMessage, close, - // eslint-disable-next-line deprecation/deprecation - configureScope, createTransport, - // eslint-disable-next-line deprecation/deprecation - extractTraceparentData, continueTrace, flush, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, - getHubFromCarrier, // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, @@ -50,11 +43,8 @@ export { getIsolationScope, Hub, // eslint-disable-next-line deprecation/deprecation - lastEventId, - // eslint-disable-next-line deprecation/deprecation makeMain, setCurrentClient, - runWithAsyncContext, Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, @@ -65,12 +55,8 @@ export { setTag, setTags, setUser, - // eslint-disable-next-line deprecation/deprecation - spanStatusfromHttpCode, getSpanStatusFromHttpCode, setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - trace, withScope, withIsolationScope, captureCheckIn, @@ -80,24 +66,36 @@ export { startSpan, startInactiveSpan, startSpanManual, - metrics, + metricsDefault as metrics, inboundFiltersIntegration, linkedErrorsIntegration, functionToStringIntegration, requestDataIntegration, + captureConsoleIntegration, + debugIntegration, + dedupeIntegration, + extraErrorDataIntegration, + rewriteFramesIntegration, + sessionTimingIntegration, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + 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'; export { DenoClient } from './client'; export { - // eslint-disable-next-line deprecation/deprecation - defaultIntegrations, getDefaultIntegrations, init, } from './sdk'; -export { breadcrumbsIntegration, dedupeIntegration } from '@sentry/browser'; +export { breadcrumbsIntegration } from '@sentry/browser'; import { Integrations as CoreIntegrations } from '@sentry/core'; export { denoContextIntegration } from './integrations/context'; 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..a653d7196246 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); @@ -69,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'; @@ -118,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/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/deno/src/sdk.ts b/packages/deno/src/sdk.ts index c5bd3a1d002f..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(), ]; } @@ -65,17 +59,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/deno/test/__snapshots__/mod.test.ts.snap b/packages/deno/test/__snapshots__/mod.test.ts.snap index 607d87b968bc..6cbbe0182902 100644 --- a/packages/deno/test/__snapshots__/mod.test.ts.snap +++ b/packages/deno/test/__snapshots__/mod.test.ts.snap @@ -41,48 +41,13 @@ 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());", filename: "app:///test/mod.test.ts", - function: "", + function: "?", in_app: true, - lineno: 46, + lineno: 47, post_context: [ "", " await delay(200);", @@ -108,7 +73,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/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/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/ember/README.md b/packages/ember/README.md index 6cc6f0d55cb1..0e56f6e30d47 100644 --- a/packages/ember/README.md +++ b/packages/ember/README.md @@ -173,7 +173,7 @@ ENV['@sentry/ember'] = { ### Supported Versions * **Ember.js**: v4.0 or above -* **Node**: v14 or above +* **Node**: v14.8 or above ### Previous Integration diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index b3ccfffa404f..5ab053aa63df 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -9,7 +9,7 @@ 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 { TransactionSource } from '@sentry/types'; import type { EmberSentryConfig, GlobalConfig, OwnConfig } from './types'; function _getSentryInitConfig(): EmberSentryConfig['sentry'] { @@ -60,33 +60,24 @@ 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 => { // 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, ): Promise> => { return startSpan( { attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'ember', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, op, - name: description, + name, }, () => { return fn(...args); @@ -105,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 { @@ -122,6 +120,7 @@ export const instrumentRoutePerformance = (BaseRoute this.fullRouteName, super.setupController.bind(this), args, + 'custom', ); } }, diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index f4c47998ea90..7cd61dd222cd 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -11,7 +11,7 @@ import type { ExtendedBackburner } from '@sentry/ember/runloop'; import type { Span } from '@sentry/types'; import { GLOBAL_OBJ, browserPerformanceTimeOrigin, timestampInSeconds } from '@sentry/utils'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import type { BrowserClient } from '..'; import { getActiveSpan, startInactiveSpan } from '..'; import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig } from '../types'; @@ -109,23 +109,17 @@ 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, { + activeRootSpan = Sentry.startBrowserTracingPageLoadSpan(client, { name: `route:${routeInfo.name}`, - op: 'pageload', origin: 'auto.pageload.ember', - tags: { + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', url, toRoute: routeInfo.name, - 'routing.instrumentation': '@sentry/ember', }, }); - activeRootSpan = getActiveSpan(); } const finishActiveTransaction = (_: unknown, nextInstance: unknown): void => { @@ -136,10 +130,7 @@ export function _instrumentEmberRouter( getBackburner().off('end', finishActiveTransaction); }; - if ( - browserTracingOptions.startTransactionOnLocationChange === false && - browserTracingOptions.instrumentNavigation === false - ) { + if (browserTracingOptions.instrumentNavigation === false) { return; } @@ -147,19 +138,16 @@ export function _instrumentEmberRouter( const { fromRoute, toRoute } = getTransitionInformation(transition, routerService); activeRootSpan?.end(); - Sentry.startBrowserTracingNavigationSpan(client, { + activeRootSpan = Sentry.startBrowserTracingNavigationSpan(client, { name: `route:${toRoute}`, - op: 'navigation', origin: 'auto.navigation.ember', - tags: { + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', fromRoute, toRoute, - 'routing.instrumentation': '@sentry/ember', }, }); - activeRootSpan = getActiveSpan(); - transitionSpan = startInactiveSpan({ attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.ember', 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/package.json b/packages/ember/package.json index e0ed8133ffc5..a86c29bc429d 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -32,10 +32,10 @@ }, "dependencies": { "@embroider/macros": "^1.9.0", - "@sentry/browser": "7.100.0", - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", + "@sentry/browser": "8.0.0-alpha.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", "ember-auto-import": "^1.12.1 || ^2.4.3", "ember-cli-babel": "^7.26.11", "ember-cli-htmlbars": "^6.1.1", @@ -83,7 +83,7 @@ "webpack": "~5.74.0" }, "engines": { - "node": "14.* || 16.* || >= 18" + "node": ">=14.8" }, "ember": { "edition": "octane" diff --git a/packages/ember/tests/acceptance/sentry-performance-test.ts b/packages/ember/tests/acceptance/sentry-performance-test.ts index 7ee5ccfcc14a..db22ff063c21 100644 --- a/packages/ember/tests/acceptance/sentry-performance-test.ts +++ b/packages/ember/tests/acceptance/sentry-performance-test.ts @@ -21,7 +21,7 @@ module('Acceptance | Sentry Performance', function (hooks) { 'ui.ember.component.render | component:test-section', ], transaction: 'route:tracing', - tags: { + attributes: { fromRoute: undefined, toRoute: 'tracing', }, @@ -50,7 +50,7 @@ module('Acceptance | Sentry Performance', function (hooks) { ], transaction: 'route:slow-loading-route.index', durationCheck: duration => duration > SLOW_TRANSITION_WAIT, - tags: { + attributes: { fromRoute: 'tracing', toRoute: 'slow-loading-route.index', }, diff --git a/packages/ember/tests/dummy/config/environment.js b/packages/ember/tests/dummy/config/environment.js index 70c45d17ff0c..144a6aebe1fa 100644 --- a/packages/ember/tests/dummy/config/environment.js +++ b/packages/ember/tests/dummy/config/environment.js @@ -24,8 +24,8 @@ module.exports = function (environment) { tracesSampleRate: 1, // Include fake dsn so that instrumentation is enabled when running from cli dsn: process.env.SENTRY_DSN || 'https://0@0.ingest.sentry.io/0', + tracePropagationTargets: ['localhost', 'doesntexist.example'], browserTracingOptions: { - tracingOrigins: ['localhost', 'doesntexist.example'], _experiments: { // This lead to some flaky tests, as that is sometimes logged enableLongTask: false, diff --git a/packages/ember/tests/helpers/utils.ts b/packages/ember/tests/helpers/utils.ts index 0be2c3d2f422..0833e80b0d8b 100644 --- a/packages/ember/tests/helpers/utils.ts +++ b/packages/ember/tests/helpers/utils.ts @@ -51,7 +51,7 @@ export function assertSentryTransactions( options: { spans: string[]; transaction: string; - tags: Record; + attributes: Record; durationCheck?: (duration: number) => boolean; }, ): void { @@ -72,20 +72,21 @@ 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'); assert.equal(event.transaction, options.transaction); - assert.equal(event.tags?.fromRoute, options.tags.fromRoute); - assert.equal(event.tags?.toRoute, options.tags.toRoute); + + Object.keys(options.attributes).forEach(key => { + assert.equal(event.contexts?.trace?.data?.[key], options.attributes[key]); + }); if (options.durationCheck && event.timestamp && event.start_timestamp) { const duration = (event.timestamp - event.start_timestamp) * 1000; diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 0849cfc388b1..34dcd5b8e48b 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "src" @@ -22,8 +22,8 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "7.100.0", - "@sentry-internal/typescript": "7.100.0", + "@sentry-internal/eslint-plugin-sdk": "8.0.0-alpha.0", + "@sentry-internal/typescript": "8.0.0-alpha.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index e5c16a64c307..f3f2ec198445 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "src" diff --git a/packages/feedback/package.json b/packages/feedback/package.json index 060defb0510c..3ae1af389b07 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -1,13 +1,13 @@ { "name": "@sentry-internal/feedback", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Sentry SDK integration for user feedback", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14.8" }, "files": [ "cjs", @@ -29,9 +29,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "scripts": { "build": "run-p build:transpile build:types build:bundle", diff --git a/packages/feedback/src/util/prepareFeedbackEvent.ts b/packages/feedback/src/util/prepareFeedbackEvent.ts index cb48efeaf89d..65a61085bda3 100644 --- a/packages/feedback/src/util/prepareFeedbackEvent.ts +++ b/packages/feedback/src/util/prepareFeedbackEvent.ts @@ -1,7 +1,6 @@ -import type { Scope } from '@sentry/core'; import { getIsolationScope } from '@sentry/core'; import { prepareEvent } from '@sentry/core'; -import type { Client, FeedbackEvent } from '@sentry/types'; +import type { Client, FeedbackEvent, Scope } from '@sentry/types'; interface PrepareFeedbackEventParams { client: Client; @@ -17,9 +16,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/feedback/test/unit/util/prepareFeedbackEvent.test.ts b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts index fa90083d703f..165e3af0588d 100644 --- a/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts +++ b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts @@ -1,7 +1,6 @@ -import type { Scope } from '@sentry/core'; import { setCurrentClient } from '@sentry/core'; import { getCurrentScope } from '@sentry/core'; -import type { FeedbackEvent } from '@sentry/types'; +import type { FeedbackEvent, Scope } from '@sentry/types'; import { prepareFeedbackEvent } from '../../../src/util/prepareFeedbackEvent'; import { TestClient, getDefaultClientOptions } from '../../utils/TestClient'; 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/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/package.json b/packages/gatsby/package.json index 4fca577e0bc8..0fbebcc90f4e 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/gatsby", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Gatsby.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby", @@ -11,7 +11,7 @@ "gatsby-plugin" ], "engines": { - "node": ">=8" + "node": ">=14.8" }, "files": [ "cjs", @@ -37,15 +37,15 @@ "access": "public" }, "dependencies": { - "@sentry/core": "7.100.0", - "@sentry/react": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/react": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", "@sentry/webpack-plugin": "1.19.0" }, "peerDependencies": { "gatsby": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "react": "15.x || 16.x || 17.x || 18.x" + "react": "16.x || 17.x || 18.x" }, "devDependencies": { "@testing-library/react": "^13.0.0", 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/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/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 b3dcf9f3045f..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": ">=8" - }, - "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/global.test.ts b/packages/hub/test/global.test.ts deleted file mode 100644 index 23bc51193a29..000000000000 --- a/packages/hub/test/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'; - -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 typescript complains that this can be `any` - 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/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/integration-shims/package.json b/packages/integration-shims/package.json index 5bc5f40f039d..c19d6f8f3fe7 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/integration-shims", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Shims for integrations in Sentry SDK.", "main": "build/cjs/index.js", "module": "build/esm/index.js", @@ -39,11 +39,12 @@ "url": "https://github.com/getsentry/sentry-javascript/issues" }, "dependencies": { - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0" + "@sentry/core": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0" }, "engines": { - "node": ">=12" + "node": ">=14.8" }, "volta": { "extends": "../../package.json" 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/integrations/.eslintrc.js b/packages/integrations/.eslintrc.js deleted file mode 100644 index 46741a6b1a82..000000000000 --- a/packages/integrations/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - overrides: [ - { - files: ['scripts/**/*.ts'], - parserOptions: { - project: ['../../tsconfig.dev.json'], - }, - }, - ], -}; diff --git a/packages/integrations/LICENSE b/packages/integrations/LICENSE deleted file mode 100644 index 535ef0561e1b..000000000000 --- a/packages/integrations/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/integrations/README.md b/packages/integrations/README.md deleted file mode 100644 index 9f94611ce045..000000000000 --- a/packages/integrations/README.md +++ /dev/null @@ -1,26 +0,0 @@ -

    - - Sentry - -

    - -# Sentry JavaScript SDK Integrations - -[![npm version](https://img.shields.io/npm/v/@sentry/integrations.svg)](https://www.npmjs.com/package/@sentry/integrations) -[![npm dm](https://img.shields.io/npm/dm/@sentry/integrations.svg)](https://www.npmjs.com/package/@sentry/integrations) -[![npm dt](https://img.shields.io/npm/dt/@sentry/integrations.svg)](https://www.npmjs.com/package/@sentry/integrations) - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -## General - -Pluggable integrations that can be used to enhance JS SDKs. - -All of the integrations can also be found on our CDN e.g.: - -Angular integration: `https://browser.sentry-cdn.com/5.0.0/angular.min.js` - -Please make sure to always to match the version of the integration with the version of the JS SDK you are loading. diff --git a/packages/integrations/jest.config.js b/packages/integrations/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/integrations/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/integrations/package.json b/packages/integrations/package.json deleted file mode 100644 index c232487a6e95..000000000000 --- a/packages/integrations/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "@sentry/integrations", - "version": "7.100.0", - "description": "Pluggable integrations that can be used to enhance JS SDKs", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "publishConfig": { - "access": "public" - }, - "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" - ] - } - }, - "dependencies": { - "@sentry/core": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", - "localforage": "^1.8.1" - }, - "devDependencies": { - "@sentry/browser": "7.100.0", - "chai": "^4.1.2" - }, - "scripts": { - "build": "run-p build:transpile build:types build:bundle", - "build:bundle": "ts-node scripts/buildBundles.ts --parallel", - "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": "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 --bundles && npm pack ./build/npm", - "circularDepCheck": "madge --circular src/index.ts", - "clean": "rimraf build coverage .rpt2_cache sentry-integrations-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "validate:es5": "es-check es5 'build/bundles/*.es5*.js'", - "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": false -} diff --git a/packages/integrations/rollup.bundle.config.mjs b/packages/integrations/rollup.bundle.config.mjs deleted file mode 100644 index 366585b8abe3..000000000000 --- a/packages/integrations/rollup.bundle.config.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import commonjs from '@rollup/plugin-commonjs'; - -import { insertAt, makeBaseBundleConfig, makeBundleConfigVariants } from '@sentry-internal/rollup-utils'; - -const builds = []; - -const file = process.env.INTEGRATION_FILE; -const jsVersion = process.env.JS_VERSION; - -const baseBundleConfig = makeBaseBundleConfig({ - bundleType: 'addon', - entrypoints: [`src/${file}`], - jsVersion, - licenseTitle: '@sentry/integrations', - 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)); - -export default builds; diff --git a/packages/integrations/rollup.npm.config.mjs b/packages/integrations/rollup.npm.config.mjs deleted file mode 100644 index 6d09adefc859..000000000000 --- a/packages/integrations/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/integrations/scripts/buildBundles.ts b/packages/integrations/scripts/buildBundles.ts deleted file mode 100644 index 97730f10afe2..000000000000 --- a/packages/integrations/scripts/buildBundles.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { spawn } from 'child_process'; -import { readdirSync } from 'fs'; -import { join } from 'path'; - -const runParallel = process.argv.includes('--parallel'); - -/** Gets a list of src filenames, one for each integration and excludes the index.ts */ -function getIntegrations(): string[] { - const srcDir = join(__dirname, '..', 'src'); - const srcFiles = readdirSync(srcDir); - // The index file is only there for the purposes of npm builds - // (for the CDN we create a separate bundle for each integration) - return srcFiles.filter(file => file !== 'index.ts'); -} - -/** Builds a bundle for a specific integration and JavaScript ES version */ -async function buildBundle(integration: string, jsVersion: string): Promise { - return new Promise((resolve, reject) => { - const child = spawn('yarn', ['--silent', 'rollup', '--config', 'rollup.bundle.config.mjs'], { - shell: true, // required to run on Windows - env: { ...process.env, INTEGRATION_FILE: integration, JS_VERSION: jsVersion }, - }); - - child.on('exit', exitcode => { - if (exitcode !== 0) { - reject(new Error(`Failed to build bundle for integration "${integration}" with exit code: ${exitcode}`)); - } else { - resolve(); - } - }); - }); -} - -if (runParallel) { - // We're building a bundle for each integration and each JavaScript version. - const tasks = getIntegrations().reduce( - (tasks, integration) => { - tasks.push(buildBundle(integration, 'es5'), buildBundle(integration, 'es6')); - return tasks; - }, - [] as Promise[], - ); - - Promise.all(tasks) - // eslint-disable-next-line no-console - .then(_ => console.log('\nIntegration bundles built successfully')) - .catch(error => { - // eslint-disable-next-line no-console - console.error(error); - // Important to exit with a non-zero exit code, so that the build fails. - process.exit(1); - }); -} else { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - (async () => { - for (const integration of getIntegrations()) { - await buildBundle(integration, 'es5'); - await buildBundle(integration, 'es6'); - } - // eslint-disable-next-line no-console - console.log('\nIntegration bundles built successfully'); - })(); -} diff --git a/packages/integrations/src/debug-build.ts b/packages/integrations/src/debug-build.ts deleted file mode 100644 index 60aa50940582..000000000000 --- a/packages/integrations/src/debug-build.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const __DEBUG_BUILD__: boolean; - -/** - * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. - * - * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. - */ -export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts deleted file mode 100644 index 445e10fa463e..000000000000 --- a/packages/integrations/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -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'; -export { Transaction } from './transaction'; -export { HttpClient, httpClientIntegration } from './httpclient'; -export { ContextLines, contextLinesIntegration } from './contextlines'; 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/src/sessiontiming.ts b/packages/integrations/src/sessiontiming.ts deleted file mode 100644 index 81ef3f2627be..000000000000 --- a/packages/integrations/src/sessiontiming.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; - -const INTEGRATION_NAME = 'SessionTiming'; - -const _sessionTimingIntegration = (() => { - const startTime = Date.now(); - - return { - name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function - processEvent(event) { - const now = Date.now(); - - return { - ...event, - extra: { - ...event.extra, - ['session:start']: startTime, - ['session:duration']: now - startTime, - ['session:end']: now, - }, - }; - }, - }; -}) satisfies IntegrationFn; - -export const sessionTimingIntegration = defineIntegration(_sessionTimingIntegration); - -/** - * This function adds duration since Sentry was initialized till the time event was sent. - * @deprecated Use `sessionTimingIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const SessionTiming = convertIntegrationFnToClass( - INTEGRATION_NAME, - sessionTimingIntegration, -) as IntegrationClass Event }>; diff --git a/packages/integrations/src/transaction.ts b/packages/integrations/src/transaction.ts deleted file mode 100644 index 5d6d0cd595e9..000000000000 --- a/packages/integrations/src/transaction.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { convertIntegrationFnToClass } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; - -const INTEGRATION_NAME = 'Transaction'; - -const transactionIntegration = (() => { - return { - name: INTEGRATION_NAME, - // TODO v8: Remove this - setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function - processEvent(event) { - const frames = _getFramesFromEvent(event); - - // use for loop so we don't have to reverse whole frames array - for (let i = frames.length - 1; i >= 0; i--) { - const frame = frames[i]; - - if (frame.in_app === true) { - event.transaction = _getTransaction(frame); - break; - } - } - - return event; - }, - }; -}) satisfies IntegrationFn; - -/** - * Add node transaction to the event. - * @deprecated This integration will be removed in v8. - */ -// eslint-disable-next-line deprecation/deprecation -export const Transaction = convertIntegrationFnToClass(INTEGRATION_NAME, transactionIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Event } ->; - -function _getFramesFromEvent(event: Event): StackFrame[] { - const exception = event.exception && event.exception.values && event.exception.values[0]; - return (exception && exception.stacktrace && exception.stacktrace.frames) || []; -} - -function _getTransaction(frame: StackFrame): string { - return frame.module || frame.function ? `${frame.module || '?'}/${frame.function || '?'}` : ''; -} 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/packages/integrations/test/transaction.test.ts b/packages/integrations/test/transaction.test.ts deleted file mode 100644 index 617af17c171a..000000000000 --- a/packages/integrations/test/transaction.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Transaction } from '../src/transaction'; - -// eslint-disable-next-line deprecation/deprecation -const transaction = new Transaction(); - -describe('Transaction', () => { - describe('extracts info from module/function of the first `in_app` frame', () => { - it('using module only', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - module: 'Foo', - }, - { - filename: '/some/file2.js', - in_app: true, - module: 'Qux', - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('Qux/?'); - }); - - it('using function only', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - function: 'Bar', - in_app: false, - }, - { - filename: '/some/file2.js', - function: 'Baz', - in_app: true, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('?/Baz'); - }); - - it('using module and function', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - function: 'Bar', - in_app: true, - module: 'Foo', - }, - { - filename: '/some/file2.js', - function: 'Baz', - in_app: false, - module: 'Qux', - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual('Foo/Bar'); - }); - - it('using default', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - }, - { - filename: '/some/file2.js', - in_app: true, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toEqual(''); - }); - - it('no value with no `in_app` frame', () => { - const event = transaction.processEvent({ - exception: { - values: [ - { - stacktrace: { - frames: [ - { - filename: '/some/file1.js', - in_app: false, - }, - { - filename: '/some/file2.js', - in_app: false, - }, - ], - }, - }, - ], - }, - }); - expect(event.transaction).toBeUndefined(); - }); - }); -}); diff --git a/packages/integrations/tsconfig.json b/packages/integrations/tsconfig.json deleted file mode 100644 index e5ec1017893d..000000000000 --- a/packages/integrations/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - // package-specific options - "esModuleInterop": true, - } -} diff --git a/packages/integrations/tsconfig.test.json b/packages/integrations/tsconfig.test.json deleted file mode 100644 index 87f6afa06b86..000000000000 --- a/packages/integrations/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/integrations/tsconfig.types.json b/packages/integrations/tsconfig.types.json deleted file mode 100644 index 374fd9bc9364..000000000000 --- a/packages/integrations/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/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/package.json b/packages/nextjs/package.json index 59a83f0b80a9..1fae1de57adf 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,13 +1,13 @@ { "name": "@sentry/nextjs", - "version": "7.100.0", + "version": "8.0.0-alpha.0", "description": "Official Sentry SDK for Next.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs", "author": "Sentry", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=14.8" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", @@ -25,13 +25,12 @@ }, "dependencies": { "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.100.0", - "@sentry/integrations": "7.100.0", - "@sentry/node": "7.100.0", - "@sentry/react": "7.100.0", - "@sentry/types": "7.100.0", - "@sentry/utils": "7.100.0", - "@sentry/vercel-edge": "7.100.0", + "@sentry/core": "8.0.0-alpha.0", + "@sentry/node-experimental": "8.0.0-alpha.0", + "@sentry/react": "8.0.0-alpha.0", + "@sentry/types": "8.0.0-alpha.0", + "@sentry/utils": "8.0.0-alpha.0", + "@sentry/vercel-edge": "8.0.0-alpha.0", "@sentry/webpack-plugin": "1.21.0", "chalk": "3.0.0", "resolve": "1.22.8", diff --git a/packages/nextjs/src/client/browserTracingIntegration.ts b/packages/nextjs/src/client/browserTracingIntegration.ts index af8f59f53b6f..d70eb3da0746 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'; +import { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation'; /** - * 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, - }); - } -} - -/** - * A custom BrowserTracing integration for Next.js. + * A custom browser tracing 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..06f010c980c9 --- /dev/null +++ b/packages/nextjs/src/client/clientNormalizationIntegration.ts @@ -0,0 +1,41 @@ +import { rewriteFramesIntegration } from '@sentry/browser'; +import { defineIntegration } from '@sentry/core'; + +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..fd155705a885 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,45 +1,22 @@ -import { applySdkMetadata, hasTracingEnabled } from '@sentry/core'; +import { addEventProcessor, applySdkMetadata, hasTracingEnabled, setTag } from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; -import { - Integrations as OriginalIntegrations, - getCurrentScope, - 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'; 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; @@ -49,86 +26,26 @@ export function init(options: BrowserOptions): void { environment: getVercelEnv(true) || process.env.NODE_ENV, defaultIntegrations: getDefaultIntegrations(options), ...options, - }; - - fixBrowserTracingIntegration(opts); + } satisfies BrowserOptions; applyTunnelRouteOption(opts); applySdkMetadata(opts, 'nextjs', ['nextjs', 'react']); 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); - } -} - -// 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; + addEventProcessor(devErrorSymbolicationEventProcessor); } - - 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 +55,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..8459f88454a3 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -1,47 +1,41 @@ +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 = { - 'routing.instrumentation': 'next-app-router', -} as const; - /** * 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', - tags: DEFAULT_TAGS, + if (shouldInstrumentPageload) { + startPageloadSpanCallback({ + name: currPathname, // 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 +54,18 @@ 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, + attributes: { + from: currPathname, + [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..fe28ad71558e 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; /** @@ -93,19 +97,6 @@ function extractNextDataTagInformation(): NextDataTagInfo { return nextDataTagInfo; } -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 +106,63 @@ 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, + attributes: { + from: prevLocationName, + [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/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/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts index 62124e46912e..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'; @@ -25,29 +24,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, @@ -78,9 +54,10 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri 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 sentryTrace: req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined, baggage: req.headers?.baggage, }, @@ -104,7 +81,7 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri const reqMethod = `${(req.method || 'GET').toUpperCase()} `; - getCurrentScope().setSDKProcessingMetadata({ request: req }); + isolationScope.setSDKProcessingMetadata({ request: req }); return startSpanManual( { @@ -144,14 +121,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/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/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts index 218ed18b5f26..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, @@ -80,8 +77,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..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, @@ -49,8 +47,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..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, @@ -73,8 +70,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..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, @@ -69,8 +66,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..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,39 +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; }, }); } - -/** - * @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..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,21 +26,12 @@ 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); }, }); } - -/** - * @deprecated Use `wrapGetStaticPropsWithSentry` instead. - */ -export const withSentryGetStaticProps = wrapGetStaticPropsWithSentry; 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/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/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 75f27bb9e649..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'; @@ -764,7 +764,7 @@ export function getWebpackPluginOptions( project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, configFile: hasSentryProperties ? 'sentry.properties' : undefined, - stripPrefix: ['webpack://_N_E/'], + stripPrefix: ['webpack://_N_E/', 'webpack://'], urlPrefix, entries: [], // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. release: getSentryRelease(buildId), diff --git a/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts b/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts new file mode 100644 index 000000000000..d2e1b519c29b --- /dev/null +++ b/packages/nextjs/src/edge/distDirRewriteFramesIntegration.ts @@ -0,0 +1,23 @@ +import { defineIntegration, rewriteFramesIntegration } from '@sentry/core'; +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..d63aa9ec0256 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, @@ -37,12 +48,8 @@ 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'; -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 index 96e626178c4b..f19a3c79a4c5 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -1,8 +1,4 @@ -import { defineIntegration } from '@sentry/core'; -import { - RewriteFrames as OriginalRewriteFrames, - rewriteFramesIntegration as originalRewriteFramesIntegration, -} from '@sentry/integrations'; +import { defineIntegration, rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/core'; import type { IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; @@ -42,7 +38,7 @@ export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) // Do nothing if we can't find a distDirName return { // eslint-disable-next-line deprecation/deprecation - name: OriginalRewriteFrames.id, + name: 'RewriteFrames', // eslint-disable-next-line @typescript-eslint/no-empty-function setupOnce: () => {}, processEvent: event => event, 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..976644d0992a 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -19,42 +19,25 @@ 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; +export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration; -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; 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'; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; -/** - * @deprecated Use `wrapApiHandlerWithSentry` instead - */ -export declare function withSentryAPI any>( - handler: APIHandler, - parameterizedRoute: string, -): ( - ...args: Parameters -) => ReturnType extends Promise ? ReturnType : Promise>; +export { withSentryConfig } from './config'; /** * Wraps a Next.js API handler with Sentry error and performance instrumentation. @@ -80,13 +63,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 +73,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 +83,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 +93,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 +105,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 +117,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..8ac80d91d61c --- /dev/null +++ b/packages/nextjs/src/server/distDirRewriteFramesIntegration.ts @@ -0,0 +1,24 @@ +import * as path from 'path'; +import { defineIntegration, rewriteFramesIntegration } from '@sentry/core'; +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/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 1373bb7a0905..5820b783b1b9 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,11 +1,10 @@ -import { addTracingExtensions, applySdkMetadata, getClient } from '@sentry/core'; -import type { NodeOptions } from '@sentry/node'; +import { addEventProcessor, addTracingExtensions, applySdkMetadata, getClient, setTag } from '@sentry/core'; +import type { NodeOptions } from '@sentry/node-experimental'; import { Integrations as OriginalIntegrations, - getCurrentScope, getDefaultIntegrations, init as nodeInit, -} from '@sentry/node'; +} from '@sentry/node-experimental'; import type { EventProcessor } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -13,12 +12,12 @@ 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'; +export * from '@sentry/node-experimental'; export { captureUnderscoreErrorException } from '../common/_error'; export const Integrations = { @@ -27,7 +26,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 +65,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 +79,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, @@ -118,16 +119,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'); @@ -137,20 +137,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/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/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index f27ff9a9993d..5b7c64d81b7a 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -1,9 +1,5 @@ import * as path from 'path'; -import { defineIntegration } from '@sentry/core'; -import { - RewriteFrames as OriginalRewriteFrames, - rewriteFramesIntegration as originalRewriteFramesIntegration, -} from '@sentry/integrations'; +import { defineIntegration, rewriteFramesIntegration as originalRewriteFramesIntegration } from '@sentry/core'; import type { IntegrationFn, StackFrame } from '@sentry/types'; import { escapeStringForRegex } from '@sentry/utils'; @@ -42,7 +38,7 @@ export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) // Do nothing if we can't find a distDirName return { // eslint-disable-next-line deprecation/deprecation - name: OriginalRewriteFrames.id, + name: 'RewriteFrames', // eslint-disable-next-line @typescript-eslint/no-empty-function setupOnce: () => {}, processEvent: event => event, diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 0ce7733dc137..2a19b74d5fcd 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 { BaseClient, getGlobalScope, getIsolationScope } 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'); @@ -37,7 +36,11 @@ 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(); + getCurrentScope().setClient(undefined); }); it('inits the React SDK', () => { @@ -65,7 +68,7 @@ describe('Client init()', () => { environment: 'test', defaultIntegrations: expect.arrayContaining([ expect.objectContaining({ - name: 'RewriteFrames', + name: 'NextjsClientStackFrameNormalization', }), ]), }), @@ -73,15 +76,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', () => { @@ -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/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index db17062041c8..ddcc8965a6c1 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -35,7 +35,7 @@ describe('Sentry webpack plugin config', () => { org: 'squirrelChasers', // from user webpack plugin config project: 'simulator', // from user webpack plugin config authToken: 'dogsarebadatkeepingsecrets', // picked up from env - stripPrefix: ['webpack://_N_E/'], // default + stripPrefix: ['webpack://_N_E/', 'webpack://'], // default urlPrefix: '~/_next', // default entries: [], release: 'doGsaREgReaT', // picked up from env 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/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/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/package.json b/packages/nextjs/test/integration/package.json index 14ac5a38aa6b..fd66eb8ec267 100644 --- a/packages/nextjs/test/integration/package.json +++ b/packages/nextjs/test/integration/package.json @@ -27,12 +27,12 @@ "resolutions": { "@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/opentelemetry": "file:../../../opentelemetry", "@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/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 => (