diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index dd41d7d50e5e..39e73722dfa5 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -9,7 +9,7 @@ on: # This workflow tirggers a release when merging a branch with the pattern `prepare-release/VERSION` into master. jobs: release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Prepare a new version' steps: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24f3ee0454f2..5972af74bb89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ env: jobs: job_get_metadata: name: Get Metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: pull-requests: read steps: @@ -84,8 +84,6 @@ jobs: echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV # Most changed packages are determined in job_build via Nx - # However, for profiling-node we only want to run certain things when in this specific package - # something changed, not in any of the dependencies (which include core, utils, ...) - name: Determine changed packages uses: dorny/paths-filter@v3.0.1 id: changed @@ -93,9 +91,6 @@ jobs: filters: | workflow: - '.github/**' - profiling_node: - - 'packages/profiling-node/**' - - 'dev-packages/e2e-tests/test-applications/node-profiling/**' any_code: - '!**/*.md' @@ -109,7 +104,6 @@ jobs: # Note: These next three have to be checked as strings ('true'/'false')! is_base_branch: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v9' || github.ref == 'refs/heads/v8'}} is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} - changed_profiling_node: ${{ steps.changed.outputs.profiling_node == 'true' }} changed_ci: ${{ steps.changed.outputs.workflow == 'true' }} changed_any_code: ${{ steps.changed.outputs.any_code == 'true' }} @@ -124,7 +118,7 @@ jobs: job_build: name: Build needs: job_get_metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 if: | needs.job_get_metadata.outputs.changed_any_code == 'true' || @@ -198,12 +192,11 @@ jobs: changed_deno: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/deno') }} changed_bun: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry/bun') }} changed_browser_integration: ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected, '@sentry-internal/browser-integration-tests') }} - # If you are looking for changed_profiling_node, this is defined in job_get_metadata job_check_branches: name: Check PR branches needs: job_get_metadata - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.event_name == 'pull_request' permissions: pull-requests: write @@ -219,7 +212,7 @@ jobs: name: Size Check needs: [job_get_metadata, job_build] timeout-minutes: 15 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true' @@ -249,7 +242,7 @@ jobs: # inter-package dependencies resolve cleanly. needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -274,7 +267,7 @@ jobs: name: Check file formatting needs: [job_get_metadata] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -297,7 +290,7 @@ jobs: name: Circular Dependency Check needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -316,8 +309,8 @@ jobs: job_artifacts: name: Upload Artifacts - needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] - runs-on: ubuntu-20.04 + needs: [job_get_metadata, job_build] + runs-on: ubuntu-22.04 # Build artifacts are only needed for releasing workflow. if: needs.job_get_metadata.outputs.is_release == 'true' steps: @@ -334,13 +327,6 @@ jobs: with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Extract Profiling Node Prebuilt Binaries - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - - name: Pack tarballs run: yarn build:tarball @@ -361,7 +347,7 @@ jobs: name: Browser Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) uses: actions/checkout@v4 @@ -400,7 +386,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_bun == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false steps: @@ -427,7 +413,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_deno == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false steps: @@ -457,7 +443,7 @@ jobs: name: Node (${{ matrix.node }}) Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -498,37 +484,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - job_profiling_node_unit_tests: - name: Node Profiling Unit Tests - needs: [job_get_metadata, job_build] - if: | - needs.job_build.outputs.changed_node == 'true' || - needs.job_get_metadata.outputs.changed_profiling_node == 'true' || - github.event_name != 'pull_request' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check out current commit - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - uses: actions/setup-node@v4 - with: - node-version: 20 - - uses: actions/setup-python@v5 - with: - python-version: '3.11.7' - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Build Configure node-gyp - run: yarn lerna run build:bindings:configure --scope @sentry/profiling-node - - name: Build Bindings for Current Environment - run: yarn build --scope @sentry/profiling-node - - name: Unit Test - run: yarn lerna run test --scope @sentry/profiling-node - job_browser_playwright_tests: name: Playwright ${{ matrix.bundle }}${{ matrix.project && matrix.project != 'chromium' && format(' {0}', matrix.project) || ''}}${{ matrix.shard && format(' ({0}/{1})', matrix.shard, matrix.shards) || ''}} Tests needs: [job_get_metadata, job_build] @@ -600,7 +555,7 @@ jobs: env: PW_BUNDLE: ${{ matrix.bundle }} working-directory: dev-packages/browser-integration-tests - run: yarn test:ci${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }} + run: yarn test:all${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }} - name: Upload Playwright Traces uses: actions/upload-artifact@v4 @@ -623,7 +578,7 @@ jobs: name: PW ${{ matrix.bundle }} Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 strategy: fail-fast: false @@ -683,7 +638,7 @@ jobs: job_check_for_faulty_dts: name: Check for faulty .d.ts files needs: [job_get_metadata, job_build] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -711,12 +666,12 @@ jobs: Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_node_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 strategy: fail-fast: false matrix: - node: [18, 20, 22] + node: ['18.20.5', 20, 22] typescript: - false include: @@ -750,7 +705,7 @@ jobs: name: Remix (Node ${{ matrix.node }}) Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_remix == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 10 strategy: fail-fast: false @@ -786,12 +741,10 @@ jobs: name: Prepare E2E tests # We want to run this if: # - The build job was successful, not skipped - # - AND if the profiling node bindings were either successful or skipped if: | always() && - needs.job_build.result == 'success' && - (needs.job_compile_bindings_profiling_node.result == 'success' || needs.job_compile_bindings_profiling_node.result == 'skipped') - needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] + needs.job_build.result == 'success' + needs: [job_get_metadata, job_build] runs-on: ubuntu-20.04-large-js timeout-minutes: 15 outputs: @@ -823,26 +776,6 @@ jobs: # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }} - # Rebuild profiling by compiling TS and pull the precompiled binary artifacts - - name: Build Profiling Node - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') || - (github.event_name != 'pull_request') - run: yarn lerna run build:lib --scope @sentry/profiling-node - - - name: Extract Profiling Node Prebuilt Binaries - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') || - (github.event_name != 'pull_request') - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - # End rebuild profiling - - name: Build tarballs run: yarn build:tarball @@ -868,7 +801,7 @@ jobs: # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix != '{"include":[]}' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -990,7 +923,7 @@ jobs: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} @@ -1089,137 +1022,20 @@ jobs: directory: dist workingDirectory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - job_profiling_e2e_tests: - name: E2E ${{ matrix.label || matrix.test-application }} Test - # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks - # Dependabot specifically also has access to secrets - # We need to add the `always()` check here because the previous step has this as well :( - # See: https://github.com/actions/runner/issues/2205 - if: - # Only run profiling e2e tests if profiling node bindings have changed - always() && needs.job_e2e_prepare.result == 'success' && - (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && - ( - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') - ) - needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - env: - E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} - E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} - E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' - E2E_TEST_SENTRY_PROJECT: 'sentry-javascript-e2e-tests' - strategy: - fail-fast: false - matrix: - test-application: ['node-profiling'] - build-command: - - false - label: - - false - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - - uses: pnpm/action-setup@v4 - with: - version: 9.4.0 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - - name: Build Profiling Node - run: yarn lerna run build:lib --scope @sentry/profiling-node - - - name: Extract Profiling Node Prebuilt Binaries - uses: actions/download-artifact@v4 - with: - pattern: profiling-node-binaries-${{ github.sha }}-* - path: ${{ github.workspace }}/packages/profiling-node/lib/ - merge-multiple: true - - - name: Restore tarball cache - uses: actions/cache/restore@v4 - id: restore-tarball-cache - with: - path: ${{ github.workspace }}/packages/*/*.tgz - key: ${{ env.BUILD_CACHE_TARBALL_KEY }} - - - name: Build tarballs if not cached - if: steps.restore-tarball-cache.outputs.cache-hit != 'true' - run: yarn build:tarball - - - name: Install Playwright - uses: ./.github/actions/install-playwright - with: - browsers: chromium - - - name: Get node version - id: versions - run: | - echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT - - - name: Validate Verdaccio - run: yarn test:validate - working-directory: dev-packages/e2e-tests - - - name: Prepare Verdaccio - run: yarn test:prepare - working-directory: dev-packages/e2e-tests - env: - E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} - - - name: Setup xvfb and update ubuntu dependencies - run: | - sudo apt-get install xvfb x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps - sudo apt-get install build-essential clang libdbus-1-dev libgtk2.0-dev \ - libnotify-dev libgconf2-dev \ - libasound2-dev libcap-dev libcups2-dev libxtst-dev \ - libxss1 libnss3-dev gcc-multilib g++-multilib - - - name: Install dependencies - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - run: yarn install --ignore-engines --frozen-lockfile - - - name: Build E2E app - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 7 - run: yarn ${{ matrix.build-command || 'test:build' }} - - - name: Run E2E test - working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 10 - run: | - xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert - job_required_jobs_passed: name: All required jobs passed or were skipped needs: [ job_build, - job_compile_bindings_profiling_node, job_browser_unit_tests, job_bun_unit_tests, job_deno_unit_tests, job_node_unit_tests, - job_profiling_node_unit_tests, job_node_integration_tests, job_browser_playwright_tests, job_browser_loader_tests, job_remix_integration_tests, job_e2e_tests, - job_profiling_e2e_tests, job_artifacts, job_lint, job_check_format, @@ -1227,257 +1043,9 @@ jobs: ] # Always run this, even if a dependent job failed if: always() - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check for failures if: contains(needs.*.result, 'failure') run: | echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 - - job_compile_bindings_profiling_node: - name: Compile profiling-node (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} - needs: [job_get_metadata, job_build] - # Compiling bindings can be very slow (especially on windows), so only run precompile - # Skip precompile unless we are on a release branch as precompile slows down CI times. - if: | - (needs.job_get_metadata.outputs.changed_profiling_node == 'true') || - (needs.job_get_metadata.outputs.is_release == 'true') - runs-on: ${{ matrix.os }} - container: - image: ${{ matrix.container }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - # x64 glibc - - os: ubuntu-20.04 - node: 18 - binary: linux-x64-glibc-108 - - os: ubuntu-20.04 - node: 20 - binary: linux-x64-glibc-115 - - os: ubuntu-20.04 - node: 22 - binary: linux-x64-glibc-127 - - # x64 musl - - os: ubuntu-20.04 - container: node:18-alpine3.17 - node: 18 - binary: linux-x64-musl-108 - - os: ubuntu-20.04 - container: node:20-alpine3.17 - node: 20 - binary: linux-x64-musl-115 - - os: ubuntu-20.04 - container: node:22-alpine3.18 - node: 22 - binary: linux-x64-musl-127 - - # arm64 glibc - - os: ubuntu-20.04 - arch: arm64 - node: 18 - binary: linux-arm64-glibc-108 - - os: ubuntu-20.04 - arch: arm64 - node: 20 - binary: linux-arm64-glibc-115 - - os: ubuntu-20.04 - arch: arm64 - node: 22 - binary: linux-arm64-glibc-127 - - # arm64 musl - - os: ubuntu-20.04 - arch: arm64 - container: node:18-alpine3.17 - node: 18 - binary: linux-arm64-musl-108 - - os: ubuntu-20.04 - arch: arm64 - container: node:20-alpine3.17 - node: 20 - binary: linux-arm64-musl-115 - - os: ubuntu-20.04 - arch: arm64 - container: node:22-alpine3.18 - node: 22 - binary: linux-arm64-musl-127 - - # macos x64 - - os: macos-13 - node: 18 - arch: x64 - binary: darwin-x64-108 - - os: macos-13 - node: 20 - arch: x64 - binary: darwin-x64-115 - - os: macos-13 - node: 22 - arch: x64 - binary: darwin-x64-127 - - # macos arm64 - - os: macos-13 - arch: arm64 - node: 18 - target_platform: darwin - binary: darwin-arm64-108 - - os: macos-13 - arch: arm64 - node: 20 - target_platform: darwin - binary: darwin-arm64-115 - - os: macos-13 - arch: arm64 - node: 22 - target_platform: darwin - binary: darwin-arm64-127 - - # windows x64 - - os: windows-2022 - node: 18 - arch: x64 - binary: win32-x64-108 - - os: windows-2022 - node: 20 - arch: x64 - binary: win32-x64-115 - - os: windows-2022 - node: 22 - arch: x64 - binary: win32-x64-127 - - steps: - - name: Setup (alpine) - if: contains(matrix.container, 'alpine') - run: | - apk add --no-cache build-base git g++ make curl python3 - ln -sf python3 /usr/bin/python - - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - # Note: On alpine images, this does nothing - # The node version will be the one that is installed in the image - # If you want to change the node version, you need to change the image - # For non-alpine imgages, this will install the correct version of node - - name: Setup Node - uses: actions/setup-node@v4 - if: contains(matrix.container, 'alpine') == false - with: - node-version: ${{ matrix.node }} - - - name: Restore dependency cache - uses: actions/cache/restore@v4 - id: restore-dependencies - with: - path: ${{ env.CACHED_DEPENDENCY_PATHS }} - key: ${{ needs.job_build.outputs.dependency_cache_key }} - enableCrossOsArchive: true - - - name: Increase yarn network timeout on Windows - if: contains(matrix.os, 'windows') - run: yarn config set network-timeout 600000 -g - - - name: Install dependencies - if: steps.restore-dependencies.outputs.cache-hit != 'true' - run: yarn install --ignore-engines --frozen-lockfile - env: - SKIP_PLAYWRIGHT_BROWSER_INSTALL: "1" - - - name: Configure safe directory - run: | - git config --global --add safe.directory "*" - - - name: Setup python - uses: actions/setup-python@v5 - if: ${{ !contains(matrix.container, 'alpine') }} - id: python-setup - with: - python-version: '3.8.10' - - - name: Setup (arm64| ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - sudo apt-get update - sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - - name: Setup Musl - if: contains(matrix.container, 'alpine') - run: | - cd packages/profiling-node - curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz - tar -xzvf aarch64-linux-musl-cross.tgz - $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version - - # configure node-gyp - - name: Configure node-gyp - if: matrix.arch != 'arm64' - run: | - cd packages/profiling-node - yarn build:bindings:configure - - - name: Configure node-gyp (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - yarn build:bindings:configure:arm64 - - - name: Configure node-gyp (arm64, darwin) - if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' - run: | - cd packages/profiling-node - yarn build:bindings:configure:arm64 - - # build bindings - - name: Build Bindings - if: matrix.arch != 'arm64' - run: | - yarn lerna run build:bindings --scope @sentry/profiling-node - - - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - CC=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \ - CXX=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ \ - BUILD_ARCH=arm64 \ - yarn build:bindings - - - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) - if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' - run: | - cd packages/profiling-node - CC=aarch64-linux-gnu-gcc \ - CXX=aarch64-linux-gnu-g++ \ - BUILD_ARCH=arm64 \ - yarn build:bindings:arm64 - - - name: Build Bindings (arm64, darwin) - if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' - run: | - cd packages/profiling-node - BUILD_PLATFORM=darwin \ - BUILD_ARCH=arm64 \ - yarn build:bindings:arm64 - - - name: Build profiling-node & its dependencies - run: yarn build --scope @sentry/profiling-node - - - name: Test Bindings - if: matrix.arch != 'arm64' - run: | - yarn lerna run test --scope @sentry/profiling-node - - - name: Archive Binary - uses: actions/upload-artifact@v4 - with: - name: profiling-node-binaries-${{ github.sha }}-${{ matrix.binary }} - path: ${{ github.workspace }}/packages/profiling-node/lib/sentry_cpu_profiler-${{matrix.binary}}.node - if-no-files-found: error diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 24f25fd1ea9a..614a971623b3 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -27,7 +27,7 @@ permissions: jobs: job_e2e_prepare: name: Prepare E2E Canary tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 30 steps: - name: Check out current commit @@ -54,7 +54,7 @@ jobs: job_e2e_tests: name: E2E ${{ matrix.label }} Test needs: [job_e2e_prepare] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 20 env: # We just use a dummy DSN here, only send to the tunnel anyhow diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml index 5c327553e3b8..78f1e3f66586 100644 --- a/.github/workflows/clear-cache.yml +++ b/.github/workflows/clear-cache.yml @@ -21,7 +21,7 @@ on: jobs: clear-caches: name: Delete all caches - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index 776f8135178d..f83a03a51b42 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -17,7 +17,7 @@ on: jobs: enforce-license-compliance: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: 'Enforce License Compliance' uses: getsentry/action-enforce-license-compliance@main diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index e9b1e05a2c92..cc2cbdb72774 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -12,7 +12,7 @@ jobs: permissions: pull-requests: write contents: write - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: | github.event.pull_request.merged == true && github.event.pull_request.author_association != 'COLLABORATOR' diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index 5d0d5af5d247..a232e1e735b1 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -23,7 +23,7 @@ concurrency: jobs: flaky-detector: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 name: 'Check tests for flakiness' # Also skip if PR is from master -> develop diff --git a/.github/workflows/gitflow-sync-develop.yml b/.github/workflows/gitflow-sync-develop.yml index 893dbbbf56fb..9bf8b6a556d6 100644 --- a/.github/workflows/gitflow-sync-develop.yml +++ b/.github/workflows/gitflow-sync-develop.yml @@ -17,7 +17,7 @@ env: jobs: main: name: Create PR master->develop - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: pull-requests: write contents: write diff --git a/.github/workflows/release-comment-issues.yml b/.github/workflows/release-comment-issues.yml index 4bbcb29aba21..ab4c45fc8f17 100644 --- a/.github/workflows/release-comment-issues.yml +++ b/.github/workflows/release-comment-issues.yml @@ -12,7 +12,7 @@ on: # This workflow is triggered when a release is published jobs: release-comment-issues: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Notify issues' steps: - name: Get version diff --git a/.github/workflows/release-size-info.yml b/.github/workflows/release-size-info.yml index 04e51e5ae14e..ea0cef636b8e 100644 --- a/.github/workflows/release-size-info.yml +++ b/.github/workflows/release-size-info.yml @@ -13,7 +13,7 @@ on: # It fetches the size-limit info from the release branch and adds it to the release jobs: release-size-info: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Add size-limit info to release' steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2768f18a5bc8..99569fd7f1aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ on: default: master jobs: release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: 'Release a new version' steps: - name: Get auth token diff --git a/CHANGELOG.md b/CHANGELOG.md index dcbfecab7da4..54ee78937f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,51 +10,15 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @tjhiggins, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, and @kunal-511. Thank you for your contributions! +Work in this release was contributed by @tjhiggins, @chris-basebone, @GrizliK1988, @davidturissini, @nwalters512, @aloisklink, @arturovt, @benjick, @maximepvrt, @mstrokin, @kunal-511, @jahands, @jrandolf, @tannerlinsley, @Zen-cronic and @nathankleyn. Thank you for your contributions! -- **feat(solidstart)!: Default to `--import` setup and add `autoInjectServerSentry` ([#14862](https://github.com/getsentry/sentry-javascript/pull/14862))** +## 9.0.0-alpha.1 -To enable the SolidStart SDK, wrap your SolidStart Config with `withSentry`. The `sentrySolidStartVite` plugin is now automatically -added by `withSentry` and you can pass the Sentry build-time options like this: - -```js -import { defineConfig } from '@solidjs/start/config'; -import { withSentry } from '@sentry/solidstart'; - -export default defineConfig( - withSentry( - { - /* Your SolidStart config options... */ - }, - { - // Options for setting up source maps - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - }, - ), -); -``` +This is an alpha release of the upcoming major release of version 9. +This release does not yet entail a comprehensive changelog as version 9 is not yet stable. -With the `withSentry` wrapper, the Sentry server config should not be added to the `public` directory anymore. -Add the Sentry server config in `src/instrument.server.ts`. Then, the server config will be placed inside the server build output as `instrument.server.mjs`. - -Now, there are two options to set up the SDK: - -1. **(recommended)** Provide an `--import` CLI flag to the start command like this (path depends on your server setup): - `node --import ./.output/server/instrument.server.mjs .output/server/index.mjs` -2. Add `autoInjectServerSentry: 'top-level-import'` and the Sentry config will be imported at the top of the server entry (comes with tracing limitations) - ```js - withSentry( - { - /* Your SolidStart config options... */ - }, - { - // Optional: Install Sentry with a top-level import - autoInjectServerSentry: 'top-level-import', - }, - ); - ``` +For this release's iteration of the migration guide, see the [Migration Guide as per `9.0.0-alpha.1`](https://github.com/getsentry/sentry-javascript/blob/e4333e5ce2d65be319ee6a5a5976f7c93983a417/docs/migration/v8-to-v9.md). +Please note that the migration guide is work in progress and subject to change. ## 9.0.0-alpha.0 diff --git a/LICENSE b/LICENSE index 4e1c2c384991..84d8d8c065fc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2012 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index bfd1ed3d2717..77cf36078a2a 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -35,13 +35,12 @@ "test:loader:replay_buffer": "PW_BUNDLE=loader_replay_buffer yarn test:loader", "test:loader:full": "PW_BUNDLE=loader_tracing_replay yarn test:loader", "test:loader:debug": "PW_BUNDLE=loader_debug yarn test:loader", - "test:ci": "yarn test:all", "test:update-snapshots": "yarn test:all --update-snapshots", "test:detect-flaky": "ts-node scripts/detectFlakyTests.ts" }, "dependencies": { "@babel/preset-typescript": "^7.16.7", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/rrweb": "2.31.0", "@sentry/browser": "9.0.0-alpha.0", "axios": "1.7.7", diff --git a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts index bf653dfad6b7..6fa8e8ddd416 100644 --- a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts +++ b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts @@ -4,14 +4,9 @@ import * as path from 'path'; import * as glob from 'glob'; /** - * The number of browsers we run the tests in. + * Assume that each test runs for 3s. */ -const NUM_BROWSERS = 3; - -/** - * Assume that each test runs for 2s. - */ -const ASSUMED_TEST_DURATION_SECONDS = 2; +const ASSUMED_TEST_DURATION_SECONDS = 3; /** * We keep the runtime of the detector if possible under 30min. @@ -51,22 +46,12 @@ ${changedPaths.join('\n')} try { await new Promise((resolve, reject) => { const cp = childProcess.spawn( - `npx playwright test ${ - testPaths.length ? testPaths.join(' ') : './suites' - } --reporter='line' --repeat-each ${repeatEachCount}`, - { shell: true, cwd }, + `npx playwright test ${testPaths.length ? testPaths.join(' ') : './suites'} --repeat-each ${repeatEachCount}`, + { shell: true, cwd, stdio: 'inherit' }, ); let error: Error | undefined; - cp.stdout.on('data', data => { - console.log(data ? (data as object).toString() : ''); - }); - - cp.stderr.on('data', data => { - console.log(data ? (data as object).toString() : ''); - }); - cp.on('error', e => { console.error(e); error = e; @@ -107,15 +92,16 @@ function getPerTestRunCount(testPaths: string[]) { const estimatedNumberOfTests = testPaths.map(getApproximateNumberOfTests).reduce((a, b) => a + b); console.log(`Estimated number of tests: ${estimatedNumberOfTests}`); - // tests are usually run against all browsers we test with, so let's assume this - const testRunCount = estimatedNumberOfTests * NUM_BROWSERS; + const testRunCount = estimatedNumberOfTests; console.log(`Estimated test runs for one round: ${testRunCount}`); const estimatedTestRuntime = testRunCount * ASSUMED_TEST_DURATION_SECONDS; console.log(`Estimated test runtime: ${estimatedTestRuntime}s`); const expectedPerTestRunCount = Math.floor(MAX_TARGET_TEST_RUNTIME_SECONDS / estimatedTestRuntime); - console.log(`Expected per-test run count: ${expectedPerTestRunCount}`); + console.log( + `Calculated # of repetitions: ${expectedPerTestRunCount} (min ${MIN_PER_TEST_RUN_COUNT}, max ${MAX_PER_TEST_RUN_COUNT})`, + ); return Math.min(MAX_PER_TEST_RUN_COUNT, Math.max(expectedPerTestRunCount, MIN_PER_TEST_RUN_COUNT)); } @@ -128,22 +114,7 @@ function getTestPaths(): string[] { cwd: path.join(__dirname, '../'), }); - return paths.map(p => path.dirname(p)); -} - -function logError(error: unknown) { - if (process.env.CI) { - console.log('::group::Test failed'); - } else { - console.error(' ⚠️ Test failed:'); - } - - console.log((error as any).stdout); - console.log((error as any).stderr); - - if (process.env.CI) { - console.log('::endgroup::'); - } + return paths.map(p => `${path.dirname(p)}/`); } /** @@ -156,7 +127,7 @@ function logError(error: unknown) { function getApproximateNumberOfTests(testPath: string): number { try { const content = fs.readFileSync(path.join(process.cwd(), testPath, 'test.ts'), 'utf-8'); - const matches = content.match(/it\(|test\(|sentryTest\(/g); + const matches = content.match(/sentryTest\(/g); return Math.max(matches ? matches.length : 1, 1); } catch (e) { console.error(`Could not read file ${testPath}`); diff --git a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts index c11d1897e1c2..51ac86e0cf62 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/onError/syntax-errors/test.ts @@ -27,7 +27,7 @@ sentryTest('should catch syntax errors', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData.exception?.values?.[0]).toMatchObject({ type: 'SyntaxError', - value: "Unexpected token '{'", + value: "Failed to execute 'appendChild' on 'Node': Unexpected token '{'", mechanism: { type: 'onerror', handled: false, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-0-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-1-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-2-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-3-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full deleted file mode 100644 index 1c3d1f22aeba..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-4-snap-full +++ /dev/null @@ -1,156 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "h1", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "********* ****", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "spa-navigation" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** * *** **********", - "id": 16 - } - ], - "id": 15 - }, - { - "type": 3, - "textContent": "\n ", - "id": 17 - }, - { - "type": 3, - "textContent": "\n ", - "id": 18 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/index.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** **** ** ***** ****", - "id": 20 - } - ], - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "id": 21 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 22 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental deleted file mode 100644 index 6dd84be3e2dc..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-5-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental deleted file mode 100644 index 575f1210087b..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-6-snap-incremental +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 15, - "x": 157.13999938964844, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 6, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 15 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 15, - "x": 157.13999938964844, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 15, - "x": 157, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental deleted file mode 100644 index f952a6e3bfaa..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-7-snap-incremental +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 6, - "id": 15 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 12 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 12, - "x": 41.810001373291016, - "y": 90.37000274658203 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 12, - "x": 41, - "y": 90, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full deleted file mode 100644 index 0d77b67cb862..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-8-snap-full +++ /dev/null @@ -1,113 +0,0 @@ -[ - { - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "id": "go-background" - }, - "childNodes": [ - { - "type": 3, - "textContent": "*** ***", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "a", - "attributes": { - "href": "http://sentry-test.io/page-0.html" - }, - "childNodes": [ - { - "type": 3, - "textContent": "** ** *** ****", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] - } -] diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental deleted file mode 100644 index 02a3e3f893d6..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "type": 3, - "data": { - "source": 2, - "type": 1, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 5, - "id": 9 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 0, - "id": 9, - "x": 41.810001373291016, - "y": 18.479999542236328 - }, - "timestamp": [timestamp] - }, - { - "type": 3, - "data": { - "source": 2, - "type": 2, - "id": 9, - "x": 41, - "y": 18, - "pointerType": 0 - }, - "timestamp": [timestamp] - } -] \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium index 02a3e3f893d6..4e76cedf3b0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts-snapshots/seg-9-snap-incremental-chromium @@ -6,7 +6,7 @@ "type": 1, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, @@ -26,7 +26,7 @@ "type": 0, "id": 9, "x": 41.810001373291016, - "y": 18.479999542236328 + "y": 18.5 }, "timestamp": [timestamp] }, diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts index 79c7758ec099..6bb199c42146 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts @@ -15,7 +15,7 @@ import { const SESSION_TIMEOUT = 2000; sentryTest('handles an expired session', async ({ browserName, forceFlushReplay, getLocalTestUrl, page }) => { - if (shouldSkipReplayTest() || browserName === 'webkit') { + if (shouldSkipReplayTest() || browserName !== 'chromium') { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json deleted file mode 100644 index d510b410a343..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-0-webkit.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "id": 2 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "id": 5 - } - ], - "id": 4 - }, - { - "type": 3, - "textContent": "\n ", - "id": 6 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "id": 8 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 1')", - "id": "button1" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 10 - } - ], - "id": 9 - }, - { - "type": 3, - "textContent": "\n ", - "id": 11 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 2')", - "id": "button2" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "id": 13 - } - ], - "id": 12 - }, - { - "type": 3, - "textContent": "\n ", - "id": 14 - }, - { - "type": 3, - "textContent": "\n\n", - "id": 15 - } - ], - "id": 7 - } - ], - "id": 3 - } - ], - "id": 1 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] -} \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json deleted file mode 100644 index 13e5b1b70103..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts-snapshots/snapshot-2-webkit.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "type": 2, - "data": { - "node": { - "type": 0, - "childNodes": [ - { - "type": 1, - "name": "html", - "publicId": "", - "systemId": "", - "rootId": 16, - "id": 17 - }, - { - "type": 2, - "tagName": "html", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "head", - "attributes": {}, - "childNodes": [ - { - "type": 2, - "tagName": "meta", - "attributes": { - "charset": "utf-8" - }, - "childNodes": [], - "rootId": 16, - "id": 20 - } - ], - "rootId": 16, - "id": 19 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 21 - }, - { - "type": 2, - "tagName": "body", - "attributes": {}, - "childNodes": [ - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 23 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 1')", - "id": "button1" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "rootId": 16, - "id": 25 - } - ], - "rootId": 16, - "id": 24 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 26 - }, - { - "type": 2, - "tagName": "button", - "attributes": { - "onclick": "console.log('Test log 2')", - "id": "button2" - }, - "childNodes": [ - { - "type": 3, - "textContent": "***** **", - "rootId": 16, - "id": 28 - } - ], - "rootId": 16, - "id": 27 - }, - { - "type": 3, - "textContent": "\n ", - "rootId": 16, - "id": 29 - }, - { - "type": 3, - "textContent": "\n\n", - "rootId": 16, - "id": 30 - } - ], - "rootId": 16, - "id": 22 - } - ], - "rootId": 16, - "id": 18 - } - ], - "id": 16 - }, - "initialOffset": { - "left": 0, - "top": 0 - } - }, - "timestamp": [timestamp] -} \ No newline at end of file diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/init.js b/dev-packages/browser-integration-tests/suites/stacktraces/init.js index d8c94f36fdd0..ce283e32d303 100644 --- a/dev-packages/browser-integration-tests/suites/stacktraces/init.js +++ b/dev-packages/browser-integration-tests/suites/stacktraces/init.js @@ -4,4 +4,10 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/stacktraces/template.html b/dev-packages/browser-integration-tests/suites/stacktraces/template.html index d91677daaab5..39082f45e532 100644 --- a/dev-packages/browser-integration-tests/suites/stacktraces/template.html +++ b/dev-packages/browser-integration-tests/suites/stacktraces/template.html @@ -3,9 +3,7 @@ - - diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js index e32d09a13fab..f6291c3183c7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/init.js @@ -13,4 +13,10 @@ Sentry.init({ }), ], tracesSampleRate: 1, + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js index 846538e7f3f0..718f7cca0005 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/init.js @@ -14,4 +14,10 @@ Sentry.init({ }), ], tracesSampleRate: 1, + transportOptions: { + fetchOptions: { + // See: https://github.com/microsoft/playwright/issues/34497 + keepalive: false, + }, + }, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts index b127a1f674a1..ca46bc078e90 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -84,13 +84,11 @@ sentryTest( const eventData = await promise; - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || []; - expect(uiSpans?.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick', - )!; + const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; expect(eventListenerUISpan).toEqual( expect.objectContaining({ @@ -100,6 +98,7 @@ sentryTest( data: { 'browser.script.invoker': 'BUTTON#clickme.onclick', 'browser.script.invoker_type': 'event-listener', + 'code.filepath': 'https://example.com/path/to/script.js', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts index 51447ee84586..b43e383be985 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -86,13 +86,11 @@ sentryTest( const eventData = await promise; - const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')) || []; - expect(uiSpans?.length).toBeGreaterThanOrEqual(2); + expect(uiSpans.length).toBeGreaterThanOrEqual(2); - const eventListenerUISpan = (uiSpans || []).find( - span => span.data?.['browser.script.invoker'] === 'BUTTON#clickme.onclick', - )!; + const eventListenerUISpan = uiSpans.find(span => span.data['browser.script.invoker'] === 'BUTTON#clickme.onclick')!; expect(eventListenerUISpan).toEqual( expect.objectContaining({ @@ -102,6 +100,7 @@ sentryTest( data: { 'browser.script.invoker': 'BUTTON#clickme.onclick', 'browser.script.invoker_type': 'event-listener', + 'code.filepath': 'https://example.com/path/to/script.js', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts index 2343d77f55c7..9478b8f833f7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts @@ -1,24 +1,44 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; +import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -sentryTest('should add resource spans to pageload transaction', async ({ getLocalTestUrl, page }) => { +sentryTest('should add resource spans to pageload transaction', async ({ getLocalTestUrl, page, browserName }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } + const isWebkitRun = browserName === 'webkit'; + // Intercepting asset requests to avoid network-related flakiness and random retries (on Firefox). await page.route('https://example.com/path/to/image.svg', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/image.svg` }), + route.fulfill({ + path: `${__dirname}/assets/image.svg`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'image/svg+xml', + }, + }), ); await page.route('https://example.com/path/to/script.js', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/script.js` }), + route.fulfill({ + path: `${__dirname}/assets/script.js`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'application/javascript', + }, + }), ); await page.route('https://example.com/path/to/style.css', (route: Route) => - route.fulfill({ path: `${__dirname}/assets/style.css` }), + route.fulfill({ + path: `${__dirname}/assets/style.css`, + headers: { + 'Timing-Allow-Origin': '*', + 'Content-Type': 'text/css', + }, + }), ); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -27,11 +47,14 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource')); const scriptSpans = resourceSpans?.filter(({ op }) => op === 'resource.script'); - const linkSpans = resourceSpans?.filter(({ op }) => op === 'resource.link'); - const imgSpans = resourceSpans?.filter(({ op }) => op === 'resource.img'); + const linkSpan = resourceSpans?.filter(({ op }) => op === 'resource.link')[0]; + const imgSpan = resourceSpans?.filter(({ op }) => op === 'resource.img')[0]; + + const spanId = eventData.contexts?.trace?.span_id; + const traceId = eventData.contexts?.trace?.trace_id; - expect(imgSpans).toHaveLength(1); - expect(linkSpans).toHaveLength(1); + expect(spanId).toBeDefined(); + expect(traceId).toBeDefined(); const hasCdnBundle = (process.env.PW_BUNDLE || '').startsWith('bundle'); @@ -41,11 +64,90 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca } expect(scriptSpans?.map(({ description }) => description).sort()).toEqual(expectedScripts); + expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); - const spanId = eventData.contexts?.trace?.span_id; + const customScriptSpan = scriptSpans?.find( + ({ description }) => description === 'https://example.com/path/to/script.js', + ); - expect(spanId).toBeDefined(); - expect(imgSpans?.[0].parent_span_id).toBe(spanId); - expect(linkSpans?.[0].parent_span_id).toBe(spanId); - expect(scriptSpans?.map(({ parent_span_id }) => parent_span_id)).toEqual(expectedScripts.map(() => spanId)); + expect(imgSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.img', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/image.svg', + op: 'resource.img', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(linkSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'resource.link', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/style.css', + op: 'resource.link', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); + + expect(customScriptSpan).toEqual({ + data: { + 'http.decoded_response_content_length': expect.any(Number), + 'http.response_content_length': expect.any(Number), + 'http.response_transfer_size': expect.any(Number), + 'network.protocol.name': '', + 'network.protocol.version': 'unknown', + 'sentry.op': 'resource.script', + 'sentry.origin': 'auto.resource.browser.metrics', + 'server.address': 'example.com', + 'url.same_origin': false, + 'url.scheme': 'https', + ...(!isWebkitRun && { + 'resource.render_blocking_status': 'non-blocking', + 'http.response_delivery_type': '', + }), + }, + description: 'https://example.com/path/to/script.js', + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + parent_span_id: spanId, + span_id: expect.stringMatching(/^[a-f0-9]{16}$/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts index 1cfee3b670a7..c4b41923a082 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp/test.ts @@ -5,21 +5,16 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -/* - Because we "serve" the html test page as a static file, all requests for the image - are considered 3rd party requests. So the LCP value we obtain for the image is also - considered a 3rd party LCP value, meaning `renderTime` is only set if we also - return the `Timing-Allow-Origin` header. -*/ - -sentryTest('captures LCP vitals with element details.', async ({ browserName, getLocalTestUrl, page }) => { +sentryTest('captures LCP vitals with element details', async ({ browserName, getLocalTestUrl, page }) => { if (shouldSkipTracingTest() || browserName !== 'chromium') { sentryTest.skip(); } page.route('**', route => route.continue()); page.route('**/my/image.png', async (route: Route) => { - return route.fulfill({ path: `${__dirname}/assets/sentry-logo-600x179.png` }); + return route.fulfill({ + path: `${__dirname}/assets/sentry-logo-600x179.png`, + }); }); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -31,42 +26,8 @@ sentryTest('captures LCP vitals with element details.', async ({ browserName, ge expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true); expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0); expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0); + expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0); - // renderTime is 0 because we don't return the `Timing-Allow-Origin` header - // and the image is loaded from a 3rd party origin - expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBe(0); - - // The LCP value should be the loadTime because the renderTime is not set - expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.loadTime']); + // The LCP value should be the renderTime because the renderTime is set + expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']); }); - -sentryTest( - 'captures LCP renderTime when returning Timing-Allow-Origin header.', - async ({ browserName, getLocalTestUrl, page }) => { - if (shouldSkipTracingTest() || browserName !== 'chromium') { - sentryTest.skip(); - } - - page.route('**', route => route.continue()); - page.route('**/my/image.png', async (route: Route) => { - return route.fulfill({ - path: `${__dirname}/assets/sentry-logo-600x179.png`, - headers: { 'Timing-Allow-Origin': '*' }, - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - const [eventData] = await Promise.all([getFirstSentryEnvelopeRequest(page), page.goto(url)]); - - expect(eventData.measurements).toBeDefined(); - expect(eventData.measurements?.lcp?.value).toBeDefined(); - - expect(eventData.contexts?.trace?.data?.['lcp.element'].startsWith('body >')).toBe(true); - expect(eventData.contexts?.trace?.data?.['lcp.size']).toBeGreaterThan(0); - expect(eventData.contexts?.trace?.data?.['lcp.loadTime']).toBeGreaterThan(0); - expect(eventData.contexts?.trace?.data?.['lcp.renderTime']).toBeGreaterThan(0); - - // The LCP value should be the renderTime because the renderTime is set - expect(eventData.measurements?.lcp?.value).toBeCloseTo(eventData.contexts?.trace?.data?.['lcp.renderTime']); - }, -); diff --git a/dev-packages/e2e-tests/test-applications/angular-17/package.json b/dev-packages/e2e-tests/test-applications/angular-17/package.json index 682c47d30329..e6b1df6cd387 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-17/package.json @@ -29,7 +29,7 @@ "zone.js": "~0.14.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@angular-devkit/build-angular": "^17.1.1", "@angular/cli": "^17.1.1", diff --git a/dev-packages/e2e-tests/test-applications/angular-18/package.json b/dev-packages/e2e-tests/test-applications/angular-18/package.json index aec1b1d9dac0..288b1b119912 100644 --- a/dev-packages/e2e-tests/test-applications/angular-18/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-18/package.json @@ -29,7 +29,7 @@ "zone.js": "~0.14.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@angular-devkit/build-angular": "^18.0.0", "@angular/cli": "^18.0.0", diff --git a/dev-packages/e2e-tests/test-applications/angular-19/package.json b/dev-packages/e2e-tests/test-applications/angular-19/package.json index c8ae32b52378..f7544ccb5239 100644 --- a/dev-packages/e2e-tests/test-applications/angular-19/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-19/package.json @@ -32,7 +32,7 @@ "@angular-devkit/build-angular": "^19.0.0", "@angular/cli": "^19.0.0", "@angular/compiler-cli": "^19.0.0", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/jasmine": "~5.1.0", "http-server": "^14.1.1", diff --git a/dev-packages/e2e-tests/test-applications/astro-4/package.json b/dev-packages/e2e-tests/test-applications/astro-4/package.json index b80e408b3e32..4e49cf150bb4 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/package.json +++ b/dev-packages/e2e-tests/test-applications/astro-4/package.json @@ -14,7 +14,7 @@ "dependencies": { "@astrojs/check": "0.9.2", "@astrojs/node": "8.3.4", - "@playwright/test": "^1.46.0", + "@playwright/test": "~1.50.0", "@sentry/astro": "* || latest", "@sentry-internal/test-utils": "link:../../../test-utils", "@spotlightjs/astro": "2.1.6", diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json index 170c716be756..189c2834f3ae 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/package.json +++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json @@ -13,7 +13,7 @@ "dependencies": { "@astrojs/internal-helpers": "^0.4.2", "@astrojs/node": "^9.0.0", - "@playwright/test": "^1.46.0", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/astro": "latest || *", "astro": "^5.0.3" diff --git a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json index 01375fe0c346..ab822e9d669d 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json index 67aa6bc247d5..746abf5b4cbc 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", - "@playwright/test": "^1.41.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/package.json b/dev-packages/e2e-tests/test-applications/create-next-app/package.json index e70e7ed4c797..3e8416b3aaee 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/package.json +++ b/dev-packages/e2e-tests/test-applications/create-next-app/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json index b37c7a8c0705..20433c4cd15f 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/package.json @@ -25,7 +25,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "^2.7.2", "@sentry/core": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json index 5c362ffb97a1..c44b7a8fe5bc 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json @@ -28,7 +28,7 @@ "source-map-support": "^0.5.21" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "^2.7.2", "@sentry/core": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json index 977408d0945a..e8a03932560a 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@remix-run/dev": "2.7.2", "@remix-run/eslint-config": "2.7.2", diff --git a/dev-packages/e2e-tests/test-applications/default-browser/package.json b/dev-packages/e2e-tests/test-applications/default-browser/package.json index a147fa576d8a..5f70156c1e1c 100644 --- a/dev-packages/e2e-tests/test-applications/default-browser/package.json +++ b/dev-packages/e2e-tests/test-applications/default-browser/package.json @@ -28,7 +28,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "webpack": "^5.91.0", "serve": "14.0.1", diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json index a0c0b4101d09..d29714415897 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -24,7 +24,7 @@ "@ember/optional-features": "~2.0.0", "@glimmer/component": "~1.1.2", "@glimmer/tracking": "~1.1.2", - "@playwright/test": "~1.44.1", + "@playwright/test": "~1.50.0", "@ember/string": "~3.1.1", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/ember": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json index b96b70876f53..a7b63be1218f 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -50,7 +50,7 @@ "loader.js": "^4.7.0", "tracked-built-ins": "^3.3.0", "webpack": "^5.91.0", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry/ember": "latest || *", "@sentry-internal/test-utils": "link:../../../test-utils", "@tsconfig/ember": "^3.0.6", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json index 2c749afae8a4..a4a046b9106d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-8/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/package.json @@ -25,7 +25,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json index dea9e11f1423..816c5ec0fcce 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/package.json @@ -27,7 +27,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json index 0de1aa1b3e6a..3d367978c6c7 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json @@ -25,7 +25,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json index 7ea84e7afe05..133c56648f1f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json @@ -24,7 +24,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json index d456c22370df..88092675cc98 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/package.json @@ -26,7 +26,7 @@ "fastify": "^4.28.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json index d6e198cd7567..0f645056e025 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-graphql/package.json @@ -27,7 +27,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json index 34d6004ebd8e..791c3df2305b 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/package.json @@ -23,7 +23,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json index a54eb72275a8..dfd015acade9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json @@ -23,7 +23,7 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json index d81ca4fa6443..cb78ab4ecb4b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/browser-utils": "latest || *", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx index 0d877296cced..c73e6f79db00 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/app/request-instrumentation/page.tsx @@ -3,9 +3,9 @@ import https from 'https'; export const dynamic = 'force-dynamic'; export default async function Page() { - await fetch('https://example.com/', { cache: 'no-cache' }).then(res => res.text()); + await fetch('https://github.com/', { cache: 'no-cache' }).then(res => res.text()); await new Promise(resolve => { - https.get('https://example.com/', res => { + https.get('https://github.com/', res => { res.on('data', () => { // Noop consuming some data so that request can close :) }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json index b041f71f7f7e..a1e33dbb10ec 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts index d26d4e871b6e..2446ffa68659 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/request-instrumentation.test.ts @@ -19,7 +19,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => { 'sentry.op': 'http.client', 'sentry.origin': 'auto.http.otel.node_fetch', }), - description: 'GET https://example.com/', + description: 'GET https://github.com/', }), ); @@ -30,7 +30,7 @@ test('Should send a transaction with a fetch span', async ({ page }) => { 'sentry.op': 'http.client', 'sentry.origin': 'auto.http.otel.http', }), - description: 'GET https://example.com/', + description: 'GET https://github.com/', }), ); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 9e99e182f4db..66b38e2e5cc0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -24,7 +24,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts index 955988101724..70564e0c12bb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts @@ -10,7 +10,7 @@ const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8'); // Assert that there was no funky build time warning when we are on a stable (pinned) version if (nextjsVersion !== 'latest' && !nextjsVersion.includes('-canary') && !nextjsVersion.includes('-rc')) { - assert.doesNotMatch(buildStderr, /Import trace for requested module/); // This is Next.js/Webpack speech for "something is off" + assert.doesNotMatch(buildStderr, /Import trace for requested module/, `Build warning in output:\n${buildStderr}`); // This is Next.js/Webpack speech for "something is off" } // Assert that all static components stay static and all dynamic components stay dynamic diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json index 492f8d94ef71..f779d9ece306 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json @@ -25,7 +25,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json index 1ebef0ce37ae..4c6f9f281406 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json @@ -29,7 +29,7 @@ "zod": "^3.23.3" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/eslint": "^8.56.10", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json index d0d1a3abe349..d2cdcfb89561 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json @@ -23,7 +23,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry-internal/feedback": "latest || *", "@sentry-internal/replay-canvas": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-connect/package.json b/dev-packages/e2e-tests/test-applications/node-connect/package.json index 743b079c8af1..f882e55d8797 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect/package.json +++ b/dev-packages/e2e-tests/test-applications/node-connect/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json index 363c1e06636c..c1544b4df93b 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json index 6156211e27f8..2d61a1c4f7ef 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json index 03f483307290..fac430272a92 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json index 844ca51fd038..81a659a7dbfb 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/package.json @@ -14,7 +14,7 @@ "express": "4.20.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json index b6daa54355f3..e9989a5790a6 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-incorrect-instrumentation/package.json @@ -22,7 +22,7 @@ "zod": "~3.22.4" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json index 5f3442eb3af9..338a7ccbd604 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/package.json @@ -19,7 +19,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/node-express/package.json b/dev-packages/e2e-tests/test-applications/node-express/package.json index 37d5ed592db3..c1a608833bdb 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express/package.json @@ -22,7 +22,7 @@ "zod": "~3.22.4" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "resolutions": { diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json index f720b711d1fa..f9f4f726eb0e 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.2" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/package.json b/dev-packages/e2e-tests/test-applications/node-fastify/package.json index 255238f0f74f..9b9f584cc359 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify/package.json @@ -20,7 +20,7 @@ "ts-node": "10.9.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/package.json b/dev-packages/e2e-tests/test-applications/node-hapi/package.json index 2eda8acc7589..3f18419d1d4a 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/package.json +++ b/dev-packages/e2e-tests/test-applications/node-hapi/package.json @@ -16,7 +16,7 @@ "@sentry/node": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-koa/package.json b/dev-packages/e2e-tests/test-applications/node-koa/package.json index 9bcb3cc8754b..55299faa3e4a 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/package.json +++ b/dev-packages/e2e-tests/test-applications/node-koa/package.json @@ -18,7 +18,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json index ff4017ca0f3c..c5fe928a931e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json @@ -21,7 +21,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json index 1240dc5e9923..efb74e7346ad 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index a45eb3470c7d..b097e5b91930 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -25,7 +25,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel/package.json b/dev-packages/e2e-tests/test-applications/node-otel/package.json index e01886a3318f..3112ce669479 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json @@ -22,7 +22,7 @@ "typescript": "~5.0.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/.gitignore rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/.npmrc rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/build-cjs.mjs b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/build-cjs.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/build-cjs.mjs rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/build-cjs.mjs diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.ts b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/index.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.ts rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/index.ts diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json new file mode 100644 index 000000000000..aa729a030a5d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-profiling-cjs", + "version": "1.0.0", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "node dist/cjs/index.js", + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install && node build-cjs.mjs", + "test:assert": "pnpm run typecheck && pnpm run test" + }, + "dependencies": { + "@playwright/test": "~1.50.0", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "esbuild": "0.20.0", + "typescript": "^5.7.3" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/tsconfig.json rename to dev-packages/e2e-tests/test-applications/node-profiling-cjs/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc new file mode 100644 index 000000000000..949fbddc2343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/.npmrc @@ -0,0 +1,2 @@ +# @sentry:registry=http://127.0.0.1:4873 +# @sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/__tests__/electron.spec.js b/dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js similarity index 95% rename from dev-packages/e2e-tests/test-applications/node-profiling/__tests__/electron.spec.js rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js index 4519220008d1..49c0666fc1f7 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling/__tests__/electron.spec.js +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/__tests__/electron.spec.js @@ -7,6 +7,7 @@ test('an h1 contains hello world"', async () => { process: { env: { ...process.env, + NODE_ENV: 'development', }, }, }); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.electron.js b/dev-packages/e2e-tests/test-applications/node-profiling-electron/index.electron.js similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.electron.js rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/index.electron.js diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/index.html b/dev-packages/e2e-tests/test-applications/node-profiling-electron/index.html similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/index.html rename to dev-packages/e2e-tests/test-applications/node-profiling-electron/index.html diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json new file mode 100644 index 000000000000..dc176c847538 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json @@ -0,0 +1,24 @@ +{ + "name": "node-profiling-electron", + "version": "1.0.0", + "private": true, + "scripts": { + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install", + "test:assert": "$(pnpm bin)/electron-rebuild && pnpm playwright test" + }, + "dependencies": { + "@electron/rebuild": "^3.7.0", + "@playwright/test": "~1.50.0", + "@sentry/electron": "latest || *", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "electron": "^33.2.0" + }, + "volta": { + "extends": "../../package.json" + }, + "sentryTest": { + "skip": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc new file mode 100644 index 000000000000..949fbddc2343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/.npmrc @@ -0,0 +1,2 @@ +# @sentry:registry=http://127.0.0.1:4873 +# @sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/build-esm.mjs b/dev-packages/e2e-tests/test-applications/node-profiling-esm/build-esm.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/node-profiling/build-esm.mjs rename to dev-packages/e2e-tests/test-applications/node-profiling-esm/build-esm.mjs diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts b/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts new file mode 100644 index 000000000000..e956a1d9de33 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/index.ts @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/node'; +import { nodeProfilingIntegration } from '@sentry/profiling-node'; + +const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +Sentry.init({ + dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', + integrations: [nodeProfilingIntegration()], + tracesSampleRate: 1.0, + profilesSampleRate: 1.0, +}); + +Sentry.startSpan({ name: 'Precompile test' }, async () => { + await wait(500); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json new file mode 100644 index 000000000000..b633df2df172 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-profiling-esm", + "version": "1.0.0", + "private": true, + "scripts": { + "typecheck": "tsc --noEmit", + "test": "node dist/esm/index.mjs", + "clean": "npx rimraf node_modules dist", + "test:build": "pnpm install && node build-esm.mjs", + "test:assert": "pnpm run typecheck && pnpm run test" + }, + "dependencies": { + "@playwright/test": "~1.50.0", + "@sentry/node": "latest || *", + "@sentry/profiling-node": "latest || *", + "esbuild": "0.20.0", + "typescript": "^5.7.3" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json new file mode 100644 index 000000000000..1308ed76609c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["es2018"], + "strict": true, + "outDir": "dist", + "target": "ESNext", + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["index.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/package.json b/dev-packages/e2e-tests/test-applications/node-profiling/package.json deleted file mode 100644 index d78ca10fa25d..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-profiling/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "node-profiling", - "version": "1.0.0", - "private": true, - "scripts": { - "typecheck": "tsc --noEmit", - "build": "node build-cjs.mjs && node build-esm.mjs", - "test": "node dist/cjs/index.js && node --experimental-require-module dist/cjs/index.js && node dist/esm/index.mjs", - "clean": "npx rimraf node_modules dist", - "test:electron": "$(pnpm bin)/electron-rebuild && playwright test", - "test:build": "pnpm run typecheck && pnpm run build", - "test:assert": "pnpm run test && pnpm run test:electron" - }, - "dependencies": { - "@electron/rebuild": "^3.7.0", - "@playwright/test": "^1.48.2", - "@sentry/electron": "latest || *", - "@sentry/node": "latest || *", - "@sentry/profiling-node": "latest || *", - "electron": "^33.2.0", - "esbuild": "0.20.0" - }, - "volta": { - "extends": "../../package.json" - }, - "sentryTest": { - "skip": true - } -} diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json index ac18cebec975..8555f0da7a58 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json index 54bacf4ee358..7fb034434fdf 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json index 9d3dc0066912..1e6590da7a10 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" } } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json index 80b76aed58ac..df88dd717a8d 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.1", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" } } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json index 0a278f07eedd..a1cbc9164e9e 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@nuxt/test-utils": "^3.14.2", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/react-17/package.json b/dev-packages/e2e-tests/test-applications/react-17/package.json index ab3022bb3c80..c9273d01f090 100644 --- a/dev-packages/e2e-tests/test-applications/react-17/package.json +++ b/dev-packages/e2e-tests/test-applications/react-17/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-19/package.json b/dev-packages/e2e-tests/test-applications/react-19/package.json index 1abc74715831..e2f4ec9cfd16 100644 --- a/dev-packages/e2e-tests/test-applications/react-19/package.json +++ b/dev-packages/e2e-tests/test-applications/react-19/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json index a4e7dae6d1e2..fae000b256e3 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/package.json @@ -30,7 +30,7 @@ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx index 88f8cfa502ec..c7ad16eebcf7 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/index.tsx @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/react'; -import React from 'react'; +import React, { lazy, Suspense } from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider, @@ -42,6 +42,7 @@ Sentry.init({ }); const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter); +const LazyLoadedUser = lazy(() => import('./pages/LazyLoadedUser')); const router = sentryCreateBrowserRouter( [ @@ -49,6 +50,14 @@ const router = sentryCreateBrowserRouter( path: '/', element: , }, + { + path: '/lazy-loaded-user/*', + element: ( + Loading...}> + + + ), + }, { path: '/user/:id', element: , diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx index d6b71a1d1279..12bfb12ec3a9 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/Index.tsx @@ -16,6 +16,9 @@ const Index = () => { navigate + + lazy navigate + ); }; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx new file mode 100644 index 000000000000..1410df69124b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/src/pages/LazyLoadedInnerRoute.tsx @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/react'; +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Route, Routes } from 'react-router-dom'; + +const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes); + +const InnerRoute = () => ( + + I am a lazy loaded user

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

}> + + + } + /> +
+ ); +}; + +export default LazyLoadedUser; diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts index 5ecd098daf94..c35d731915d6 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/tests/transactions.test.ts @@ -76,3 +76,105 @@ test('Captures a navigation transaction', async ({ page }) => { expect(transactionEvent.spans).toEqual([]); }); + +test('Captures a lazy pageload transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/lazy-loaded-user/5/foo'); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'pageload', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.pageload.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); + +test('Captures a lazy navigation transaction', async ({ page }) => { + const transactionEventPromise = waitForTransaction('react-create-browser-router', event => { + return event.contexts?.trace?.op === 'navigation'; + }); + + await page.goto('/'); + const linkElement = page.locator('id=lazy-navigation'); + await linkElement.click(); + + const transactionEvent = await transactionEventPromise; + expect(transactionEvent.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'navigation', + 'sentry.origin': 'auto.navigation.react.reactrouter_v6', + 'sentry.sample_rate': 1, + 'sentry.source': 'route', + }), + op: 'navigation', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.navigation.react.reactrouter_v6', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: '/lazy-loaded-user/:id/:innerId', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(await page.innerText('id=content')).toContain('I am a lazy loaded user'); + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + // This one is the outer lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + // This one is the inner lazy route + expect.objectContaining({ + op: 'resource.script', + origin: 'auto.resource.browser.metrics', + }), + ]), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json index 757b27c65b84..b086b8228aec 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/package.json @@ -41,7 +41,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json index dc6c9b4340f0..1f00f44ff43b 100644 --- a/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json +++ b/dev-packages/e2e-tests/test-applications/react-create-memory-router/package.json @@ -30,7 +30,7 @@ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/package.json b/dev-packages/e2e-tests/test-applications/react-router-5/package.json index 16c7f16df16d..f0c52de6990e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-5/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-5/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json index 3c3323d2c4cc..768c836abba8 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1", "npm-run-all2": "^6.2.0" diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json index 7f68ec2a7ec4..fdb36e4d3b7b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json @@ -41,7 +41,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/package.json b/dev-packages/e2e-tests/test-applications/react-router-6/package.json index 575f417e2bc2..0a9f0cd2a5c0 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6/package.json @@ -44,7 +44,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "serve": "14.0.1", "npm-run-all2": "^6.2.0" diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json index 1c505f3195a3..e8730c8f9091 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa/package.json @@ -11,7 +11,7 @@ "react-router": "^7.0.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "vite": "^6.0.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json index 35b01833874a..07eec924f7b7 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json @@ -42,7 +42,7 @@ ] }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "serve": "14.0.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts index d9c3e09f2ad2..dc33d271bc18 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/tests/send-to-sentry.test.ts @@ -190,7 +190,7 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { if (response.ok) { const data = await response.json(); - return data[0]; + return { data: data[0], length: data[0].length }; } return response.status; @@ -199,5 +199,6 @@ test('Sends a Replay recording to Sentry', async ({ browser }) => { timeout: EVENT_POLLING_TIMEOUT, }, ) - .toEqual(ReplayRecordingData); + // Check that that all expected data is present but relax the order to avoid flakes + .toEqual({ data: expect.arrayContaining(ReplayRecordingData), length: ReplayRecordingData.length }); }); diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json index 0c727d46de50..36d0fc2854f7 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json @@ -14,7 +14,7 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "autoprefixer": "^10.4.17", diff --git a/dev-packages/e2e-tests/test-applications/solid/package.json b/dev-packages/e2e-tests/test-applications/solid/package.json index d61ac0a0a322..830450e7328e 100644 --- a/dev-packages/e2e-tests/test-applications/solid/package.json +++ b/dev-packages/e2e-tests/test-applications/solid/package.json @@ -14,7 +14,7 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "autoprefixer": "^10.4.17", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json index 62393e038dce..495b9cb8c94d 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json index 9495309f0464..57a83c35b0e8 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json index 559477a58baa..3d96e0481158 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json index f4059823617a..2bb3afaa29b1 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json @@ -15,7 +15,7 @@ "@sentry/solidstart": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.13.4", "@solidjs/start": "^1.0.2", diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/package.json b/dev-packages/e2e-tests/test-applications/svelte-5/package.json index ed6cf3ada0d7..39d6250f3b75 100644 --- a/dev-packages/e2e-tests/test-applications/svelte-5/package.json +++ b/dev-packages/e2e-tests/test-applications/svelte-5/package.json @@ -13,7 +13,7 @@ "test:assert": "pnpm test:prod" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/vite-plugin-svelte": "^3.0.2", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json index 88d9a37ab98c..4fa8983ae153 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/package.json @@ -19,7 +19,7 @@ "@spotlightjs/spotlight": "2.0.0-alpha.1" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.gitignore b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.gitignore rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.npmrc b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/.npmrc rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/README.md b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/README.md similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/README.md rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/README.md diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json similarity index 90% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json index 5a2d9ce7b4d5..344eba5705fd 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/package.json @@ -1,5 +1,5 @@ { - "name": "sveltekit-2-svelte-5", + "name": "sveltekit-2.5.0-twp", "version": "0.0.1", "private": true, "scripts": { @@ -18,11 +18,11 @@ "@sentry/sveltekit": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", + "@sveltejs/kit": "2.5.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^5.0.0-next.115", "svelte-check": "^3.6.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/playwright.config.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/playwright.config.mjs rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/playwright.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.d.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.d.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.d.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.d.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.html b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.html similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/app.html rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/app.html diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.client.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.client.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.client.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.client.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.server.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/hooks.server.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/hooks.server.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+layout.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+layout.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+layout.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+page.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/+page.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.server.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.server.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.server.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.svelte similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/src/routes/errors/+page.svelte rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/src/routes/errors/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs similarity index 72% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs index 01e1095d6956..1ba4492f150a 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/start-event-proxy.mjs @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, - proxyServerName: 'sveltekit-2-twp', + proxyServerName: 'sveltekit-2.5.0-twp', }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/static/favicon.png b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/static/favicon.png similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/static/favicon.png rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/static/favicon.png diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/svelte.config.js similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/svelte.config.js rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/svelte.config.js diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts similarity index 92% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts index 0e16a2588982..984af2ec23a6 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/errors.test.ts @@ -2,11 +2,11 @@ import { expect, test } from '@playwright/test'; import { waitForError } from '@sentry-internal/test-utils'; test('errors on frontend and backend are connected by the same trace', async ({ page }) => { - const clientErrorPromise = waitForError('sveltekit-2-twp', evt => { + const clientErrorPromise = waitForError('sveltekit-2.5.0-twp', evt => { return evt.exception?.values?.[0].value === 'Client Error'; }); - const serverErrorPromise = waitForError('sveltekit-2-twp', evt => { + const serverErrorPromise = waitForError('sveltekit-2.5.0-twp', evt => { return evt.exception?.values?.[0].value === 'No search query provided'; }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts new file mode 100644 index 000000000000..c1131913d057 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/sdk.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; + +test.describe('SDK-internal behavior', () => { + test('Injects fetch proxy script for SvelteKit<2.16.0', async ({ page }) => { + await page.goto('/'); + + const sentryCarrier = await page.evaluate('typeof window.__SENTRY__'); + const proxyHandle = await page.evaluate('typeof window._sentryFetchProxy'); + + // sanity check + expect(sentryCarrier).toBe('object'); + + // fetch proxy script ran + expect(proxyHandle).toBe('function'); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/tracing.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/tracing.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tests/tracing.test.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tests/tracing.test.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tsconfig.json b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/tsconfig.json rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-twp/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/vite.config.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-2-twp/vite.config.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/vite.config.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json index 3f2f87500e25..91bc78290199 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -18,12 +18,12 @@ "@sentry/sveltekit": "latest || *" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-node": "^2.0.0", - "@sveltejs/kit": "^2.5.0", + "@sveltejs/kit": "^2.16.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^4.2.8", "svelte-check": "^3.6.0", diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts new file mode 100644 index 000000000000..f35a81e91238 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/tests/sdk.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from '@playwright/test'; +import { waitForInitialPageload } from './utils'; + +test.describe('SDK-internal behavior', () => { + test("Doesn't inject fetch proxy script for SvelteKit>=2.16.0", async ({ page }) => { + await waitForInitialPageload(page, { route: '/' }); + const sentryCarrier = await page.evaluate('typeof window.__SENTRY__'); + const proxyHandle = await page.evaluate('typeof window._sentryFetchProxy'); + + // sanity check + expect(sentryCarrier).toBe('object'); + + // fetch proxy script didn't run + expect(proxyHandle).toBe('undefined'); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json index 96a26ee98447..ae25dcc0870b 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json @@ -25,7 +25,7 @@ "@vitejs/plugin-react-swc": "^3.5.0", "typescript": "^5.2.2", "vite": "^5.4.11", - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/vue-3/package.json b/dev-packages/e2e-tests/test-applications/vue-3/package.json index 3a2c38f43633..54cbc679e0f4 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/package.json +++ b/dev-packages/e2e-tests/test-applications/vue-3/package.json @@ -21,7 +21,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "latest || *", "@tsconfig/node20": "^20.1.2", diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/package.json b/dev-packages/e2e-tests/test-applications/webpack-4/package.json index 95d3d5c39a3e..c143fd9ab82e 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-4/package.json +++ b/dev-packages/e2e-tests/test-applications/webpack-4/package.json @@ -8,7 +8,7 @@ "test:assert": "playwright test" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/browser": "latest || *", "babel-loader": "^8.0.0", diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/package.json b/dev-packages/e2e-tests/test-applications/webpack-5/package.json index 389f817292cd..00cff9198262 100644 --- a/dev-packages/e2e-tests/test-applications/webpack-5/package.json +++ b/dev-packages/e2e-tests/test-applications/webpack-5/package.json @@ -8,7 +8,7 @@ "test:assert": "playwright test" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/browser": "latest || *", "webpack": "^5.91.0", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 571397d12c8c..7b7fe3482200 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -16,11 +16,12 @@ "build:types": "tsc -p tsconfig.types.json", "clean": "rimraf -g **/node_modules && run-p clean:script", "clean:script": "node scripts/clean.js", - "prisma:init": "cd suites/tracing/prisma-orm && yarn && yarn setup", + "prisma-v5:init": "cd suites/tracing/prisma-orm-v5 && yarn && yarn setup", + "prisma-v6:init": "cd suites/tracing/prisma-orm-v6 && yarn && yarn setup", "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", "type-check": "tsc", - "pretest": "run-s --silent prisma:init", + "pretest": "run-s --silent prisma-v5:init prisma-v6:init", "test": "jest --config ./jest.config.js", "test:watch": "yarn test --watch" }, @@ -30,7 +31,6 @@ "@nestjs/common": "10.4.6", "@nestjs/core": "10.4.6", "@nestjs/platform-express": "10.4.6", - "@prisma/client": "6.2.1", "@sentry/aws-serverless": "9.0.0-alpha.0", "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", diff --git a/dev-packages/node-integration-tests/suites/anr/basic-session.js b/dev-packages/node-integration-tests/suites/anr/basic-session.js index 9700131a6040..7971d547c884 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic-session.js +++ b/dev-packages/node-integration-tests/suites/anr/basic-session.js @@ -9,7 +9,7 @@ setTimeout(() => { Sentry.init({ dsn: process.env.SENTRY_DSN, - release: '1.0', + release: '1.0.0', integrations: [Sentry.anrIntegration({ captureStackTrace: true, anrThreshold: 100 })], }); diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index ec980f07f123..9a91d4f205c6 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -188,6 +188,9 @@ describe('should report ANR when event loop blocked', () => { session: { status: 'abnormal', abnormal_mechanism: 'anr_foreground', + attrs: { + release: '1.0.0', + }, }, }) .expect({ event: ANR_EVENT_WITH_SCOPE }) diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml similarity index 81% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml index 45caa4bb3179..37d45547b537 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/docker-compose.yml +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/docker-compose.yml @@ -4,7 +4,7 @@ services: db: image: postgres:13 restart: always - container_name: integration-tests-prisma + container_name: integration-tests-prisma-v5 ports: - '5433:5432' environment: diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json new file mode 100644 index 000000000000..b8721038c83b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/package.json @@ -0,0 +1,22 @@ +{ + "name": "sentry-prisma-test", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": ">=18" + }, + "scripts": { + "db-up": "docker compose up -d", + "generate": "prisma generate", + "migrate": "prisma migrate dev -n sentry-test", + "setup": "run-s --silent db-up generate migrate" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@prisma/client": "5.22.0", + "prisma": "5.22.0" + } +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/migration_lock.toml rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/migration_lock.toml diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/migrations/sentry_test/migration.sql rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/migrations/sentry_test/migration.sql diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma similarity index 90% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma index 4363c97738ee..52682f1b6cf5 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/prisma/schema.prisma +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/prisma/schema.prisma @@ -5,6 +5,7 @@ datasource db { generator client { provider = "prisma-client-js" + previewFeatures = ["tracing"] } model User { diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js new file mode 100644 index 000000000000..767a6f27bdaa --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/scenario.js @@ -0,0 +1,52 @@ +const Sentry = require('@sentry/node'); +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.prismaIntegration()], +}); + +const { randomBytes } = require('crypto'); +const { PrismaClient } = require('@prisma/client'); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +async function run() { + const client = new PrismaClient(); + + await Sentry.startSpanManual( + { + name: 'Test Transaction', + op: 'transaction', + }, + async span => { + await client.user.create({ + data: { + name: 'Tilda', + email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, + }, + }); + + await client.user.findMany(); + + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, + }, + }); + + setTimeout(async () => { + span.end(); + await client.$disconnect(); + }, 500); + }, + ); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts new file mode 100644 index 000000000000..0ece02f2f1cb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts @@ -0,0 +1,69 @@ +import { createRunner } from '../../../utils/runner'; + +describe('Prisma ORM Tests', () => { + test('CJS - should instrument PostgreSQL queries from Prisma ORM', done => { + createRunner(__dirname, 'scenario.js') + .expect({ + transaction: transaction => { + expect(transaction.transaction).toBe('Test Transaction'); + const spans = transaction.spans || []; + expect(spans.length).toBeGreaterThanOrEqual(5); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + method: 'create', + model: 'User', + name: 'User.create', + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:operation', + status: 'ok', + }), + ); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:serialize', + status: 'ok', + }), + ); + + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:connect', + status: 'ok', + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + method: 'findMany', + model: 'User', + name: 'User.findMany', + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:operation', + status: 'ok', + }), + ); + expect(spans).toContainEqual( + expect.objectContaining({ + data: { + 'sentry.origin': 'auto.db.otel.prisma', + }, + description: 'prisma:client:serialize', + status: 'ok', + }), + ); + }, + }) + .start(done); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock new file mode 100644 index 000000000000..860aa032d6cc --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/yarn.lock @@ -0,0 +1,58 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@prisma/client@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.22.0.tgz#da1ca9c133fbefe89e0da781c75e1c59da5f8802" + integrity sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA== + +"@prisma/debug@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.22.0.tgz#58af56ed7f6f313df9fb1042b6224d3174bbf412" + integrity sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ== + +"@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2": + version "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz#d534dd7235c1ba5a23bacd5b92cc0ca3894c28f4" + integrity sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ== + +"@prisma/engines@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.22.0.tgz#28f3f52a2812c990a8b66eb93a0987816a5b6d84" + integrity sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA== + dependencies: + "@prisma/debug" "5.22.0" + "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/fetch-engine" "5.22.0" + "@prisma/get-platform" "5.22.0" + +"@prisma/fetch-engine@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz#4fb691b483a450c5548aac2f837b267dd50ef52e" + integrity sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA== + dependencies: + "@prisma/debug" "5.22.0" + "@prisma/engines-version" "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2" + "@prisma/get-platform" "5.22.0" + +"@prisma/get-platform@5.22.0": + version "5.22.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.22.0.tgz#fc675bc9d12614ca2dade0506c9c4a77e7dddacd" + integrity sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q== + dependencies: + "@prisma/debug" "5.22.0" + +fsevents@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +prisma@5.22.0: + version "5.22.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.22.0.tgz#1f6717ff487cdef5f5799cc1010459920e2e6197" + integrity sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A== + dependencies: + "@prisma/engines" "5.22.0" + optionalDependencies: + fsevents "2.3.3" diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml new file mode 100644 index 000000000000..ddab7cb9c563 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma-v6 + ports: + - '5434:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/package.json rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/package.json diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000000..fbffa92c2bb7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql new file mode 100644 index 000000000000..8619aaceb2b0 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/migrations/sentry_test/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "email" TEXT NOT NULL, + "name" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma new file mode 100644 index 000000000000..71a4923afb8c --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/prisma/schema.prisma @@ -0,0 +1,15 @@ +datasource db { + url = "postgresql://prisma:prisma@localhost:5434/tests" + provider = "postgresql" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + email String @unique + name String? +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.js rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/scenario.js diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/prisma-orm/yarn.lock rename to dev-packages/node-integration-tests/suites/tracing/prisma-orm-v6/yarn.lock diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts new file mode 100644 index 000000000000..77884dab80c7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/scenario.ts @@ -0,0 +1,61 @@ +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracePropagationTargets: [/\/v0/, 'v1'], + integrations: [Sentry.httpIntegration({ spans: false })], + transport: loggingTransport, + // Ensure this gets a correct hint + beforeBreadcrumb(breadcrumb, hint) { + breadcrumb.data = breadcrumb.data || {}; + const req = hint?.request as { path?: string }; + breadcrumb.data.ADDED_PATH = req?.path; + return breadcrumb; + }, +}); + +import * as http from 'http'; + +async function run(): Promise { + Sentry.addBreadcrumb({ message: 'manual breadcrumb' }); + + await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`); + await makeHttpGet(`${process.env.SERVER_URL}/api/v1`); + await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`); + await makeHttpRequest(`${process.env.SERVER_URL}/api/v3`); + + Sentry.captureException(new Error('foo')); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); + +function makeHttpRequest(url: string): Promise { + return new Promise(resolve => { + http + .request(url, httpRes => { + httpRes.on('data', () => { + // we don't care about data + }); + httpRes.on('end', () => { + resolve(); + }); + }) + .end(); + }); +} + +function makeHttpGet(url: string): Promise { + return new Promise(resolve => { + http.get(url, httpRes => { + httpRes.on('data', () => { + // we don't care about data + }); + httpRes.on('end', () => { + resolve(); + }); + }); + }); +} diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts new file mode 100644 index 000000000000..b85cc7913c2c --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing-no-spans/test.ts @@ -0,0 +1,95 @@ +import { createRunner } from '../../../../utils/runner'; +import { createTestServer } from '../../../../utils/server'; + +test('outgoing http requests are correctly instrumented with tracing & spans disabled', done => { + expect.assertions(11); + + createTestServer(done) + .get('/api/v0', headers => { + expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); + expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); + expect(headers['baggage']).toEqual(expect.any(String)); + }) + .get('/api/v1', headers => { + expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); + expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); + expect(headers['baggage']).toEqual(expect.any(String)); + }) + .get('/api/v2', headers => { + expect(headers['baggage']).toBeUndefined(); + expect(headers['sentry-trace']).toBeUndefined(); + }) + .get('/api/v3', headers => { + expect(headers['baggage']).toBeUndefined(); + expect(headers['sentry-trace']).toBeUndefined(); + }) + .start() + .then(([SERVER_URL, closeTestServer]) => { + createRunner(__dirname, 'scenario.ts') + .withEnv({ SERVER_URL }) + .ensureNoErrorOutput() + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'foo', + }, + ], + }, + breadcrumbs: [ + { + message: 'manual breadcrumb', + timestamp: expect.any(Number), + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v0`, + status_code: 200, + ADDED_PATH: '/api/v0', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v1`, + status_code: 200, + ADDED_PATH: '/api/v1', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v2`, + status_code: 200, + ADDED_PATH: '/api/v2', + }, + timestamp: expect.any(Number), + type: 'http', + }, + { + category: 'http', + data: { + 'http.method': 'GET', + url: `${SERVER_URL}/api/v3`, + status_code: 200, + ADDED_PATH: '/api/v3', + }, + timestamp: expect.any(Number), + type: 'http', + }, + ], + }, + }) + .start(closeTestServer); + }); +}); diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 5758b5229e80..a0d9d7e51def 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -41,10 +41,10 @@ "clean": "rimraf -g ./node_modules ./build" }, "peerDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "~1.50.0" }, "devDependencies": { - "@playwright/test": "^1.44.1", + "@playwright/test": "~1.50.0", "@sentry/core": "9.0.0-alpha.0" }, "volta": { diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index a6bd0aa35e7c..487dbee7cf4c 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -1,7 +1,5 @@ # Upgrading from 8.x to 9.x -**DISCLAIMER: THIS MIGRATION GUIDE IS WORK IN PROGRESS** - Version 9 of the Sentry SDK concerns API cleanup and compatibility updates. This update contains behavioral changes that will not be caught by TypeScript or linters, so we recommend carefully reading the section on [Behavioral Changes](#2-behavior-changes). @@ -24,7 +22,7 @@ This includes features like Nullish Coalescing (`??`), Optional Chaining (`?.`), If you observe failures due to syntax or features listed above, it may indicate that your current runtime does not support ES2020. If your runtime does not support ES2020, we recommend transpiling the SDK using Babel or similar tooling. -**Node.js:** The minimum supported Node.js version is **18.0.0**, except for ESM-only SDKs (nuxt, solidstart, astro) which require Node **18.19.1** or up. +**Node.js:** The minimum supported Node.js version is **18.0.0**, except for ESM-only SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/sveltekit`) which require Node.js version **18.19.1** or higher. We no longer test against Node 14 and Node 16 and cannot guarantee that the SDK will work as expected on these versions. **Browsers:** Due to SDK code now including ES2020 features, the minimum supported browser list now looks as follows: @@ -52,7 +50,7 @@ Support for the following frameworks and library versions are dropped: ### TypeScript Version Policy -In preparation for the OpenTelemetry SDK v2, which will raise the minimum required TypeScript version, the minimum required TypeScript version is increased to version `5.0.4` (TBD https://github.com/open-telemetry/opentelemetry-js/pull/5145). +In preparation for the OpenTelemetry SDK v2, which will raise the minimum required TypeScript version, the minimum required TypeScript version is increased to version `5.0.4`. Additionally, like the OpenTelemetry SDK, the Sentry JavaScript SDK will follow [DefinitelyType's version support policy](https://github.com/DefinitelyTyped/DefinitelyTyped#support-window) which has a support time frame of 2 years for any released version of TypeScript. @@ -71,7 +69,7 @@ Older Typescript versions _may_ still work, but we will not test them anymore an }); ``` -- Dropping spans in the `beforeSendSpan` hook is no longer possible. +- Dropping spans in the `beforeSendSpan` hook is no longer possible. This means you cannot return `null` from the `beforeSendSpan` hook anymore. - The `beforeSendSpan` hook now receives the root span as well as the child spans. - In previous versions, we determined if tracing is enabled (for Tracing Without Performance) by checking if either `tracesSampleRate` or `traceSampler` are _defined_ at all, in `Sentry.init()`. This means that e.g. the following config would lead to tracing without performance (=tracing being enabled, even if no spans would be started): @@ -154,7 +152,7 @@ Older Typescript versions _may_ still work, but we will not test them anymore an - The `sentry` property on the Next.js config object has officially been discontinued. Pass options to `withSentryConfig` directly. -### All Meta-Framework SDKs (`@sentry/astro`, `@sentry/nuxt`, `@sentry/solidstart`) +### All Meta-Framework SDKs (`@sentry/astro`, `@sentry/nextjs`, `@sentry/nuxt`, `@sentry/sveltekit`, `@sentry/solidstart`) - Updated source map generation to respect the user-provided value of your build config, such as `vite.build.sourcemap`: @@ -170,10 +168,6 @@ The `componentStack` field in the `ErrorBoundary` component is now typed as `str In the `onUnmount` lifecycle method, the `componentStack` field is now typed as `string | null`. The `componentStack` is `null` when no error has been thrown at time of unmount. -### Uncategorized (TODO) - -TODO - ## 3. Package Removals As part of an architectural cleanup, we deprecated the following packages: @@ -190,27 +184,34 @@ You may experience slight compatibility issues in the future by using it. We decided to keep this package around to temporarily lessen the upgrade burden. It will be removed in a future major version. -## 4. Removal of Deprecated APIs (TODO) +## 4. Removal of Deprecated APIs ### `@sentry/core` / All SDKs +- **The metrics API has been removed from the SDK.** + + The Sentry metrics beta has ended and the metrics API has been removed from the SDK. Learn more in the Sentry [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th). + - The `debugIntegration` has been removed. To log outgoing events, use [Hook Options](https://docs.sentry.io/platforms/javascript/configuration/options/#hooks) (`beforeSend`, `beforeSendTransaction`, ...). + - The `sessionTimingIntegration` has been removed. To capture session durations alongside events, use [Context](https://docs.sentry.io/platforms/javascript/enriching-events/context/) (`Sentry.setContext()`). + - The `addOpenTelemetryInstrumentation` method has been removed. Use the `openTelemetryInstrumentations` option in `Sentry.init()` or your custom Sentry Client instead. -```js -import * as Sentry from '@sentry/node'; + ```js + import * as Sentry from '@sentry/node'; -// before -Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation()); + // before + Sentry.addOpenTelemetryInstrumentation(new GenericPoolInstrumentation()); -// after -Sentry.init({ - openTelemetryInstrumentations: [new GenericPoolInstrumentation()], -}); -``` + // after + Sentry.init({ + openTelemetryInstrumentations: [new GenericPoolInstrumentation()], + }); + ``` - The `DEFAULT_USER_INCLUDES` constant has been removed. + - The `getCurrentHub()`, `Hub` and `getCurrentHubShim()` APIs have been removed. They were on compatibility life support since the release of v8 and have now been fully removed from the SDK. ### `@sentry/browser` @@ -234,12 +235,27 @@ Sentry.init({ - The `addNormalizedRequestDataToEvent` method has been removed. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. - A `sampleRand` field on `PropagationContext` is now required. This is relevant if you used `scope.setPropagationContext(...)` -#### Other/Internal Changes +### `@sentry/solidstart` -The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior. +- The `sentrySolidStartVite` plugin is no longer exported. Instead, wrap the SolidStart config with `withSentry` and + provide Sentry options as the second parameter. -- `client._prepareEvent()` now requires a currentScope & isolationScope to be passed as last arugments -- `client.recordDroppedEvent()` no longer accepts an `event` as third argument. The event was no longer used for some time, instead you can (optionally) pass a count of dropped events as third argument. + ```ts + // app.config.ts + import { defineConfig } from '@solidjs/start/config'; + import { withSentry } from '@sentry/solidstart'; + + export default defineConfig( + withSentry( + { + /* SolidStart config */ + }, + { + /* Sentry build-time config (like project and org) */ + }, + ), + ); + ``` ### `@sentry/nestjs` @@ -262,7 +278,7 @@ The following changes are unlikely to affect users of the SDK. They are listed h - The `wrapUseRoutes` method has been removed. Use the `wrapUseRoutesV6` or `wrapUseRoutesV7` methods instead depending on what version of react router you are using. - The `wrapCreateBrowserRouter` method has been removed. Use the `wrapCreateBrowserRouterV6` or `wrapCreateBrowserRouterV7` methods depending on what version of react router you are using. -## `@sentry/vue` +### `@sentry/vue` - The options `tracingOptions`, `trackComponents`, `timeout`, `hooks` have been removed everywhere except in the `tracingOptions` option of `vueIntegration()`. @@ -297,6 +313,13 @@ The following changes are unlikely to affect users of the SDK. They are listed h - The `fetchProxyScriptNonce` option in `sentryHandle()` was removed due to security concerns. If you previously specified this option for your CSP policy, specify a [script hash](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#configure-csp-for-client-side-fetch-instrumentation) in your CSP config or [disable](https://docs.sentry.io/platforms/javascript/guides/sveltekit/manual-setup/#disable-client-side-fetch-proxy-script) the injection of the script entirely. +#### Other/Internal Changes + +The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior. + +- `client._prepareEvent()` now requires a currentScope & isolationScope to be passed as last arugments +- `client.recordDroppedEvent()` no longer accepts an `event` as third argument. The event was no longer used for some time, instead you can (optionally) pass a count of dropped events as third argument. + ## 5. Build Changes Previously the CJS versions of the SDK code (wrongfully) contained compatibility statements for default exports in ESM: @@ -310,19 +333,18 @@ Let us know if this is causing issues in your setup by opening an issue on GitHu ### `@sentry/deno` -The minimum supported Deno version is now **2.0.0**. +- The minimum supported Deno version for the Deno SDK (`@sentry/deno`) is now **2.0.0**. -- `@sentry/deno` is no longer published on `deno.land` so you'll need to import - from npm: +- `@sentry/deno` is no longer published on the `deno.land` registry so you'll need to import the SDK from npm: -```javascript -import * as Sentry from 'npm:@sentry/deno'; + ```javascript + import * as Sentry from 'npm:@sentry/deno'; -Sentry.init({ - dsn: '__DSN__', - // ... -}); -``` + Sentry.init({ + dsn: '__DSN__', + // ... + }); + ``` ## 6. Type Changes @@ -339,7 +361,7 @@ This should not affect most users unless you relied on passing things with a sim - The `Request` type has been removed. Use `RequestEventData` type instead. - The `IntegrationClass` type is no longer exported - it was not used anymore. Instead, use `Integration` or `IntegrationFn`. - The `samplingContext.request` attribute in the `tracesSampler` has been removed. Use `samplingContext.normalizedRequest` instead. Note that the type of `normalizedRequest` differs from `request`. -- The `samplingContext.transactionContext` object in the `tracesSampler` has been removed. All object attributes are available in the top-level of `samplingContext`. +- The `samplingContext.transactionContext` object in the `tracesSampler` and `profilesSampler` has been removed. All object attributes are available in the top-level of `samplingContext`. - `Client` now always expects the `BaseClient` class - there is no more abstract `Client` that can be implemented! Any `Client` class has to extend from `BaseClient`. - `ReportDialogOptions` now extends `Record` instead of `Record` - this should not affect most users. - The `RequestDataIntegrationOptions` type has been removed. There is no replacement. @@ -367,49 +389,49 @@ The following outlines deprecations that were introduced in version 8 of the SDK - **Passing `undefined` to `tracesSampleRate` / `tracesSampler` / `enableTracing` will be handled differently in v9** -In v8, explicitly setting `tracesSampleRate` (even if it is set to `undefined`) resulted in tracing being _enabled_, although no spans were generated. + In v8, explicitly setting `tracesSampleRate` (even if it is set to `undefined`) resulted in tracing being _enabled_, although no spans were generated. -```ts -Sentry.init({ - tracesSampleRate: undefined, -}); -``` + ```ts + Sentry.init({ + tracesSampleRate: undefined, + }); + ``` -In v9, we will streamline this behavior so that passing `undefined` will result in tracing being disabled, the same as not passing the option at all. + In v9, we will streamline this behavior so that passing `undefined` will result in tracing being disabled, the same as not passing the option at all. -If you are relying on `undefined` being passed in and having tracing enabled because of this, you should update your config to set e.g. `tracesSampleRate: 0` instead, which will also enable tracing in v9. + If you are relying on `undefined` being passed in and having tracing enabled because of this, you should update your config to set e.g. `tracesSampleRate: 0` instead, which will also enable tracing in v9. -The `enableTracing` option was removed. In v9, to emulate `enableTracing: true`, set `tracesSampleRate: 1`. To emulate `enableTracing: false`, remove the `tracesSampleRate` and `tracesSampler` options (if configured). + The `enableTracing` option was removed. In v9, to emulate `enableTracing: true`, set `tracesSampleRate: 1`. To emulate `enableTracing: false`, remove the `tracesSampleRate` and `tracesSampler` options (if configured). - **The `autoSessionTracking` option is deprecated.** -To enable session tracking, it is recommended to unset `autoSessionTracking` and ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. + To enable session tracking, it is recommended to unset `autoSessionTracking` and ensure that either, in browser environments the `browserSessionIntegration` is added, or in server environments the `httpIntegration` is added. -To disable session tracking, it is recommended unset `autoSessionTracking` and to remove the `browserSessionIntegration` in browser environments, or in server environments configure the `httpIntegration` with the `trackIncomingRequestsAsSessions` option set to `false`. -Additionally, in Node.js environments, a session was automatically created for every node process when `autoSessionTracking` was set to `true`. This behavior has been replaced by the `processSessionIntegration` which is configured by default. + To disable session tracking, it is recommended unset `autoSessionTracking` and to remove the `browserSessionIntegration` in browser environments, or in server environments configure the `httpIntegration` with the `trackIncomingRequestsAsSessions` option set to `false`. + Additionally, in Node.js environments, a session was automatically created for every node process when `autoSessionTracking` was set to `true`. This behavior has been replaced by the `processSessionIntegration` which is configured by default. -- **The metrics API has been removed from the SDK.** +- **The metrics API is deprecated.** -The Sentry metrics beta has ended and the metrics API has been removed from the SDK. Learn more in [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th). + The Sentry metrics beta has ended and the metrics API has been deprecated in `8.x`. The entire API will be removed in `9.x` of the SDK. Learn more in the Sentry [help center docs](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Ended-on-October-7th). ## `@sentry/utils` - **The `@sentry/utils` package has been deprecated. Import everything from `@sentry/core` instead.** -- Deprecated `AddRequestDataToEventOptions.transaction`. This option effectively doesn't do anything anymore, and will be removed in v9. -- Deprecated `TransactionNamingScheme` type. -- Deprecated `validSeverityLevels`. Will not be replaced. -- Deprecated `urlEncode`. No replacements. -- Deprecated `addRequestDataToEvent`. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. -- Deprecated `extractRequestData`. Instead manually extract relevant data off request. -- Deprecated `arrayify`. No replacements. -- Deprecated `memoBuilder`. No replacements. -- Deprecated `getNumberOfUrlSegments`. No replacements. -- Deprecated `BAGGAGE_HEADER_NAME`. Use the `"baggage"` string constant directly instead. -- Deprecated `makeFifoCache`. No replacements. -- Deprecated `dynamicRequire`. No replacements. -- Deprecated `flatten`. No replacements. -- Deprecated `_browserPerformanceTimeOriginMode`. No replacements. + - Deprecated `AddRequestDataToEventOptions.transaction`. This option effectively doesn't do anything anymore, and will be removed in v9. + - Deprecated `TransactionNamingScheme` type. + - Deprecated `validSeverityLevels`. Will not be replaced. + - Deprecated `urlEncode`. No replacements. + - Deprecated `addRequestDataToEvent`. Use `httpRequestToRequestData` instead and put the resulting object directly on `event.request`. + - Deprecated `extractRequestData`. Instead manually extract relevant data off request. + - Deprecated `arrayify`. No replacements. + - Deprecated `memoBuilder`. No replacements. + - Deprecated `getNumberOfUrlSegments`. No replacements. + - Deprecated `BAGGAGE_HEADER_NAME`. Use the `"baggage"` string constant directly instead. + - Deprecated `makeFifoCache`. No replacements. + - Deprecated `dynamicRequire`. No replacements. + - Deprecated `flatten`. No replacements. + - Deprecated `_browserPerformanceTimeOriginMode`. No replacements. ## `@sentry/core` @@ -445,10 +467,10 @@ The Sentry metrics beta has ended and the metrics API has been removed from the - **The `@sentry/types` package has been deprecated. Import everything from `@sentry/core` instead.** -- Deprecated `Request` in favor of `RequestEventData`. -- Deprecated `RequestSession`. No replacements. -- Deprecated `RequestSessionStatus`. No replacements. -- Deprecated `SessionFlusherLike`. No replacements. + - Deprecated `Request` in favor of `RequestEventData`. + - Deprecated `RequestSession`. No replacements. + - Deprecated `RequestSessionStatus`. No replacements. + - Deprecated `SessionFlusherLike`. No replacements. ## `@sentry/nuxt` @@ -458,23 +480,23 @@ The Sentry metrics beta has ended and the metrics API has been removed from the - Deprecated `tracingOptions`, `trackComponents`, `timeout`, `hooks` options everywhere other than in the `tracingOptions` option of the `vueIntegration()`. -These options should now be set as follows: + These options should now be set as follows: -```ts -import * as Sentry from '@sentry/vue'; + ```ts + import * as Sentry from '@sentry/vue'; -Sentry.init({ - integrations: [ - Sentry.vueIntegration({ - tracingOptions: { - trackComponents: true, - timeout: 1000, - hooks: ['mount', 'update', 'unmount'], - }, - }), - ], -}); -``` + Sentry.init({ + integrations: [ + Sentry.vueIntegration({ + tracingOptions: { + trackComponents: true, + timeout: 1000, + hooks: ['mount', 'update', 'unmount'], + }, + }), + ], + }); + ``` - Deprecated `logErrors` in the `vueIntegration`. The Sentry Vue error handler will propagate the error to a user-defined error handler or just re-throw the error (which will log the error without modifying). @@ -483,24 +505,24 @@ Sentry.init({ - When component tracking is enabled, "update" spans are no longer created by default. -Add an `"update"` item to the `tracingOptions.hooks` option via the `vueIntegration()` to restore this behavior. - -```ts -Sentry.init({ - integrations: [ - Sentry.vueIntegration({ - tracingOptions: { - trackComponents: true, - hooks: [ - 'mount', - 'update', // <-- - 'unmount', - ], - }, - }), - ], -}); -``` + Add an `"update"` item to the `tracingOptions.hooks` option via the `vueIntegration()` to restore this behavior. + + ```ts + Sentry.init({ + integrations: [ + Sentry.vueIntegration({ + tracingOptions: { + trackComponents: true, + hooks: [ + 'mount', + 'update', // add this line to re-enable update spans + 'unmount', + ], + }, + }), + ], + }); + ``` ## `@sentry/astro` diff --git a/docs/pr-reviews.md b/docs/pr-reviews.md index 87b5faafb950..65703c2cebb6 100644 --- a/docs/pr-reviews.md +++ b/docs/pr-reviews.md @@ -2,12 +2,8 @@ Make sure to open PRs against `develop` branch. -For feedback in PRs, we use the [LOGAF scale](https://blog.danlew.net/2020/04/15/the-logaf-scale/) to specify how -important a comment is: - -- `l`: low - nitpick. You may address this comment, but you don't have to. -- `m`: medium - normal comment. Worth addressing and fixing. -- `h`: high - Very important. We must not merge this PR without addressing this issue. +For feedback in PRs, we use the [LOGAF scale](https://develop.sentry.dev/engineering-practices/code-review/#logaf-scale) to specify how +important a comment is. You only need one approval from a maintainer to be able to merge. For some PRs, asking specific or multiple people for review might be adequate. You can either assign SDK team members directly (e.g. if you have some people in mind who are diff --git a/packages/astro/LICENSE b/packages/astro/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/astro/LICENSE +++ b/packages/astro/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/aws-serverless/LICENSE b/packages/aws-serverless/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/aws-serverless/LICENSE +++ b/packages/aws-serverless/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser-utils/LICENSE b/packages/browser-utils/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/browser-utils/LICENSE +++ b/packages/browser-utils/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index c71b2d70e31d..30bc3a29888e 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -17,6 +17,8 @@ export { registerInpInteractionListener, } from './metrics/browserMetrics'; +export { extractNetworkProtocol } from './metrics/utils'; + export { addClickKeypressInstrumentationHandler } from './instrument/dom'; export { addHistoryInstrumentationHandler } from './instrument/history'; diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 1d2b6b47c87e..794faa197ad5 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -20,7 +20,13 @@ import { addPerformanceInstrumentationHandler, addTtfbInstrumentationHandler, } from './instrument'; -import { getBrowserPerformanceAPI, isMeasurementValue, msToSec, startAndEndSpan } from './utils'; +import { + extractNetworkProtocol, + getBrowserPerformanceAPI, + isMeasurementValue, + msToSec, + startAndEndSpan, +} from './utils'; import { getActivationStart } from './web-vitals/lib/getActivationStart'; import { getNavigationEntry } from './web-vitals/lib/getNavigationEntry'; import { getVisibilityWatcher } from './web-vitals/lib/getVisibilityWatcher'; @@ -596,6 +602,10 @@ export function _addResourceSpans( attributes['url.same_origin'] = resourceUrl.includes(WINDOW.location.origin); + const { name, version } = extractNetworkProtocol(entry.nextHopProtocol); + attributes['network.protocol.name'] = name; + attributes['network.protocol.version'] = version; + const startTimestamp = timeOrigin + startTime; const endTimestamp = startTimestamp + duration; diff --git a/packages/browser-utils/src/metrics/utils.ts b/packages/browser-utils/src/metrics/utils.ts index 91aefa8a8918..aef40d4cf613 100644 --- a/packages/browser-utils/src/metrics/utils.ts +++ b/packages/browser-utils/src/metrics/utils.ts @@ -137,3 +137,34 @@ export function getBrowserPerformanceAPI(): Performance | undefined { export function msToSec(time: number): number { return time / 1000; } + +/** + * Converts ALPN protocol ids to name and version. + * + * (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) + * @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol + */ +export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } { + let name = 'unknown'; + let version = 'unknown'; + let _name = ''; + for (const char of nextHopProtocol) { + // http/1.1 etc. + if (char === '/') { + [name, version] = nextHopProtocol.split('/') as [string, string]; + break; + } + // h2, h3 etc. + if (!isNaN(Number(char))) { + name = _name === 'h' ? 'http' : _name; + version = nextHopProtocol.split(_name)[1] as string; + break; + } + _name += char; + } + if (_name === nextHopProtocol) { + // webrtc, ftp, etc. + name = _name; + } + return { name, version }; +} diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index 2ff1c2df209a..98a3bb375c00 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -131,6 +131,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 256, decodedBodySize: 256, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, resourceEntryName, 123, 456, 100); @@ -150,6 +151,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 256, decodedBodySize: 256, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 456, 100); @@ -169,6 +171,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 456, decodedBodySize: 593, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'http/1.1', }); const timeOrigin = 100; @@ -195,6 +198,8 @@ describe('_addResourceSpans', () => { ['url.scheme']: 'https', ['server.address']: 'example.com', ['url.same_origin']: true, + ['network.protocol.name']: 'http', + ['network.protocol.version']: '1.1', }, }), ); @@ -233,6 +238,7 @@ describe('_addResourceSpans', () => { const { initiatorType, op } = table[i]!; const entry = mockPerformanceResourceTiming({ initiatorType, + nextHopProtocol: 'http/1.1', }); _addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465); @@ -254,6 +260,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 0, decodedBodySize: 0, renderBlockingStatus: 'non-blocking', + nextHopProtocol: 'h2', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -271,6 +278,8 @@ describe('_addResourceSpans', () => { ['url.scheme']: 'https', ['server.address']: 'example.com', ['url.same_origin']: true, + ['network.protocol.name']: 'http', + ['network.protocol.version']: '2', }, }), ); @@ -288,6 +297,7 @@ describe('_addResourceSpans', () => { transferSize: 2147483647, encodedBodySize: 2147483647, decodedBodySize: 2147483647, + nextHopProtocol: 'h3', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -301,6 +311,8 @@ describe('_addResourceSpans', () => { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https', + ['network.protocol.name']: 'http', + ['network.protocol.version']: '3', }, description: '/assets/to/css', timestamp: 468, @@ -325,6 +337,7 @@ describe('_addResourceSpans', () => { transferSize: null, encodedBodySize: null, decodedBodySize: null, + nextHopProtocol: 'h3', } as unknown as PerformanceResourceTiming; _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -338,6 +351,8 @@ describe('_addResourceSpans', () => { 'server.address': 'example.com', 'url.same_origin': true, 'url.scheme': 'https', + ['network.protocol.name']: 'http', + ['network.protocol.version']: '3', }, description: '/assets/to/css', timestamp: 468, @@ -365,6 +380,7 @@ describe('_addResourceSpans', () => { encodedBodySize: 0, decodedBodySize: 0, deliveryType, + nextHopProtocol: 'h3', }); _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); diff --git a/packages/browser-utils/test/browser/utils.test.ts b/packages/browser-utils/test/browser/utils.test.ts index bb7a757e4b6a..01fb5da605c4 100644 --- a/packages/browser-utils/test/browser/utils.test.ts +++ b/packages/browser-utils/test/browser/utils.test.ts @@ -1,5 +1,5 @@ import { SentrySpan, getCurrentScope, getIsolationScope, setCurrentClient, spanToJSON } from '@sentry/core'; -import { startAndEndSpan } from '../../src/metrics/utils'; +import { extractNetworkProtocol, startAndEndSpan } from '../../src/metrics/utils'; import { TestClient, getDefaultClientOptions } from '../utils/TestClient'; describe('startAndEndSpan()', () => { @@ -54,3 +54,44 @@ describe('startAndEndSpan()', () => { expect(spanToJSON(parentSpan).start_timestamp).toEqual(123); }); }); + +describe('HTTPTimings', () => { + test.each([ + ['http/0.9', { name: 'http', version: '0.9' }], + ['http/1.0', { name: 'http', version: '1.0' }], + ['http/1.1', { name: 'http', version: '1.1' }], + ['spdy/1', { name: 'spdy', version: '1' }], + ['spdy/2', { name: 'spdy', version: '2' }], + ['spdy/3', { name: 'spdy', version: '3' }], + ['stun.turn', { name: 'stun.turn', version: 'unknown' }], + ['stun.nat-discovery', { name: 'stun.nat-discovery', version: 'unknown' }], + ['h2', { name: 'http', version: '2' }], + ['h2c', { name: 'http', version: '2c' }], + ['webrtc', { name: 'webrtc', version: 'unknown' }], + ['c-webrtc', { name: 'c-webrtc', version: 'unknown' }], + ['ftp', { name: 'ftp', version: 'unknown' }], + ['imap', { name: 'imap', version: 'unknown' }], + ['pop3', { name: 'pop', version: '3' }], + ['managesieve', { name: 'managesieve', version: 'unknown' }], + ['coap', { name: 'coap', version: 'unknown' }], + ['xmpp-client', { name: 'xmpp-client', version: 'unknown' }], + ['xmpp-server', { name: 'xmpp-server', version: 'unknown' }], + ['acme-tls/1', { name: 'acme-tls', version: '1' }], + ['mqtt', { name: 'mqtt', version: 'unknown' }], + ['dot', { name: 'dot', version: 'unknown' }], + ['ntske/1', { name: 'ntske', version: '1' }], + ['sunrpc', { name: 'sunrpc', version: 'unknown' }], + ['h3', { name: 'http', version: '3' }], + ['smb', { name: 'smb', version: 'unknown' }], + ['irc', { name: 'irc', version: 'unknown' }], + ['nntp', { name: 'nntp', version: 'unknown' }], + ['nnsp', { name: 'nnsp', version: 'unknown' }], + ['doq', { name: 'doq', version: 'unknown' }], + ['sip/2', { name: 'sip', version: '2' }], + ['tds/8.0', { name: 'tds', version: '8.0' }], + ['dicom', { name: 'dicom', version: 'unknown' }], + ['', { name: '', version: 'unknown' }], + ])('Extracting version from ALPN protocol %s', (protocol, expected) => { + expect(extractNetworkProtocol(protocol)).toMatchObject(expected); + }); +}); diff --git a/packages/browser/LICENSE b/packages/browser/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/browser/LICENSE +++ b/packages/browser/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index d6fedeba769b..36dcade62859 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -53,9 +53,7 @@ export function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOpt release: typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value ? __SENTRY_RELEASE__ - : WINDOW.SENTRY_RELEASE?.id // This supports the variable that sentry-webpack-plugin injects - ? WINDOW.SENTRY_RELEASE.id - : undefined, + : WINDOW.SENTRY_RELEASE?.id, // This supports the variable that sentry-webpack-plugin injects sendClientReports: true, }; diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 92a8f2924084..368ea450b0d0 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -2,6 +2,7 @@ import { SENTRY_XHR_DATA_KEY, addPerformanceInstrumentationHandler, addXhrInstrumentationHandler, + extractNetworkProtocol, } from '@sentry-internal/browser-utils'; import type { Client, HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/core'; import { @@ -228,37 +229,6 @@ function addHTTPTimings(span: Span): void { }); } -/** - * Converts ALPN protocol ids to name and version. - * - * (https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) - * @param nextHopProtocol PerformanceResourceTiming.nextHopProtocol - */ -export function extractNetworkProtocol(nextHopProtocol: string): { name: string; version: string } { - let name = 'unknown'; - let version = 'unknown'; - let _name = ''; - for (const char of nextHopProtocol) { - // http/1.1 etc. - if (char === '/') { - [name, version] = nextHopProtocol.split('/') as [string, string]; - break; - } - // h2, h3 etc. - if (!isNaN(Number(char))) { - name = _name === 'h' ? 'http' : _name; - version = nextHopProtocol.split(_name)[1] as string; - break; - } - _name += char; - } - if (_name === nextHopProtocol) { - // webrtc, ftp, etc. - name = _name; - } - return { name, version }; -} - function getAbsoluteTime(time: number = 0): number { return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000; } diff --git a/packages/browser/src/transports/fetch.ts b/packages/browser/src/transports/fetch.ts index 5712c78f0c18..9a414a95e927 100644 --- a/packages/browser/src/transports/fetch.ts +++ b/packages/browser/src/transports/fetch.ts @@ -22,7 +22,7 @@ export function makeFetchTransport( const requestOptions: RequestInit = { body: request.body, method: 'POST', - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', headers: options.headers, // Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to // fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)". diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index 2e215fbf764e..e6fea13c4e2a 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -56,7 +56,7 @@ export async function lazyLoadIntegration( const script = WINDOW.document.createElement('script'); script.src = url; script.crossOrigin = 'anonymous'; - script.referrerPolicy = 'origin'; + script.referrerPolicy = 'strict-origin'; if (scriptNonce) { script.setAttribute('nonce', scriptNonce); diff --git a/packages/browser/test/tracing/request.test.ts b/packages/browser/test/tracing/request.test.ts index b262053190eb..67cd96ee6717 100644 --- a/packages/browser/test/tracing/request.test.ts +++ b/packages/browser/test/tracing/request.test.ts @@ -5,7 +5,7 @@ import * as browserUtils from '@sentry-internal/browser-utils'; import * as utils from '@sentry/core'; import type { Client } from '@sentry/core'; -import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request'; +import { instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/tracing/request'; beforeAll(() => { // @ts-expect-error need to override global Request because it's not in the vi environment (even with an @@ -64,57 +64,6 @@ describe('instrumentOutgoingRequests', () => { }); }); -interface ProtocolInfo { - name: string; - version: string; -} - -describe('HTTPTimings', () => { - test('Extracting version from ALPN protocol', () => { - const nextHopToNetworkVersion: Record = { - 'http/0.9': { name: 'http', version: '0.9' }, - 'http/1.0': { name: 'http', version: '1.0' }, - 'http/1.1': { name: 'http', version: '1.1' }, - 'spdy/1': { name: 'spdy', version: '1' }, - 'spdy/2': { name: 'spdy', version: '2' }, - 'spdy/3': { name: 'spdy', version: '3' }, - 'stun.turn': { name: 'stun.turn', version: 'unknown' }, - 'stun.nat-discovery': { name: 'stun.nat-discovery', version: 'unknown' }, - h2: { name: 'http', version: '2' }, - h2c: { name: 'http', version: '2c' }, - webrtc: { name: 'webrtc', version: 'unknown' }, - 'c-webrtc': { name: 'c-webrtc', version: 'unknown' }, - ftp: { name: 'ftp', version: 'unknown' }, - imap: { name: 'imap', version: 'unknown' }, - pop3: { name: 'pop', version: '3' }, - managesieve: { name: 'managesieve', version: 'unknown' }, - coap: { name: 'coap', version: 'unknown' }, - 'xmpp-client': { name: 'xmpp-client', version: 'unknown' }, - 'xmpp-server': { name: 'xmpp-server', version: 'unknown' }, - 'acme-tls/1': { name: 'acme-tls', version: '1' }, - mqtt: { name: 'mqtt', version: 'unknown' }, - dot: { name: 'dot', version: 'unknown' }, - 'ntske/1': { name: 'ntske', version: '1' }, - sunrpc: { name: 'sunrpc', version: 'unknown' }, - h3: { name: 'http', version: '3' }, - smb: { name: 'smb', version: 'unknown' }, - irc: { name: 'irc', version: 'unknown' }, - nntp: { name: 'nntp', version: 'unknown' }, - nnsp: { name: 'nnsp', version: 'unknown' }, - doq: { name: 'doq', version: 'unknown' }, - 'sip/2': { name: 'sip', version: '2' }, - 'tds/8.0': { name: 'tds', version: '8.0' }, - dicom: { name: 'dicom', version: 'unknown' }, - }; - - const protocols = Object.keys(nextHopToNetworkVersion); - for (const protocol of protocols) { - const expected = nextHopToNetworkVersion[protocol]!; - expect(extractNetworkProtocol(protocol)).toMatchObject(expected); - } - }); -}); - describe('shouldAttachHeaders', () => { describe('should prefer `tracePropagationTargets` over defaults', () => { it('should return `true` if the url matches the new tracePropagationTargets', () => { diff --git a/packages/browser/test/transports/fetch.test.ts b/packages/browser/test/transports/fetch.test.ts index 9a760439733b..89ba25fd5fd8 100644 --- a/packages/browser/test/transports/fetch.test.ts +++ b/packages/browser/test/transports/fetch.test.ts @@ -50,7 +50,7 @@ describe('NewFetchTransport', () => { body: serializeEnvelope(ERROR_ENVELOPE), method: 'POST', keepalive: true, - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', }); }); diff --git a/packages/bun/LICENSE b/packages/bun/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/bun/LICENSE +++ b/packages/bun/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index d8ee46abae73..1f1974839455 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -47,7 +47,18 @@ export function instrumentBunServe(): void { Bun.serve = new Proxy(Bun.serve, { apply(serveTarget, serveThisArg, serveArgs: Parameters) { instrumentBunServeOptions(serveArgs[0]); - return serveTarget.apply(serveThisArg, serveArgs); + const server: ReturnType = serveTarget.apply(serveThisArg, serveArgs); + + // A Bun server can be reloaded, re-wrap any fetch function passed to it + // We can't use a Proxy for this as Bun does `instanceof` checks internally that fail if we + // wrap the Server instance. + const originalReload: typeof server.reload = server.reload.bind(server); + server.reload = (serveOptions: Parameters[0]) => { + instrumentBunServeOptions(serveOptions); + return originalReload(serveOptions); + }; + + return server; }, }); } diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index b1dc17381ccb..dd1f738a334b 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -1,67 +1,87 @@ -import { beforeAll, beforeEach, describe, expect, test } from 'bun:test'; +import { afterEach, beforeAll, beforeEach, describe, expect, test } from 'bun:test'; +import type { Span } from '@sentry/core'; import { getDynamicSamplingContextFromSpan, setCurrentClient, spanIsSampled, spanToJSON } from '@sentry/core'; import { BunClient } from '../../src/client'; import { instrumentBunServe } from '../../src/integrations/bunserver'; import { getDefaultBunClientOptions } from '../helpers'; -// Fun fact: Bun = 2 21 14 :) -const DEFAULT_PORT = 22114; - describe('Bun Serve Integration', () => { let client: BunClient; + // Fun fact: Bun = 2 21 14 :) + let port: number = 22114; beforeAll(() => { instrumentBunServe(); }); beforeEach(() => { - const options = getDefaultBunClientOptions({ tracesSampleRate: 1, debug: true }); + const options = getDefaultBunClientOptions({ tracesSampleRate: 1 }); client = new BunClient(options); setCurrentClient(client); client.init(); }); + afterEach(() => { + // Don't reuse the port; Bun server stops lazily so tests may accidentally hit a server still closing from a + // previous test + port += 1; + }); + test('generates a transaction around a request', async () => { + let generatedSpan: Span | undefined; + client.on('spanEnd', span => { - expect(spanToJSON(span).status).toBe('ok'); - expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(span).op).toEqual('http.server'); - expect(spanToJSON(span).description).toEqual('GET /'); + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); + await fetch(`http://localhost:${port}/`); + server.stop(); - await fetch('http://localhost:22114/'); + if (!generatedSpan) { + throw 'No span was generated in the test'; + } - server.stop(); + expect(spanToJSON(generatedSpan).status).toBe('ok'); + expect(spanToJSON(generatedSpan).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(generatedSpan).op).toEqual('http.server'); + expect(spanToJSON(generatedSpan).description).toEqual('GET /'); }); test('generates a post transaction', async () => { + let generatedSpan: Span | undefined; + client.on('spanEnd', span => { - expect(spanToJSON(span).status).toBe('ok'); - expect(spanToJSON(span).data?.['http.response.status_code']).toEqual(200); - expect(spanToJSON(span).op).toEqual('http.server'); - expect(spanToJSON(span).description).toEqual('POST /'); + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'POST', }); server.stop(); + + if (!generatedSpan) { + throw 'No span was generated in the test'; + } + + expect(spanToJSON(generatedSpan).status).toBe('ok'); + expect(spanToJSON(generatedSpan).data?.['http.response.status_code']).toEqual(200); + expect(spanToJSON(generatedSpan).op).toEqual('http.server'); + expect(spanToJSON(generatedSpan).description).toEqual('POST /'); }); test('continues a trace', async () => { @@ -70,55 +90,93 @@ describe('Bun Serve Integration', () => { const PARENT_SAMPLED = '1'; const SENTRY_TRACE_HEADER = `${TRACE_ID}-${PARENT_SPAN_ID}-${PARENT_SAMPLED}`; - const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-environment=production'; + const SENTRY_BAGGAGE_HEADER = 'sentry-version=1.0,sentry-sample_rand=0.42,sentry-environment=production'; - client.on('spanEnd', span => { - expect(span.spanContext().traceId).toBe(TRACE_ID); - expect(spanToJSON(span).parent_span_id).toBe(PARENT_SPAN_ID); - expect(spanIsSampled(span)).toBe(true); - expect(span.isRecording()).toBe(false); + let generatedSpan: Span | undefined; - expect(getDynamicSamplingContextFromSpan(span)).toStrictEqual({ - version: '1.0', - environment: 'production', - }); + client.on('spanEnd', span => { + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { headers: { 'sentry-trace': SENTRY_TRACE_HEADER, baggage: SENTRY_BAGGAGE_HEADER }, }); server.stop(); + + if (!generatedSpan) { + throw 'No span was generated in the test'; + } + + expect(generatedSpan.spanContext().traceId).toBe(TRACE_ID); + expect(spanToJSON(generatedSpan).parent_span_id).toBe(PARENT_SPAN_ID); + expect(spanIsSampled(generatedSpan)).toBe(true); + expect(generatedSpan.isRecording()).toBe(false); + + expect(getDynamicSamplingContextFromSpan(generatedSpan)).toStrictEqual({ + version: '1.0', + sample_rand: '0.42', + environment: 'production', + }); }); test('does not create transactions for OPTIONS or HEAD requests', async () => { - client.on('spanEnd', () => { - // This will never run, but we want to make sure it doesn't run. - expect(false).toEqual(true); + let generatedSpan: Span | undefined; + + client.on('spanEnd', span => { + generatedSpan = span; }); const server = Bun.serve({ async fetch(_req) { return new Response('Bun!'); }, - port: DEFAULT_PORT, + port, }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'OPTIONS', }); - await fetch('http://localhost:22114/', { + await fetch(`http://localhost:${port}/`, { method: 'HEAD', }); server.stop(); + + expect(generatedSpan).toBeUndefined(); + }); + + test('intruments the server again if it is reloaded', async () => { + let serverWasInstrumented = false; + client.on('spanEnd', () => { + serverWasInstrumented = true; + }); + + const server = Bun.serve({ + async fetch(_req) { + return new Response('Bun!'); + }, + port, + }); + + server.reload({ + async fetch(_req) { + return new Response('Reloaded Bun!'); + }, + }); + + await fetch(`http://localhost:${port}/`); + + server.stop(); + + expect(serverWasInstrumented).toBeTrue(); }); }); diff --git a/packages/bun/test/sdk.test.ts b/packages/bun/test/sdk.test.ts index a548cc2614c7..11870f30c101 100644 --- a/packages/bun/test/sdk.test.ts +++ b/packages/bun/test/sdk.test.ts @@ -1,14 +1,20 @@ -import { expect, test } from 'bun:test'; +import { describe, expect, test } from 'bun:test'; import { init } from '../src/index'; -test("calling init shouldn't fail", () => { - init({ +describe('Bun SDK', () => { + const initOptions = { dsn: 'https://00000000000000000000000000000000@o000000.ingest.sentry.io/0000000', + tracesSampleRate: 1, + }; + + test("calling init shouldn't fail", () => { + expect(() => { + init(initOptions); + }).not.toThrow(); }); - expect(true).toBe(true); -}); -test('should return client from init', () => { - expect(init({})).not.toBeUndefined(); + test('should return client from init', () => { + expect(init(initOptions)).not.toBeUndefined(); + }); }); diff --git a/packages/core/LICENSE b/packages/core/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/core/LICENSE +++ b/packages/core/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/core/package.json b/packages/core/package.json index 06e8d253a628..7724f703833e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -61,5 +61,8 @@ "volta": { "extends": "../../package.json" }, - "sideEffects": false + "sideEffects": false, + "devDependencies": { + "zod": "^3.24.1" + } } diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 7334b2d294ed..805596a8a855 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -903,11 +903,10 @@ export abstract class Client { if (DEBUG_BUILD) { // If something's gone wrong, log the error as a warning. If it's just us having used a `SentryError` for // control flow, log just the message (no stack) as a log-level log. - const sentryError = reason as SentryError; - if (sentryError.logLevel === 'log') { - logger.log(sentryError.message); + if (reason instanceof SentryError && reason.logLevel === 'log') { + logger.log(reason.message); } else { - logger.warn(sentryError); + logger.warn(reason); } } return undefined; diff --git a/packages/core/src/integrations/zoderrors.ts b/packages/core/src/integrations/zoderrors.ts index a408285800d9..4859ca5167fa 100644 --- a/packages/core/src/integrations/zoderrors.ts +++ b/packages/core/src/integrations/zoderrors.ts @@ -5,33 +5,45 @@ import { truncate } from '../utils-hoist/string'; interface ZodErrorsOptions { key?: string; + /** + * Limits the number of Zod errors inlined in each Sentry event. + * + * @default 10 + */ limit?: number; + /** + * Save full list of Zod issues as an attachment in Sentry + * + * @default false + */ + saveZodIssuesAsAttachment?: boolean; } const DEFAULT_LIMIT = 10; const INTEGRATION_NAME = 'ZodErrors'; -// Simplified ZodIssue type definition +/** + * Simplified ZodIssue type definition + */ interface ZodIssue { path: (string | number)[]; message?: string; - expected?: string | number; - received?: string | number; + expected?: unknown; + received?: unknown; unionErrors?: unknown[]; keys?: unknown[]; + invalid_literal?: unknown; } interface ZodError extends Error { issues: ZodIssue[]; - - get errors(): ZodError['issues']; } function originalExceptionIsZodError(originalException: unknown): originalException is ZodError { return ( isError(originalException) && originalException.name === 'ZodError' && - Array.isArray((originalException as ZodError).errors) + Array.isArray((originalException as ZodError).issues) ); } @@ -45,9 +57,18 @@ type SingleLevelZodIssue = { /** * Formats child objects or arrays to a string - * That is preserved when sent to Sentry + * that is preserved when sent to Sentry. + * + * Without this, we end up with something like this in Sentry: + * + * [ + * [Object], + * [Object], + * [Object], + * [Object] + * ] */ -function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { +export function flattenIssue(issue: ZodIssue): SingleLevelZodIssue { return { ...issue, path: 'path' in issue && Array.isArray(issue.path) ? issue.path.join('.') : undefined, @@ -56,64 +77,145 @@ function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { }; } +/** + * Takes ZodError issue path array and returns a flattened version as a string. + * This makes it easier to display paths within a Sentry error message. + * + * Array indexes are normalized to reduce duplicate entries + * + * @param path ZodError issue path + * @returns flattened path + * + * @example + * flattenIssuePath([0, 'foo', 1, 'bar']) // -> '.foo..bar' + */ +export function flattenIssuePath(path: Array): string { + return path + .map(p => { + if (typeof p === 'number') { + return ''; + } else { + return p; + } + }) + .join('.'); +} + /** * Zod error message is a stringified version of ZodError.issues * This doesn't display well in the Sentry UI. Replace it with something shorter. */ -function formatIssueMessage(zodError: ZodError): string { +export function formatIssueMessage(zodError: ZodError): string { const errorKeyMap = new Set(); for (const iss of zodError.issues) { - if (iss.path?.[0]) { - errorKeyMap.add(iss.path[0]); + const issuePath = flattenIssuePath(iss.path); + if (issuePath.length > 0) { + errorKeyMap.add(issuePath); } } - const errorKeys = Array.from(errorKeyMap); + const errorKeys = Array.from(errorKeyMap); + if (errorKeys.length === 0) { + // If there are no keys, then we're likely validating the root + // variable rather than a key within an object. This attempts + // to extract what type it was that failed to validate. + // For example, z.string().parse(123) would return "string" here. + let rootExpectedType = 'variable'; + if (zodError.issues.length > 0) { + const iss = zodError.issues[0]; + if (iss !== undefined && 'expected' in iss && typeof iss.expected === 'string') { + rootExpectedType = iss.expected; + } + } + return `Failed to validate ${rootExpectedType}`; + } return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`; } /** - * Applies ZodError issues to an event extras and replaces the error message + * Applies ZodError issues to an event extra and replaces the error message */ -export function applyZodErrorsToEvent(limit: number, event: Event, hint?: EventHint): Event { +export function applyZodErrorsToEvent( + limit: number, + saveZodIssuesAsAttachment: boolean = false, + event: Event, + hint: EventHint, +): Event { if ( !event.exception?.values || - !hint?.originalException || + !hint.originalException || !originalExceptionIsZodError(hint.originalException) || hint.originalException.issues.length === 0 ) { return event; } - return { - ...event, - exception: { - ...event.exception, - values: [ - { - ...event.exception.values[0], - value: formatIssueMessage(hint.originalException), + try { + const issuesToFlatten = saveZodIssuesAsAttachment + ? hint.originalException.issues + : hint.originalException.issues.slice(0, limit); + const flattenedIssues = issuesToFlatten.map(flattenIssue); + + if (saveZodIssuesAsAttachment) { + // Sometimes having the full error details can be helpful. + // Attachments have much higher limits, so we can include the full list of issues. + if (!Array.isArray(hint.attachments)) { + hint.attachments = []; + } + hint.attachments.push({ + filename: 'zod_issues.json', + data: JSON.stringify({ + issues: flattenedIssues, + }), + }); + } + + return { + ...event, + exception: { + ...event.exception, + values: [ + { + ...event.exception.values[0], + value: formatIssueMessage(hint.originalException), + }, + ...event.exception.values.slice(1), + ], + }, + extra: { + ...event.extra, + 'zoderror.issues': flattenedIssues.slice(0, limit), + }, + }; + } catch (e) { + // Hopefully we never throw errors here, but record it + // with the event just in case. + return { + ...event, + extra: { + ...event.extra, + 'zoderrors sentry integration parse error': { + message: 'an exception was thrown while processing ZodError within applyZodErrorsToEvent()', + error: e instanceof Error ? `${e.name}: ${e.message}\n${e.stack}` : 'unknown', }, - ...event.exception.values.slice(1), - ], - }, - extra: { - ...event.extra, - 'zoderror.issues': hint.originalException.errors.slice(0, limit).map(formatIssueTitle), - }, - }; + }, + }; + } } const _zodErrorsIntegration = ((options: ZodErrorsOptions = {}) => { - const limit = options.limit || DEFAULT_LIMIT; + const limit = options.limit ?? DEFAULT_LIMIT; return { name: INTEGRATION_NAME, - processEvent(originalEvent, hint) { - const processedEvent = applyZodErrorsToEvent(limit, originalEvent, hint); + processEvent(originalEvent, hint): Event { + const processedEvent = applyZodErrorsToEvent(limit, options.saveZodIssuesAsAttachment, originalEvent, hint); return processedEvent; }, }; }) satisfies IntegrationFn; +/** + * Sentry integration to process Zod errors, making them easier to work with in Sentry. + */ export const zodErrorsIntegration = defineIntegration(_zodErrorsIntegration); diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index ce559d589fe3..a302e5a14c34 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -479,9 +479,11 @@ export class Scope { ...breadcrumb, }; - const breadcrumbs = this._breadcrumbs; - breadcrumbs.push(mergedBreadcrumb); - this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs; + this._breadcrumbs.push(mergedBreadcrumb); + if (this._breadcrumbs.length > maxCrumbs) { + this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs); + this._client?.recordDroppedEvent('buffer_overflow', 'log_item'); + } this._notifyScopeListeners(); diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index eb19643164aa..70c62cd20992 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -41,7 +41,12 @@ export function sampleSpan( const parsedSampleRate = parseSampleRate(sampleRate); if (parsedSampleRate === undefined) { - DEBUG_BUILD && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.'); + DEBUG_BUILD && + logger.warn( + `[Tracing] Discarding root span because of invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( + sampleRate, + )} of type ${JSON.stringify(typeof sampleRate)}.`, + ); return [false]; } diff --git a/packages/core/src/types-hoist/clientreport.ts b/packages/core/src/types-hoist/clientreport.ts index b6ab1766e68c..069adec43c62 100644 --- a/packages/core/src/types-hoist/clientreport.ts +++ b/packages/core/src/types-hoist/clientreport.ts @@ -8,7 +8,8 @@ export type EventDropReason = | 'ratelimit_backoff' | 'sample_rate' | 'send_error' - | 'internal_sdk_error'; + | 'internal_sdk_error' + | 'buffer_overflow'; export type Outcome = { reason: EventDropReason; diff --git a/packages/core/src/types-hoist/datacategory.ts b/packages/core/src/types-hoist/datacategory.ts index da90cc0ca90b..2e636b605fcf 100644 --- a/packages/core/src/types-hoist/datacategory.ts +++ b/packages/core/src/types-hoist/datacategory.ts @@ -14,7 +14,7 @@ export type DataCategory = | 'replay' // Events with `event_type` csp, hpkp, expectct, expectstaple | 'security' - // Attachment bytes stored (unused for rate limiting + // Attachment bytes stored (unused for rate limiting) | 'attachment' // Session update events | 'session' @@ -28,5 +28,9 @@ export type DataCategory = | 'feedback' // Span | 'span' + // Log event + | 'log_item' + // Log bytes stored (unused for rate limiting) + | 'log_byte' // Unknown data category | 'unknown'; diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts index 49f3daa93b8e..58634930c993 100644 --- a/packages/core/src/types-hoist/options.ts +++ b/packages/core/src/types-hoist/options.ts @@ -138,15 +138,6 @@ export interface ClientOptions { * @returns a baggage header string, or `undefined` if the object didn't have any values, since an empty baggage header * is not spec compliant. */ -function objectToBaggageHeader(object: Record): string | undefined { +export function objectToBaggageHeader(object: Record): string | undefined { if (Object.keys(object).length === 0) { // An empty baggage header is not spec compliant: We return undefined. return undefined; diff --git a/packages/core/src/utils-hoist/error.ts b/packages/core/src/utils-hoist/error.ts index 622aaff9cf80..5ae28093a8bf 100644 --- a/packages/core/src/utils-hoist/error.ts +++ b/packages/core/src/utils-hoist/error.ts @@ -2,9 +2,6 @@ import type { ConsoleLevel } from '../types-hoist'; /** An error emitted by Sentry SDKs and related utilities. */ export class SentryError extends Error { - /** Display name of this error instance. */ - public name: string; - public logLevel: ConsoleLevel; public constructor( @@ -13,11 +10,6 @@ export class SentryError extends Error { ) { super(message); - this.name = new.target.prototype.constructor.name; - // This sets the prototype to be `Error`, not `SentryError`. It's unclear why we do this, but commenting this line - // out causes various (seemingly totally unrelated) playwright tests consistently time out. FYI, this makes - // instances of `SentryError` fail `obj instanceof SentryError` checks. - Object.setPrototypeOf(this, new.target.prototype); this.logLevel = logLevel; } } diff --git a/packages/core/src/utils-hoist/index.ts b/packages/core/src/utils-hoist/index.ts index a593b72e73ad..1b2032dbc4bc 100644 --- a/packages/core/src/utils-hoist/index.ts +++ b/packages/core/src/utils-hoist/index.ts @@ -128,6 +128,7 @@ export { baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader, parseBaggageHeader, + objectToBaggageHeader, } from './baggage'; export { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from './url'; diff --git a/packages/core/src/utils-hoist/node.ts b/packages/core/src/utils-hoist/node.ts index a0311efc7a93..94e8001863aa 100644 --- a/packages/core/src/utils-hoist/node.ts +++ b/packages/core/src/utils-hoist/node.ts @@ -41,21 +41,23 @@ function dynamicRequire(mod: any, request: string): any { * That is to mimic the behavior of `require.resolve` exactly. * * @param moduleName module name to require + * @param existingModule module to use for requiring * @returns possibly required module */ -export function loadModule(moduleName: string): T | undefined { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function loadModule(moduleName: string, existingModule: any = module): T | undefined { let mod: T | undefined; try { - mod = dynamicRequire(module, moduleName); + mod = dynamicRequire(existingModule, moduleName); } catch (e) { // no-empty } if (!mod) { try { - const { cwd } = dynamicRequire(module, 'process'); - mod = dynamicRequire(module, `${cwd()}/node_modules/${moduleName}`) as T; + const { cwd } = dynamicRequire(existingModule, 'process'); + mod = dynamicRequire(existingModule, `${cwd()}/node_modules/${moduleName}`) as T; } catch (e) { // no-empty } diff --git a/packages/core/src/utils/hasTracingEnabled.ts b/packages/core/src/utils/hasTracingEnabled.ts index f00bf10ff367..a125c7a0cc9e 100644 --- a/packages/core/src/utils/hasTracingEnabled.ts +++ b/packages/core/src/utils/hasTracingEnabled.ts @@ -20,7 +20,7 @@ export function hasTracingEnabled( const options = maybeOptions || client?.getOptions(); return ( !!options && - // Note: This check is `!= null`, meaning "nullish" + // Note: This check is `!= null`, meaning "nullish". `0` is not "nullish", `undefined` and `null` are. (This comment was brought to you by 15 minutes of questioning life) (options.tracesSampleRate != null || !!options.tracesSampler) ); } diff --git a/packages/core/src/utils/parseSampleRate.ts b/packages/core/src/utils/parseSampleRate.ts index 3e297025b52e..acd22509e568 100644 --- a/packages/core/src/utils/parseSampleRate.ts +++ b/packages/core/src/utils/parseSampleRate.ts @@ -1,6 +1,3 @@ -import { DEBUG_BUILD } from '../debug-build'; -import { logger } from '../utils-hoist/logger'; - /** * Parse a sample rate from a given value. * This will either return a boolean or number sample rate, if the sample rate is valid (between 0 and 1). @@ -15,12 +12,6 @@ export function parseSampleRate(sampleRate: unknown): number | undefined { const rate = typeof sampleRate === 'string' ? parseFloat(sampleRate) : sampleRate; if (typeof rate !== 'number' || isNaN(rate) || rate < 0 || rate > 1) { - DEBUG_BUILD && - logger.warn( - `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( - sampleRate, - )} of type ${JSON.stringify(typeof sampleRate)}.`, - ); return undefined; } diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 19a10f7f509a..c415f1ceb411 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -164,6 +164,22 @@ describe('Client', () => { expect(isolationScopeBreadcrumbs).toEqual([{ message: 'hello3', timestamp: expect.any(Number) }]); }); + test('it records `buffer_overflow` client discard reason when buffer overflows', () => { + const options = getDefaultTestClientOptions({ maxBreadcrumbs: 1 }); + const client = new TestClient(options); + const recordLostEventSpy = jest.spyOn(client, 'recordDroppedEvent'); + setCurrentClient(client); + getIsolationScope().setClient(client); + client.init(); + + addBreadcrumb({ message: 'hello1' }); + addBreadcrumb({ message: 'hello2' }); + addBreadcrumb({ message: 'hello3' }); + + expect(recordLostEventSpy).toHaveBeenCalledTimes(2); + expect(recordLostEventSpy).toHaveBeenLastCalledWith('buffer_overflow', 'log_item'); + }); + test('calls `beforeBreadcrumb` and adds the breadcrumb without any changes', () => { const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); const options = getDefaultTestClientOptions({ beforeBreadcrumb }); diff --git a/packages/core/test/lib/integrations/zoderrrors.test.ts b/packages/core/test/lib/integrations/zoderrrors.test.ts index 924ee5dd27da..cd80e2347f36 100644 --- a/packages/core/test/lib/integrations/zoderrrors.test.ts +++ b/packages/core/test/lib/integrations/zoderrrors.test.ts @@ -1,6 +1,12 @@ +import { z } from 'zod'; import type { Event, EventHint } from '../../../src/types-hoist'; -import { applyZodErrorsToEvent } from '../../../src/integrations/zoderrors'; +import { + applyZodErrorsToEvent, + flattenIssue, + flattenIssuePath, + formatIssueMessage, +} from '../../../src/integrations/zoderrors'; // Simplified type definition interface ZodIssue { @@ -40,13 +46,13 @@ describe('applyZodErrorsToEvent()', () => { test('should not do anything if exception is not a ZodError', () => { const event: Event = {}; const eventHint: EventHint = { originalException: new Error() }; - applyZodErrorsToEvent(100, event, eventHint); + applyZodErrorsToEvent(100, false, event, eventHint); // no changes expect(event).toStrictEqual({}); }); - test('should add ZodError issues to extras and format message', () => { + test('should add ZodError issues to extra and format message', () => { const issues = [ { code: 'invalid_type', @@ -71,13 +77,13 @@ describe('applyZodErrorsToEvent()', () => { }; const eventHint: EventHint = { originalException }; - const processedEvent = applyZodErrorsToEvent(100, event, eventHint); + const processedEvent = applyZodErrorsToEvent(100, false, event, eventHint); expect(processedEvent.exception).toStrictEqual({ values: [ { type: 'Error', - value: 'Failed to validate keys: names', + value: 'Failed to validate keys: names.', }, ], }); @@ -92,5 +98,421 @@ describe('applyZodErrorsToEvent()', () => { }, ], }); + + // No attachments added + expect(eventHint.attachments).toBe(undefined); + }); + + test('should add all ZodError issues as attachment', () => { + const issues = [ + { + code: 'invalid_type', + expected: 'string', + received: 'number', + path: ['names', 1], + keys: ['extra'], + message: 'Invalid input: expected string, received number', + }, + { + code: 'invalid_type', + expected: 'string', + received: 'number', + path: ['foo', 1], + keys: ['extra2'], + message: 'Invalid input: expected string, received number', + }, + ] satisfies ZodIssue[]; + const originalException = ZodError.create(issues); + + const event: Event = { + exception: { + values: [ + { + type: 'Error', + value: originalException.message, + }, + ], + }, + }; + + const eventHint: EventHint = { originalException }; + const processedEvent = applyZodErrorsToEvent(1, true, event, eventHint); + + expect(processedEvent.exception).toStrictEqual({ + values: [ + { + type: 'Error', + value: 'Failed to validate keys: names., foo.', + }, + ], + }); + + // Only adds the first issue to extra due to the limit + expect(processedEvent.extra).toStrictEqual({ + 'zoderror.issues': [ + { + ...issues[0], + path: issues[0]?.path.join('.'), + keys: JSON.stringify(issues[0]?.keys), + unionErrors: undefined, + }, + ], + }); + + // hint attachments contains the full issue list + expect(Array.isArray(eventHint.attachments)).toBe(true); + expect(eventHint.attachments?.length).toBe(1); + const attachment = eventHint.attachments?.[0]; + if (attachment === undefined) { + throw new Error('attachment is undefined'); + } + expect(attachment.filename).toBe('zod_issues.json'); + expect(JSON.parse(attachment.data.toString())).toMatchInlineSnapshot(` +Object { + "issues": Array [ + Object { + "code": "invalid_type", + "expected": "string", + "keys": "[\\"extra\\"]", + "message": "Invalid input: expected string, received number", + "path": "names.1", + "received": "number", + }, + Object { + "code": "invalid_type", + "expected": "string", + "keys": "[\\"extra2\\"]", + "message": "Invalid input: expected string, received number", + "path": "foo.1", + "received": "number", + }, + ], +} +`); + }); +}); + +describe('flattenIssue()', () => { + it('flattens path field', () => { + const zodError = z + .object({ + foo: z.string().min(1), + nested: z.object({ + bar: z.literal('baz'), + }), + }) + .safeParse({ + foo: '', + nested: { + bar: 'not-baz', + }, + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": Array [ + "foo", + ], + "type": "string", + }, + Object { + "code": "invalid_literal", + "expected": "baz", + "message": "Invalid literal value, expected \\"baz\\"", + "path": Array [ + "nested", + "bar", + ], + "received": "not-baz", + }, +] +`); + + const issues = zodError.issues; + expect(issues.length).toBe(2); + + // Format it for use in Sentry + expect(issues.map(flattenIssue)).toMatchInlineSnapshot(` +Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "keys": undefined, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": "foo", + "type": "string", + "unionErrors": undefined, + }, + Object { + "code": "invalid_literal", + "expected": "baz", + "keys": undefined, + "message": "Invalid literal value, expected \\"baz\\"", + "path": "nested.bar", + "received": "not-baz", + "unionErrors": undefined, + }, +] +`); + + expect(zodError.flatten(flattenIssue)).toMatchInlineSnapshot(` +Object { + "fieldErrors": Object { + "foo": Array [ + Object { + "code": "too_small", + "exact": false, + "inclusive": true, + "keys": undefined, + "message": "String must contain at least 1 character(s)", + "minimum": 1, + "path": "foo", + "type": "string", + "unionErrors": undefined, + }, + ], + "nested": Array [ + Object { + "code": "invalid_literal", + "expected": "baz", + "keys": undefined, + "message": "Invalid literal value, expected \\"baz\\"", + "path": "nested.bar", + "received": "not-baz", + "unionErrors": undefined, + }, + ], + }, + "formErrors": Array [], +} +`); + }); + + it('flattens keys field to string', () => { + const zodError = z + .object({ + foo: z.string().min(1), + }) + .strict() + .safeParse({ + foo: 'bar', + extra_key_abc: 'hello', + extra_key_def: 'world', + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "unrecognized_keys", + "keys": Array [ + "extra_key_abc", + "extra_key_def", + ], + "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'", + "path": Array [], + }, +] +`); + + const issues = zodError.issues; + expect(issues.length).toBe(1); + + // Format it for use in Sentry + const iss = issues[0]; + if (iss === undefined) { + throw new Error('iss is undefined'); + } + const formattedIssue = flattenIssue(iss); + + // keys is now a string rather than array. + // Note: path is an empty string because the issue is at the root. + // TODO: Maybe somehow make it clearer that this is at the root? + expect(formattedIssue).toMatchInlineSnapshot(` +Object { + "code": "unrecognized_keys", + "keys": "[\\"extra_key_abc\\",\\"extra_key_def\\"]", + "message": "Unrecognized key(s) in object: 'extra_key_abc', 'extra_key_def'", + "path": "", + "unionErrors": undefined, +} +`); + expect(typeof formattedIssue.keys === 'string').toBe(true); + }); +}); + +describe('flattenIssuePath()', () => { + it('returns single path', () => { + expect(flattenIssuePath(['foo'])).toBe('foo'); + }); + + it('flattens nested string paths', () => { + expect(flattenIssuePath(['foo', 'bar'])).toBe('foo.bar'); + }); + + it('uses placeholder for path index within array', () => { + expect(flattenIssuePath([0, 'foo', 1, 'bar', 'baz'])).toBe('.foo..bar.baz'); + }); +}); + +describe('formatIssueMessage()', () => { + it('adds invalid keys to message', () => { + const zodError = z + .object({ + foo: z.string().min(1), + nested: z.object({ + bar: z.literal('baz'), + }), + }) + .safeParse({ + foo: '', + nested: { + bar: 'not-baz', + }, + }).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate keys: foo, nested.bar"'); + }); + + describe('adds expected type if root variable is invalid', () => { + test('object', () => { + const zodError = z + .object({ + foo: z.string().min(1), + }) + .safeParse(123).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "object", + "message": "Expected object, received number", + "path": Array [], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate object"'); + }); + + test('number', () => { + const zodError = z.number().safeParse('123').error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "number", + "message": "Expected number, received string", + "path": Array [], + "received": "string", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate number"'); + }); + + test('string', () => { + const zodError = z.string().safeParse(123).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "string", + "message": "Expected string, received number", + "path": Array [], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate string"'); + }); + + test('array', () => { + const zodError = z.string().array().safeParse('123').error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "array", + "message": "Expected array, received string", + "path": Array [], + "received": "string", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate array"'); + }); + + test('wrong type in array', () => { + const zodError = z.string().array().safeParse([123]).error; + if (zodError === undefined) { + throw new Error('zodError is undefined'); + } + + // Original zod error + expect(zodError.issues).toMatchInlineSnapshot(` +Array [ + Object { + "code": "invalid_type", + "expected": "string", + "message": "Expected string, received number", + "path": Array [ + 0, + ], + "received": "number", + }, +] +`); + + const message = formatIssueMessage(zodError); + expect(message).toMatchInlineSnapshot('"Failed to validate keys: "'); + }); }); }); diff --git a/packages/deno/LICENSE b/packages/deno/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/deno/LICENSE +++ b/packages/deno/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/deno/src/transports/index.ts b/packages/deno/src/transports/index.ts index 3d4e26a9f805..9a83dadfff63 100644 --- a/packages/deno/src/transports/index.ts +++ b/packages/deno/src/transports/index.ts @@ -31,7 +31,7 @@ export function makeFetchTransport(options: DenoTransportOptions): Transport { const requestOptions: RequestInit = { body: request.body, method: 'POST', - referrerPolicy: 'origin', + referrerPolicy: 'strict-origin', headers: options.headers, }; diff --git a/packages/ember/LICENSE b/packages/ember/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/ember/LICENSE +++ b/packages/ember/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-config-sdk/LICENSE b/packages/eslint-config-sdk/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/eslint-config-sdk/LICENSE +++ b/packages/eslint-config-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/eslint-plugin-sdk/LICENSE b/packages/eslint-plugin-sdk/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/eslint-plugin-sdk/LICENSE +++ b/packages/eslint-plugin-sdk/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/feedback/LICENSE +++ b/packages/feedback/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/gatsby/LICENSE b/packages/gatsby/LICENSE index 5af93a5bdae5..b956a1944c7b 100644 --- a/packages/gatsby/LICENSE +++ b/packages/gatsby/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2020 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/integration-shims/LICENSE b/packages/integration-shims/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/integration-shims/LICENSE +++ b/packages/integration-shims/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nextjs/LICENSE b/packages/nextjs/LICENSE index 5b55ec3c5dcb..917e31f85b7a 100644 --- a/packages/nextjs/LICENSE +++ b/packages/nextjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2021 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nextjs/src/common/captureRequestError.ts b/packages/nextjs/src/common/captureRequestError.ts index 26fdaab4953b..c872d70f8334 100644 --- a/packages/nextjs/src/common/captureRequestError.ts +++ b/packages/nextjs/src/common/captureRequestError.ts @@ -1,5 +1,7 @@ import type { RequestEventData } from '@sentry/core'; +import { vercelWaitUntil } from '@sentry/core'; import { captureException, headersToDict, withScope } from '@sentry/core'; +import { flushSafelyWithTimeout } from './utils/responseEnd'; type RequestInfo = { path: string; @@ -39,5 +41,7 @@ export function captureRequestError(error: unknown, request: RequestInfo, errorC handled: false, }, }); + + vercelWaitUntil(flushSafelyWithTimeout()); }); } diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index bb73a2fb1859..f82bb4a0476e 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -332,7 +332,7 @@ export function constructWebpackConfigFunction( // Symbolication for dev-mode errors is done elsewhere. if (!isDev) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin') ?? {}; + const { sentryWebpackPlugin } = loadModule<{ sentryWebpackPlugin: any }>('@sentry/webpack-plugin', module) ?? {}; if (sentryWebpackPlugin) { if (!userSentryOptions.sourcemaps?.disable) { @@ -728,6 +728,7 @@ function addOtelWarningIgnoreRule(newConfig: WebpackConfigObjectWithModuleRules) // We provide these objects in addition to the hook above to provide redundancy in case the hook fails. { module: /@opentelemetry\/instrumentation/, message: /Critical dependency/ }, { module: /@prisma\/instrumentation/, message: /Critical dependency/ }, + { module: /require-in-the-middle/, message: /Critical dependency/ }, ] satisfies IgnoreWarningsOption; if (newConfig.ignoreWarnings === undefined) { diff --git a/packages/node/LICENSE b/packages/node/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/node/LICENSE +++ b/packages/node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 8900b423710b..2eebfe40309b 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -46,7 +46,13 @@ async function sendAbnormalSession(): Promise { // of we have an existing session passed from the main thread, send it as abnormal if (session) { log('Sending abnormal session'); - updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' }); + + updateSession(session, { + status: 'abnormal', + abnormal_mechanism: 'anr_foreground', + release: options.release, + environment: options.environment, + }); const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata, options.tunnel); // Log the envelope so to aid in testing diff --git a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts index d645ac5c9ec2..60fec28a434c 100644 --- a/packages/node/src/integrations/http/SentryHttpInstrumentation.ts +++ b/packages/node/src/integrations/http/SentryHttpInstrumentation.ts @@ -1,26 +1,31 @@ -/* eslint-disable max-lines */ import type * as http from 'node:http'; import type { IncomingMessage, RequestOptions } from 'node:http'; import type * as https from 'node:https'; import type { EventEmitter } from 'node:stream'; +/* eslint-disable max-lines */ import { VERSION } from '@opentelemetry/core'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; import type { AggregationCounts, Client, RequestEventData, SanitizedRequestData, Scope } from '@sentry/core'; import { + LRUMap, addBreadcrumb, generateSpanId, getBreadcrumbLogLevelFromHttpStatusCode, getClient, getIsolationScope, getSanitizedUrlString, + getTraceData, httpRequestToRequestData, logger, + objectToBaggageHeader, + parseBaggageHeader, parseUrl, stripUrlQueryAndFragment, withIsolationScope, withScope, } from '@sentry/core'; +import { shouldPropagateTraceForUrl } from '@sentry/opentelemetry'; import { DEBUG_BUILD } from '../../debug-build'; import { getRequestUrl } from '../../utils/getRequestUrl'; import { getRequestInfo } from './vendor/getRequestInfo'; @@ -28,6 +33,12 @@ import { getRequestInfo } from './vendor/getRequestInfo'; type Http = typeof http; type Https = typeof https; +type RequestArgs = + // eslint-disable-next-line @typescript-eslint/ban-types + | [url: string | URL, options?: RequestOptions, callback?: Function] + // eslint-disable-next-line @typescript-eslint/ban-types + | [options: RequestOptions, callback?: Function]; + type SentryHttpInstrumentationOptions = InstrumentationConfig & { /** * Whether breadcrumbs should be recorded for requests. @@ -80,8 +91,11 @@ const MAX_BODY_BYTE_LENGTH = 1024 * 1024; * https://github.com/open-telemetry/opentelemetry-js/blob/f8ab5592ddea5cba0a3b33bf8d74f27872c0367f/experimental/packages/opentelemetry-instrumentation-http/src/http.ts */ export class SentryHttpInstrumentation extends InstrumentationBase { + private _propagationDecisionMap: LRUMap; + public constructor(config: SentryHttpInstrumentationOptions = {}) { super('@sentry/instrumentation-http', VERSION, config); + this._propagationDecisionMap = new LRUMap(100); } /** @inheritdoc */ @@ -208,22 +222,21 @@ export class SentryHttpInstrumentation extends InstrumentationBase; + const request = original.apply(this, [optionsParsed, ...requestArgs.slice(1)]) as ReturnType< + typeof http.request + >; request.prependListener('response', (response: http.IncomingMessage) => { const _breadcrumbs = instrumentation.getConfig().breadcrumbs; @@ -457,6 +470,44 @@ function patchRequestToCaptureBody(req: IncomingMessage, isolationScope: Scope): } } +/** + * Mutates the passed in `options` and adds `sentry-trace` / `baggage` headers, if they are not already set. + */ +function addSentryHeadersToRequestOptions( + url: string, + options: RequestOptions, + propagationDecisionMap: LRUMap, +): void { + // Manually add the trace headers, if it applies + // Note: We do not use `propagation.inject()` here, because our propagator relies on an active span + // Which we do not have in this case + const tracePropagationTargets = getClient()?.getOptions().tracePropagationTargets; + const addedHeaders = shouldPropagateTraceForUrl(url, tracePropagationTargets, propagationDecisionMap) + ? getTraceData() + : undefined; + + if (!addedHeaders) { + return; + } + + if (!options.headers) { + options.headers = {}; + } + const headers = options.headers; + + const { 'sentry-trace': sentryTrace, baggage } = addedHeaders; + + // We do not want to overwrite existing header here, if it was already set + if (sentryTrace && !headers['sentry-trace']) { + headers['sentry-trace'] = sentryTrace; + } + + // For baggage, we make sure to merge this into a possibly existing header + if (baggage) { + headers['baggage'] = mergeBaggageHeaders(headers['baggage'], baggage); + } +} + /** * Starts a session and tracks it in the context of a given isolation scope. * When the passed response is finished, the session is put into a task and is @@ -531,3 +582,49 @@ const clientToRequestSessionAggregatesMap = new Map< Client, { [timestampRoundedToSeconds: string]: { exited: number; crashed: number; errored: number } } >(); + +function getAbsoluteUrl(origin: string, path: string = '/'): string { + try { + const url = new URL(path, origin); + return url.toString(); + } catch { + // fallback: Construct it on our own + const url = `${origin}`; + + if (url.endsWith('/') && path.startsWith('/')) { + return `${url}${path.slice(1)}`; + } + + if (!url.endsWith('/') && !path.startsWith('/')) { + return `${url}/${path.slice(1)}`; + } + + return `${url}${path}`; + } +} + +function mergeBaggageHeaders( + existing: string | string[] | number | undefined, + baggage: string, +): string | string[] | number | undefined { + if (!existing) { + return baggage; + } + + const existingBaggageEntries = parseBaggageHeader(existing); + const newBaggageEntries = parseBaggageHeader(baggage); + + if (!newBaggageEntries) { + return existing; + } + + // Existing entries take precedence, ensuring order remains stable for minimal changes + const mergedBaggageEntries = { ...existingBaggageEntries }; + Object.entries(newBaggageEntries).forEach(([key, value]) => { + if (!mergedBaggageEntries[key]) { + mergedBaggageEntries[key] = value; + } + }); + + return objectToBaggageHeader(mergedBaggageEntries); +} diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index ec9bc149410d..58516671c9a3 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -1,11 +1,61 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; // When importing CJS modules into an ESM module, we cannot import the named exports directly. import * as prismaInstrumentation from '@prisma/instrumentation'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, consoleSandbox, defineIntegration, spanToJSON } from '@sentry/core'; import { generateInstrumentOnce } from '../../otel/instrument'; +import type { PrismaV5TracingHelper } from './prisma/vendor/v5-tracing-helper'; +import type { PrismaV6TracingHelper } from './prisma/vendor/v6-tracing-helper'; const INTEGRATION_NAME = 'Prisma'; +const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = + // @ts-expect-error We need to do the following for interop reasons + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; + +type CompatibilityLayerTraceHelper = PrismaV5TracingHelper & PrismaV6TracingHelper; + +function isPrismaV6TracingHelper(helper: unknown): helper is PrismaV6TracingHelper { + return !!helper && typeof helper === 'object' && 'dispatchEngineSpans' in helper; +} + +class SentryPrismaInteropInstrumentation extends EsmInteropPrismaInstrumentation { + public constructor() { + super(); + } + + public enable(): void { + super.enable(); + + // The PrismaIntegration (super class) defines a global variable `global["PRISMA_INSTRUMENTATION"]` when `enable()` is called. This global variable holds a "TracingHelper" which Prisma uses internally to create tracing data. It's their way of not depending on OTEL with their main package. The sucky thing is, prisma broke the interface of the tracing helper with the v6 major update. This means that if you use Prisma 5 with the v6 instrumentation (or vice versa) Prisma just blows up, because tries to call methods on the helper that no longer exist. + // Because we actually want to use the v6 instrumentation and not blow up in Prisma 5 user's faces, what we're doing here is backfilling the v5 method (`createEngineSpan`) with a noop so that no longer crashes when it attempts to call that function. + // We still won't fully emit all the spans, but this could potentially be implemented in the future. + const prismaInstrumentationObject = (globalThis as Record).PRISMA_INSTRUMENTATION; + const prismaTracingHelper = + prismaInstrumentationObject && + typeof prismaInstrumentationObject === 'object' && + 'helper' in prismaInstrumentationObject + ? prismaInstrumentationObject.helper + : undefined; + + let emittedWarning = false; + + if (isPrismaV6TracingHelper(prismaTracingHelper)) { + (prismaTracingHelper as CompatibilityLayerTraceHelper).createEngineSpan = () => { + consoleSandbox(() => { + if (!emittedWarning) { + emittedWarning = true; + // eslint-disable-next-line no-console + console.warn( + '[Sentry] The Sentry SDK supports tracing with Prisma version 5 only with limited capabilities. For full tracing capabilities pass `prismaInstrumentation` for version 5 to the Sentry `prismaIntegration`. Read more: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/', + ); + } + }); + }; + } + } +} + export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: Instrumentation }>( INTEGRATION_NAME, options => { @@ -14,12 +64,7 @@ export const instrumentPrisma = generateInstrumentOnce<{ prismaInstrumentation?: return options.prismaInstrumentation; } - const EsmInteropPrismaInstrumentation: typeof prismaInstrumentation.PrismaInstrumentation = - // @ts-expect-error We need to do the following for interop reasons - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - prismaInstrumentation.default?.PrismaInstrumentation || prismaInstrumentation.PrismaInstrumentation; - - return new EsmInteropPrismaInstrumentation({}); + return new SentryPrismaInteropInstrumentation(); }, ); diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts new file mode 100644 index 000000000000..8823a8ca7728 --- /dev/null +++ b/packages/node/src/integrations/tracing/prisma/vendor/v5-tracing-helper.ts @@ -0,0 +1,41 @@ +// Vendored from https://github.com/prisma/prisma/blob/718358aa37975c18e5ea62f5b659fb47630b7609/packages/internals/src/tracing/types.ts#L1 + +import type { Context, Span, SpanOptions } from '@opentelemetry/api'; + +type V5SpanCallback = (span?: Span, context?: Context) => R; + +type V5ExtendedSpanOptions = SpanOptions & { + name: string; + internal?: boolean; + middleware?: boolean; + active?: boolean; + context?: Context; +}; + +type EngineSpanEvent = { + span: boolean; + spans: V5EngineSpan[]; +}; + +type V5EngineSpanKind = 'client' | 'internal'; + +type V5EngineSpan = { + span: boolean; + name: string; + trace_id: string; + span_id: string; + parent_span_id: string; + start_time: [number, number]; + end_time: [number, number]; + attributes?: Record; + links?: { trace_id: string; span_id: string }[]; + kind: V5EngineSpanKind; +}; + +export interface PrismaV5TracingHelper { + isEnabled(): boolean; + getTraceParent(context?: Context): string; + createEngineSpan(engineSpanEvent: EngineSpanEvent): void; + getActiveContext(): Context | undefined; + runInChildSpan(nameOrOptions: string | V5ExtendedSpanOptions, callback: V5SpanCallback): R; +} diff --git a/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts new file mode 100644 index 000000000000..2ad1482a2e1a --- /dev/null +++ b/packages/node/src/integrations/tracing/prisma/vendor/v6-tracing-helper.ts @@ -0,0 +1,38 @@ +// https://github.com/prisma/prisma/blob/d45607dfa10c4ef08cb8f79f18fa84ef33910150/packages/internals/src/tracing/types.ts#L1 + +import type { Context, Span, SpanOptions } from '@opentelemetry/api'; + +type V6SpanCallback = (span?: Span, context?: Context) => R; + +type V6ExtendedSpanOptions = SpanOptions & { + name: string; + internal?: boolean; + middleware?: boolean; + active?: boolean; + context?: Context; +}; + +type V6EngineSpanId = string; + +type V6HrTime = [number, number]; + +type EngineSpanKind = 'client' | 'internal'; + +type PrismaV6EngineSpan = { + id: V6EngineSpanId; + parentId: string | null; + name: string; + startTime: V6HrTime; + endTime: V6HrTime; + kind: EngineSpanKind; + attributes?: Record; + links?: V6EngineSpanId[]; +}; + +export interface PrismaV6TracingHelper { + isEnabled(): boolean; + getTraceParent(context?: Context): string; + dispatchEngineSpans(spans: PrismaV6EngineSpan[]): void; + getActiveContext(): Context | undefined; + runInChildSpan(nameOrOptions: string | V6ExtendedSpanOptions, callback: V6SpanCallback): R; +} diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index 43a93d16b563..bf4913688470 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -120,6 +120,14 @@ export interface BaseNodeOptions { */ disableInstrumentationWarnings?: boolean; + /** + * Controls how many milliseconds to wait before shutting down. The default is 2 seconds. Setting this too low can cause + * problems for sending events from command line applications. Setting it too + * high can cause the application to block for users with network connectivity + * problems. + */ + shutdownTimeout?: number; + /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; } diff --git a/packages/nuxt/LICENSE b/packages/nuxt/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/nuxt/LICENSE +++ b/packages/nuxt/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/nuxt/src/common/types.ts b/packages/nuxt/src/common/types.ts index 93ca94016924..8a9a453ff7db 100644 --- a/packages/nuxt/src/common/types.ts +++ b/packages/nuxt/src/common/types.ts @@ -32,6 +32,13 @@ type SourceMapsOptions = { */ org?: string; + /** + * The URL of your Sentry instance if you're using self-hosted Sentry. + * + * @default https://sentry.io by default the plugin will point towards the Sentry SaaS URL + */ + url?: string; + /** * The project slug of your Sentry project. * Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable. diff --git a/packages/nuxt/src/vite/sourceMaps.ts b/packages/nuxt/src/vite/sourceMaps.ts index 2f90094e6138..0b264e822bcc 100644 --- a/packages/nuxt/src/vite/sourceMaps.ts +++ b/packages/nuxt/src/vite/sourceMaps.ts @@ -91,6 +91,7 @@ export function getPluginOptions( project: sourceMapsUploadOptions.project ?? process.env.SENTRY_PROJECT, authToken: sourceMapsUploadOptions.authToken ?? process.env.SENTRY_AUTH_TOKEN, telemetry: sourceMapsUploadOptions.telemetry ?? true, + url: sourceMapsUploadOptions.url ?? process.env.SENTRY_URL, debug: moduleOptions.debug ?? false, _metaOptions: { telemetry: { diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts index 0c90429fa8d5..b33d314f5166 100644 --- a/packages/nuxt/test/vite/sourceMaps.test.ts +++ b/packages/nuxt/test/vite/sourceMaps.test.ts @@ -20,6 +20,7 @@ describe('getPluginOptions', () => { SENTRY_ORG: 'default-org', SENTRY_PROJECT: 'default-project', SENTRY_AUTH_TOKEN: 'default-token', + SENTRY_URL: 'https://santry.io', }; process.env = { ...defaultEnv }; @@ -31,6 +32,7 @@ describe('getPluginOptions', () => { org: 'default-org', project: 'default-project', authToken: 'default-token', + url: 'https://santry.io', telemetry: true, sourcemaps: expect.objectContaining({ rewriteSources: expect.any(Function), @@ -114,6 +116,7 @@ describe('getPluginOptions', () => { assets: ['custom-assets/**/*'], filesToDeleteAfterUpload: ['delete-this.js'], }, + url: 'https://santry.io', }, debug: true, unstable_sentryBundlerPluginOptions: { @@ -124,6 +127,7 @@ describe('getPluginOptions', () => { release: { name: 'test-release', }, + url: 'https://suntry.io', }, }; const options = getPluginOptions(customOptions, false); @@ -140,6 +144,7 @@ describe('getPluginOptions', () => { release: expect.objectContaining({ name: 'test-release', }), + url: 'https://suntry.io', }), ); }); diff --git a/packages/opentelemetry/LICENSE b/packages/opentelemetry/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/opentelemetry/LICENSE +++ b/packages/opentelemetry/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/profiling-node/LICENSE b/packages/profiling-node/LICENSE index 048dee5adaa8..293314012679 100644 --- a/packages/profiling-node/LICENSE +++ b/packages/profiling-node/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node b/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node deleted file mode 100755 index 65e97eca7e48..000000000000 Binary files a/packages/profiling-node/bin/darwin-arm64-130/profiling-node.node and /dev/null differ diff --git a/packages/profiling-node/binding.gyp b/packages/profiling-node/binding.gyp deleted file mode 100644 index 1c1aad075e39..000000000000 --- a/packages/profiling-node/binding.gyp +++ /dev/null @@ -1,20 +0,0 @@ -{ - "targets": [ - { - "target_name": "sentry_cpu_profiler", - "sources": [ "bindings/cpu_profiler.cc" ], - # Silence gcc8 deprecation warning https://github.com/nodejs/nan/issues/807#issuecomment-455750192 - "cflags": ["-Wno-cast-function-type"] - }, - ], - 'conditions': [ - [ 'OS=="win"', { - 'defines': [ - # Stop from defining macros that conflict with - # std::min() and std::max(). We don't use (much) - # but we still inherit it from uv.h. - 'NOMINMAX', - ] - }], - ], -} diff --git a/packages/profiling-node/bindings/cpu_profiler.cc b/packages/profiling-node/bindings/cpu_profiler.cc deleted file mode 100644 index bf3762867769..000000000000 --- a/packages/profiling-node/bindings/cpu_profiler.cc +++ /dev/null @@ -1,1226 +0,0 @@ -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static const uint8_t kMaxStackDepth(128); -static const float kSamplingFrequency(99.0); // 99 to avoid lockstep sampling -static const float kSamplingHz(1 / kSamplingFrequency); -static const int kSamplingInterval(kSamplingHz * 1e6); -static const v8::CpuProfilingNamingMode - kNamingMode(v8::CpuProfilingNamingMode::kDebugNaming); -static const v8::CpuProfilingLoggingMode - kDefaultLoggingMode(v8::CpuProfilingLoggingMode::kEagerLogging); - -enum ProfileFormat { - kFormatThread = 0, - kFormatChunk = 1, -}; - -// Allow users to override the default logging mode via env variable. This is -// useful because sometimes the flow of the profiled program can be to execute -// many sequential transaction - in that case, it may be preferable to set eager -// logging to avoid paying the high cost of profiling for each individual -// transaction (one example for this are jest tests when run with --runInBand -// option). -static const char *kEagerLoggingMode = "eager"; -static const char *kLazyLoggingMode = "lazy"; - -v8::CpuProfilingLoggingMode GetLoggingMode() { - static const char *logging_mode(getenv("SENTRY_PROFILER_LOGGING_MODE")); - - // most times this wont be set so just bail early - if (!logging_mode) { - return kDefaultLoggingMode; - } - - // other times it'll likely be set to lazy as eager is the default - if (strcmp(logging_mode, kLazyLoggingMode) == 0) { - return v8::CpuProfilingLoggingMode::kLazyLogging; - } else if (strcmp(logging_mode, kEagerLoggingMode) == 0) { - return v8::CpuProfilingLoggingMode::kEagerLogging; - } - - return kDefaultLoggingMode; -} - -uint64_t timestamp_milliseconds() { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); -} - -class SentryProfile; -class Profiler; - -enum class ProfileStatus { - kNotStarted, - kStarted, - kStopped, -}; - -class MeasurementsTicker { -private: - uv_timer_t *timer; - uint64_t period_ms; - std::unordered_map> - heap_listeners; - std::unordered_map> - cpu_listeners; - v8::Isolate *isolate; - v8::HeapStatistics heap_stats; - uv_cpu_info_t cpu_stats; - -public: - MeasurementsTicker(uv_loop_t *loop) - : period_ms(100), isolate(v8::Isolate::GetCurrent()) { - timer = new uv_timer_t; - uv_timer_init(loop, timer); - uv_handle_set_data((uv_handle_t *)timer, this); - uv_ref((uv_handle_t *)timer); - } - - static void ticker(uv_timer_t *); - // Memory listeners - void heap_callback(); - void add_heap_listener( - std::string &profile_id, - const std::function cb); - void remove_heap_listener( - std::string &profile_id, - const std::function &cb); - - // CPU listeners - void cpu_callback(); - void add_cpu_listener(std::string &profile_id, - const std::function cb); - void remove_cpu_listener(std::string &profile_id, - const std::function &cb); - - size_t listener_count(); - - ~MeasurementsTicker() { - uv_handle_t *handle = (uv_handle_t *)timer; - - uv_timer_stop(timer); - uv_unref(handle); - - if (!uv_is_closing(handle)) { - uv_close(handle, [](uv_handle_t *handle) { delete handle; }); - } - } -}; - -size_t MeasurementsTicker::listener_count() { - return heap_listeners.size() + cpu_listeners.size(); -} - -// Heap tickers -void MeasurementsTicker::heap_callback() { - isolate->GetHeapStatistics(&heap_stats); - uint64_t ts = uv_hrtime(); - - for (auto cb : heap_listeners) { - cb.second(ts, heap_stats); - } -} - -void MeasurementsTicker::add_heap_listener( - std::string &profile_id, - const std::function cb) { - heap_listeners.emplace(profile_id, cb); - - if (listener_count() == 1) { - uv_timer_set_repeat(timer, period_ms); - uv_timer_start(timer, ticker, 0, period_ms); - } -} - -void MeasurementsTicker::remove_heap_listener( - std::string &profile_id, - const std::function &cb) { - heap_listeners.erase(profile_id); - - if (listener_count() == 0) { - uv_timer_stop(timer); - } -}; - -// CPU tickers -void MeasurementsTicker::cpu_callback() { - uv_cpu_info_t *cpu = &cpu_stats; - int count; - int err = uv_cpu_info(&cpu, &count); - - if (err) { - return; - } - - if (count < 1) { - return; - } - - uint64_t ts = uv_hrtime(); - uint64_t total = 0; - uint64_t idle_total = 0; - - for (int i = 0; i < count; i++) { - uv_cpu_info_t *core = cpu + i; - - total += core->cpu_times.user; - total += core->cpu_times.nice; - total += core->cpu_times.sys; - total += core->cpu_times.idle; - total += core->cpu_times.irq; - - idle_total += core->cpu_times.idle; - } - - double idle_avg = idle_total / count; - double total_avg = total / count; - double rate = 1.0 - idle_avg / total_avg; - - if (rate < 0.0 || isinf(rate) || isnan(rate)) { - rate = 0.0; - } - - auto it = cpu_listeners.begin(); - while (it != cpu_listeners.end()) { - if (it->second(ts, rate)) { - it = cpu_listeners.erase(it); - } else { - ++it; - } - }; - - uv_free_cpu_info(cpu, count); -}; - -void MeasurementsTicker::ticker(uv_timer_t *handle) { - if (handle == nullptr) { - return; - } - - MeasurementsTicker *self = static_cast(handle->data); - self->heap_callback(); - self->cpu_callback(); -} - -void MeasurementsTicker::add_cpu_listener( - std::string &profile_id, const std::function cb) { - cpu_listeners.emplace(profile_id, cb); - - if (listener_count() == 1) { - uv_timer_set_repeat(timer, period_ms); - uv_timer_start(timer, ticker, 0, period_ms); - } -} - -void MeasurementsTicker::remove_cpu_listener( - std::string &profile_id, const std::function &cb) { - cpu_listeners.erase(profile_id); - - if (listener_count() == 0) { - uv_timer_stop(timer); - } -}; - -class Profiler { -public: - std::unordered_map active_profiles; - - MeasurementsTicker measurements_ticker; - v8::CpuProfiler *cpu_profiler; - - explicit Profiler(const napi_env &env, v8::Isolate *isolate) - : measurements_ticker(uv_default_loop()), - cpu_profiler( - v8::CpuProfiler::New(isolate, kNamingMode, GetLoggingMode())) {} -}; - -class SentryProfile { -private: - uint64_t started_at; - uint64_t timestamp; - uint16_t heap_write_index = 0; - uint16_t cpu_write_index = 0; - - std::vector heap_stats_ts; - std::vector heap_stats_usage; - - std::vector cpu_stats_ts; - std::vector cpu_stats_usage; - - const std::function memory_sampler_cb; - const std::function cpu_sampler_cb; - - ProfileStatus status = ProfileStatus::kNotStarted; - std::string id; - -public: - explicit SentryProfile(const char *id) - : started_at(uv_hrtime()), timestamp(timestamp_milliseconds()), - memory_sampler_cb([this](uint64_t ts, v8::HeapStatistics &stats) { - if ((heap_write_index >= heap_stats_ts.capacity()) || - heap_write_index >= heap_stats_usage.capacity()) { - return true; - } - - heap_stats_ts.insert(heap_stats_ts.begin() + heap_write_index, - ts - started_at); - heap_stats_usage.insert( - heap_stats_usage.begin() + heap_write_index, - static_cast(stats.used_heap_size())); - ++heap_write_index; - - return false; - }), - - cpu_sampler_cb([this](uint64_t ts, double rate) { - if (cpu_write_index >= cpu_stats_ts.capacity() || - cpu_write_index >= cpu_stats_usage.capacity()) { - return true; - } - cpu_stats_ts.insert(cpu_stats_ts.begin() + cpu_write_index, - ts - started_at); - cpu_stats_usage.insert(cpu_stats_usage.begin() + cpu_write_index, - rate); - ++cpu_write_index; - return false; - }), - - status(ProfileStatus::kNotStarted), id(id) { - heap_stats_ts.reserve(300); - heap_stats_usage.reserve(300); - cpu_stats_ts.reserve(300); - cpu_stats_usage.reserve(300); - } - - const std::vector &heap_usage_timestamps() const; - const std::vector &heap_usage_values() const; - const uint16_t &heap_usage_write_index() const; - - const std::vector &cpu_usage_timestamps() const; - const std::vector &cpu_usage_values() const; - const uint16_t &cpu_usage_write_index() const; - const uint64_t &profile_start_timestamp() const; - - void Start(Profiler *profiler); - v8::CpuProfile *Stop(Profiler *profiler); -}; - -void SentryProfile::Start(Profiler *profiler) { - v8::Local profile_title = - v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(), - v8::NewStringType::kNormal) - .ToLocalChecked(); - - started_at = uv_hrtime(); - timestamp = timestamp_milliseconds(); - - // Initialize the CPU Profiler - profiler->cpu_profiler->StartProfiling( - profile_title, v8::CpuProfilingMode::kCallerLineNumbers, true, - v8::CpuProfilingOptions::kNoSampleLimit); - - // listen for memory sample ticks - profiler->measurements_ticker.add_cpu_listener(id, cpu_sampler_cb); - profiler->measurements_ticker.add_heap_listener(id, memory_sampler_cb); - - status = ProfileStatus::kStarted; -} - -v8::CpuProfile *SentryProfile::Stop(Profiler *profiler) { - // Stop the CPU Profiler - v8::CpuProfile *profile = profiler->cpu_profiler->StopProfiling( - v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), id.c_str(), - v8::NewStringType::kNormal) - .ToLocalChecked()); - - // Remove the memory sampler - profiler->measurements_ticker.remove_heap_listener(id, memory_sampler_cb); - profiler->measurements_ticker.remove_cpu_listener(id, cpu_sampler_cb); - // If for some reason stopProfiling was called with an invalid profile title - // or if that title had somehow been stopped already, profile will be null. - status = ProfileStatus::kStopped; - return profile; -} - -// Memory getters -const std::vector &SentryProfile::heap_usage_timestamps() const { - return heap_stats_ts; -}; - -const std::vector &SentryProfile::heap_usage_values() const { - return heap_stats_usage; -}; - -const uint16_t &SentryProfile::heap_usage_write_index() const { - return heap_write_index; -}; - -// CPU getters -const std::vector &SentryProfile::cpu_usage_timestamps() const { - return cpu_stats_ts; -}; - -const std::vector &SentryProfile::cpu_usage_values() const { - return cpu_stats_usage; -}; -const uint16_t &SentryProfile::cpu_usage_write_index() const { - return cpu_write_index; -}; -const uint64_t &SentryProfile::profile_start_timestamp() const { - return timestamp; -} - -static void CleanupSentryProfile(Profiler *profiler, - SentryProfile *sentry_profile, - const std::string &profile_id) { - if (sentry_profile == nullptr) { - return; - } - - sentry_profile->Stop(profiler); - profiler->active_profiles.erase(profile_id); - delete sentry_profile; -}; - -#ifdef _WIN32 -static const char kPlatformSeparator = '\\'; -static const char kWinDiskPrefix = ':'; -#else -static const char kPlatformSeparator = '/'; -#endif - -static const char kSentryPathDelimiter = '.'; -static const char kSentryFileDelimiter = ':'; -static const std::string kNodeModulesPath = - std::string("node_modules") + kPlatformSeparator; - -static void GetFrameModule(const std::string &abs_path, std::string &module) { - if (abs_path.empty()) { - return; - } - - module = abs_path; - - // Drop .js extension - size_t module_len = module.length(); - if (module.compare(module_len - 3, 3, ".js") == 0) { - module = module.substr(0, module_len - 3); - } - - // Drop anything before and including node_modules/ - size_t node_modules_pos = module.rfind(kNodeModulesPath); - if (node_modules_pos != std::string::npos) { - module = module.substr(node_modules_pos + 13); - } - - // Replace all path separators with dots except the last one, that one is - // replaced with a colon - int match_count = 0; - for (int pos = module.length() - 1; pos >= 0; pos--) { - // if there is a match and it's not the first character, replace it - if (module[pos] == kPlatformSeparator) { - module[pos] = - match_count == 0 ? kSentryFileDelimiter : kSentryPathDelimiter; - match_count++; - } - } - -#ifdef _WIN32 - // Strip out C: prefix. On Windows, the drive letter is not part of the module - // name - if (module[1] == kWinDiskPrefix) { - // We will try and strip our the disk prefix. - module = module.substr(2, std::string::npos); - } -#endif - - if (module[0] == '.') { - module = module.substr(1, std::string::npos); - } -} - -static napi_value GetFrameModuleWrapped(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2]; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *abs_path = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], abs_path, len + 1, &len) == - napi_ok); - - std::string module; - napi_value napi_module; - - GetFrameModule(abs_path, module); - - assert(napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH, - &napi_module) == napi_ok); - return napi_module; -} - -napi_value -CreateFrameNode(const napi_env &env, const v8::CpuProfileNode &node, - std::unordered_map &module_cache, - napi_value &resources) { - napi_value js_node; - napi_create_object(env, &js_node); - - napi_value lineno_prop; - napi_create_int32(env, node.GetLineNumber(), &lineno_prop); - napi_set_named_property(env, js_node, "lineno", lineno_prop); - - napi_value colno_prop; - napi_create_int32(env, node.GetColumnNumber(), &colno_prop); - napi_set_named_property(env, js_node, "colno", colno_prop); - - if (node.GetSourceType() != v8::CpuProfileNode::SourceType::kScript) { - napi_value system_frame_prop; - napi_get_boolean(env, false, &system_frame_prop); - napi_set_named_property(env, js_node, "in_app", system_frame_prop); - } - - napi_value function; - napi_create_string_utf8(env, node.GetFunctionNameStr(), NAPI_AUTO_LENGTH, - &function); - napi_set_named_property(env, js_node, "function", function); - - const char *resource = node.GetScriptResourceNameStr(); - - if (resource != nullptr) { - // resource is absolute path, set it on the abs_path property - napi_value abs_path_prop; - napi_create_string_utf8(env, resource, NAPI_AUTO_LENGTH, &abs_path_prop); - napi_set_named_property(env, js_node, "abs_path", abs_path_prop); - // Error stack traces are not relative to root dir, doing our own path - // normalization breaks people's code mapping configs so we need to leave it - // as is. - napi_set_named_property(env, js_node, "filename", abs_path_prop); - - std::string module; - std::string resource_str = std::string(resource); - - if (resource_str.empty()) { - return js_node; - } - - if (module_cache.find(resource_str) != module_cache.end()) { - module = module_cache[resource_str]; - } else { - napi_value resource; - napi_create_string_utf8(env, resource_str.c_str(), NAPI_AUTO_LENGTH, - &resource); - napi_set_element(env, resources, module_cache.size(), resource); - - GetFrameModule(resource_str, module); - module_cache.emplace(resource_str, module); - } - - if (!module.empty()) { - napi_value filename_prop; - napi_create_string_utf8(env, module.c_str(), NAPI_AUTO_LENGTH, - &filename_prop); - napi_set_named_property(env, js_node, "module", filename_prop); - } - } - - return js_node; -}; - -napi_value CreateSample(const napi_env &env, const enum ProfileFormat format, - const uint32_t stack_id, - const int64_t sample_timestamp_ns, - const double chunk_timestamp, - const uint32_t thread_id) { - napi_value js_node; - napi_create_object(env, &js_node); - - napi_value stack_id_prop; - napi_create_uint32(env, stack_id, &stack_id_prop); - napi_set_named_property(env, js_node, "stack_id", stack_id_prop); - - napi_value thread_id_prop; - napi_create_string_utf8(env, std::to_string(thread_id).c_str(), - NAPI_AUTO_LENGTH, &thread_id_prop); - napi_set_named_property(env, js_node, "thread_id", thread_id_prop); - - switch (format) { - case ProfileFormat::kFormatThread: { - napi_value timestamp; - napi_create_int64(env, sample_timestamp_ns, ×tamp); - napi_set_named_property(env, js_node, "elapsed_since_start_ns", timestamp); - } break; - case ProfileFormat::kFormatChunk: { - napi_value timestamp; - napi_create_double(env, chunk_timestamp, ×tamp); - napi_set_named_property(env, js_node, "timestamp", timestamp); - } break; - default: - break; - } - - return js_node; -}; - -std::string kDelimiter = std::string(";"); -std::string hashCpuProfilerNodeByPath(const v8::CpuProfileNode *node, - std::string &path) { - path.clear(); - - while (node != nullptr) { - path.append(std::to_string(node->GetNodeId())); - node = node->GetParent(); - } - - return path; -} - -static void GetSamples(const napi_env &env, const v8::CpuProfile *profile, - ProfileFormat format, - const uint64_t profile_start_timestamp_ms, - const uint32_t thread_id, napi_value &samples, - napi_value &stacks, napi_value &frames, - napi_value &resources) { - const int64_t profile_start_time_us = profile->GetStartTime(); - const int64_t sampleCount = profile->GetSamplesCount(); - - uint32_t unique_stack_id = 0; - uint32_t unique_frame_id = 0; - - // Initialize the lookup tables for stacks and frames, both of these are - // indexed in the sample format we are using to optimize for size. - std::unordered_map frame_lookup_table; - std::unordered_map stack_lookup_table; - std::unordered_map module_cache; - - // At worst, all stacks are unique so reserve the maximum amount of space - stack_lookup_table.reserve(sampleCount); - - std::string node_hash = ""; - - for (int i = 0; i < sampleCount; i++) { - uint32_t stack_index = unique_stack_id; - - const v8::CpuProfileNode *node = profile->GetSample(i); - const int64_t sample_timestamp_us = profile->GetSampleTimestamp(i); - - // If a node was only on top of the stack once, then it will only ever - // be inserted once and there is no need for hashing. - if (node->GetHitCount() > 1) { - hashCpuProfilerNodeByPath(node, node_hash); - - std::unordered_map::iterator - stack_index_cache_hit = stack_lookup_table.find(node_hash); - - // If we have a hit, update the stack index, otherwise - // insert it into the hash table and continue. - if (stack_index_cache_hit == stack_lookup_table.end()) { - stack_lookup_table.emplace(node_hash, stack_index); - } else { - stack_index = stack_index_cache_hit->second; - } - } - - uint64_t sample_delta_us = sample_timestamp_us - profile_start_time_us; - uint64_t sample_timestamp_ns = sample_delta_us * 1e3; - uint64_t sample_offset_from_profile_start_ms = - (sample_timestamp_us - profile_start_time_us) * 1e-3; - double seconds_since_start = - (profile_start_timestamp_ms + sample_offset_from_profile_start_ms) * - 1e-3; - - napi_value sample = nullptr; - sample = CreateSample(env, format, stack_index, sample_timestamp_ns, - seconds_since_start, thread_id); - - if (stack_index != unique_stack_id) { - napi_value index; - napi_create_uint32(env, i, &index); - napi_set_property(env, samples, index, sample); - continue; - } - - // A stack is a list of frames ordered from outermost (top) to innermost - // frame (bottom) - napi_value stack; - napi_create_array(env, &stack); - - uint32_t stack_depth = 0; - - while (node != nullptr && stack_depth < kMaxStackDepth) { - auto nodeId = node->GetNodeId(); - auto frame_index = frame_lookup_table.find(nodeId); - - // If the frame does not exist in the index - if (frame_index == frame_lookup_table.end()) { - frame_lookup_table.emplace(nodeId, unique_frame_id); - - napi_value frame_id; - napi_create_uint32(env, unique_frame_id, &frame_id); - - napi_value depth; - napi_create_uint32(env, stack_depth, &depth); - napi_set_property(env, stack, depth, frame_id); - napi_set_property(env, frames, frame_id, - CreateFrameNode(env, *node, module_cache, resources)); - - unique_frame_id++; - } else { - // If it was already indexed, just add it's id to the stack - napi_value depth; - napi_create_uint32(env, stack_depth, &depth); - - napi_value frame; - napi_create_uint32(env, frame_index->second, &frame); - napi_set_property(env, stack, depth, frame); - }; - - // Continue walking down the stack - node = node->GetParent(); - stack_depth++; - } - - napi_value napi_sample_index; - napi_value napi_stack_index; - - napi_create_uint32(env, i, &napi_sample_index); - napi_set_property(env, samples, napi_sample_index, sample); - napi_create_uint32(env, stack_index, &napi_stack_index); - napi_set_property(env, stacks, napi_stack_index, stack); - - unique_stack_id++; - } -} - -static napi_value TranslateMeasurementsDouble( - const napi_env &env, const enum ProfileFormat format, const char *unit, - const uint64_t profile_start_timestamp_ms, const uint16_t size, - const std::vector &values, - const std::vector ×tamps_ns) { - if (size > values.size() || size > timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "CPU measurement size is larger than the number of " - "values or timestamps"); - return nullptr; - } - - if (values.size() != timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "CPU measurement entries are corrupt, expected " - "values and timestamps to be of equal length"); - return nullptr; - } - - napi_value measurement; - napi_create_object(env, &measurement); - - napi_value unit_string; - napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string); - napi_set_named_property(env, measurement, "unit", unit_string); - - napi_value values_array; - napi_create_array(env, &values_array); - - uint16_t idx = size; - - for (size_t i = 0; i < idx; i++) { - napi_value entry; - napi_create_object(env, &entry); - - napi_value value; - if (napi_create_double(env, values[i], &value) != napi_ok) { - if (napi_create_double(env, 0.0, &value) != napi_ok) { - continue; - } - } - - napi_set_named_property(env, entry, "value", value); - - if (format == ProfileFormat::kFormatThread) { - napi_value ts; - napi_create_int64(env, timestamps_ns[i], &ts); - napi_set_named_property(env, entry, "elapsed_since_start_ns", ts); - } else if (format == ProfileFormat::kFormatChunk) { - napi_value ts; - napi_create_double( - env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts); - napi_set_named_property(env, entry, "timestamp", ts); - } - - napi_set_element(env, values_array, i, entry); - } - - napi_set_named_property(env, measurement, "values", values_array); - - return measurement; -} - -static napi_value -TranslateMeasurements(const napi_env &env, const enum ProfileFormat format, - const char *unit, - const uint64_t profile_start_timestamp_ms, - const uint16_t size, const std::vector &values, - const std::vector ×tamps_ns) { - if (size > values.size() || size > timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "Memory measurement size is larger than the number " - "of values or timestamps"); - return nullptr; - } - - if (values.size() != timestamps_ns.size()) { - napi_throw_range_error(env, "NAPI_ERROR", - "Memory measurement entries are corrupt, expected " - "values and timestamps to be of equal length"); - return nullptr; - } - - napi_value measurement; - napi_create_object(env, &measurement); - - napi_value unit_string; - napi_create_string_utf8(env, unit, NAPI_AUTO_LENGTH, &unit_string); - napi_set_named_property(env, measurement, "unit", unit_string); - - napi_value values_array; - napi_create_array(env, &values_array); - - for (size_t i = 0; i < size; i++) { - napi_value entry; - napi_create_object(env, &entry); - - napi_value value; - napi_create_int64(env, values[i], &value); - - napi_set_named_property(env, entry, "value", value); - switch (format) { - case ProfileFormat::kFormatThread: { - napi_value ts; - napi_create_int64(env, timestamps_ns[i], &ts); - napi_set_named_property(env, entry, "elapsed_since_start_ns", ts); - } break; - case ProfileFormat::kFormatChunk: { - napi_value ts; - napi_create_double( - env, profile_start_timestamp_ms + (timestamps_ns[i] * 1e-9), &ts); - napi_set_named_property(env, entry, "timestamp", ts); - } break; - default: - break; - } - napi_set_element(env, values_array, i, entry); - } - - napi_set_named_property(env, measurement, "values", values_array); - - return measurement; -} - -static napi_value TranslateProfile(const napi_env &env, - const v8::CpuProfile *profile, - const enum ProfileFormat format, - const uint64_t profile_start_timestamp_ms, - const uint32_t thread_id, - bool collect_resources) { - napi_value js_profile; - - napi_create_object(env, &js_profile); - - napi_value logging_mode; - napi_value samples; - napi_value stacks; - napi_value frames; - napi_value resources; - - napi_create_string_utf8( - env, - GetLoggingMode() == v8::CpuProfilingLoggingMode::kEagerLogging ? "eager" - : "lazy", - NAPI_AUTO_LENGTH, &logging_mode); - - napi_create_array(env, &samples); - napi_create_array(env, &stacks); - napi_create_array(env, &frames); - napi_create_array(env, &resources); - - napi_set_named_property(env, js_profile, "samples", samples); - napi_set_named_property(env, js_profile, "stacks", stacks); - napi_set_named_property(env, js_profile, "frames", frames); - napi_set_named_property(env, js_profile, "profiler_logging_mode", - logging_mode); - - GetSamples(env, profile, format, profile_start_timestamp_ms, thread_id, - samples, stacks, frames, resources); - - if (collect_resources) { - napi_set_named_property(env, js_profile, "resources", resources); - } else { - napi_create_array(env, &resources); - napi_set_named_property(env, js_profile, "resources", resources); - } - - return js_profile; -} - -static napi_value StartProfiling(napi_env env, napi_callback_info info) { - size_t argc = 1; - napi_value argv[1]; - - assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok); - - napi_valuetype callbacktype0; - assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok); - - if (callbacktype0 != napi_string) { - napi_throw_error( - env, "NAPI_ERROR", - "TypeError: StartProfiling expects a string as first argument."); - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *title = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) == - napi_ok); - - if (len < 1) { - napi_throw_error(env, "NAPI_ERROR", - "StartProfiling expects a non-empty string as first " - "argument, got an empty string."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - v8::Isolate *isolate = v8::Isolate::GetCurrent(); - assert(isolate != 0); - - Profiler *profiler; - assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok); - - if (!profiler) { - napi_throw_error(env, "NAPI_ERROR", - "StartProfiling: Profiler is not initialized."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - const std::string profile_id(title); - // In case we have a collision, cleanup the old profile first - auto existing_profile = profiler->active_profiles.find(profile_id); - if (existing_profile != profiler->active_profiles.end()) { - existing_profile->second->Stop(profiler); - CleanupSentryProfile(profiler, existing_profile->second, profile_id); - } - - SentryProfile *sentry_profile = new SentryProfile(title); - sentry_profile->Start(profiler); - - profiler->active_profiles.emplace(profile_id, sentry_profile); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; -} - -// StopProfiling(string title) -// https://v8docs.nodesource.com/node-18.2/d2/d34/classv8_1_1_cpu_profiler.html#a40ca4c8a8aa4c9233aa2a2706457cc80 -static napi_value StopProfiling(napi_env env, napi_callback_info info) { - size_t argc = 4; - napi_value argv[4]; - - assert(napi_get_cb_info(env, info, &argc, argv, NULL, NULL) == napi_ok); - - if (argc < 3) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects at least three arguments."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the first argument is a string - napi_valuetype callbacktype0; - assert(napi_typeof(env, argv[0], &callbacktype0) == napi_ok); - - if (callbacktype0 != napi_string) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects a string as first argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - size_t len; - assert(napi_get_value_string_utf8(env, argv[0], NULL, 0, &len) == napi_ok); - - char *title = (char *)malloc(len + 1); - assert(napi_get_value_string_utf8(env, argv[0], title, len + 1, &len) == - napi_ok); - - if (len < 1) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a non empty string as first argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the second argument is a number - napi_valuetype callbacktype1; - assert(napi_typeof(env, argv[1], &callbacktype1) == napi_ok); - - if (callbacktype1 != napi_number) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling expects a format type as second argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - - return napi_null; - } - - // Verify the second argument is a number - napi_valuetype callbacktype2; - assert(napi_typeof(env, argv[2], &callbacktype2) == napi_ok); - - if (callbacktype2 != napi_number) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a thread_id integer as third argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - // Get the value of the second argument and convert it to uint8 - int32_t format; - assert(napi_get_value_int32(env, argv[1], &format) == napi_ok); - - // Get the value of the second argument and convert it to uint64 - int64_t thread_id; - assert(napi_get_value_int64(env, argv[2], &thread_id) == napi_ok); - - // Get profiler from instance data - Profiler *profiler; - assert(napi_get_instance_data(env, (void **)&profiler) == napi_ok); - - if (!profiler) { - napi_throw_error(env, "NAPI_ERROR", - "StopProfiling: Profiler is not initialized."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - const std::string profile_id(title); - auto profile = profiler->active_profiles.find(profile_id); - - // If the profile was never started, silently ignore the call and return null - if (profile == profiler->active_profiles.end()) { - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - v8::CpuProfile *cpu_profile = profile->second->Stop(profiler); - - // If for some reason stopProfiling was called with an invalid profile title - // or if that title had somehow been stopped already, profile will be null. - if (!cpu_profile) { - CleanupSentryProfile(profiler, profile->second, profile_id); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - }; - - napi_valuetype callbacktype3; - assert(napi_typeof(env, argv[3], &callbacktype3) == napi_ok); - - bool collect_resources; - napi_get_value_bool(env, argv[3], &collect_resources); - - const ProfileFormat format_type = static_cast(format); - - if (format_type != ProfileFormat::kFormatThread && - format_type != ProfileFormat::kFormatChunk) { - napi_throw_error( - env, "NAPI_ERROR", - "StopProfiling expects a valid format type as second argument."); - - napi_value napi_null; - assert(napi_get_null(env, &napi_null) == napi_ok); - return napi_null; - } - - napi_value js_profile = TranslateProfile( - env, cpu_profile, format_type, profile->second->profile_start_timestamp(), - thread_id, collect_resources); - - napi_value measurements; - napi_create_object(env, &measurements); - - if (profile->second->heap_usage_write_index() > 0) { - static const char *memory_unit = "byte"; - napi_value heap_usage_measurements = - TranslateMeasurements(env, format_type, memory_unit, - profile->second->profile_start_timestamp(), - profile->second->heap_usage_write_index(), - profile->second->heap_usage_values(), - profile->second->heap_usage_timestamps()); - - if (heap_usage_measurements != nullptr) { - napi_set_named_property(env, measurements, "memory_footprint", - heap_usage_measurements); - }; - }; - - if (profile->second->cpu_usage_write_index() > 0) { - static const char *cpu_unit = "percent"; - napi_value cpu_usage_measurements = TranslateMeasurementsDouble( - env, format_type, cpu_unit, profile->second->profile_start_timestamp(), - profile->second->cpu_usage_write_index(), - profile->second->cpu_usage_values(), - profile->second->cpu_usage_timestamps()); - - if (cpu_usage_measurements != nullptr) { - napi_set_named_property(env, measurements, "cpu_usage", - cpu_usage_measurements); - }; - }; - - napi_set_named_property(env, js_profile, "measurements", measurements); - - CleanupSentryProfile(profiler, profile->second, profile_id); - cpu_profile->Delete(); - - return js_profile; -}; - -void FreeAddonData(napi_env env, void *data, void *hint) { - Profiler *profiler = static_cast(data); - - if (profiler == nullptr) { - return; - } - - if (!profiler->active_profiles.empty()) { - for (auto &profile : profiler->active_profiles) { - CleanupSentryProfile(profiler, profile.second, profile.first); - } - } - - if (profiler->cpu_profiler != nullptr) { - profiler->cpu_profiler->Dispose(); - profiler->cpu_profiler = nullptr; - } - - delete profiler; -} - -napi_value Init(napi_env env, napi_value exports) { - v8::Isolate *isolate = v8::Isolate::GetCurrent(); - - if (isolate == nullptr) { - napi_throw_error(env, nullptr, - "Failed to initialize Sentry profiler: isolate is null."); - return NULL; - } - - Profiler *profiler = new Profiler(env, isolate); - profiler->cpu_profiler->SetSamplingInterval(kSamplingInterval); - - if (napi_set_instance_data(env, profiler, FreeAddonData, NULL) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to set instance data for profiler."); - return NULL; - } - - napi_value start_profiling; - if (napi_create_function(env, "startProfiling", NAPI_AUTO_LENGTH, - StartProfiling, exports, - &start_profiling) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create startProfiling function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "startProfiling", - start_profiling) != napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set startProfiling property on exports."); - return NULL; - } - - napi_value stop_profiling; - if (napi_create_function(env, "stopProfiling", NAPI_AUTO_LENGTH, - StopProfiling, exports, - &stop_profiling) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create stopProfiling function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "stopProfiling", stop_profiling) != - napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set stopProfiling property on exports."); - return NULL; - } - - napi_value get_frame_module; - if (napi_create_function(env, "getFrameModule", NAPI_AUTO_LENGTH, - GetFrameModuleWrapped, exports, - &get_frame_module) != napi_ok) { - napi_throw_error(env, nullptr, "Failed to create getFrameModule function."); - return NULL; - } - - if (napi_set_named_property(env, exports, "getFrameModule", - get_frame_module) != napi_ok) { - napi_throw_error(env, nullptr, - "Failed to set getFrameModule property on exports."); - return NULL; - } - - return exports; -} - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/packages/profiling-node/clang-format.js b/packages/profiling-node/clang-format.js deleted file mode 100644 index dd001cf28ad7..000000000000 --- a/packages/profiling-node/clang-format.js +++ /dev/null @@ -1,26 +0,0 @@ -const child_process = require('child_process'); - -const args = ['--Werror', '-i', '--style=file', 'bindings/cpu_profiler.cc']; -const cmd = `./node_modules/.bin/clang-format ${args.join(' ')}`; - -try { - child_process.execSync(cmd); -} catch (e) { - // This fails on linux_arm64 - // eslint-disable-next-line no-console - console.log('Running clang format command failed.'); -} - -// eslint-disable-next-line no-console -console.log('clang-format: done, checking tree...'); - -const diff = child_process.execSync('git status --short').toString(); - -if (diff) { - // eslint-disable-next-line no-console - console.error('clang-format: check failed ❌'); - process.exit(1); -} - -// eslint-disable-next-line no-console -console.log('clang-format: check passed ✅'); diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index d07295ba2679..f6713484381b 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -40,53 +40,34 @@ }, "files": [ "/lib", - "/bindings", - "/binding.gyp", "package.json", - "/scripts/binaries.js", - "/scripts/check-build.js", - "/scripts/copy-target.js", "/scripts/prune-profiler-binaries.js" ], "scripts": { - "install": "node scripts/check-build.js", "clean": "rm -rf build && rm -rf lib", - "lint": "yarn lint:eslint && yarn lint:clang", - "lint:eslint": "eslint . --format stylish", - "lint:clang": "node clang-format.js", + "lint": "eslint . --format stylish", "fix": "eslint . --format stylish --fix", - "lint:fix": "yarn fix:eslint && yarn fix:clang", - "lint:fix:clang": "node clang-format.js --fix", - "build": "yarn build:lib && yarn build:bindings:configure && yarn build:bindings", + "build": "yarn build:lib", "build:lib": "yarn build:types && rollup -c rollup.npm.config.mjs", - "build:transpile": "yarn build:bindings:configure && yarn build:bindings && yarn build:lib", + "build:transpile": "yarn build:lib", "build:types:downlevel": "yarn downlevel-dts lib/types lib/types-ts3.8 --to ts3.8", "build:types": "tsc -p tsconfig.types.json && yarn build:types:downlevel", "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:bindings:configure": "node-gyp configure", - "build:bindings:configure:arm64": "node-gyp configure --arch=arm64 --target_arch=arm64", - "build:bindings": "node-gyp build && node scripts/copy-target.js", - "build:bindings:arm64": "node-gyp build --arch=arm64 && node scripts/copy-target.js", - "build:dev": "yarn clean && yarn build:bindings:configure && yarn build", + "build:dev": "yarn clean && yarn build", "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", "build:watch": "run-p build:transpile:watch build:types:watch", "build:tarball": "npm pack", - "test:watch": "cross-env SENTRY_PROFILER_BINARY_DIR=build jest --watch", + "test:watch": "jest --watch", "test:bundle": "node test-binaries.esbuild.js", - "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js" + "test": "jest --config jest.config.js" }, "dependencies": { "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", - "detect-libc": "^2.0.2", - "node-abi": "^3.61.0" + "@sentry-internal/node-cpu-profiler": "^2.0.0" }, "devDependencies": { - "@types/node": "^18.19.1", - "@types/node-abi": "^3.0.3", - "clang-format": "^1.8.0", - "cross-env": "^7.0.3", - "node-gyp": "^9.4.1" + "@types/node": "^18.19.1" }, "volta": { "extends": "../../package.json" diff --git a/packages/profiling-node/rollup.npm.config.mjs b/packages/profiling-node/rollup.npm.config.mjs index a9c148306709..05327bc1a29a 100644 --- a/packages/profiling-node/rollup.npm.config.mjs +++ b/packages/profiling-node/rollup.npm.config.mjs @@ -1,20 +1,19 @@ -import commonjs from '@rollup/plugin-commonjs'; -import replace from '@rollup/plugin-replace'; import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; export default makeNPMConfigVariants( makeBaseNPMConfig({ packageSpecificConfig: { - output: { dir: 'lib', preserveModules: false }, - plugins: [ - commonjs(), - replace({ - preventAssignment: false, - values: { - __IMPORT_META_URL_REPLACEMENT__: 'import.meta.url', - }, - }), - ], + output: { + dir: 'lib', + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + // set preserveModules to false because for profiling we actually want + // to bundle everything into one file. + preserveModules: + process.env.SENTRY_BUILD_PRESERVE_MODULES === undefined + ? false + : Boolean(process.env.SENTRY_BUILD_PRESERVE_MODULES), + }, }, }), ); diff --git a/packages/profiling-node/scripts/binaries.js b/packages/profiling-node/scripts/binaries.js deleted file mode 100644 index 2c0c6be2642b..000000000000 --- a/packages/profiling-node/scripts/binaries.js +++ /dev/null @@ -1,27 +0,0 @@ -const os = require('os'); -const path = require('path'); - -const abi = require('node-abi'); -const libc = require('detect-libc'); - -function getModuleName() { - const stdlib = libc.familySync(); - const platform = process.env['BUILD_PLATFORM'] || os.platform(); - const arch = process.env['BUILD_ARCH'] || os.arch(); - - if (platform === 'darwin' && arch === 'arm64') { - const identifier = [platform, 'arm64', abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-'); - return `sentry_cpu_profiler-${identifier}.node`; - } - - const identifier = [platform, arch, stdlib, abi.getAbi(process.versions.node, 'node')].filter(Boolean).join('-'); - - return `sentry_cpu_profiler-${identifier}.node`; -} - -const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node'); -const target = path.join(__dirname, '..', 'lib', getModuleName()); - -module.exports.source = source; -module.exports.target = target; -module.exports.getModuleName = getModuleName; diff --git a/packages/profiling-node/scripts/check-build.js b/packages/profiling-node/scripts/check-build.js deleted file mode 100644 index dda96e66b900..000000000000 --- a/packages/profiling-node/scripts/check-build.js +++ /dev/null @@ -1,56 +0,0 @@ -// This is a build script, so some logging is desirable as it allows -// us to follow the code path that triggered the error. -/* eslint-disable no-console */ -const fs = require('fs'); -const child_process = require('child_process'); -const binaries = require('./binaries.js'); - -function clean(err) { - return err.toString().trim(); -} - -function recompileFromSource() { - console.log('@sentry/profiling-node: Compiling from source...'); - let spawn = child_process.spawnSync('npm', ['run', 'build:bindings:configure'], { - stdio: ['inherit', 'inherit', 'pipe'], - env: process.env, - shell: true, - }); - - if (spawn.status !== 0) { - console.log('@sentry/profiling-node: Failed to configure gyp'); - console.log('@sentry/profiling-node:', clean(spawn.stderr)); - return; - } - - spawn = child_process.spawnSync('npm', ['run', 'build:bindings'], { - stdio: ['inherit', 'inherit', 'pipe'], - env: process.env, - shell: true, - }); - if (spawn.status !== 0) { - console.log('@sentry/profiling-node: Failed to build bindings'); - console.log('@sentry/profiling-node:', clean(spawn.stderr)); - return; - } -} - -if (fs.existsSync(binaries.target)) { - try { - console.log(`@sentry/profiling-node: Precompiled binary found, attempting to load ${binaries.target}`); - require(binaries.target); - console.log('@sentry/profiling-node: Precompiled binary found, skipping build from source.'); - } catch (e) { - console.log('@sentry/profiling-node: Precompiled binary found but failed loading'); - console.log('@sentry/profiling-node:', e); - try { - recompileFromSource(); - } catch (e) { - console.log('@sentry/profiling-node: Failed to compile from source'); - throw e; - } - } -} else { - console.log('@sentry/profiling-node: No precompiled binary found'); - recompileFromSource(); -} diff --git a/packages/profiling-node/scripts/copy-target.js b/packages/profiling-node/scripts/copy-target.js deleted file mode 100644 index 8277f1d45290..000000000000 --- a/packages/profiling-node/scripts/copy-target.js +++ /dev/null @@ -1,27 +0,0 @@ -// This is a build script, so some logging is desirable as it allows -// us to follow the code path that triggered the error. -/* eslint-disable no-console */ -const fs = require('fs'); -const path = require('path'); -const process = require('process'); -const binaries = require('./binaries.js'); - -const build = path.resolve(__dirname, '..', 'lib'); - -if (!fs.existsSync(build)) { - fs.mkdirSync(build, { recursive: true }); -} - -const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node'); -const target = path.join(__dirname, '..', 'lib', binaries.getModuleName()); - -if (!fs.existsSync(source)) { - console.log('Source file does not exist:', source); - process.exit(1); -} else { - if (fs.existsSync(target)) { - console.log('Target file already exists, overwriting it'); - } - console.log('Renaming', source, 'to', target); - fs.renameSync(source, target); -} diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts deleted file mode 100644 index a9a6d65ce191..000000000000 --- a/packages/profiling-node/src/cpu_profiler.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { createRequire } from 'node:module'; -import { arch as _arch, platform as _platform } from 'node:os'; -import { join, resolve } from 'node:path'; -import { dirname } from 'node:path'; -import { env, versions } from 'node:process'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { threadId } from 'node:worker_threads'; -import { familySync } from 'detect-libc'; -import { getAbi } from 'node-abi'; - -import { GLOBAL_OBJ, logger } from '@sentry/core'; -import { DEBUG_BUILD } from './debug-build'; -import type { - PrivateV8CpuProfilerBindings, - RawChunkCpuProfile, - RawThreadCpuProfile, - V8CpuProfilerBindings, -} from './types'; -import type { ProfileFormat } from './types'; - -declare const __IMPORT_META_URL_REPLACEMENT__: string; - -const stdlib = familySync(); -const platform = process.env['BUILD_PLATFORM'] || _platform(); -const arch = process.env['BUILD_ARCH'] || _arch(); -const abi = getAbi(versions.node, 'node'); -const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-'); - -/** - * Imports cpp bindings based on the current platform and architecture. - */ -// eslint-disable-next-line complexity -export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { - // We need to work around using import.meta.url directly with __IMPORT_META_URL_REPLACEMENT__ because jest complains about it. - const importMetaUrl = - typeof __IMPORT_META_URL_REPLACEMENT__ !== 'undefined' - ? // This case is always hit when the SDK is built - __IMPORT_META_URL_REPLACEMENT__ - : // This case is hit when the tests are run - pathToFileURL(__filename).href; - - const createdRequire = createRequire(importMetaUrl); - const esmCompatibleDirname = dirname(fileURLToPath(importMetaUrl)); - - // If a binary path is specified, use that. - if (env['SENTRY_PROFILER_BINARY_PATH']) { - const envPath = env['SENTRY_PROFILER_BINARY_PATH']; - return createdRequire(envPath); - } - - // If a user specifies a different binary dir, they are in control of the binaries being moved there - if (env['SENTRY_PROFILER_BINARY_DIR']) { - const binaryPath = join(resolve(env['SENTRY_PROFILER_BINARY_DIR']), `sentry_cpu_profiler-${identifier}`); - return createdRequire(`${binaryPath}.node`); - } - - // We need the fallthrough so that in the end, we can fallback to the dynamic require. - // This is for cases where precompiled binaries were not provided, but may have been compiled from source. - if (platform === 'darwin') { - if (arch === 'x64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-darwin-x64-127.node'); - } - } - - if (arch === 'arm64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-darwin-arm64-127.node'); - } - } - } - - if (platform === 'win32') { - if (arch === 'x64') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-win32-x64-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-win32-x64-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-win32-x64-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-win32-x64-127.node'); - } - } - } - - if (platform === 'linux') { - if (arch === 'x64') { - if (stdlib === 'musl') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-x64-musl-127.node'); - } - } - if (stdlib === 'glibc') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-x64-glibc-127.node'); - } - } - } - if (arch === 'arm64') { - if (stdlib === 'musl') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-musl-127.node'); - } - } - - if (stdlib === 'glibc') { - if (abi === '93') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-93.node'); - } - if (abi === '108') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-108.node'); - } - if (abi === '115') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-115.node'); - } - if (abi === '127') { - return createdRequire('../sentry_cpu_profiler-linux-arm64-glibc-127.node'); - } - } - } - } - - const built_from_source_path = resolve(esmCompatibleDirname, '..', `sentry_cpu_profiler-${identifier}`); - return createdRequire(`${built_from_source_path}.node`); -} - -const PrivateCpuProfilerBindings: PrivateV8CpuProfilerBindings = importCppBindingsModule(); - -class Bindings implements V8CpuProfilerBindings { - public startProfiling(name: string): void { - if (!PrivateCpuProfilerBindings) { - DEBUG_BUILD && logger.log('[Profiling] Bindings not loaded, ignoring call to startProfiling.'); - return; - } - - if (typeof PrivateCpuProfilerBindings.startProfiling !== 'function') { - DEBUG_BUILD && - logger.log('[Profiling] Native startProfiling function is not available, ignoring call to startProfiling.'); - return; - } - - return PrivateCpuProfilerBindings.startProfiling(name); - } - - public stopProfiling(name: string, format: ProfileFormat.THREAD): RawThreadCpuProfile | null; - public stopProfiling(name: string, format: ProfileFormat.CHUNK): RawChunkCpuProfile | null; - public stopProfiling( - name: string, - format: ProfileFormat.CHUNK | ProfileFormat.THREAD, - ): RawThreadCpuProfile | RawChunkCpuProfile | null { - if (!PrivateCpuProfilerBindings) { - DEBUG_BUILD && - logger.log('[Profiling] Bindings not loaded or profile was never started, ignoring call to stopProfiling.'); - return null; - } - - if (typeof PrivateCpuProfilerBindings.stopProfiling !== 'function') { - DEBUG_BUILD && - logger.log('[Profiling] Native stopProfiling function is not available, ignoring call to stopProfiling.'); - return null; - } - - return PrivateCpuProfilerBindings.stopProfiling( - name, - format as unknown as any, - threadId, - !!GLOBAL_OBJ._sentryDebugIds, - ); - } -} - -const CpuProfilerBindings = new Bindings(); - -export { PrivateCpuProfilerBindings }; -export { CpuProfilerBindings }; diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index ca9db531d1e9..5b455f72974d 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import type { Event, IntegrationFn, Profile, ProfileChunk, ProfilingIntegration, Span } from '@sentry/core'; import { LRUMap, @@ -14,7 +15,6 @@ import { uuid4, } from '@sentry/core'; import type { NodeClient } from '@sentry/node'; -import { CpuProfilerBindings } from './cpu_profiler'; import { DEBUG_BUILD } from './debug-build'; import { NODE_MAJOR, NODE_VERSION } from './nodeVersion'; import { MAX_PROFILE_DURATION_MS, maybeProfileSpan, stopSpanProfile } from './spanProfileUtils'; @@ -120,8 +120,8 @@ function setupAutomatedSpanProfiling(client: NodeClient): void { const profilesToAddToEnvelope: Profile[] = []; for (const profiledTransaction of profiledTransactionEvents) { - const profileContext = profiledTransaction.contexts?.['profile']; - const profile_id = profileContext?.['profile_id']; + const profileContext = profiledTransaction.contexts?.profile; + const profile_id = profileContext?.profile_id; if (!profile_id) { throw new TypeError('[Profiling] cannot find profile for a transaction without a profile context'); @@ -129,7 +129,7 @@ function setupAutomatedSpanProfiling(client: NodeClient): void { // Remove the profile from the transaction context before sending, relay will take care of the rest. if (profileContext) { - delete profiledTransaction.contexts?.['profile']; + delete profiledTransaction.contexts?.profile; } const cpuProfile = takeFromProfileQueue(profile_id); @@ -400,7 +400,7 @@ class ContinuousProfiler { * Assigns thread_id and thread name context to a profiled event. */ private _assignThreadIdContext(event: Event): void { - if (!event?.['contexts']?.['profile']) { + if (!event?.contexts?.profile) { return; } @@ -410,10 +410,10 @@ class ContinuousProfiler { // @ts-expect-error the trace fallback value is wrong, though it should never happen // and in case it does, we dont want to override whatever was passed initially. - event.contexts['trace'] = { - ...(event.contexts?.['trace'] ?? {}), + event.contexts.trace = { + ...(event.contexts?.trace ?? {}), data: { - ...(event.contexts?.['trace']?.['data'] ?? {}), + ...(event.contexts?.trace?.data ?? {}), ['thread.id']: PROFILER_THREAD_ID_STRING, ['thread.name']: PROFILER_THREAD_NAME, }, diff --git a/packages/profiling-node/src/spanProfileUtils.ts b/packages/profiling-node/src/spanProfileUtils.ts index 1ee050ce22e5..342075bde890 100644 --- a/packages/profiling-node/src/spanProfileUtils.ts +++ b/packages/profiling-node/src/spanProfileUtils.ts @@ -1,7 +1,7 @@ +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import type { CustomSamplingContext, Span } from '@sentry/core'; import { logger, spanIsSampled, spanToJSON, uuid4 } from '@sentry/core'; import type { NodeClient } from '@sentry/node'; -import { CpuProfilerBindings } from './cpu_profiler'; import { DEBUG_BUILD } from './debug-build'; import type { RawThreadCpuProfile } from './types'; import { isValidSampleRate } from './utils'; diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index baf3370d6ce8..e6ab3803ebdd 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -1,7 +1,6 @@ import * as os from 'os'; import type { Client, - Context, ContinuousThreadCpuProfile, DebugImage, DsnComponents, @@ -14,6 +13,7 @@ import type { ProfileChunkItem, SdkInfo, ThreadCpuProfile, + TransactionEvent, } from '@sentry/core'; import { createEnvelope, @@ -99,7 +99,7 @@ export function createProfilingEvent(client: Client, profile: RawThreadCpuProfil event_id: event.event_id ?? '', transaction: event.transaction ?? '', start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(), - trace_id: event.contexts?.['trace']?.['trace_id'] ?? '', + trace_id: event.contexts?.trace?.trace_id ?? '', profile_id: profile.profile_id, }); } @@ -352,7 +352,7 @@ export function addProfilesToEnvelope(envelope: Envelope, profiles: Profile[]): * @returns {Event[]} */ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[] { - const events: Event[] = []; + const events: TransactionEvent[] = []; forEachEnvelopeItem(envelope, (item, type) => { if (type !== 'transaction') { @@ -361,18 +361,17 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[ // First item is the type, so we can skip it, everything else is an event for (let j = 1; j < item.length; j++) { - const event = item[j]; + const event = item[j] as TransactionEvent; if (!event) { // Shouldn't happen, but lets be safe continue; } - // @ts-expect-error profile_id is not part of the metadata type - const profile_id = (event.contexts as Context)?.['profile']?.['profile_id']; + const profile_id = event.contexts?.profile?.profile_id; if (event && profile_id) { - events.push(item[j] as Event); + events.push(event); } } }); diff --git a/packages/profiling-node/test/bindings.test.ts b/packages/profiling-node/test/bindings.test.ts deleted file mode 100644 index 27361a87d941..000000000000 --- a/packages/profiling-node/test/bindings.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { platform } from 'os'; -// Contains unit tests for some of the C++ bindings. These functions -// are exported on the private bindings object, so we can test them and -// they should not be used outside of this file. -import { PrivateCpuProfilerBindings } from '../src/cpu_profiler'; - -const cases = [ - ['/Users/jonas/code/node_modules/@scope/package/file.js', '@scope.package:file'], - ['/Users/jonas/code/node_modules/package/dir/file.js', 'package.dir:file'], - ['/Users/jonas/code/node_modules/package/file.js', 'package:file'], - ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'], - - // Preserves non .js extensions - ['/Users/jonas/code/src/file.ts', 'Users.jonas.code.src:file.ts'], - // No extension - ['/Users/jonas/code/src/file', 'Users.jonas.code.src:file'], - // Edge cases that shouldn't happen in practice, but try and handle them so we don't crash - ['/Users/jonas/code/src/file.js', 'Users.jonas.code.src:file'], - ['', ''], -]; - -describe('GetFrameModule', () => { - it.each( - platform() === 'win32' - ? cases.map(([abs_path, expected]) => [abs_path ? `C:${abs_path.replace(/\//g, '\\')}` : '', expected]) - : cases, - )('%s => %s', (abs_path: string, expected: string) => { - expect(PrivateCpuProfilerBindings.getFrameModule(abs_path)).toBe(expected); - }); -}); diff --git a/packages/profiling-node/test/cpu_profiler.test.ts b/packages/profiling-node/test/cpu_profiler.test.ts deleted file mode 100644 index 9240ad636129..000000000000 --- a/packages/profiling-node/test/cpu_profiler.test.ts +++ /dev/null @@ -1,364 +0,0 @@ -import type { ContinuousThreadCpuProfile, ThreadCpuProfile } from '@sentry/core'; -import { CpuProfilerBindings, PrivateCpuProfilerBindings } from '../src/cpu_profiler'; -import type { RawThreadCpuProfile } from '../src/types'; -import { ProfileFormat } from '../src/types'; - -// Required because we test a hypothetical long profile -// and we cannot use advance timers as the c++ relies on -// actual event loop ticks that we cannot advance from jest. -jest.setTimeout(60_000); - -function fail(message: string): never { - throw new Error(message); -} - -const fibonacci = (n: number): number => { - if (n <= 1) { - return n; - } - return fibonacci(n - 1) + fibonacci(n - 2); -}; - -const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -const profiled = async (name: string, fn: () => void) => { - CpuProfilerBindings.startProfiling(name); - await fn(); - return CpuProfilerBindings.stopProfiling(name, ProfileFormat.THREAD); -}; - -const assertValidSamplesAndStacks = ( - stacks: ThreadCpuProfile['stacks'], - samples: ThreadCpuProfile['samples'] | ContinuousThreadCpuProfile['samples'], -) => { - expect(stacks.length).toBeGreaterThan(0); - expect(samples.length).toBeGreaterThan(0); - expect(stacks.length <= samples.length).toBe(true); - - for (const sample of samples) { - if (sample.stack_id === undefined) { - throw new Error(`Sample ${JSON.stringify(sample)} has not stack id associated`); - } - if (!stacks[sample.stack_id]) { - throw new Error(`Failed to find stack for sample: ${JSON.stringify(sample)}`); - } - expect(stacks[sample.stack_id]).not.toBe(undefined); - } - - for (const stack of stacks) { - expect(stack).not.toBe(undefined); - } -}; - -const isValidMeasurementValue = (v: any) => { - if (isNaN(v)) return false; - return typeof v === 'number' && v > 0; -}; - -const assertValidMeasurements = (measurement: RawThreadCpuProfile['measurements']['memory_footprint'] | undefined) => { - if (!measurement) { - throw new Error('Measurement is undefined'); - } - expect(measurement).not.toBe(undefined); - expect(typeof measurement.unit).toBe('string'); - expect(measurement.unit.length).toBeGreaterThan(0); - - for (let i = 0; i < measurement.values.length; i++) { - expect(measurement?.values?.[i]?.elapsed_since_start_ns).toBeGreaterThan(0); - expect(measurement?.values?.[i]?.value).toBeGreaterThan(0); - } -}; - -describe('Private bindings', () => { - it('does not crash if collect resources is false', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - expect(() => { - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false); - if (!profile) throw new Error('No profile'); - }).not.toThrow(); - }); - - it('throws if invalid format is supplied', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - expect(() => { - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', Number.MAX_SAFE_INTEGER, 0, false); - if (!profile) throw new Error('No profile'); - }).toThrow('StopProfiling expects a valid format type as second argument.'); - }); - - it('collects resources', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, true); - if (!profile) throw new Error('No profile'); - - expect(profile.resources.length).toBeGreaterThan(0); - - expect(new Set(profile.resources).size).toBe(profile.resources.length); - - for (const resource of profile.resources) { - expect(typeof resource).toBe('string'); - expect(resource).not.toBe(undefined); - } - }); - - it('does not collect resources', async () => { - PrivateCpuProfilerBindings.startProfiling!('profiled-program'); - await wait(100); - - const profile = PrivateCpuProfilerBindings.stopProfiling!('profiled-program', 0, 0, false); - if (!profile) throw new Error('No profile'); - - expect(profile.resources.length).toBe(0); - }); -}); - -describe('Profiler bindings', () => { - it('exports profiler binding methods', () => { - expect(typeof CpuProfilerBindings['startProfiling']).toBe('function'); - expect(typeof CpuProfilerBindings['stopProfiling']).toBe('function'); - }); - - it('profiles a program', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - - assertValidSamplesAndStacks(profile.stacks, profile.samples); - }); - - it('adds thread_id info', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - const samples = profile.samples; - - if (!samples.length) { - throw new Error('No samples'); - } - for (const sample of samples) { - expect(sample.thread_id).toBe('0'); - } - }); - - it('caps stack depth at 128', async () => { - const recurseToDepth = async (depth: number): Promise => { - if (depth === 0) { - // Wait a bit to make sure stack gets sampled here - await wait(1000); - return 0; - } - const v = await recurseToDepth(depth - 1); - return v; - }; - - const profile = await profiled('profiled-program', async () => { - await recurseToDepth(256); - }); - - if (!profile) fail('Profile is null'); - - for (const stack of profile.stacks) { - expect(stack.length).toBeLessThanOrEqual(128); - } - }); - - it('does not record two profiles when titles match', () => { - CpuProfilerBindings.startProfiling('same-title'); - CpuProfilerBindings.startProfiling('same-title'); - - const first = CpuProfilerBindings.stopProfiling('same-title', 0); - const second = CpuProfilerBindings.stopProfiling('same-title', 0); - - expect(first).not.toBe(null); - expect(second).toBe(null); - }); - - it('multiple calls with same title', () => { - CpuProfilerBindings.startProfiling('same-title'); - expect(() => { - CpuProfilerBindings.stopProfiling('same-title', 0); - CpuProfilerBindings.stopProfiling('same-title', 0); - }).not.toThrow(); - }); - - it('does not crash if stopTransaction is called before startTransaction', () => { - expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); - }); - - it('does crash if name is invalid', () => { - expect(() => CpuProfilerBindings.stopProfiling('', 0)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling(undefined)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling(null)).toThrow(); - // @ts-expect-error test invalid input - expect(() => CpuProfilerBindings.stopProfiling({})).toThrow(); - }); - - it('does not throw if stopTransaction is called before startTransaction', () => { - expect(CpuProfilerBindings.stopProfiling('does not exist', 0)).toBe(null); - expect(() => CpuProfilerBindings.stopProfiling('does not exist', 0)).not.toThrow(); - }); - - it('compiles with eager logging by default', async () => { - const profile = await profiled('profiled-program', async () => { - await wait(100); - }); - - if (!profile) fail('Profile is null'); - expect(profile.profiler_logging_mode).toBe('eager'); - }); - - it('chunk format type', async () => { - const fn = async () => { - await wait(1000); - fibonacci(36); - await wait(1000); - }; - - CpuProfilerBindings.startProfiling('non nullable stack'); - await fn(); - const profile = CpuProfilerBindings.stopProfiling('non nullable stack', ProfileFormat.CHUNK); - - if (!profile) fail('Profile is null'); - - for (const sample of profile.samples) { - if (!('timestamp' in sample)) { - throw new Error(`Sample ${JSON.stringify(sample)} has no timestamp`); - } - expect(sample.timestamp).toBeDefined(); - // No older than a minute and not in the future. Timestamp is in seconds so convert to ms - // as the constructor expects ms. - expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeGreaterThan(Date.now() - 60 * 1e3); - expect(new Date((sample.timestamp as number) * 1e3).getTime()).toBeLessThanOrEqual(Date.now()); - } - }); - - it('stacks are not null', async () => { - const profile = await profiled('non nullable stack', async () => { - await wait(1000); - fibonacci(36); - await wait(1000); - }); - - if (!profile) fail('Profile is null'); - assertValidSamplesAndStacks(profile.stacks, profile.samples); - }); - - it('samples at ~99hz', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(100); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - if (!profile) fail('Profile is null'); - - // Exception for macos and windows - we seem to get way less samples there, but I'm not sure if that's due to poor - // performance of the actions runner, machine or something else. This needs more investigation to determine - // the cause of low sample count. https://github.com/actions/runner-images/issues/1336 seems relevant. - if (process.platform === 'darwin' || process.platform === 'win32') { - if (profile.samples.length < 2) { - fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 2`); - } - } else { - if (profile.samples.length < 6) { - fail(`Only ${profile.samples.length} samples obtained on ${process.platform}, expected at least 6`); - } - } - if (profile.samples.length > 15) { - fail(`Too many samples on ${process.platform}, got ${profile.samples.length}`); - } - }); - - it('collects memory footprint', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(1000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - const heap_usage = profile?.measurements['memory_footprint']; - if (!heap_usage) { - throw new Error('memory_footprint is null'); - } - expect(heap_usage.values.length).toBeGreaterThan(6); - expect(heap_usage.values.length).toBeLessThanOrEqual(11); - expect(heap_usage.unit).toBe('byte'); - expect(heap_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true); - assertValidMeasurements(profile.measurements['memory_footprint']); - }); - - it('collects cpu usage', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(1000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - - const cpu_usage = profile?.measurements['cpu_usage']; - if (!cpu_usage) { - throw new Error('cpu_usage is null'); - } - expect(cpu_usage.values.length).toBeGreaterThan(6); - expect(cpu_usage.values.length).toBeLessThanOrEqual(11); - expect(cpu_usage.values.every(v => isValidMeasurementValue(v.value))).toBe(true); - expect(cpu_usage.unit).toBe('percent'); - assertValidMeasurements(profile.measurements['cpu_usage']); - }); - - it('does not overflow measurement buffer if profile runs longer than 30s', async () => { - CpuProfilerBindings.startProfiling('profile'); - await wait(35000); - const profile = CpuProfilerBindings.stopProfiling('profile', 0); - expect(profile).not.toBe(null); - expect(profile?.measurements?.['cpu_usage']?.values.length).toBeLessThanOrEqual(300); - expect(profile?.measurements?.['memory_footprint']?.values.length).toBeLessThanOrEqual(300); - }); - - // eslint-disable-next-line @sentry-internal/sdk/no-skipped-tests - it.skip('includes deopt reason', async () => { - // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#52-the-object-being-iterated-is-not-a-simple-enumerable - function iterateOverLargeHashTable() { - const table: Record = {}; - for (let i = 0; i < 1e5; i++) { - table[i] = i; - } - // eslint-disable-next-line - for (const _ in table) { - } - } - - const profile = await profiled('profiled-program', async () => { - iterateOverLargeHashTable(); - }); - - // @ts-expect-error deopt reasons are disabled for now as we need to figure out the backend support - const hasDeoptimizedFrame = profile.frames.some(f => f.deopt_reasons?.length > 0); - expect(hasDeoptimizedFrame).toBe(true); - }); - - it('does not crash if the native startProfiling function is not available', async () => { - const original = PrivateCpuProfilerBindings.startProfiling; - PrivateCpuProfilerBindings.startProfiling = undefined; - - expect(() => { - CpuProfilerBindings.startProfiling('profiled-program'); - }).not.toThrow(); - - PrivateCpuProfilerBindings.startProfiling = original; - }); - - it('does not crash if the native stopProfiling function is not available', async () => { - // eslint-disable-next-line @typescript-eslint/unbound-method - const original = PrivateCpuProfilerBindings.stopProfiling; - PrivateCpuProfilerBindings.stopProfiling = undefined; - - expect(() => { - CpuProfilerBindings.stopProfiling('profiled-program', 0); - }).not.toThrow(); - - PrivateCpuProfilerBindings.stopProfiling = original; - }); -}); diff --git a/packages/profiling-node/test/spanProfileUtils.test.ts b/packages/profiling-node/test/spanProfileUtils.test.ts index 9974eb6ebc64..758307d3fa34 100644 --- a/packages/profiling-node/test/spanProfileUtils.test.ts +++ b/packages/profiling-node/test/spanProfileUtils.test.ts @@ -1,11 +1,11 @@ import * as Sentry from '@sentry/node'; +import { CpuProfilerBindings } from '@sentry-internal/node-cpu-profiler'; import { getMainCarrier } from '@sentry/core'; import { GLOBAL_OBJ, createEnvelope, logger } from '@sentry/core'; import type { ProfilingIntegration } from '@sentry/core'; import type { ProfileChunk, Transport } from '@sentry/core'; import type { NodeClientOptions } from '@sentry/node/build/types/types'; -import { CpuProfilerBindings } from '../src/cpu_profiler'; import { _nodeProfilingIntegration } from '../src/integration'; function makeClientWithHooks(): [Sentry.NodeClient, Transport] { diff --git a/packages/react/LICENSE b/packages/react/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/react/LICENSE +++ b/packages/react/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index 9925ef1a8308..97f89357f38d 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -2,8 +2,8 @@ import type { ReportDialogOptions } from '@sentry/browser'; import { getClient, showReportDialog, withScope } from '@sentry/browser'; import { logger } from '@sentry/core'; import type { Scope } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import { DEBUG_BUILD } from './debug-build'; import { captureReactException } from './error'; diff --git a/packages/react/src/hoist-non-react-statics.ts b/packages/react/src/hoist-non-react-statics.ts new file mode 100644 index 000000000000..970928c80d23 --- /dev/null +++ b/packages/react/src/hoist-non-react-statics.ts @@ -0,0 +1,5 @@ +import * as hoistNonReactStaticsImport from 'hoist-non-react-statics'; + +// Ensure we use the default export from hoist-non-react-statics if available, +// falling back to the module itself. This handles both ESM and CJS usage. +export const hoistNonReactStatics = hoistNonReactStaticsImport.default || hoistNonReactStaticsImport; diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 2a147541b4b2..d7478ce6fb33 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -1,10 +1,10 @@ import { startInactiveSpan } from '@sentry/browser'; import type { Span } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON, timestampInSeconds, withActiveSpan } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; import { REACT_MOUNT_OP, REACT_RENDER_OP, REACT_UPDATE_OP } from './constants'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; export const UNKNOWN_COMPONENT = 'unknown'; @@ -236,4 +236,4 @@ function useProfiler( }, []); } -export { withProfiler, Profiler, useProfiler }; +export { Profiler, useProfiler, withProfiler }; diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index 42acef91522b..2692659de6c4 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -14,9 +14,9 @@ import { spanToJSON, } from '@sentry/core'; import type { Client, Integration, Span, TransactionSource } from '@sentry/core'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; import type { ReactElement } from 'react'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import type { Action, Location } from './types'; diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 9ca96a03b0de..f9cf76cfc498 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -22,8 +22,8 @@ import { } from '@sentry/core'; import * as React from 'react'; -import hoistNonReactStatics from 'hoist-non-react-statics'; import { DEBUG_BUILD } from './debug-build'; +import { hoistNonReactStatics } from './hoist-non-react-statics'; import type { Action, AgnosticDataRouteMatch, @@ -61,6 +61,9 @@ export interface ReactRouterOptions { type V6CompatibleVersion = '6' | '7'; +// Keeping as a global variable for cross-usage in multiple functions +const allRoutes = new Set(); + /** * Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions. */ @@ -81,6 +84,10 @@ export function createV6CompatibleWrapCreateBrowserRouter< } return function (routes: RouteObject[], opts?: Record & { basename?: string }): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -90,19 +97,40 @@ export function createV6CompatibleWrapCreateBrowserRouter< // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, router.state.location, routes, undefined, basename); + updatePageloadTransaction( + activeRootSpan, + router.state.location, + routes, + undefined, + basename, + Array.from(allRoutes), + ); } router.subscribe((state: RouterState) => { - const location = state.location; if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - handleNavigation({ - location, - routes, - navigationType: state.historyAction, - version, - basename, - }); + // Wait for the next render if loading an unsettled route + if (state.navigation.state !== 'idle') { + requestAnimationFrame(() => { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + allRoutes: Array.from(allRoutes), + }); + }); + } else { + handleNavigation({ + location: state.location, + routes, + navigationType: state.historyAction, + version, + basename, + allRoutes: Array.from(allRoutes), + }); + } } }); @@ -137,6 +165,10 @@ export function createV6CompatibleWrapCreateMemoryRouter< initialIndex?: number; }, ): TRouter { + routes.forEach(route => { + allRoutes.add(route); + }); + const router = createRouterFunction(routes, opts); const basename = opts?.basename; @@ -162,7 +194,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< : router.state.location; if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename); + updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes)); } router.subscribe((state: RouterState) => { @@ -174,6 +206,7 @@ export function createV6CompatibleWrapCreateMemoryRouter< navigationType: state.historyAction, version, basename, + allRoutes: Array.from(allRoutes), }); } }); @@ -248,8 +281,6 @@ export function createV6CompatibleWrapUseRoutes(origUseRoutes: UseRoutes, versio return origUseRoutes; } - const allRoutes: Set = new Set(); - const SentryRoutes: React.FC<{ children?: React.ReactNode; routes: RouteObject[]; @@ -319,7 +350,6 @@ export function handleNavigation(opts: { allRoutes?: RouteObject[]; }): void { const { location, routes, navigationType, version, matches, basename, allRoutes } = opts; - const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); const client = getClient(); @@ -553,7 +583,7 @@ function updatePageloadTransaction( ): void { const branches = Array.isArray(matches) ? matches - : (_matchRoutes(routes, location, basename) as unknown as RouteMatch[]); + : (_matchRoutes(allRoutes || routes, location, basename) as unknown as RouteMatch[]); if (branches) { let name, @@ -569,7 +599,7 @@ function updatePageloadTransaction( [name, source] = getNormalizedName(routes, location, branches, basename); } - getCurrentScope().setTransactionName(name); + getCurrentScope().setTransactionName(name || '/'); if (activeRootSpan) { activeRootSpan.updateName(name); @@ -592,8 +622,6 @@ export function createV6CompatibleWithSentryReactRouterRouting

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

= (props: P) => { const isMountRenderPass = React.useRef(true); diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts index 17ad3aff0a66..779528ec6d97 100644 --- a/packages/react/src/tanstackrouter.ts +++ b/packages/react/src/tanstackrouter.ts @@ -65,7 +65,7 @@ export function tanstackRouterBrowserTracingIntegration( // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected castRouterInstance.subscribe('onBeforeNavigate', onBeforeNavigateArgs => { // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments. - if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation.state) { + if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation?.state) { return; } diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 1a40ec4fce91..b29a2dbd1cad 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -182,10 +182,14 @@ export interface RouterInit { hydrationData?: HydrationState; } +export type NavigationState = { + state: 'idle' | 'loading' | 'submitting'; +}; + export type NavigationStates = { - Idle: any; - Loading: any; - Submitting: any; + Idle: NavigationState; + Loading: NavigationState; + Submitting: NavigationState; }; export type Navigation = NavigationStates[keyof NavigationStates]; @@ -202,6 +206,7 @@ export declare enum HistoryAction { export interface RouterState { historyAction: Action | HistoryAction | any; location: Location; + navigation: Navigation; } export interface Router { state: TState; diff --git a/packages/react/src/vendor/tanstackrouter-types.ts b/packages/react/src/vendor/tanstackrouter-types.ts index e5eeba71aa87..417d2b1447b1 100644 --- a/packages/react/src/vendor/tanstackrouter-types.ts +++ b/packages/react/src/vendor/tanstackrouter-types.ts @@ -46,7 +46,7 @@ export interface VendoredTanstackRouter { eventType: 'onResolved' | 'onBeforeNavigate', callback: (stateUpdate: { toLocation: VendoredTanstackRouterLocation; - fromLocation: VendoredTanstackRouterLocation; + fromLocation?: VendoredTanstackRouterLocation; }) => void, ): () => void; } diff --git a/packages/remix/LICENSE b/packages/remix/LICENSE index 048dee5adaa8..293314012679 100644 --- a/packages/remix/LICENSE +++ b/packages/remix/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/remix/package.json b/packages/remix/package.json index 6c88c8f08797..c40305ee8140 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -54,7 +54,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.0", "@remix-run/router": "1.x", - "@sentry/cli": "^2.39.1", + "@sentry/cli": "^2.41.1", "@sentry/core": "9.0.0-alpha.0", "@sentry/node": "9.0.0-alpha.0", "@sentry/opentelemetry": "9.0.0-alpha.0", diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index a0dd11874416..c6113ea7f0a3 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -301,7 +301,7 @@ const makeWrappedCreateRequestHandler = () => export function instrumentServer(): void { const pkg = loadModule<{ createRequestHandler: CreateRequestHandlerFunction; - }>('@remix-run/server-runtime'); + }>('@remix-run/server-runtime', module); if (!pkg) { DEBUG_BUILD && logger.warn('Remix SDK was unable to require `@remix-run/server-runtime` package.'); diff --git a/packages/replay-internal/LICENSE b/packages/replay-internal/LICENSE index 048dee5adaa8..293314012679 100644 --- a/packages/replay-internal/LICENSE +++ b/packages/replay-internal/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/replay-internal/src/session/index.ts b/packages/replay-internal/src/session/index.ts deleted file mode 100644 index 02c9d7a43422..000000000000 --- a/packages/replay-internal/src/session/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './createSession'; diff --git a/packages/replay-worker/LICENSE b/packages/replay-worker/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/replay-worker/LICENSE +++ b/packages/replay-worker/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solidstart/LICENSE b/packages/solidstart/LICENSE index 6bfafc44539c..63e7eb28e19c 100644 --- a/packages/solidstart/LICENSE +++ b/packages/solidstart/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2024 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/solidstart/src/config/withSentry.ts b/packages/solidstart/src/config/withSentry.ts index c1050f0da1cc..aa045ade00ab 100644 --- a/packages/solidstart/src/config/withSentry.ts +++ b/packages/solidstart/src/config/withSentry.ts @@ -1,6 +1,6 @@ import { logger } from '@sentry/core'; import type { Nitro } from 'nitropack'; -import { addSentryPluginToVite } from '../vite'; +import { addSentryPluginToVite } from '../vite/sentrySolidStartVite'; import type { SentrySolidStartPluginOptions } from '../vite/types'; import { addDynamicImportEntryFileWrapper, diff --git a/packages/solidstart/src/index.server.ts b/packages/solidstart/src/index.server.ts index a20a0367f557..82b6fe6cbff4 100644 --- a/packages/solidstart/src/index.server.ts +++ b/packages/solidstart/src/index.server.ts @@ -1,3 +1,2 @@ export * from './server'; -export * from './vite'; export * from './config'; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 39f9831c543c..54a5ec6d6a3c 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -3,7 +3,6 @@ // exports in this file - which we do below. export * from './client'; export * from './server'; -export * from './vite'; export * from './config'; import type { Client, Integration, Options, StackParser } from '@sentry/core'; diff --git a/packages/solidstart/src/vite/index.ts b/packages/solidstart/src/vite/index.ts deleted file mode 100644 index 464bbd604fbe..000000000000 --- a/packages/solidstart/src/vite/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './sentrySolidStartVite'; diff --git a/packages/svelte/LICENSE b/packages/svelte/LICENSE index 048dee5adaa8..293314012679 100644 --- a/packages/svelte/LICENSE +++ b/packages/svelte/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2022 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/sveltekit/LICENSE b/packages/sveltekit/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/sveltekit/LICENSE +++ b/packages/sveltekit/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 077988a47bd1..94cb6fd32c39 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -9,9 +9,7 @@ "engines": { "node": ">=18" }, - "files": [ - "/build" - ], + "files": ["/build"], "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index d0e5e2e689f1..9bb9de9ce394 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -93,10 +93,10 @@ export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean } * ``` */ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { - const options: Required = { - handleUnknownRoutes: false, - injectFetchProxyScript: true, - ...handlerOptions, + const { handleUnknownRoutes, ...rest } = handlerOptions ?? {}; + const options = { + handleUnknownRoutes: handleUnknownRoutes ?? false, + ...rest, }; const sentryRequestHandler: Handle = input => { @@ -131,12 +131,24 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { async function instrumentHandle( { event, resolve }: Parameters[0], - options: Required, + options: SentryHandleOptions, ): Promise { if (!event.route?.id && !options.handleUnknownRoutes) { return resolve(event); } + // caching the result of the version check in `options.injectFetchProxyScript` + // to avoid doing the dynamic import on every request + if (options.injectFetchProxyScript == null) { + try { + // @ts-expect-error - the dynamic import is fine here + const { VERSION } = await import('@sveltejs/kit'); + options.injectFetchProxyScript = isFetchProxyRequired(VERSION); + } catch { + options.injectFetchProxyScript = true; + } + } + const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; if (getIsolationScope() !== getDefaultIsolationScope()) { @@ -161,7 +173,7 @@ async function instrumentHandle( normalizedRequest: winterCGRequestToRequestData(event.request.clone()), }); const res = await resolve(event, { - transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript }), + transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript ?? true }), }); if (span) { setHttpStatus(span, res.status); @@ -177,3 +189,19 @@ async function instrumentHandle( await flushIfServerless(); } } + +/** + * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. + * Exported only for testing. + */ +export function isFetchProxyRequired(version: string): boolean { + try { + const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); + if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { + return false; + } + } catch { + // ignore + } + return true; +} diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index f6556f8ddcea..150f59ae9bc8 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -14,7 +14,7 @@ import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, sentryHandle } from '../../src/server/handle'; +import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, isFetchProxyRequired, sentryHandle } from '../../src/server/handle'; import { getDefaultNodeClientOptions } from '../utils'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); @@ -462,3 +462,20 @@ describe('addSentryCodeToPage', () => { expect(transformed).not.toContain(``); }); }); + +describe('isFetchProxyRequired', () => { + it.each(['2.16.0', '2.16.1', '2.17.0', '3.0.0', '3.0.0-alpha.0'])( + 'returns false if the version is greater than or equal to 2.16.0 (%s)', + version => { + expect(isFetchProxyRequired(version)).toBe(false); + }, + ); + + it.each(['2.15.0', '2.15.1', '1.30.0', '1.0.0'])('returns true if the version is lower than 2.16.0 (%s)', version => { + expect(isFetchProxyRequired(version)).toBe(true); + }); + + it.each(['invalid', 'a.b.c'])('returns true for an invalid version (%s)', version => { + expect(isFetchProxyRequired(version)).toBe(true); + }); +}); diff --git a/packages/types/LICENSE b/packages/types/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/types/LICENSE +++ b/packages/types/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/typescript/LICENSE b/packages/typescript/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/typescript/LICENSE +++ b/packages/typescript/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE index 6bfafc44539c..b3c4b18a6317 100644 --- a/packages/vercel-edge/LICENSE +++ b/packages/vercel-edge/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2023 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/vercel-edge/src/transports/types.ts b/packages/vercel-edge/src/transports/types.ts deleted file mode 100644 index 70bf888f2666..000000000000 --- a/packages/vercel-edge/src/transports/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { BaseTransportOptions } from '@sentry/core'; - -export interface VercelEdgeTransportOptions extends BaseTransportOptions { - /** Fetch API init parameters. */ - fetchOptions?: RequestInit; - /** Custom headers for the transport. */ - headers?: { [key: string]: string }; -} diff --git a/packages/vue/LICENSE b/packages/vue/LICENSE index d5b40b7c4219..9f2152e89993 100644 --- a/packages/vue/LICENSE +++ b/packages/vue/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2019 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/packages/wasm/LICENSE b/packages/wasm/LICENSE index 5b55ec3c5dcb..917e31f85b7a 100644 --- a/packages/wasm/LICENSE +++ b/packages/wasm/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 Functional Software, Inc. dba Sentry +Copyright (c) 2021 Functional Software, Inc. dba Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts index 85f852052bac..13cbc6957d5c 100644 --- a/scripts/ci-unit-tests.ts +++ b/scripts/ci-unit-tests.ts @@ -6,7 +6,7 @@ const UNIT_TEST_ENV = process.env.UNIT_TEST_ENV as 'node' | 'browser' | undefine const RUN_AFFECTED = process.argv.includes('--affected'); // These packages are tested separately in CI, so no need to run them here -const DEFAULT_SKIP_PACKAGES = ['@sentry/profiling-node', '@sentry/bun', '@sentry/deno']; +const DEFAULT_SKIP_PACKAGES = ['@sentry/bun', '@sentry/deno']; // All other packages are run for multiple node versions const BROWSER_TEST_PACKAGES = [ @@ -17,7 +17,6 @@ const BROWSER_TEST_PACKAGES = [ '@sentry/angular', '@sentry/solid', '@sentry/svelte', - '@sentry/profiling-node', '@sentry-internal/browser-utils', '@sentry-internal/replay', '@sentry-internal/replay-canvas', diff --git a/yarn.lock b/yarn.lock index 5627089a7941..83ff270a83f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6054,23 +6054,18 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@playwright/test@^1.44.1": - version "1.44.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.1.tgz#cc874ec31342479ad99838040e99b5f604299bcb" - integrity sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q== +"@playwright/test@~1.50.0": + version "1.50.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.50.0.tgz#25c63a09f833f89da4d54ad67db7900359e2d11d" + integrity sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw== dependencies: - playwright "1.44.1" + playwright "1.50.0" "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@prisma/client@6.2.1": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.2.1.tgz#3d7d0c8669bba490247e1ffff67b93a516bd789f" - integrity sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA== - "@prisma/instrumentation@6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.2.1.tgz#261b885467d36759b7ca01d1b2ca4e1120bda886" @@ -6535,6 +6530,14 @@ "@angular-devkit/schematics" "14.2.13" jsonc-parser "3.1.0" +"@sentry-internal/node-cpu-profiler@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/node-cpu-profiler/-/node-cpu-profiler-2.0.0.tgz#76a0d363055876b91663769daee2d4b12321ba3b" + integrity sha512-0pZId+HY/AbNs1+CoCi8wogBWTrRv+DYeOgbevhekzMr5HYsA6PRY21NtHBXMbu0WcswFwaveDKR+sOW1EDHAA== + dependencies: + detect-libc "^2.0.2" + node-abi "^3.61.0" + "@sentry-internal/rrdom@2.31.0": version "2.31.0" resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.31.0.tgz#548773964167ec104d3cbb9d7a4b25103c091e06" @@ -6612,37 +6615,72 @@ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz#75c338a53834b4cf72f57599f4c72ffb36cf0781" integrity sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ== +"@sentry/cli-darwin@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.41.1.tgz#ca7e12bf1ad59bc2df35868ae98abc8869108efa" + integrity sha512-7pS3pu/SuhE6jOn3wptstAg6B5nUP878O6s+2svT7b5fKNfYUi/6NPK6dAveh2Ca0rwVq40TO4YFJabWMgTpdQ== + "@sentry/cli-linux-arm64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz#27db44700c33fcb1e8966257020b43f8494373e6" integrity sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw== +"@sentry/cli-linux-arm64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.41.1.tgz#948e8af8290418b1562db3531db08e69e39d74bb" + integrity sha512-EzYCEnnENBnS5kpNW+2dBcrPZn1MVfywh2joGVQZTpmgDL5YFJ59VOd+K0XuEwqgFI8BSNI14KXZ75s4DD1/Vw== + "@sentry/cli-linux-arm@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz#451683fa9a5a60b1359d104ec71334ed16f4b63c" integrity sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ== +"@sentry/cli-linux-arm@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.41.1.tgz#1e5fa971ae8dfb3ea5564c8503b4e635ae6aed8a" + integrity sha512-wNUvquD6qjOCczvuBGf9OiD29nuQ6yf8zzfyPJa5Bdx1QXuteKsKb6HBrMwuIR3liyuu0duzHd+H/+p1n541Hg== + "@sentry/cli-linux-i686@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz#9965a81f97a94e8b6d1d15589e43fee158e35201" integrity sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg== +"@sentry/cli-linux-i686@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.41.1.tgz#3f01aff314f2ad8fd761f3e6e807a5ec09ae4eb4" + integrity sha512-urpQCWrdYnSAsZY3udttuMV88wTJzKZL10xsrp7sjD/Hd+O6qSLVLkxebIlxts70jMLLFHYrQ2bkRg5kKuX6Fg== + "@sentry/cli-linux-x64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz#31fe008b02f92769543dc9919e2a5cbc4cda7889" integrity sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA== +"@sentry/cli-linux-x64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.41.1.tgz#30dbf966a4b4c1721ffccd901dfcb6f967db073d" + integrity sha512-ZqpYwHXAaK4MMEFlyaLYr6mJTmpy9qP6n30jGhLTW7kHKS3s6GPLCSlNmIfeClrInEt0963fM633ZRnXa04VPw== + "@sentry/cli-win32-i686@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz#609e8790c49414011445e397130560c777850b35" integrity sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q== +"@sentry/cli-win32-i686@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.41.1.tgz#f88eeb5d2d4ee46c38d8616ae1eb484108ea71c2" + integrity sha512-AuRimCeVsx99DIOr9cwdYBHk39tlmAuPDdy2r16iNzY0InXs4xOys4gGzM7N4vlFQvFkzuc778Su0HkfasgprA== + "@sentry/cli-win32-x64@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz#1a874a5570c6d162b35d9d001c96e5389d07d2cb" integrity sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw== -"@sentry/cli@2.39.1", "@sentry/cli@^2.36.1", "@sentry/cli@^2.39.1": +"@sentry/cli-win32-x64@2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.41.1.tgz#eefd95a2aa184adb464334e265b55a9142070f6f" + integrity sha512-6JcPvXGye61+wPp0xdzfc2YLE/Dcud8JdaK8VxLM3b/8+Em7E+UyliDu3uF8+YGUqizY5JYTd3fs17DC8DZhLw== + +"@sentry/cli@2.39.1": version "2.39.1" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.39.1.tgz#916bb5b7567ccf7fdf94ef6cf8a2b9ab78370d29" integrity sha512-JIb3e9vh0+OmQ0KxmexMXg9oZsR/G7HMwxt5BUIKAXZ9m17Xll4ETXTRnRUBT3sf7EpNGAmlQk1xEmVN9pYZYQ== @@ -6661,6 +6699,25 @@ "@sentry/cli-win32-i686" "2.39.1" "@sentry/cli-win32-x64" "2.39.1" +"@sentry/cli@^2.36.1", "@sentry/cli@^2.41.1": + version "2.41.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.41.1.tgz#a9467ca3ff4acfcdedec1565c9ff726b93758d29" + integrity sha512-0GVmDiTV7R1492wkVY4bGcfC0fSmRmQjuxaaPI8CIV9B2VP9pBVCUizi1mevXaaE4I3fM60LI+XYrKFEneuVog== + dependencies: + https-proxy-agent "^5.0.0" + node-fetch "^2.6.7" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + optionalDependencies: + "@sentry/cli-darwin" "2.41.1" + "@sentry/cli-linux-arm" "2.41.1" + "@sentry/cli-linux-arm64" "2.41.1" + "@sentry/cli-linux-i686" "2.41.1" + "@sentry/cli-linux-x64" "2.41.1" + "@sentry/cli-win32-i686" "2.41.1" + "@sentry/cli-win32-x64" "2.41.1" + "@sentry/rollup-plugin@2.22.7": version "2.22.7" resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.7.tgz#994bb859437eb1e5fd34c485aaa79ba14354778f" @@ -8059,11 +8116,6 @@ dependencies: "@types/unist" "^2" -"@types/node-abi@^3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" - integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== - "@types/node-cron@^3.0.11": version "3.0.11" resolved "https://registry.yarnpkg.com/@types/node-cron/-/node-cron-3.0.11.tgz#70b7131f65038ae63cfe841354c8aba363632344" @@ -11861,15 +11913,6 @@ cjs-module-lexer@^1.0.0, cjs-module-lexer@^1.2.2: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== -clang-format@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.8.0.tgz#7779df1c5ce1bc8aac1b0b02b4e479191ef21d46" - integrity sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw== - dependencies: - async "^3.2.3" - glob "^7.0.0" - resolve "^1.1.6" - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -12703,13 +12746,6 @@ cronstrue@^2.50.0: resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.50.0.tgz#eabba0f915f186765258b707b7a3950c663b5573" integrity sha512-ULYhWIonJzlScCCQrPUG5uMXzXxSixty4djud9SS37DoNxDdkeRocxzHuAo4ImRBUK+mAuU5X9TSwEDccnnuPg== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== - dependencies: - cross-spawn "^7.0.1" - cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -12721,7 +12757,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -22280,7 +22316,7 @@ node-gyp-build@^4.2.2, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== -node-gyp@^9.0.0, node-gyp@^9.4.1: +node-gyp@^9.0.0: version "9.4.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== @@ -23930,17 +23966,17 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" - integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== +playwright-core@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.50.0.tgz#28dd6a1488211c193933695ed337a5b44d46867c" + integrity sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ== -playwright@1.44.1: - version "1.44.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" - integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== +playwright@1.50.0: + version "1.50.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.50.0.tgz#ccaf334f948d78139922844de55a18f8ae785410" + integrity sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w== dependencies: - playwright-core "1.44.1" + playwright-core "1.50.0" optionalDependencies: fsevents "2.3.2" @@ -30646,6 +30682,11 @@ zod@^3.22.3, zod@^3.22.4: resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== +zod@^3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== + zone.js@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.12.0.tgz#a4a6e5fab6d34bd37d89c77e89ac2e6f4a3d2c30"