From 12c2a6c4a8854e15db471e784789b6603fdcd169 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:18:36 +0200 Subject: [PATCH 01/14] feat(deps): bump @sentry/cli from 2.51.1 to 2.52.0 (#17458) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@sentry/cli](https://github.com/getsentry/sentry-cli) from 2.51.1 to 2.52.0.
Release notes

Sourced from @​sentry/cli's releases.

2.52.0

Various fixes & improvements

Changelog

Sourced from @​sentry/cli's changelog.

2.52.0

Various fixes & improvements

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/cli&package-manager=npm_and_yarn&previous-version=2.51.1&new-version=2.52.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/react-router/package.json | 2 +- packages/remix/package.json | 2 +- yarn.lock | 106 ++++++++++++++--------------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/packages/react-router/package.json b/packages/react-router/package.json index f1148295a328..fb404dda61e9 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -50,7 +50,7 @@ "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@sentry/browser": "10.5.0", - "@sentry/cli": "^2.51.1", + "@sentry/cli": "^2.52.0", "@sentry/core": "10.5.0", "@sentry/node": "10.5.0", "@sentry/react": "10.5.0", diff --git a/packages/remix/package.json b/packages/remix/package.json index 34040c025fa2..d11426e8f68f 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -68,7 +68,7 @@ "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@remix-run/router": "1.x", - "@sentry/cli": "^2.51.1", + "@sentry/cli": "^2.52.0", "@sentry/core": "10.5.0", "@sentry/node": "10.5.0", "@sentry/react": "10.5.0", diff --git a/yarn.lock b/yarn.lock index 16f91521a991..408de9566b00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6915,7 +6915,7 @@ mitt "^3.0.0" "@sentry-internal/test-utils@link:dev-packages/test-utils": - version "10.4.0" + version "10.5.0" dependencies: express "^4.21.1" @@ -6938,50 +6938,50 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.51.1.tgz#3a1db065651893f72dad3a502b2d7c2f5e6a7dd8" - integrity sha512-R1u8IQdn/7Rr8sf6bVVr0vJT4OqwCFdYsS44Y3OoWGVJW2aAQTWRJOTlV4ueclVLAyUQzmgBjfR8AtiUhd/M5w== - -"@sentry/cli-linux-arm64@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.51.1.tgz#b4c957a06bafc13623c48971eadb0cff7d3662a3" - integrity sha512-nvA/hdhsw4bKLhslgbBqqvETjXwN1FVmwHLOrRvRcejDO6zeIKUElDiL5UOjGG0NC+62AxyNw5ri8Wzp/7rg9Q== - -"@sentry/cli-linux-arm@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.51.1.tgz#f761d0c58d27be503471cee4ffc41875a7d9430b" - integrity sha512-Klro17OmSSKOOSaxVKBBNPXet2+HrIDZUTSp8NRl4LQsIubdc1S/aQ79cH/g52Muwzpl3aFwPxyXw+46isfEgA== - -"@sentry/cli-linux-i686@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.51.1.tgz#62baaf83c5995e478186289a45315d0acd5bd3bf" - integrity sha512-jp4TmR8VXBdT9dLo6mHniQHN0xKnmJoPGVz9h9VDvO2Vp/8o96rBc555D4Am5wJOXmfuPlyjGcmwHlB3+kQRWw== - -"@sentry/cli-linux-x64@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.51.1.tgz#0010fe24ad8ef492a917c12feb351ba768e72603" - integrity sha512-JuLt0MXM2KHNFmjqXjv23sly56mJmUQzGBWktkpY3r+jE08f5NLKPd5wQ6W/SoLXGIOKnwLz0WoUg7aBVyQdeQ== - -"@sentry/cli-win32-arm64@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.51.1.tgz#0894f9a91e6ecb3021ca09fe644f995ff4ff826d" - integrity sha512-PiwjTdIFDazTQCTyDCutiSkt4omggYSKnO3HE1+LDjElsFrWY9pJs4fU3D40WAyE2oKu0MarjNH/WxYGdqEAlg== - -"@sentry/cli-win32-i686@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.51.1.tgz#6a6c6402cdce4fd038716b2c1e0bfa788b54f3e9" - integrity sha512-TMvZZpeiI2HmrDFNVQ0uOiTuYKvjEGOZdmUxe3WlhZW82A/2Oka7sQ24ljcOovbmBOj5+fjCHRUMYvLMCWiysA== - -"@sentry/cli-win32-x64@2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.51.1.tgz#d361e37146c9269d40c37459271a6c2cfa1fa8a6" - integrity sha512-v2hreYUPPTNK1/N7+DeX7XBN/zb7p539k+2Osf0HFyVBaoUC3Y3+KBwSf4ASsnmgTAK7HCGR+X0NH1vP+icw4w== - -"@sentry/cli@^2.51.0", "@sentry/cli@^2.51.1": - version "2.51.1" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.51.1.tgz#c6bdc6025e8f600e44fc76f8274c369aeb5d4df4" - integrity sha512-FU+54kNcKJABU0+ekvtnoXHM9zVrDe1zXVFbQT7mS0On0m1P0zFRGdzbnWe2XzpzuEAJXtK6aog/W+esRU9AIA== +"@sentry/cli-darwin@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.52.0.tgz#05178cd819c2a33eb22a6e90bf7bb8f853f1b476" + integrity sha512-ieQs/p4yTHT27nBzy0wtAb8BSISfWlpXdgsACcwXimYa36NJRwyCqgOXUaH/BYiTdwWSHpuANbUHGJW6zljzxw== + +"@sentry/cli-linux-arm64@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.52.0.tgz#1979141afc93022614f868374ecc4d3090e84833" + integrity sha512-RxT5uzxjCkcvplmx0bavJIEYerRex2Rg/2RAVBdVvWLKFOcmeerTn/VVxPZVuDIVMVyjlZsteWPYwfUm+Ia3wQ== + +"@sentry/cli-linux-arm@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.52.0.tgz#6957e11af62e50d1040488ec75b3d96ae33fbb5a" + integrity sha512-tWMLU+hj+iip5Akx+S76biAOE1eMMWTDq8c0MqMv/ahHgb6/HiVngMcUsp59Oz3EczJGbTkcnS3vRTDodEcMDw== + +"@sentry/cli-linux-i686@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.52.0.tgz#e369ce3afa4b83a482d34cfd25fae4af792b211a" + integrity sha512-sKcJmIg7QWFtlNU5Bs5OZprwdIzzyYMRpFkWioPZ4TE82yvP1+2SAX31VPUlTx+7NLU6YVEWNwvSxh8LWb7iOw== + +"@sentry/cli-linux-x64@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.52.0.tgz#2b447afac1bb96624823a49c0d9f23c54475bff2" + integrity sha512-aPZ7bP02zGkuEqTiOAm4np/ggfgtzrq4ti1Xze96Csi/DV3820SCfLrPlsvcvnqq7x69IL9cI3kXjdEpgrfGxw== + +"@sentry/cli-win32-arm64@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.52.0.tgz#059063774ab5437ea05d82ce316faa77582b8b51" + integrity sha512-90hrB5XdwJVhRpCmVrEcYoKW8nl5/V9OfVvOGeKUPvUkApLzvsInK74FYBZEVyAn1i/NdUv+Xk9q2zqUGK1aLQ== + +"@sentry/cli-win32-i686@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.52.0.tgz#bee3cded721fcf45db2e77bf84ea8653e4d803d9" + integrity sha512-HXlSE4CaLylNrELx4KVmOQjV5bURCNuky6sjCWiTH7HyDqHEak2Rk8iLE0JNLj5RETWMvmaZnZZFfmyGlY1opg== + +"@sentry/cli-win32-x64@2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.52.0.tgz#16e501e5f00834b1f64765774c59740580043dfc" + integrity sha512-hJT0C3FwHk1Mt9oFqcci88wbO1D+yAWUL8J29HEGM5ZAqlhdh7sAtPDIC3P2LceUJOjnXihow47Bkj62juatIQ== + +"@sentry/cli@^2.51.0", "@sentry/cli@^2.52.0": + version "2.52.0" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.52.0.tgz#5162900bbfae57ddfc414bbe5780837622125aed" + integrity sha512-PXyo7Yv7+rVMSBGZfI/eFEzzhiKedTs25sDCjz4a3goAZ/F5R5tn3MKq30pnze5wNnoQmLujAa0uUjfNcWP+uQ== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -6989,14 +6989,14 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.51.1" - "@sentry/cli-linux-arm" "2.51.1" - "@sentry/cli-linux-arm64" "2.51.1" - "@sentry/cli-linux-i686" "2.51.1" - "@sentry/cli-linux-x64" "2.51.1" - "@sentry/cli-win32-arm64" "2.51.1" - "@sentry/cli-win32-i686" "2.51.1" - "@sentry/cli-win32-x64" "2.51.1" + "@sentry/cli-darwin" "2.52.0" + "@sentry/cli-linux-arm" "2.52.0" + "@sentry/cli-linux-arm64" "2.52.0" + "@sentry/cli-linux-i686" "2.52.0" + "@sentry/cli-linux-x64" "2.52.0" + "@sentry/cli-win32-arm64" "2.52.0" + "@sentry/cli-win32-i686" "2.52.0" + "@sentry/cli-win32-x64" "2.52.0" "@sentry/rollup-plugin@^4.1.0": version "4.1.0" From 3efcb402629c758ea9303b943d61b39019332121 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 19:18:58 +0200 Subject: [PATCH 02/14] feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.56.0 to 0.57.0 (#17455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@opentelemetry/instrumentation-aws-sdk](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-aws-sdk) from 0.56.0 to 0.57.0.
Changelog

Sourced from @​opentelemetry/instrumentation-aws-sdk's changelog.

0.57.0 (2025-08-13)

Features

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@opentelemetry/instrumentation-aws-sdk&package-manager=npm_and_yarn&previous-version=0.56.0&new-version=0.57.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/aws-serverless/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index d24ff2560a05..8282a9bab4c5 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -67,7 +67,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation": "^0.203.0", - "@opentelemetry/instrumentation-aws-sdk": "0.56.0", + "@opentelemetry/instrumentation-aws-sdk": "0.57.0", "@opentelemetry/semantic-conventions": "^1.36.0", "@sentry/core": "10.5.0", "@sentry/node": "10.5.0", diff --git a/yarn.lock b/yarn.lock index 408de9566b00..90a1bcb26e95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5876,10 +5876,10 @@ "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-aws-sdk@0.56.0": - version "0.56.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.56.0.tgz#a65cd88351b7bd8566413798764679295166754a" - integrity sha512-Jl2B/FYEb6tBCk9G31CMomKPikGU2g+CEhrGddDI0o1YeNpg3kAO9dExF+w489/IJUGZX6/wudyNvV7z4k9NjQ== +"@opentelemetry/instrumentation-aws-sdk@0.57.0": + version "0.57.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.57.0.tgz#22d0a2ac113718c85c39d3561ee338dfad2234f6" + integrity sha512-RfbyjaeZzX3mPhuaRHlSAQyfX3skfeWOl30jrqSXtE9k0DPdnIqpHhdYS0C/DEDuZbwTmruVJ4cUwMBw5Z6FAg== dependencies: "@opentelemetry/core" "^2.0.0" "@opentelemetry/instrumentation" "^0.203.0" From fc233bef0120542c0954e20fd628768452415646 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Tue, 26 Aug 2025 19:35:57 +0200 Subject: [PATCH 03/14] test(aws): Run E2E tests in all supported Node versions (#17446) This runs AWS E2E tests in Node 18, 20, 22 and confirms that using `--import` works for both CJS and ESM functions. --- .github/workflows/build.yml | 8 ++-- .../aws-serverless/package.json | 14 ++++++ .../aws-serverless/playwright.config.ts | 4 +- .../aws-serverless/pull-sam-image.sh | 15 ++++++ .../aws-serverless/src/stack.ts | 8 ++-- .../aws-serverless/tests/lambda-fixtures.ts | 46 ++++++++++--------- .../create-next-app/package.json | 2 +- .../create-react-app/package.json | 2 +- .../test-applications/nextjs-13/package.json | 4 +- .../test-applications/nextjs-14/package.json | 4 +- .../test-applications/nextjs-15/package.json | 6 +-- .../nextjs-app-dir/package.json | 6 +-- .../nextjs-pages-dir/package.json | 6 +-- .../nextjs-turbo/package.json | 4 +- .../test-applications/nuxt-3/package.json | 2 +- .../test-applications/nuxt-4/package.json | 2 +- .../react-router-6/package.json | 2 +- .../react-router-7-spa/package.json | 2 +- 18 files changed, 85 insertions(+), 52 deletions(-) create mode 100755 dev-packages/e2e-tests/test-applications/aws-serverless/pull-sam-image.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44057f39da11..41e4e5c2a3b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -954,7 +954,7 @@ jobs: - name: Build E2E app working-directory: ${{ runner.temp }}/test-application timeout-minutes: 7 - run: pnpm ${{ matrix.build-command || 'test:build' }} + run: ${{ matrix.build-command || 'pnpm test:build' }} - name: Install Playwright uses: ./.github/actions/install-playwright @@ -965,7 +965,7 @@ jobs: - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 - run: pnpm test:assert + run: ${{ matrix.assert-command || 'pnpm test:assert' }} - name: Upload Playwright Traces uses: actions/upload-artifact@v4 @@ -1075,7 +1075,7 @@ jobs: - name: Build E2E app working-directory: ${{ runner.temp }}/test-application timeout-minutes: 7 - run: pnpm ${{ matrix.build-command || 'test:build' }} + run: ${{ matrix.build-command || 'pnpm test:build' }} - name: Install Playwright uses: ./.github/actions/install-playwright @@ -1086,7 +1086,7 @@ jobs: - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 - run: pnpm ${{ matrix.assert-command || 'test:assert' }} + run: ${{ matrix.assert-command || 'pnpm test:assert' }} - name: Pre-process E2E Test Dumps if: failure() diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless/package.json index 83437b2f9fbf..bf8085c4e892 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/package.json @@ -23,5 +23,19 @@ }, "volta": { "extends": "../../package.json" + }, + "sentryTest": { + "variants": [ + { + "build-command": "NODE_VERSION=20 ./pull-sam-image.sh && pnpm test:build", + "assert-command": "NODE_VERSION=20 pnpm test:assert", + "label": "aws-serverless (Node 20)" + }, + { + "build-command": "NODE_VERSION=18 ./pull-sam-image.sh && pnpm test:build", + "assert-command": "NODE_VERSION=18 pnpm test:assert", + "label": "aws-serverless (Node 18)" + } + ] } } diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts index 174593c307df..e47333c66e76 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/playwright.config.ts @@ -1,3 +1,5 @@ import { getPlaywrightConfig } from '@sentry-internal/test-utils'; -export default getPlaywrightConfig(); +export default getPlaywrightConfig(undefined, { + timeout: 60 * 1000 * 3, // 3 minutes +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/pull-sam-image.sh b/dev-packages/e2e-tests/test-applications/aws-serverless/pull-sam-image.sh new file mode 100755 index 000000000000..0c27c1eac24d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/pull-sam-image.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Script to pull the correct SAM docker image based on the NODE_VERSION environment variable. + +set -e + +if [[ -z "$NODE_VERSION" ]]; then + echo "Error: NODE_VERSION not set" + exit 1 +fi + +echo "Pulling SAM Node $NODE_VERSION docker image..." +docker pull "public.ecr.aws/sam/build-nodejs${NODE_VERSION}.x:latest" + +echo "Successfully pulled SAM Node $NODE_VERSION docker image" diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index 825c9648ee66..d23feae60811 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -12,8 +12,8 @@ const LAMBDA_FUNCTIONS_WITH_LAYER_DIR = './src/lambda-functions-layer'; const LAMBDA_FUNCTIONS_WITH_NPM_DIR = './src/lambda-functions-npm'; const LAMBDA_FUNCTION_TIMEOUT = 10; const LAYER_DIR = './node_modules/@sentry/aws-serverless/'; +const DEFAULT_NODE_VERSION = '22'; export const SAM_PORT = 3001; -const NODE_RUNTIME = `nodejs${process.version.split('.').at(0)?.replace('v', '')}.x`; export class LocalLambdaStack extends Stack { sentryLayer: CfnResource; @@ -73,14 +73,12 @@ export class LocalLambdaStack extends Stack { execFileSync('npm', ['install', '--prefix', path.join(functionsDir, lambdaDir)], { stdio: 'inherit' }); } - const isEsm = fs.existsSync(path.join(functionsDir, lambdaDir, 'index.mjs')); - new CfnResource(this, functionName, { type: 'AWS::Serverless::Function', properties: { CodeUri: path.join(functionsDir, lambdaDir), Handler: 'index.handler', - Runtime: NODE_RUNTIME, + Runtime: `nodejs${process.env.NODE_VERSION ?? DEFAULT_NODE_VERSION}.x`, Timeout: LAMBDA_FUNCTION_TIMEOUT, Layers: addLayer ? [{ Ref: this.sentryLayer.logicalId }] : undefined, Environment: { @@ -88,7 +86,7 @@ export class LocalLambdaStack extends Stack { SENTRY_DSN: dsn, SENTRY_TRACES_SAMPLE_RATE: 1.0, SENTRY_DEBUG: true, - NODE_OPTIONS: `--${isEsm ? 'import' : 'require'}=@sentry/aws-serverless/awslambda-auto`, + NODE_OPTIONS: `--import=@sentry/aws-serverless/awslambda-auto`, }, }, }, diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts index 707f808218fb..561086fcdb9d 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts @@ -29,23 +29,27 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien const debugLog = tmp.fileSync({ prefix: 'sentry_aws_lambda_tests_sam_debug', postfix: '.log' }); console.log(`[test_environment fixture] Writing SAM debug log to: ${debugLog.name}`); - const process = spawn( - 'sam', - [ - 'local', - 'start-lambda', - '--debug', - '--template', - SAM_TEMPLATE_FILE, - '--warm-containers', - 'EAGER', - '--docker-network', - DOCKER_NETWORK_NAME, - ], - { - stdio: ['ignore', debugLog.fd, debugLog.fd], - }, - ); + const args = [ + 'local', + 'start-lambda', + '--debug', + '--template', + SAM_TEMPLATE_FILE, + '--warm-containers', + 'EAGER', + '--docker-network', + DOCKER_NETWORK_NAME, + ]; + + if (process.env.NODE_VERSION) { + args.push('--invoke-image', `public.ecr.aws/sam/build-nodejs${process.env.NODE_VERSION}.x:latest`); + } + + console.log(`[testEnvironment fixture] Running SAM with args: ${args.join(' ')}`); + + const samProcess = spawn('sam', args, { + stdio: ['ignore', debugLog.fd, debugLog.fd], + }); try { await LocalLambdaStack.waitForStack(); @@ -54,12 +58,12 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien } finally { console.log('[testEnvironment fixture] Tearing down AWS Lambda test infrastructure'); - process.kill('SIGTERM'); + samProcess.kill('SIGTERM'); await new Promise(resolve => { - process.once('exit', resolve); + samProcess.once('exit', resolve); setTimeout(() => { - if (!process.killed) { - process.kill('SIGKILL'); + if (!samProcess.killed) { + samProcess.kill('SIGKILL'); } resolve(void 0); }, 5000); 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 5eeac69d422e..d87aed0de03e 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 @@ -31,7 +31,7 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-13", + "build-command": "pnpm test:build-13", "label": "create-next-app (next@13)" } ] diff --git a/dev-packages/e2e-tests/test-applications/create-react-app/package.json b/dev-packages/e2e-tests/test-applications/create-react-app/package.json index 981123625b96..0c2bc337d396 100644 --- a/dev-packages/e2e-tests/test-applications/create-react-app/package.json +++ b/dev-packages/e2e-tests/test-applications/create-react-app/package.json @@ -47,7 +47,7 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-ts3.8", + "build-command": "pnpm test:build-ts3.8", "label": "create-react-app (TS 3.8)" } ] 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 ebb4d632127d..32430917ddc0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json @@ -32,11 +32,11 @@ "sentryTest": { "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-13 (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-13 (latest)" } ] diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json index acffda8eeed5..822d321b2028 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json @@ -33,11 +33,11 @@ "sentryTest": { "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-14 (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-14 (latest)" } ] diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 063f36d3b164..052dd62697a1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -37,15 +37,15 @@ "sentryTest": { "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-15 (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-15 (latest)" }, { - "build-command": "test:build-turbo", + "build-command": "pnpm test:build-turbo", "assert-command": "pnpm test:prod && pnpm test:dev-turbo", "label": "nextjs-15 (turbo)" } 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 e25c4ec84053..e37e4a0c8ca3 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 @@ -36,17 +36,17 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-13", + "build-command": "pnpm test:build-13", "label": "nextjs-app-dir (next@13)" } ], "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-app-dir (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-app-dir (latest)" } ] diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json index 03a7efd1d521..233ceb802536 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/package.json @@ -36,17 +36,17 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-13", + "build-command": "pnpm test:build-13", "label": "nextjs-pages-dir (next@13)" } ], "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-pages-dir (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-pages-dir (latest)" } ] 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 46aba39d865c..1cfbd8eb6628 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json @@ -34,11 +34,11 @@ "optional": true, "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nextjs-turbo (canary)" }, { - "build-command": "test:build-latest", + "build-command": "pnpm test:build-latest", "label": "nextjs-turbo (latest)" } ] 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 536043eec631..b38943d6e3eb 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json @@ -27,7 +27,7 @@ "sentryTest": { "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nuxt-3 (canary)" } ] 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 f73e7ff99200..a68c4c823738 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json @@ -30,7 +30,7 @@ "sentryTest": { "optionalVariants": [ { - "build-command": "test:build-canary", + "build-command": "pnpm test:build-canary", "label": "nuxt-4 (canary)" } ] 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 6de1a0f9b76a..bcab38dad727 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 @@ -55,7 +55,7 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-ts3.8", + "build-command": "pnpm test:build-ts3.8", "label": "react-router-6 (TS 3.8)" } ] 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 41ff42c14f09..2c6bf1654cae 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 @@ -52,7 +52,7 @@ "sentryTest": { "variants": [ { - "build-command": "test:build-ts3.8", + "build-command": "pnpm test:build-ts3.8", "label": "react-router-7-spa (TS 3.8)" } ] From 5de1b47b72bce0cbb547d306a9309002222ad8da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:29:47 +0200 Subject: [PATCH 04/14] feat(deps): bump @prisma/instrumentation from 6.13.0 to 6.14.0 (#17466) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@prisma/instrumentation](https://github.com/prisma/prisma/tree/HEAD/packages/instrumentation) from 6.13.0 to 6.14.0.
Release notes

Sourced from @​prisma/instrumentation's releases.

6.14.0

Today, we are excited to share the 6.14.0 stable release 🎉

🌟 Star this repo for notifications about new releases, bug fixes & features — or follow us on X!

Highlights

@unique attributes for SQL views (Preview)

Last release, we improved the robustness of SQL views defined in the Prisma schema. Views are virtual tables that don't allows for defining unique constraints, indexes or foreign keys in the underlying database.

However, as an application developer, it can be convenient to also define relationships involving views or paginate them using cursors. We've received this feedback from several people who had been using views in that way with Prisma ORM, so in this release we're re-introducing the @unique attribute for views. This attribute enables:

  • relationships involving views
  • findUnique queries, cursor-based pagination & implicit ordering for views

Here's an example schema using @unique and defining a relationship from a model to a view:

model User {
  id        Int            @id @default(autoincrement())
  email     String         @unique
  posts     Post[]
stats UserPostStats? @relation(fields: [email], references: [userEmail])
}

model Post { id Int @​id @​default(autoincrement()) title String published Boolean @​default(false) createdAt DateTime @​default(now()) authorId Int? author User? @​relation(fields: [authorId], references: [id]) }

view UserPostStats { userEmail String @​unique totalPosts BigInt? publishedPosts BigInt? unpublishedPosts BigInt? latestPostDate DateTime? @​db.Timestamp(6) user User? }

CREATE OR REPLACE VIEW "UserPostStats"
AS
SELECT
</tr></table>

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@prisma/instrumentation&package-manager=npm_and_yarn&previous-version=6.13.0&new-version=6.14.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/node/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index e908e089349c..d45abb5368b4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -94,7 +94,7 @@ "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.34.0", - "@prisma/instrumentation": "6.13.0", + "@prisma/instrumentation": "6.14.0", "@sentry/core": "10.5.0", "@sentry/node-core": "10.5.0", "@sentry/opentelemetry": "10.5.0", diff --git a/yarn.lock b/yarn.lock index 90a1bcb26e95..775f9b66548a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6296,10 +6296,10 @@ resolved "https://registry.yarnpkg.com/@poppinss/exception/-/exception-1.2.2.tgz#8d30d42e126c54fe84e997433e4dcac610090743" integrity sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg== -"@prisma/instrumentation@6.13.0": - version "6.13.0" - resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.13.0.tgz#f2f774162b9247c870f306828da580c5102ff679" - integrity sha512-b97b0sBycGh89RQcqobSgjGl3jwPaC5cQIOFod6EX1v0zIxlXPmL3ckSXxoHpy+Js0QV/tgCzFvqicMJCtezBA== +"@prisma/instrumentation@6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.14.0.tgz#8f6e43b73ee2b88b98cec901457f4da7da13aea3" + integrity sha512-Po/Hry5bAeunRDq0yAQueKookW3glpP+qjjvvyOfm6dI2KG5/Y6Bgg3ahyWd7B0u2E+Wf9xRk2rtdda7ySgK1A== dependencies: "@opentelemetry/instrumentation" "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" From 81108d3b3990a8c8142e85c5f0c46605451a0743 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:38:06 +0200 Subject: [PATCH 05/14] feat(deps): bump @opentelemetry/instrumentation-mysql2 from 0.49.0 to 0.50.0 (#17459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@opentelemetry/instrumentation-mysql2](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-mysql2) from 0.49.0 to 0.50.0.
Changelog

Sourced from @​opentelemetry/instrumentation-mysql2's changelog.

0.50.0 (2025-08-13)

⚠ BREAKING CHANGES

  • instrumentation-mysql2: Missing masking of sql queries (#2732)

Bug Fixes

  • instrumentation-mysql2: Missing masking of sql queries (#2732) (bcf32cd)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@opentelemetry/instrumentation-mysql2&package-manager=npm_and_yarn&previous-version=0.49.0&new-version=0.50.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/node/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index d45abb5368b4..63b78e896abd 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -86,7 +86,7 @@ "@opentelemetry/instrumentation-mongodb": "0.56.0", "@opentelemetry/instrumentation-mongoose": "0.50.0", "@opentelemetry/instrumentation-mysql": "0.49.0", - "@opentelemetry/instrumentation-mysql2": "0.49.0", + "@opentelemetry/instrumentation-mysql2": "0.50.0", "@opentelemetry/instrumentation-pg": "0.55.0", "@opentelemetry/instrumentation-redis": "0.51.0", "@opentelemetry/instrumentation-tedious": "0.22.0", diff --git a/yarn.lock b/yarn.lock index 775f9b66548a..e8e1987c4742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6011,10 +6011,10 @@ "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-mysql2@0.49.0": - version "0.49.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.49.0.tgz#ad518f9420cf8d2035bd4f80519406b66b66bb1a" - integrity sha512-dCub9wc02mkJWNyHdVEZ7dvRzy295SmNJa+LrAJY2a/+tIiVBQqEAajFzKwp9zegVVnel9L+WORu34rGLQDzxA== +"@opentelemetry/instrumentation-mysql2@0.50.0": + version "0.50.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.50.0.tgz#259344ba8771fd59c70a1a91360215b4b047a323" + integrity sha512-PoOMpmq73rOIE3nlTNLf3B1SyNYGsp7QXHYKmeTZZnJ2Ou7/fdURuOhWOI0e6QZ5gSem18IR1sJi6GOULBQJ9g== dependencies: "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.27.0" From 3048f8447b5c08a1cc196f2798d2866c6180b670 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 27 Aug 2025 05:08:24 -0400 Subject: [PATCH 06/14] feat(replay): Add option to skip `requestAnimationFrame` for canvas snapshots (#17380) --- .size-limit.js | 2 +- packages/replay-canvas/package.json | 2 +- packages/replay-canvas/src/canvas.ts | 11 ++++++++--- packages/replay-internal/package.json | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 490195900900..24df3d3017c8 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -82,7 +82,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'), gzip: true, - limit: '95 KB', + limit: '96 KB', }, { name: '@sentry/browser (incl. Feedback)', diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 9ad13954d08c..5d6d0a631ad3 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -66,7 +66,7 @@ }, "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { - "@sentry-internal/rrweb": "2.35.0" + "@sentry-internal/rrweb": "2.37.0" }, "dependencies": { "@sentry-internal/replay": "10.5.0", diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index d026567e01b1..7861572b190f 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -3,8 +3,12 @@ import { defineIntegration } from '@sentry/core'; import type { CanvasManagerInterface, CanvasManagerOptions } from '@sentry-internal/replay'; import { CanvasManager } from '@sentry-internal/rrweb'; +interface SnapshotOptions { + skipRequestAnimationFrame?: boolean; +} + interface ReplayCanvasIntegration extends Integration { - snapshot: (canvasElement?: HTMLCanvasElement) => Promise; + snapshot: (canvasElement?: HTMLCanvasElement, options?: SnapshotOptions) => Promise; } interface ReplayCanvasOptions { @@ -106,9 +110,10 @@ export const _replayCanvasIntegration = ((options: Partial ...(CANVAS_QUALITY[quality || 'medium'] || CANVAS_QUALITY.medium), }; }, - async snapshot(canvasElement?: HTMLCanvasElement) { + async snapshot(canvasElement?: HTMLCanvasElement, options?: SnapshotOptions) { const canvasManager = await _canvasManager; - canvasManager.snapshot(canvasElement); + + canvasManager.snapshot(canvasElement, options); }, }; }) satisfies IntegrationFn; diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 64ea2974abb0..d0b853e496b0 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -82,8 +82,8 @@ "devDependencies": { "@babel/core": "^7.27.7", "@sentry-internal/replay-worker": "10.5.0", - "@sentry-internal/rrweb": "2.35.0", - "@sentry-internal/rrweb-snapshot": "2.35.0", + "@sentry-internal/rrweb": "2.37.0", + "@sentry-internal/rrweb-snapshot": "2.37.0", "fflate": "0.8.2", "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.3.0", From a7acc8f01bc6ffc2aadbf2e704b9550794b62088 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:48:48 +0200 Subject: [PATCH 07/14] feat(cloudflare): Add `instrumentPrototypeMethods` option to instrument RPC methods for DurableObjects (#17424) Fixes `The RPC receiver does not implement the method "METHOD_NAME error` errors by wrapping methods and putting them back on the prototype. Added an option `instrumentPrototypeMethods` to opt into wrapping RPC methods. These are potentially expensive to wrap because each invocation wraps so we provide an option to either enable this as a whole or pass method names that should be wrapped. Also removes the flag to not record spans because no spans were collected ever. I think this is related to connected traces not working correctly for the SDK? cc @AbhiPrasad. Closes: #17127 --- CHANGELOG.md | 33 ++++- .../suites/tracing/durableobject/index.ts | 41 ++++++ .../suites/tracing/durableobject/test.ts | 27 ++++ .../tracing/durableobject/wrangler.jsonc | 23 ++++ .../cloudflare-workers/src/index.ts | 65 ++++----- packages/cloudflare/src/client.ts | 23 ++++ packages/cloudflare/src/durableobject.ts | 122 ++++++++++++----- .../cloudflare/test/durableobject.test.ts | 129 ++++++++++++++++-- 8 files changed, 387 insertions(+), 76 deletions(-) create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/index.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/wrangler.jsonc diff --git a/CHANGELOG.md b/CHANGELOG.md index 6529fcfe59cb..036e1408e300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +### Important Changes + +- feat(cloudflare): Add `instrumentPrototypeMethods` option to instrument RPC methods for DurableObjects ([#17424](https://github.com/getsentry/sentry-javascript/pull/17424)) + +By default, `Sentry.instrumentDurableObjectWithSentry` will not wrap any RPC methods on the prototype. To enable wrapping for RPC methods, set `instrumentPrototypeMethods` to `true` or, if performance is a concern, a list of only the methods you want to instrument: + +```js +class MyDurableObjectBase extends DurableObject { + method1() { + // ... + } + + method2() { + // ... + } + + method3() { + // ... + } +} +// Export your named class as defined in your wrangler config +export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( + (env: Env) => ({ + dsn: "https://ac49b7af3017c458bd12dab9b3328bfc@o4508482761982032.ingest.de.sentry.io/4508482780987481", + tracesSampleRate: 1.0, + instrumentPrototypeMethods: ['method1', 'method3'], + }), + MyDurableObjectBase, +); +``` + ## 10.6.0 ### Important Changes @@ -41,8 +72,6 @@ The Sentry Nuxt SDK is now considered stable and no longer in beta! -Work in this release was contributed by @Karibash. Thank you for your contribution! - ## 10.5.0 - feat(core): better cause data extraction ([#17375](https://github.com/getsentry/sentry-javascript/pull/17375)) diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/index.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/index.ts new file mode 100644 index 000000000000..74ce2cbbdac4 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/index.ts @@ -0,0 +1,41 @@ +import * as Sentry from '@sentry/cloudflare'; +import { DurableObject } from 'cloudflare:workers'; + +interface Env { + SENTRY_DSN: string; + TEST_DURABLE_OBJECT: DurableObjectNamespace; +} + +class TestDurableObjectBase extends DurableObject { + public constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + async sayHello(name: string): Promise { + return `Hello, ${name}`; + } +} + +export const TestDurableObject = Sentry.instrumentDurableObjectWithSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 1.0, + instrumentPrototypeMethods: true, + }), + TestDurableObjectBase, +); + +export default { + async fetch(request: Request, env: Env): Promise { + const id: DurableObjectId = env.TEST_DURABLE_OBJECT.idFromName('test'); + const stub = env.TEST_DURABLE_OBJECT.get(id) as unknown as TestDurableObjectBase; + + if (request.url.includes('hello')) { + const greeting = await stub.sayHello('world'); + return new Response(greeting); + } + + return new Response('Usual response'); + }, +}; diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts new file mode 100644 index 000000000000..cfb6841004a9 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/test.ts @@ -0,0 +1,27 @@ +import { expect, it } from 'vitest'; +import { createRunner } from '../../../runner'; + +it('traces a durable object method', async () => { + const runner = createRunner(__dirname) + .expect(envelope => { + const transactionEvent = envelope[1]?.[0]?.[1]; + expect(transactionEvent).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + trace: expect.objectContaining({ + op: 'rpc', + data: expect.objectContaining({ + 'sentry.op': 'rpc', + 'sentry.origin': 'auto.faas.cloudflare_durableobjects', + }), + origin: 'auto.faas.cloudflare_durableobjects', + }), + }), + transaction: 'sayHello', + }), + ); + }) + .start(); + await runner.makeRequest('get', '/hello'); + await runner.completed(); +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/wrangler.jsonc new file mode 100644 index 000000000000..8f27c3af7a22 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/durableobject/wrangler.jsonc @@ -0,0 +1,23 @@ +{ + "name": "worker-name", + "main": "index.ts", + "compatibility_date": "2025-06-17", + "migrations": [ + { + "new_sqlite_classes": ["TestDurableObject"], + "tag": "v1" + } + ], + "durable_objects": { + "bindings": [ + { + "class_name": "TestDurableObject", + "name": "TEST_DURABLE_OBJECT" + } + ] + }, + "compatibility_flags": ["nodejs_als"], + "vars": { + "SENTRY_DSN": "https://932e620ee3921c3b4a61c72558ad88ce@o447951.ingest.us.sentry.io/4509553159831552" + } +} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-workers/src/index.ts index f329cea238e8..54786fd221cb 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers/src/index.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers/src/index.ts @@ -11,7 +11,7 @@ * Learn more at https://developers.cloudflare.com/workers/ */ import * as Sentry from '@sentry/cloudflare'; -import { DurableObject } from "cloudflare:workers"; +import { DurableObject } from 'cloudflare:workers'; class MyDurableObjectBase extends DurableObject { private throwOnExit = new WeakMap(); @@ -44,7 +44,7 @@ class MyDurableObjectBase extends DurableObject { } webSocketClose(ws: WebSocket): void | Promise { - if (this.throwOnExit.has(ws)) { + if (this.throwOnExit.has(ws)) { const error = this.throwOnExit.get(ws)!; this.throwOnExit.delete(ws); throw error; @@ -53,36 +53,37 @@ class MyDurableObjectBase extends DurableObject { } export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( - (env: Env) => ({ - dsn: env.E2E_TEST_DSN, - environment: 'qa', // dynamic sampling bias to keep transactions - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, - sendDefaultPii: true, - transportOptions: { - // We are doing a lot of events at once in this test - bufferSize: 1000, - }, - }), - MyDurableObjectBase, + (env: Env) => ({ + dsn: env.E2E_TEST_DSN, + environment: 'qa', // dynamic sampling bias to keep transactions + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + sendDefaultPii: true, + transportOptions: { + // We are doing a lot of events at once in this test + bufferSize: 1000, + }, + instrumentPrototypeMethods: true, + }), + MyDurableObjectBase, ); export default Sentry.withSentry( - (env: Env) => ({ - dsn: env.E2E_TEST_DSN, - environment: 'qa', // dynamic sampling bias to keep transactions - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, - sendDefaultPii: true, - transportOptions: { - // We are doing a lot of events at once in this test - bufferSize: 1000, - }, - }), - { - async fetch(request, env) { - const url = new URL(request.url); - switch (url.pathname) { + (env: Env) => ({ + dsn: env.E2E_TEST_DSN, + environment: 'qa', // dynamic sampling bias to keep transactions + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + sendDefaultPii: true, + transportOptions: { + // We are doing a lot of events at once in this test + bufferSize: 1000, + }, + }), + { + async fetch(request, env) { + const url = new URL(request.url); + switch (url.pathname) { case '/rpc/throwException': { const id = env.MY_DURABLE_OBJECT.idFromName('foo'); @@ -105,7 +106,7 @@ export default Sentry.withSentry( return stub.fetch(new Request(url, request)); } } - return new Response('Hello World!'); - }, - } satisfies ExportedHandler, + return new Response('Hello World!'); + }, + } satisfies ExportedHandler, ); diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index b6b4695835ba..2de5147d3d5a 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -68,6 +68,29 @@ interface BaseCloudflareOptions { * @default false */ skipOpenTelemetrySetup?: boolean; + + /** + * Enable instrumentation of prototype methods for DurableObjects. + * + * When `true`, the SDK will wrap all methods on the DurableObject prototype chain + * to automatically create spans and capture errors for RPC method calls. + * + * When an array of strings is provided, only the specified method names will be instrumented. + * + * This feature adds runtime overhead as it wraps methods at the prototype level. + * Only enable this if you need automatic instrumentation of prototype methods. + * + * @default false + * @example + * ```ts + * // Instrument all prototype methods + * instrumentPrototypeMethods: true + * + * // Instrument only specific methods + * instrumentPrototypeMethods: ['myMethod', 'anotherMethod'] + * ``` + */ + instrumentPrototypeMethods?: boolean | string[]; } /** diff --git a/packages/cloudflare/src/durableobject.ts b/packages/cloudflare/src/durableobject.ts index 4efaf33c9b1c..bda7a9aa3538 100644 --- a/packages/cloudflare/src/durableobject.ts +++ b/packages/cloudflare/src/durableobject.ts @@ -110,8 +110,7 @@ function wrapMethodWithSentry( } : {}; - // Only create these spans if they have a parent span. - return startSpan({ name: wrapperOptions.spanName, attributes, onlyIfParent: true }, () => { + return startSpan({ name: wrapperOptions.spanName, attributes }, () => { try { const result = Reflect.apply(target, thisArg, args); @@ -273,46 +272,101 @@ export function instrumentDurableObjectWithSentry< ); } } - const instrumentedPrototype = instrumentPrototype(target, options, context); - Object.setPrototypeOf(obj, instrumentedPrototype); + + // Store context and options on the instance for prototype methods to access + Object.defineProperty(obj, '__SENTRY_CONTEXT__', { + value: context, + enumerable: false, + writable: false, + configurable: false, + }); + + Object.defineProperty(obj, '__SENTRY_OPTIONS__', { + value: options, + enumerable: false, + writable: false, + configurable: false, + }); + + if (options?.instrumentPrototypeMethods) { + instrumentPrototype(target, options.instrumentPrototypeMethods); + } return obj; }, }); } -function instrumentPrototype( - target: T, - options: CloudflareOptions, - context: MethodWrapperOptions['context'], -): T { - return new Proxy(target.prototype, { - get(target, prop, receiver) { - const value = Reflect.get(target, prop, receiver); - if (prop === 'constructor' || typeof value !== 'function') { - return value; +function instrumentPrototype(target: T, methodsToInstrument: boolean | string[]): void { + const proto = target.prototype; + + // Get all methods from the prototype chain + const methodNames = new Set(); + let current = proto; + + while (current && current !== Object.prototype) { + Object.getOwnPropertyNames(current).forEach(name => { + if (name !== 'constructor' && typeof (current as Record)[name] === 'function') { + methodNames.add(name); + } + }); + current = Object.getPrototypeOf(current); + } + + // Create a set for efficient lookups when methodsToInstrument is an array + const methodsToInstrumentSet = Array.isArray(methodsToInstrument) ? new Set(methodsToInstrument) : null; + + // Instrument each method on the prototype + methodNames.forEach(methodName => { + const originalMethod = (proto as Record)[methodName]; + + if (!originalMethod || isInstrumented(originalMethod)) { + return; + } + + // If methodsToInstrument is an array, only instrument methods in that set + if (methodsToInstrumentSet && !methodsToInstrumentSet.has(methodName)) { + return; + } + + // Create a wrapper that gets context/options from the instance at runtime + const wrappedMethod = function (this: any, ...args: any[]): unknown { + const thisWithSentry = this as { + __SENTRY_CONTEXT__: DurableObjectState; + __SENTRY_OPTIONS__: CloudflareOptions; + }; + const instanceContext = thisWithSentry.__SENTRY_CONTEXT__; + const instanceOptions = thisWithSentry.__SENTRY_OPTIONS__; + + if (!instanceOptions) { + // Fallback to original method if no Sentry data found + return (originalMethod as (...args: any[]) => any).apply(this, args); } - const wrapped = wrapMethodWithSentry( - { options, context, spanName: prop.toString(), spanOp: 'rpc' }, - value, + + // Use the existing wrapper but with instance-specific context/options + const wrapper = wrapMethodWithSentry( + { + options: instanceOptions, + context: instanceContext, + spanName: methodName, + spanOp: 'rpc', + }, + originalMethod as (...args: any[]) => any, undefined, - true, + true, // noMark = true since we'll mark the prototype method ); - const instrumented = new Proxy(wrapped, { - get(target, p, receiver) { - if ('__SENTRY_INSTRUMENTED__' === p) { - return true; - } - return Reflect.get(target, p, receiver); - }, - }); - Object.defineProperty(receiver, prop, { - value: instrumented, - enumerable: true, - writable: true, - configurable: true, - }); - return instrumented; - }, + + return (wrapper as (...args: any[]) => any).apply(this, args); + }; + + markAsInstrumented(wrappedMethod); + + // Replace the prototype method + Object.defineProperty(proto, methodName, { + value: wrappedMethod, + enumerable: false, + writable: true, + configurable: true, + }); }); } diff --git a/packages/cloudflare/test/durableobject.test.ts b/packages/cloudflare/test/durableobject.test.ts index 2add5dde9343..ce794dc7fb69 100644 --- a/packages/cloudflare/test/durableobject.test.ts +++ b/packages/cloudflare/test/durableobject.test.ts @@ -10,7 +10,7 @@ describe('instrumentDurableObjectWithSentry', () => { }); it('Generic functionality', () => { - const options = vi.fn(); + const options = vi.fn().mockReturnValue({}); const instrumented = instrumentDurableObjectWithSentry(options, vi.fn()); expect(instrumented).toBeTypeOf('function'); expect(() => Reflect.construct(instrumented, [])).not.toThrow(); @@ -23,7 +23,10 @@ describe('instrumentDurableObjectWithSentry', () => { return 'sync-result'; } }; - const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn(), testClass as any), []) as any; + const obj = Reflect.construct( + instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), + [], + ) as any; expect(obj.method).toBe(obj.method); const result = obj.method(); @@ -37,7 +40,10 @@ describe('instrumentDurableObjectWithSentry', () => { return 'async-result'; } }; - const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn(), testClass as any), []) as any; + const obj = Reflect.construct( + instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), + [], + ) as any; expect(obj.asyncMethod).toBe(obj.asyncMethod); const result = obj.asyncMethod(); @@ -46,26 +52,42 @@ describe('instrumentDurableObjectWithSentry', () => { }); it('Instruments prototype methods without "sticking" to the options', () => { + const mockContext = { + waitUntil: vi.fn(), + } as any; + const mockEnv = {} as any; // Environment mock const initCore = vi.spyOn(SentryCore, 'initAndBind'); vi.spyOn(SentryCore, 'getClient').mockReturnValue(undefined); const options = vi .fn() .mockReturnValueOnce({ orgId: 1, + instrumentPrototypeMethods: true, }) .mockReturnValueOnce({ orgId: 2, + instrumentPrototypeMethods: true, }); const testClass = class { method() {} }; - (Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), []) as any).method(); - (Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), []) as any).method(); + const instance1 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ + mockContext, + mockEnv, + ]) as any; + instance1.method(); + + const instance2 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ + mockContext, + mockEnv, + ]) as any; + instance2.method(); + expect(initCore).nthCalledWith(1, expect.any(Function), expect.objectContaining({ orgId: 1 })); expect(initCore).nthCalledWith(2, expect.any(Function), expect.objectContaining({ orgId: 2 })); }); - it('All available durable object methods are instrumented', () => { + it('All available durable object methods are instrumented when instrumentPrototypeMethods is enabled', () => { const testClass = class { propertyFunction = vi.fn(); @@ -81,9 +103,11 @@ describe('instrumentDurableObjectWithSentry', () => { webSocketError() {} }; - const instrumented = instrumentDurableObjectWithSentry(vi.fn(), testClass as any); + const instrumented = instrumentDurableObjectWithSentry( + vi.fn().mockReturnValue({ instrumentPrototypeMethods: true }), + testClass as any, + ); const obj = Reflect.construct(instrumented, []); - expect(Object.getPrototypeOf(obj), 'Prototype is instrumented').not.toBe(testClass.prototype); for (const method_name of [ 'propertyFunction', 'fetch', @@ -122,4 +146,93 @@ describe('instrumentDurableObjectWithSentry', () => { await Promise.all(waitUntil.mock.calls.map(([p]) => p)); expect(flush).toBeCalled(); }); + + describe('instrumentPrototypeMethods option', () => { + it('does not instrument prototype methods when option is not set', () => { + const testClass = class { + prototypeMethod() { + return 'prototype-result'; + } + }; + const options = vi.fn().mockReturnValue({}); + const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); + const obj = Reflect.construct(instrumented, []) as any; + + expect(isInstrumented(obj.prototypeMethod)).toBeFalsy(); + }); + + it('does not instrument prototype methods when option is false', () => { + const testClass = class { + prototypeMethod() { + return 'prototype-result'; + } + }; + const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false }); + const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); + const obj = Reflect.construct(instrumented, []) as any; + + expect(isInstrumented(obj.prototypeMethod)).toBeFalsy(); + }); + + it('instruments all prototype methods when option is true', () => { + const testClass = class { + methodOne() { + return 'one'; + } + methodTwo() { + return 'two'; + } + }; + const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: true }); + const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); + const obj = Reflect.construct(instrumented, []) as any; + + expect(isInstrumented(obj.methodOne)).toBeTruthy(); + expect(isInstrumented(obj.methodTwo)).toBeTruthy(); + }); + + it('instruments only specified methods when option is array', () => { + const testClass = class { + methodOne() { + return 'one'; + } + methodTwo() { + return 'two'; + } + methodThree() { + return 'three'; + } + }; + const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: ['methodOne', 'methodThree'] }); + const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); + const obj = Reflect.construct(instrumented, []) as any; + + expect(isInstrumented(obj.methodOne)).toBeTruthy(); + expect(isInstrumented(obj.methodTwo)).toBeFalsy(); + expect(isInstrumented(obj.methodThree)).toBeTruthy(); + }); + + it('still instruments instance methods regardless of prototype option', () => { + const testClass = class { + propertyFunction = vi.fn(); + + fetch() {} + alarm() {} + webSocketMessage() {} + webSocketClose() {} + webSocketError() {} + }; + const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false }); + const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); + const obj = Reflect.construct(instrumented, []) as any; + + // Instance methods should still be instrumented + expect(isInstrumented(obj.propertyFunction)).toBeTruthy(); + expect(isInstrumented(obj.fetch)).toBeTruthy(); + expect(isInstrumented(obj.alarm)).toBeTruthy(); + expect(isInstrumented(obj.webSocketMessage)).toBeTruthy(); + expect(isInstrumented(obj.webSocketClose)).toBeTruthy(); + expect(isInstrumented(obj.webSocketError)).toBeTruthy(); + }); + }); }); From 2282ed306e5f7cc876e3b6de00118b47ec46dd61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:58:25 +0200 Subject: [PATCH 08/14] feat(deps): bump @sentry/webpack-plugin from 4.1.0 to 4.1.1 (#17467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@sentry/webpack-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.1.0 to 4.1.1.
Release notes

Sourced from @​sentry/webpack-plugin's releases.

4.1.1

  • fix(react-native): Enhance fragment detection for indirect references (#767)
Changelog

Sourced from @​sentry/webpack-plugin's changelog.

4.1.1

  • fix(react-native): Enhance fragment detection for indirect references (#767)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/webpack-plugin&package-manager=npm_and_yarn&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/gatsby/package.json | 2 +- packages/nextjs/package.json | 2 +- yarn.lock | 73 +++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index da666290e73c..153b620d0fc2 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -47,7 +47,7 @@ "dependencies": { "@sentry/core": "10.6.0", "@sentry/react": "10.6.0", - "@sentry/webpack-plugin": "^4.1.0" + "@sentry/webpack-plugin": "^4.1.1" }, "peerDependencies": { "gatsby": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 2fa6ead6889b..dcf68600466b 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -85,7 +85,7 @@ "@sentry/opentelemetry": "10.6.0", "@sentry/react": "10.6.0", "@sentry/vercel-edge": "10.6.0", - "@sentry/webpack-plugin": "^4.1.0", + "@sentry/webpack-plugin": "^4.1.1", "chalk": "3.0.0", "resolve": "1.22.8", "rollup": "^4.35.0", diff --git a/yarn.lock b/yarn.lock index e8e1987c4742..cc472481a517 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6853,22 +6853,22 @@ dependencies: "@sentry-internal/rrweb-snapshot" "2.34.0" -"@sentry-internal/rrdom@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.35.0.tgz#27dbdfe3249afb65a31f3b680cd0cc92ed2001dd" - integrity sha512-sWZjJpv7/Fu1po5ibzGUojWLMGn/GgqsayE8dqbwI6F2x5gMVYL0/yIk+9Qii0ei3Su3BybWHfftZs+5r2Bong== +"@sentry-internal/rrdom@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.37.0.tgz#1aaf382eb7b543d7c256d31b73868e81e6649fbb" + integrity sha512-Wj6W4HP6kVYL1oenYq+Ec7QKtsq1Btk/acFLfZ/O7fygLVeAM0KZ4JZirPWdJmpeNNIk1YN7a8C7CfCiM014Ag== dependencies: - "@sentry-internal/rrweb-snapshot" "2.35.0" + "@sentry-internal/rrweb-snapshot" "2.37.0" "@sentry-internal/rrweb-snapshot@2.34.0": version "2.34.0" resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.34.0.tgz#79c2049b6c887e3c128d5fa80d6f745a61dd0e68" integrity sha512-9Tb8jwVufn5GLV0d/CTuoZWo2O06ZB+xWeTJdEkbtJ6PAmO/Q7GQI3uNIx0pfFEnXP+0Km8CKKxpwkEM0z2m6w== -"@sentry-internal/rrweb-snapshot@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.35.0.tgz#656f4716e3bdda151f122868f6f92d5f4224967c" - integrity sha512-CyERHnGWIkuCtw4xYJMoyDUv+5vj38HBd0upeEhKyYzjZ8rOttwsFjfZUBdotsP8O0/RVt9KIPRbSRESC1qSJw== +"@sentry-internal/rrweb-snapshot@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.37.0.tgz#2081d1827a108a08cab219234952744e6e79c06b" + integrity sha512-fu2/Fd5J5gJrAgQgl9WykVPQkMjo+9MVFy4Y88STTP3WWsLu1u75YAQM6Lr1/tLEykoQ4NecmNDdcz/DiB/nNg== "@sentry-internal/rrweb-types@2.34.0": version "2.34.0" @@ -6878,12 +6878,12 @@ "@sentry-internal/rrweb-snapshot" "2.34.0" "@types/css-font-loading-module" "0.0.7" -"@sentry-internal/rrweb-types@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.35.0.tgz#b2e63879a23593505fc3e28aa811e718de71f15f" - integrity sha512-D0mu2bgtvYD8MGijZDSD+q3FC8fDVRvNJD4canKvI3Wy9/LHTPbJ6F4U544vp5VrdBGCYIf/cxuJwmyZDfl5RQ== +"@sentry-internal/rrweb-types@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.37.0.tgz#edc91ee032896788fa9a6f96c76f1f6dd7c9f538" + integrity sha512-ydtHzfGFO6Tyw4n7yOLUrdaNKmRdyaVfqNgObEbEgO/qobzxBV5zf8eNApTayy6SOji3NrF8PpJzm55OX/ChNA== dependencies: - "@sentry-internal/rrweb-snapshot" "2.35.0" + "@sentry-internal/rrweb-snapshot" "2.37.0" "@types/css-font-loading-module" "0.0.7" "@sentry-internal/rrweb@2.34.0": @@ -6900,14 +6900,14 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry-internal/rrweb@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.35.0.tgz#ae10b9aaf3ee379164ec52f1186ee053d369b0a3" - integrity sha512-Zy3bnzL9GY6SFTZ5x5YNxtkmIUiaLSppLA41xn6zc4UWSYI4DcA+M8OGxI4TiHkQVJhhjwBG1CevrLyrBxyEgA== +"@sentry-internal/rrweb@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.37.0.tgz#8ea0eb906e194060c60f4a6af4a0d3a16d52cdec" + integrity sha512-erN53M1WSPGpsMw+iVX6qWhI4id41+2AXcnALoB2JIyL/Q1W8f2loq/a4PxWkhaaq7mtGiHTmNTbks8Qgrsl9g== dependencies: - "@sentry-internal/rrdom" "2.35.0" - "@sentry-internal/rrweb-snapshot" "2.35.0" - "@sentry-internal/rrweb-types" "2.35.0" + "@sentry-internal/rrdom" "2.37.0" + "@sentry-internal/rrweb-snapshot" "2.37.0" + "@sentry-internal/rrweb-types" "2.37.0" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1" @@ -6915,7 +6915,7 @@ mitt "^3.0.0" "@sentry-internal/test-utils@link:dev-packages/test-utils": - version "10.5.0" + version "10.6.0" dependencies: express "^4.21.1" @@ -6924,6 +6924,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.0.tgz#6e7168f5fa59f53ac4b68e3f79c5fd54adc13f2e" integrity sha512-UkcnqC7Bp9ODyoBN7BKcRotd1jz/I2vyruE/qjNfRC7UnP+jIRItUWYaXxQPON1fTw+N+egKdByk0M1y2OPv/Q== +"@sentry/babel-plugin-component-annotate@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.1.1.tgz#371415afc602f6b2ba0987b51123bd34d1603193" + integrity sha512-HUpqrCK7zDVojTV6KL6BO9ZZiYrEYQqvYQrscyMsq04z+WCupXaH6YEliiNRvreR8DBJgdsG3lBRpebhUGmvfA== + "@sentry/bundler-plugin-core@4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.1.0.tgz#c1b2f7a890a44e5ac5decc984a133aacf6147dd4" @@ -6938,6 +6943,20 @@ magic-string "0.30.8" unplugin "1.0.1" +"@sentry/bundler-plugin-core@4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.1.1.tgz#7e273b83cc8b44f4067f05ab9ed5a7ec7ac6d625" + integrity sha512-Hx9RgXaD1HEYmL5aYoWwCKkVvPp4iklwfD9mvmdpQtcwLg6b6oLnPVDQaOry1ak6Pxt8smlrWcKy4IiKASlvig== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "4.1.1" + "@sentry/cli" "^2.51.0" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.30.8" + unplugin "1.0.1" + "@sentry/cli-darwin@2.52.0": version "2.52.0" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.52.0.tgz#05178cd819c2a33eb22a6e90bf7bb8f853f1b476" @@ -7014,12 +7033,12 @@ "@sentry/bundler-plugin-core" "4.1.0" unplugin "1.0.1" -"@sentry/webpack-plugin@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.1.0.tgz#e95e2dcd10e71dc8c3a16ba5cad9153f5e78c3bc" - integrity sha512-YqfDfyGAuT/9YW1kgAPfD7kGUKQCh1E5co+qMdToxi/Mz4xsWJY02rFS5GrJixYktYJfSMze8NiRr89yJMxYHw== +"@sentry/webpack-plugin@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.1.1.tgz#638c6b65cbc19b5027ffbb6bcd68094e0b0f82c6" + integrity sha512-2gFWcQMW1HdJDo/7rADeFs9crkH02l+mW4O1ORbxSjuegauyp1W8SBe7EfPoXbUmLdA3zwnpIxEXjjQpP5Etzg== dependencies: - "@sentry/bundler-plugin-core" "4.1.0" + "@sentry/bundler-plugin-core" "4.1.1" unplugin "1.0.1" uuid "^9.0.0" From 359610e55fa54dbe0eff7f93dbbfe2e3db81dcdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:58:40 +0200 Subject: [PATCH 09/14] feat(deps): bump @sentry/rollup-plugin from 4.1.0 to 4.1.1 (#17456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@sentry/rollup-plugin](https://github.com/getsentry/sentry-javascript-bundler-plugins) from 4.1.0 to 4.1.1.
Release notes

Sourced from @​sentry/rollup-plugin's releases.

4.1.1

  • fix(react-native): Enhance fragment detection for indirect references (#767)
Changelog

Sourced from @​sentry/rollup-plugin's changelog.

4.1.1

  • fix(react-native): Enhance fragment detection for indirect references (#767)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@sentry/rollup-plugin&package-manager=npm_and_yarn&previous-version=4.1.0&new-version=4.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/nuxt/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 79826e2e4e8c..ddd5de60197a 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -51,7 +51,7 @@ "@sentry/cloudflare": "10.6.0", "@sentry/core": "10.6.0", "@sentry/node": "10.6.0", - "@sentry/rollup-plugin": "^4.1.0", + "@sentry/rollup-plugin": "^4.1.1", "@sentry/vite-plugin": "^4.1.0", "@sentry/vue": "10.6.0" }, diff --git a/yarn.lock b/yarn.lock index cc472481a517..01ecd07bff69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7017,12 +7017,12 @@ "@sentry/cli-win32-i686" "2.52.0" "@sentry/cli-win32-x64" "2.52.0" -"@sentry/rollup-plugin@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-4.1.0.tgz#3948d067bd7cf8a61904b4042190dc9f6888bceb" - integrity sha512-HDwWgQRH7JhG15N1Y4XmPik/Qk03TGbiupDkZ8WL+8257BuyQE+s6feJJGCEUoWwROED+jvsFNvWvT2tqnILrw== +"@sentry/rollup-plugin@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-4.1.1.tgz#ece90c337d1f78a2a445d3986b63321877fd4e41" + integrity sha512-AAZ9OzR2gsJRxgKN2k5jB+MxT13Uj2GJeSofi0EHbgu/yUdod8zTGX+4NRB90aXZIEOAc0Xrwnw1sm8nZYvaFw== dependencies: - "@sentry/bundler-plugin-core" "4.1.0" + "@sentry/bundler-plugin-core" "4.1.1" unplugin "1.0.1" "@sentry/vite-plugin@^4.1.0": From 647dbfe3cfb19e62ffabbba1c88b1a7f661743b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 14:34:03 +0200 Subject: [PATCH 10/14] feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.12.0 to 0.13.0 (#17469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@opentelemetry/instrumentation-kafkajs](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-kafkajs) from 0.12.0 to 0.13.0.
Release notes

Sourced from @​opentelemetry/instrumentation-kafkajs's releases.

instrumentation-kafkajs: v0.13.0

0.13.0 (2025-08-13)

Features

  • kafkajs: instrument transaction send and sendBatch (#2939) (c0593e6)
Changelog

Sourced from @​opentelemetry/instrumentation-kafkajs's changelog.

0.13.0 (2025-08-13)

Features

  • kafkajs: instrument transaction send and sendBatch (#2939) (c0593e6)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@opentelemetry/instrumentation-kafkajs&package-manager=npm_and_yarn&previous-version=0.12.0&new-version=0.13.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/node/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index 045a88be7b0c..82a3475e62e9 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -79,7 +79,7 @@ "@opentelemetry/instrumentation-hapi": "0.50.0", "@opentelemetry/instrumentation-http": "0.203.0", "@opentelemetry/instrumentation-ioredis": "0.51.0", - "@opentelemetry/instrumentation-kafkajs": "0.12.0", + "@opentelemetry/instrumentation-kafkajs": "0.13.0", "@opentelemetry/instrumentation-knex": "0.48.0", "@opentelemetry/instrumentation-koa": "0.51.0", "@opentelemetry/instrumentation-lru-memoizer": "0.48.0", diff --git a/yarn.lock b/yarn.lock index 01ecd07bff69..0421ff01e101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5962,10 +5962,10 @@ "@opentelemetry/redis-common" "^0.38.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-kafkajs@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.12.0.tgz#231e6cc8a2a70d06162ed7e4ebe2ab5baa3a6670" - integrity sha512-bIe4aSAAxytp88nzBstgr6M7ZiEpW6/D1/SuKXdxxuprf18taVvFL2H5BDNGZ7A14K27haHqzYqtCTqFXHZOYg== +"@opentelemetry/instrumentation-kafkajs@0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.13.0.tgz#f959fecd0a9d53bed2fd662e41a5c155295ffbc8" + integrity sha512-FPQyJsREOaGH64hcxlzTsIEQC4DYANgTwHjiB7z9lldmvua1LRMVn3/FfBlzXoqF179B0VGYviz6rn75E9wsDw== dependencies: "@opentelemetry/instrumentation" "^0.203.0" "@opentelemetry/semantic-conventions" "^1.30.0" From 7303ab1cecfe90707eeb095ada5e099cbfc56964 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 27 Aug 2025 14:46:37 +0200 Subject: [PATCH 11/14] feat(aws): Add support for streaming handlers (#17463) closes #15774 --- .../Streaming/index.mjs | 8 + .../aws-serverless/tests/layer.test.ts | 48 +++++ .../instrumentation.ts | 194 +++++++++++++----- packages/aws-serverless/src/sdk.ts | 142 ++++++++++--- packages/aws-serverless/test/sdk.test.ts | 187 ++++++++++++++++- 5 files changed, 498 insertions(+), 81 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Streaming/index.mjs diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Streaming/index.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Streaming/index.mjs new file mode 100644 index 000000000000..d46b9df502b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/Streaming/index.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/aws-serverless'; + +export const handler = awslambda.streamifyResponse(async (event, responseStream, context) => { + Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => { + responseStream.write('Hello, world!'); + responseStream.end(); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts index c20659835ee8..4d68efb66b08 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts @@ -194,4 +194,52 @@ test.describe('Lambda layer', () => { }), ); }); + + test('streaming handlers work', async ({ lambdaClient }) => { + const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { + return transactionEvent?.transaction === 'LayerStreaming'; + }); + + await lambdaClient.send( + new InvokeCommand({ + FunctionName: 'LayerStreaming', + Payload: JSON.stringify({}), + }), + ); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.transaction).toEqual('LayerStreaming'); + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'sentry.origin': 'auto.otel.aws-lambda', + 'sentry.op': 'function.aws.lambda', + 'cloud.account.id': '012345678912', + 'faas.execution': expect.any(String), + 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerStreaming', + 'faas.coldstart': true, + 'otel.kind': 'SERVER', + }, + op: 'function.aws.lambda', + origin: 'auto.otel.aws-lambda', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }); + + expect(transactionEvent.spans).toHaveLength(1); + + expect(transactionEvent.spans).toContainEqual( + expect.objectContaining({ + data: expect.objectContaining({ + 'sentry.op': 'test', + 'sentry.origin': 'manual', + }), + description: 'manual-span', + op: 'test', + }), + ); + }); }); diff --git a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts index 39b63551b2aa..1e51605c2afa 100644 --- a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts +++ b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/instrumentation.ts @@ -3,6 +3,7 @@ // - Added Sentry `wrapHandler` around the OTel patch handler. // - Cancel init when handler string is invalid (TS) // - Hardcoded package version and name +// - Added support for streaming handlers /* eslint-disable */ /* * Copyright The OpenTelemetry Authors @@ -50,7 +51,7 @@ import { SEMRESATTRS_CLOUD_ACCOUNT_ID, SEMRESATTRS_FAAS_ID, } from '@opentelemetry/semantic-conventions'; -import type { APIGatewayProxyEventHeaders, Callback, Context, Handler } from 'aws-lambda'; +import type { APIGatewayProxyEventHeaders, Callback, Context, Handler, StreamifyHandler } from 'aws-lambda'; import * as fs from 'fs'; import * as path from 'path'; import type { LambdaModule } from './internal-types'; @@ -73,6 +74,9 @@ const headerGetter: TextMapGetter = { }; export const lambdaMaxInitInMilliseconds = 10_000; +const AWS_HANDLER_STREAMING_SYMBOL = Symbol.for('aws.lambda.runtime.handler.streaming'); +const AWS_HANDLER_HIGHWATERMARK_SYMBOL = Symbol.for('aws.lambda.runtime.handler.streaming.highWaterMark'); +const AWS_HANDLER_STREAMING_RESPONSE = 'response'; /** * @@ -101,6 +105,21 @@ export class AwsLambdaInstrumentation extends InstrumentationBase { + handler[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + if (typeof options?.highWaterMark === 'number') { + handler[AWS_HANDLER_HIGHWATERMARK_SYMBOL] = parseInt(options.highWaterMark); + } + return handler; + }, + }; + } + const handler = path.basename(handlerDef); const moduleRoot = handlerDef.substring(0, handlerDef.length - handler.length); @@ -187,16 +206,33 @@ export class AwsLambdaInstrumentation extends InstrumentationBase { - return wrapHandler(this._getPatchHandler(original, handlerLoadStartTime)); + private _getHandler(handlerLoadStartTime: number) { + return (original: T): T => { + if (this._isStreamingHandler(original)) { + const patchedHandler = this._getPatchHandler(original, handlerLoadStartTime); + + // Streaming handlers have special symbols that we need to copy over to the patched handler. + (patchedHandler as unknown as Record)[AWS_HANDLER_STREAMING_SYMBOL] = ( + original as unknown as Record + )[AWS_HANDLER_STREAMING_SYMBOL]; + (patchedHandler as unknown as Record)[AWS_HANDLER_HIGHWATERMARK_SYMBOL] = ( + original as unknown as Record + )[AWS_HANDLER_HIGHWATERMARK_SYMBOL]; + + return wrapHandler(patchedHandler) as T; + } + + return wrapHandler(this._getPatchHandler(original, handlerLoadStartTime)) as T; }; } + private _getPatchHandler(original: Handler, lambdaStartTime: number): Handler; + private _getPatchHandler(original: StreamifyHandler, lambdaStartTime: number): StreamifyHandler; + /** * */ - private _getPatchHandler(original: Handler, lambdaStartTime: number) { + private _getPatchHandler(original: Handler | StreamifyHandler, lambdaStartTime: number): Handler | StreamifyHandler { diag.debug('patch handler function'); const plugin = this; @@ -229,6 +265,36 @@ export class AwsLambdaInstrumentation extends InstrumentationBase[1], + context: Context, + ) { + _onRequest(); + const parent = plugin._determineParent(event, context); + const span = plugin._createSpanForRequest(event, context, requestIsColdStart, parent); + plugin._applyRequestHook(span, event, context); + + return otelContext.with(trace.setSpan(parent, span), () => { + const maybePromise = safeExecuteInTheMiddle( + () => original.apply(this, [event, responseStream, context]), + error => { + if (error != null) { + // Exception thrown synchronously before resolving promise. + plugin._applyResponseHook(span, error); + plugin._endSpan(span, error, () => {}); + } + }, + ) as Promise<{}> | undefined; + + return plugin._handlePromiseResult(span, maybePromise); + }); + }; + } + return function patchedHandler( this: never, // The event can be a user type, it truly is any. @@ -239,39 +305,10 @@ export class AwsLambdaInstrumentation extends InstrumentationBase requestHook(span, { event, context }), - e => { - if (e) diag.error('aws-lambda instrumentation: requestHook error', e); - }, - true, - ); - } + const span = plugin._createSpanForRequest(event, context, requestIsColdStart, parent); + plugin._applyRequestHook(span, event, context); return otelContext.with(trace.setSpan(parent, span), () => { // Lambda seems to pass a callback even if handler is of Promise form, so we wrap all the time before calling @@ -289,23 +326,80 @@ export class AwsLambdaInstrumentation extends InstrumentationBase | undefined; - if (typeof maybePromise?.then === 'function') { - return maybePromise.then( - value => { - plugin._applyResponseHook(span, null, value); - return new Promise(resolve => plugin._endSpan(span, undefined, () => resolve(value))); - }, - (err: Error | string) => { - plugin._applyResponseHook(span, err); - return new Promise((resolve, reject) => plugin._endSpan(span, err, () => reject(err))); - }, - ); - } - return maybePromise; + + return plugin._handlePromiseResult(span, maybePromise); }); }; } + private _createSpanForRequest(event: any, context: Context, requestIsColdStart: boolean, parent: OtelContext): Span { + const name = context.functionName; + return this.tracer.startSpan( + name, + { + kind: SpanKind.SERVER, + attributes: { + [SEMATTRS_FAAS_EXECUTION]: context.awsRequestId, + [SEMRESATTRS_FAAS_ID]: context.invokedFunctionArn, + [SEMRESATTRS_CLOUD_ACCOUNT_ID]: AwsLambdaInstrumentation._extractAccountId(context.invokedFunctionArn), + [ATTR_FAAS_COLDSTART]: requestIsColdStart, + ...AwsLambdaInstrumentation._extractOtherEventFields(event), + }, + }, + parent, + ); + } + + private _applyRequestHook(span: Span, event: any, context: Context): void { + const { requestHook } = this.getConfig(); + if (requestHook) { + safeExecuteInTheMiddle( + () => requestHook(span, { event, context }), + e => { + if (e) diag.error('aws-lambda instrumentation: requestHook error', e); + }, + true, + ); + } + } + + private _handlePromiseResult(span: Span, maybePromise: Promise<{}> | undefined): Promise<{}> | undefined { + if (typeof maybePromise?.then === 'function') { + return maybePromise.then( + value => { + this._applyResponseHook(span, null, value); + return new Promise(resolve => this._endSpan(span, undefined, () => resolve(value))); + }, + (err: Error | string) => { + this._applyResponseHook(span, err); + return new Promise((resolve, reject) => this._endSpan(span, err, () => reject(err))); + }, + ); + } + + // Handle synchronous return values by ending the span and applying response hook + this._applyResponseHook(span, null, maybePromise); + this._endSpan(span, undefined, () => {}); + return maybePromise; + } + + private _determineParent(event: any, context: Context): OtelContext { + const config = this.getConfig(); + return AwsLambdaInstrumentation._determineParent( + event, + context, + config.eventContextExtractor || AwsLambdaInstrumentation._defaultEventContextExtractor, + ); + } + + private _isStreamingHandler( + handler: Handler | StreamifyHandler, + ): handler is StreamifyHandler { + return ( + (handler as unknown as Record)[AWS_HANDLER_STREAMING_SYMBOL] === AWS_HANDLER_STREAMING_RESPONSE + ); + } + /** * */ diff --git a/packages/aws-serverless/src/sdk.ts b/packages/aws-serverless/src/sdk.ts index e6f7d5f3a4f0..fd647d3a3376 100644 --- a/packages/aws-serverless/src/sdk.ts +++ b/packages/aws-serverless/src/sdk.ts @@ -1,7 +1,7 @@ import type { Scope } from '@sentry/core'; import { consoleSandbox, debug } from '@sentry/core'; import { captureException, captureMessage, flush, getCurrentScope, withScope } from '@sentry/node'; -import type { Context, Handler } from 'aws-lambda'; +import type { Context, Handler, StreamifyHandler } from 'aws-lambda'; import { performance } from 'perf_hooks'; import { types } from 'util'; import { DEBUG_BUILD } from './debug-build'; @@ -108,6 +108,51 @@ function enhanceScopeWithEnvironmentData(scope: Scope, context: Context, startTi }); } +function setupTimeoutWatning(context: Context, options: WrapperOptions): NodeJS.Timeout | undefined { + // In seconds. You cannot go any more granular than this in AWS Lambda. + const configuredTimeout = Math.ceil(tryGetRemainingTimeInMillis(context) / 1000); + const configuredTimeoutMinutes = Math.floor(configuredTimeout / 60); + const configuredTimeoutSeconds = configuredTimeout % 60; + + const humanReadableTimeout = + configuredTimeoutMinutes > 0 + ? `${configuredTimeoutMinutes}m${configuredTimeoutSeconds}s` + : `${configuredTimeoutSeconds}s`; + + if (options.captureTimeoutWarning) { + const timeoutWarningDelay = tryGetRemainingTimeInMillis(context) - options.timeoutWarningLimit; + + return setTimeout(() => { + withScope(scope => { + scope.setTag('timeout', humanReadableTimeout); + captureMessage(`Possible function timeout: ${context.functionName}`, 'warning'); + }); + }, timeoutWarningDelay) as unknown as NodeJS.Timeout; + } + + return undefined; +} + +export const AWS_HANDLER_HIGHWATERMARK_SYMBOL = Symbol.for('aws.lambda.runtime.handler.streaming.highWaterMark'); +export const AWS_HANDLER_STREAMING_SYMBOL = Symbol.for('aws.lambda.runtime.handler.streaming'); +export const AWS_HANDLER_STREAMING_RESPONSE = 'response'; + +function isStreamingHandler(handler: Handler | StreamifyHandler): handler is StreamifyHandler { + return ( + (handler as unknown as Record)[AWS_HANDLER_STREAMING_SYMBOL] === AWS_HANDLER_STREAMING_RESPONSE + ); +} + +export function wrapHandler( + handler: Handler, + wrapOptions?: Partial, +): Handler; + +export function wrapHandler( + handler: StreamifyHandler, + wrapOptions?: Partial, +): StreamifyHandler; + /** * Wraps a lambda handler adding it error capture and tracing capabilities. * @@ -116,9 +161,9 @@ function enhanceScopeWithEnvironmentData(scope: Scope, context: Context, startTi * @returns Handler */ export function wrapHandler( - handler: Handler, + handler: Handler | StreamifyHandler, wrapOptions: Partial = {}, -): Handler { +): Handler | StreamifyHandler { const START_TIME = performance.now(); // eslint-disable-next-line deprecation/deprecation @@ -141,18 +186,22 @@ export function wrapHandler( ...wrapOptions, }; - let timeoutWarningTimer: NodeJS.Timeout; + if (isStreamingHandler(handler)) { + return wrapStreamingHandler(handler, options, START_TIME); + } + + let timeoutWarningTimer: NodeJS.Timeout | undefined; // AWSLambda is like Express. It makes a distinction about handlers based on its last argument // async (event) => async handler // async (event, context) => async handler // (event, context, callback) => sync handler // Nevertheless whatever option is chosen by user, we convert it to async handler. - const asyncHandler: AsyncHandler = + const asyncHandler: AsyncHandler> = handler.length > 2 ? (event, context) => new Promise((resolve, reject) => { - const rv = (handler as SyncHandler)(event, context, (error, result) => { + const rv = (handler as SyncHandler>)(event, context, (error, result) => { if (error === null || error === undefined) { resolve(result!); // eslint-disable-line @typescript-eslint/no-non-null-assertion } else { @@ -166,33 +215,12 @@ export function wrapHandler( void (rv as Promise>).then(resolve, reject); } }) - : (handler as AsyncHandler); + : (handler as AsyncHandler>); - return async (event, context) => { + return async (event: TEvent, context: Context) => { context.callbackWaitsForEmptyEventLoop = options.callbackWaitsForEmptyEventLoop; - // In seconds. You cannot go any more granular than this in AWS Lambda. - const configuredTimeout = Math.ceil(tryGetRemainingTimeInMillis(context) / 1000); - const configuredTimeoutMinutes = Math.floor(configuredTimeout / 60); - const configuredTimeoutSeconds = configuredTimeout % 60; - - const humanReadableTimeout = - configuredTimeoutMinutes > 0 - ? `${configuredTimeoutMinutes}m${configuredTimeoutSeconds}s` - : `${configuredTimeoutSeconds}s`; - - // When `callbackWaitsForEmptyEventLoop` is set to false, which it should when using `captureTimeoutWarning`, - // we don't have a guarantee that this message will be delivered. Because of that, we don't flush it. - if (options.captureTimeoutWarning) { - const timeoutWarningDelay = tryGetRemainingTimeInMillis(context) - options.timeoutWarningLimit; - - timeoutWarningTimer = setTimeout(() => { - withScope(scope => { - scope.setTag('timeout', humanReadableTimeout); - captureMessage(`Possible function timeout: ${context.functionName}`, 'warning'); - }); - }, timeoutWarningDelay) as unknown as NodeJS.Timeout; - } + timeoutWarningTimer = setupTimeoutWatning(context, options); async function processResult(): Promise { const scope = getCurrentScope(); @@ -229,3 +257,57 @@ export function wrapHandler( }); }; } + +function wrapStreamingHandler( + handler: StreamifyHandler, + options: WrapperOptions, + startTime: number, +): StreamifyHandler { + let timeoutWarningTimer: NodeJS.Timeout | undefined; + + const wrappedHandler = async ( + event: TEvent, + responseStream: Parameters>[1], + context: Context, + ): Promise => { + context.callbackWaitsForEmptyEventLoop = options.callbackWaitsForEmptyEventLoop; + + timeoutWarningTimer = setupTimeoutWatning(context, options); + + async function processStreamingResult(): Promise { + const scope = getCurrentScope(); + + try { + enhanceScopeWithEnvironmentData(scope, context, startTime); + + responseStream.on('error', error => { + captureException(error, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.stream')); + }); + + return await handler(event, responseStream, context); + } catch (e) { + // Errors should already captured in the instrumentation's `responseHook`, + // we capture them here just to be safe. Double captures are deduplicated by the SDK. + captureException(e, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.handler')); + throw e; + } finally { + if (timeoutWarningTimer) { + clearTimeout(timeoutWarningTimer); + } + await flush(options.flushTimeout).catch(e => { + DEBUG_BUILD && debug.error(e); + }); + } + } + + return withScope(() => processStreamingResult()); + }; + + const handlerWithSymbols = handler as unknown as Record; + (wrappedHandler as unknown as Record)[AWS_HANDLER_STREAMING_SYMBOL] = + handlerWithSymbols[AWS_HANDLER_STREAMING_SYMBOL]; + (wrappedHandler as unknown as Record)[AWS_HANDLER_HIGHWATERMARK_SYMBOL] = + handlerWithSymbols[AWS_HANDLER_HIGHWATERMARK_SYMBOL]; + + return wrappedHandler; +} diff --git a/packages/aws-serverless/test/sdk.test.ts b/packages/aws-serverless/test/sdk.test.ts index 58bb04a234b9..3bf2c42b8fd2 100644 --- a/packages/aws-serverless/test/sdk.test.ts +++ b/packages/aws-serverless/test/sdk.test.ts @@ -2,7 +2,7 @@ import type { Event } from '@sentry/core'; import type { Callback, Handler } from 'aws-lambda'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { init } from '../src/init'; -import { wrapHandler } from '../src/sdk'; +import { AWS_HANDLER_STREAMING_RESPONSE, AWS_HANDLER_STREAMING_SYMBOL, wrapHandler } from '../src/sdk'; const mockFlush = vi.fn((...args) => Promise.resolve(args)); const mockWithScope = vi.fn(); @@ -368,6 +368,191 @@ describe('AWSLambda', () => { }); }); + describe('wrapHandler() on streaming handlers', () => { + // Mock response stream with common stream interface + const mockResponseStream = { + write: vi.fn(), + end: vi.fn(), + destroy: vi.fn(), + on: vi.fn(), + setContentType: vi.fn(), + writable: true, + writableEnded: false, + writableFinished: false, + }; + + beforeEach(() => { + vi.clearAllMocks(); + mockResponseStream.write.mockClear(); + mockResponseStream.end.mockClear(); + mockResponseStream.destroy.mockClear(); + mockResponseStream.on.mockClear(); + }); + + test('successful execution', async () => { + expect.assertions(5); + + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + return 42; + }); + // Add the streaming symbol to mark it as a streaming handler + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + const rv = await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + + expect(rv).toStrictEqual(42); + expectScopeSettings(); + expect(streamingHandler).toHaveBeenCalledWith(fakeEvent, mockResponseStream, fakeContext); + expect(mockFlush).toBeCalledWith(2000); + }); + + test('preserves streaming symbol on wrapped handler', () => { + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + return 42; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + + expect((wrappedHandler as any)[AWS_HANDLER_STREAMING_SYMBOL]).toBe(AWS_HANDLER_STREAMING_RESPONSE); + }); + + test('event, responseStream and context are correctly passed along', async () => { + expect.assertions(3); + + const streamingHandler = vi.fn(async (event, responseStream, context) => { + expect(event).toHaveProperty('fortySix'); + expect(responseStream).toBe(mockResponseStream); + expect(context).toHaveProperty('ytho'); + return 'success'; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + }); + + test('capture error from handler execution', async () => { + expect.assertions(4); + + const error = new Error('streaming handler error'); + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + throw error; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + + try { + await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + } catch { + expectScopeSettings(); + expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); + expect(mockFlush).toBeCalled(); + } + }); + + test('capture stream errors', async () => { + expect.assertions(3); + + const streamError = new Error('stream error'); + const streamingHandler = vi.fn(async (_event, responseStream, _context) => { + // Simulate stream error by calling the error listener + const errorListener = (responseStream.on as any).mock.calls.find((call: any[]) => call[0] === 'error')?.[1]; + if (errorListener) { + errorListener(streamError); + } + return 'success'; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + + expect(mockResponseStream.on).toHaveBeenCalledWith('error', expect.any(Function)); + expect(mockCaptureException).toHaveBeenCalledWith(streamError, expect.any(Function)); + expect(streamingHandler).toHaveBeenCalledWith(fakeEvent, mockResponseStream, fakeContext); + }); + + test('streaming handler with flushTimeout option', async () => { + expect.assertions(2); + + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + return 'flushed'; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler, { flushTimeout: 5000 }); + const result = await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + + expect(result).toBe('flushed'); + expect(mockFlush).toBeCalledWith(5000); + }); + + test('streaming handler with captureTimeoutWarning enabled', async () => { + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + // Simulate some delay to trigger timeout warning + await new Promise(resolve => setTimeout(resolve, DEFAULT_EXECUTION_TIME)); + return 'completed'; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + + expect(mockWithScope).toBeCalledTimes(2); + expect(mockCaptureMessage).toBeCalled(); + expect(mockScope.setTag).toBeCalledWith('timeout', '1s'); + }); + + test('marks streaming handler captured errors as unhandled', async () => { + expect.assertions(3); + + const error = new Error('streaming error'); + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + throw error; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + + try { + await (wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext); + } catch { + expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); + + const scopeFunction = mockCaptureException.mock.calls[0]?.[1]; + const event: Event = { exception: { values: [{}] } }; + let evtProcessor: ((e: Event) => Event) | undefined = undefined; + if (scopeFunction) { + scopeFunction({ addEventProcessor: vi.fn().mockImplementation(proc => (evtProcessor = proc)) }); + } + + expect(evtProcessor).toBeInstanceOf(Function); + // @ts-expect-error just mocking around... + expect(evtProcessor!(event).exception.values[0]?.mechanism).toEqual({ + handled: false, + type: 'auto.function.aws-serverless.handler', + }); + } + }); + + test('should not throw when flush rejects with streaming handler', async () => { + const streamingHandler = vi.fn(async (_event, _responseStream, _context) => { + return 'flush-error-test'; + }); + (streamingHandler as any)[AWS_HANDLER_STREAMING_SYMBOL] = AWS_HANDLER_STREAMING_RESPONSE; + + const wrappedHandler = wrapHandler(streamingHandler); + mockFlush.mockImplementationOnce(() => Promise.reject(new Error('flush failed'))); + + await expect((wrappedHandler as any)(fakeEvent, mockResponseStream, fakeContext)).resolves.toBe( + 'flush-error-test', + ); + }); + }); + test('marks the captured error as unhandled', async () => { expect.assertions(3); From 6874fbe87e2562dc199d57b4c6553f8d0d29377b Mon Sep 17 00:00:00 2001 From: Rola Abuhasna Date: Wed, 27 Aug 2025 16:33:42 +0200 Subject: [PATCH 12/14] feat(core): Stream responses Anthropic AI (#17460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds support for streamed Anthropic Messages API responses in the Core AI instrumentation. It detects streaming via `messages.create({…, stream: true})` and `messages.stream(...),` and marks spans with stream related attributes. It also aggregates token usage from stream events and records streamed text when PII capture is enabled. --- .../tracing/anthropic/scenario-stream.mjs | 105 +++++++++ .../suites/tracing/anthropic/test.ts | 75 +++++++ .../core/src/utils/anthropic-ai/constants.ts | 1 + packages/core/src/utils/anthropic-ai/index.ts | 48 ++++- .../core/src/utils/anthropic-ai/streaming.ts | 202 ++++++++++++++++++ packages/core/src/utils/anthropic-ai/types.ts | 41 ++++ 6 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs create mode 100644 packages/core/src/utils/anthropic-ai/streaming.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs new file mode 100644 index 000000000000..da70a2b12467 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/scenario-stream.mjs @@ -0,0 +1,105 @@ +import { instrumentAnthropicAiClient } from '@sentry/core'; +import * as Sentry from '@sentry/node'; + +function createMockStreamEvents(model = 'claude-3-haiku-20240307') { + async function* generator() { + // Provide message metadata early so the span can capture id/model/usage input tokens + yield { + type: 'content_block_start', + message: { + id: 'msg_stream_1', + type: 'message', + role: 'assistant', + model, + content: [], + stop_reason: 'end_turn', + stop_sequence: null, + usage: { + input_tokens: 10, + }, + }, + }; + + // Streamed text chunks + yield { type: 'content_block_delta', delta: { text: 'Hello ' } }; + yield { type: 'content_block_delta', delta: { text: 'from ' } }; + yield { type: 'content_block_delta', delta: { text: 'stream!' } }; + + // Final usage totals for output tokens + yield { type: 'message_delta', usage: { output_tokens: 15 } }; + } + + return generator(); +} + +class MockAnthropic { + constructor(config) { + this.apiKey = config.apiKey; + + this.messages = { + create: this._messagesCreate.bind(this), + stream: this._messagesStream.bind(this), + }; + } + + async _messagesCreate(params) { + await new Promise(resolve => setTimeout(resolve, 5)); + if (params?.stream === true) { + return createMockStreamEvents(params.model); + } + // Fallback non-streaming behavior (not used in this scenario) + return { + id: 'msg_mock123', + type: 'message', + model: params.model, + role: 'assistant', + content: [ + { + type: 'text', + text: 'Hello from Anthropic mock!', + }, + ], + stop_reason: 'end_turn', + stop_sequence: null, + usage: { + input_tokens: 10, + output_tokens: 15, + }, + }; + } + + async _messagesStream(params) { + await new Promise(resolve => setTimeout(resolve, 5)); + return createMockStreamEvents(params?.model); + } +} + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const mockClient = new MockAnthropic({ apiKey: 'mock-api-key' }); + const client = instrumentAnthropicAiClient(mockClient); + + // 1) Streaming via stream: true param on messages.create + const stream1 = await client.messages.create({ + model: 'claude-3-haiku-20240307', + messages: [{ role: 'user', content: 'Stream this please' }], + stream: true, + }); + for await (const _ of stream1) { + void _; + } + + // 2) Streaming via messages.stream API + const stream2 = await client.messages.stream({ + model: 'claude-3-haiku-20240307', + messages: [{ role: 'user', content: 'Stream this too' }], + }); + for await (const _ of stream2) { + void _; + } + }); +} + +run(); + + diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts index 4b7d19b7cc58..9b8c7219000d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts @@ -218,4 +218,79 @@ describe('Anthropic integration', () => { .completed(); }); }); + + const EXPECTED_STREAM_SPANS_PII_FALSE = { + transaction: 'main', + spans: expect.arrayContaining([ + // messages.create with stream: true + expect.objectContaining({ + description: 'messages claude-3-haiku-20240307 stream-response', + op: 'gen_ai.messages', + data: expect.objectContaining({ + 'gen_ai.system': 'anthropic', + 'gen_ai.operation.name': 'messages', + 'gen_ai.request.model': 'claude-3-haiku-20240307', + 'gen_ai.request.stream': true, + 'gen_ai.response.streaming': true, + 'gen_ai.response.model': 'claude-3-haiku-20240307', + 'gen_ai.response.id': 'msg_stream_1', + 'gen_ai.usage.input_tokens': 10, + 'gen_ai.usage.output_tokens': 15, + 'gen_ai.usage.total_tokens': 25, + 'gen_ai.response.finish_reasons': '["end_turn"]', + }), + }), + // messages.stream + expect.objectContaining({ + description: 'messages claude-3-haiku-20240307 stream-response', + op: 'gen_ai.messages', + data: expect.objectContaining({ + 'gen_ai.system': 'anthropic', + 'gen_ai.operation.name': 'messages', + 'gen_ai.request.model': 'claude-3-haiku-20240307', + 'gen_ai.response.streaming': true, + 'gen_ai.response.model': 'claude-3-haiku-20240307', + 'gen_ai.response.id': 'msg_stream_1', + 'gen_ai.usage.input_tokens': 10, + 'gen_ai.usage.output_tokens': 15, + 'gen_ai.usage.total_tokens': 25, + }), + }), + ]), + }; + + const EXPECTED_STREAM_SPANS_PII_TRUE = { + transaction: 'main', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'messages claude-3-haiku-20240307 stream-response', + op: 'gen_ai.messages', + data: expect.objectContaining({ + 'gen_ai.response.streaming': true, + // streamed text concatenated + 'gen_ai.response.text': 'Hello from stream!', + }), + }), + expect.objectContaining({ + description: 'messages claude-3-haiku-20240307 stream-response', + op: 'gen_ai.messages', + data: expect.objectContaining({ + 'gen_ai.response.streaming': true, + 'gen_ai.response.text': 'Hello from stream!', + }), + }), + ]), + }; + + createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument.mjs', (createRunner, test) => { + test('streams produce spans with token usage and metadata (PII false)', async () => { + await createRunner().ignore('event').expect({ transaction: EXPECTED_STREAM_SPANS_PII_FALSE }).start().completed(); + }); + }); + + createEsmAndCjsTests(__dirname, 'scenario-stream.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { + test('streams record response text when PII true', async () => { + await createRunner().ignore('event').expect({ transaction: EXPECTED_STREAM_SPANS_PII_TRUE }).start().completed(); + }); + }); }); diff --git a/packages/core/src/utils/anthropic-ai/constants.ts b/packages/core/src/utils/anthropic-ai/constants.ts index 41a227f171e0..1e20745e0f1f 100644 --- a/packages/core/src/utils/anthropic-ai/constants.ts +++ b/packages/core/src/utils/anthropic-ai/constants.ts @@ -4,6 +4,7 @@ export const ANTHROPIC_AI_INTEGRATION_NAME = 'Anthropic_AI'; // https://docs.anthropic.com/en/api/models-list export const ANTHROPIC_AI_INSTRUMENTED_METHODS = [ 'messages.create', + 'messages.stream', 'messages.countTokens', 'models.get', 'completions.create', diff --git a/packages/core/src/utils/anthropic-ai/index.ts b/packages/core/src/utils/anthropic-ai/index.ts index 8d56b2a56c04..2ed95be76843 100644 --- a/packages/core/src/utils/anthropic-ai/index.ts +++ b/packages/core/src/utils/anthropic-ai/index.ts @@ -1,7 +1,8 @@ import { getCurrentScope } from '../../currentScopes'; import { captureException } from '../../exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes'; -import { startSpan } from '../../tracing/trace'; +import { SPAN_STATUS_ERROR } from '../../tracing'; +import { startSpan, startSpanManual } from '../../tracing/trace'; import type { Span, SpanAttributeValue } from '../../types-hoist/span'; import { ANTHROPIC_AI_RESPONSE_TIMESTAMP_ATTRIBUTE, @@ -22,14 +23,17 @@ import { } from '../ai/gen-ai-attributes'; import { buildMethodPath, getFinalOperationName, getSpanOperation, setTokenUsageAttributes } from '../ai/utils'; import { ANTHROPIC_AI_INTEGRATION_NAME } from './constants'; +import { instrumentStream } from './streaming'; import type { AnthropicAiClient, AnthropicAiInstrumentedMethod, AnthropicAiIntegration, AnthropicAiOptions, AnthropicAiResponse, + AnthropicAiStreamingEvent, } from './types'; import { shouldInstrument } from './utils'; + /** * Extract request attributes from method arguments */ @@ -168,7 +172,47 @@ function instrumentMethod( const model = requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] ?? 'unknown'; const operationName = getFinalOperationName(methodPath); - // TODO: Handle streaming responses + const params = typeof args[0] === 'object' ? (args[0] as Record) : undefined; + const isStreamRequested = Boolean(params?.stream); + const isStreamingMethod = methodPath === 'messages.stream'; + + if (isStreamRequested || isStreamingMethod) { + return startSpanManual( + { + name: `${operationName} ${model} stream-response`, + op: getSpanOperation(methodPath), + attributes: requestAttributes as Record, + }, + async (span: Span) => { + try { + if (finalOptions.recordInputs && params) { + addPrivateRequestAttributes(span, params); + } + + const result = await originalMethod.apply(context, args); + return instrumentStream( + result as AsyncIterable, + span, + finalOptions.recordOutputs ?? false, + ) as unknown as R; + } catch (error) { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(error, { + mechanism: { + handled: false, + type: 'auto.ai.anthropic', + data: { + function: methodPath, + }, + }, + }); + span.end(); + throw error; + } + }, + ); + } + return startSpan( { name: `${operationName} ${model}`, diff --git a/packages/core/src/utils/anthropic-ai/streaming.ts b/packages/core/src/utils/anthropic-ai/streaming.ts new file mode 100644 index 000000000000..8ebbfc0b42cd --- /dev/null +++ b/packages/core/src/utils/anthropic-ai/streaming.ts @@ -0,0 +1,202 @@ +import { captureException } from '../../exports'; +import { SPAN_STATUS_ERROR } from '../../tracing'; +import type { Span } from '../../types-hoist/span'; +import { + GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE, + GEN_AI_RESPONSE_ID_ATTRIBUTE, + GEN_AI_RESPONSE_MODEL_ATTRIBUTE, + GEN_AI_RESPONSE_STREAMING_ATTRIBUTE, + GEN_AI_RESPONSE_TEXT_ATTRIBUTE, +} from '../ai/gen-ai-attributes'; +import { setTokenUsageAttributes } from '../ai/utils'; +import type { AnthropicAiStreamingEvent } from './types'; + +/** + * State object used to accumulate information from a stream of Anthropic AI events. + */ + +interface StreamingState { + /** Collected response text fragments (for output recording). */ + responseTexts: string[]; + /** Reasons for finishing the response, as reported by the API. */ + finishReasons: string[]; + /** The response ID. */ + responseId: string; + /** The model name. */ + responseModel: string; + /** Number of prompt/input tokens used. */ + promptTokens: number | undefined; + /** Number of completion/output tokens used. */ + completionTokens: number | undefined; + /** Number of cache creation input tokens used. */ + cacheCreationInputTokens: number | undefined; + /** Number of cache read input tokens used. */ + cacheReadInputTokens: number | undefined; +} + +/** + * Checks if an event is an error event + * @param event - The event to process + * @param state - The state of the streaming process + * @param recordOutputs - Whether to record outputs + * @param span - The span to update + * @returns Whether an error occurred + */ + +function isErrorEvent( + event: AnthropicAiStreamingEvent, + state: StreamingState, + recordOutputs: boolean, + span: Span, +): boolean { + if ('type' in event && typeof event.type === 'string') { + // If the event is an error, set the span status and capture the error + // These error events are not rejected by the API by default, but are sent as metadata of the response + if (event.type === 'error') { + const message = event.error?.message ?? 'internal_error'; + span.setStatus({ code: SPAN_STATUS_ERROR, message }); + captureException(new Error(`anthropic_stream_error: ${message}`), { + mechanism: { + handled: false, + type: 'auto.ai.anthropic', + data: { + function: 'anthropic_stream_error', + }, + }, + data: { + function: 'anthropic_stream_error', + }, + }); + return true; + } + + if (recordOutputs && event.type === 'content_block_delta') { + const text = event.delta?.text; + if (text) state.responseTexts.push(text); + } + } + return false; +} + +/** + * Processes the message metadata of an event + * @param event - The event to process + * @param state - The state of the streaming process + */ + +function handleMessageMetadata(event: AnthropicAiStreamingEvent, state: StreamingState): void { + // The token counts shown in the usage field of the message_delta event are cumulative. + // @see https://docs.anthropic.com/en/docs/build-with-claude/streaming#event-types + if (event.type === 'message_delta' && event.usage) { + if ('output_tokens' in event.usage && typeof event.usage.output_tokens === 'number') { + state.completionTokens = event.usage.output_tokens; + } + } + + if (event.message) { + const message = event.message; + + if (message.id) state.responseId = message.id; + if (message.model) state.responseModel = message.model; + if (message.stop_reason) state.finishReasons.push(message.stop_reason); + + if (message.usage) { + if (typeof message.usage.input_tokens === 'number') state.promptTokens = message.usage.input_tokens; + if (typeof message.usage.cache_creation_input_tokens === 'number') + state.cacheCreationInputTokens = message.usage.cache_creation_input_tokens; + if (typeof message.usage.cache_read_input_tokens === 'number') + state.cacheReadInputTokens = message.usage.cache_read_input_tokens; + } + } +} + +/** + * Processes an event + * @param event - The event to process + * @param state - The state of the streaming process + * @param recordOutputs - Whether to record outputs + * @param span - The span to update + */ + +function processEvent( + event: AnthropicAiStreamingEvent, + state: StreamingState, + recordOutputs: boolean, + span: Span, +): void { + if (!(event && typeof event === 'object')) { + return; + } + + const isError = isErrorEvent(event, state, recordOutputs, span); + if (isError) return; + + handleMessageMetadata(event, state); +} + +/** + * Instruments an async iterable stream of Anthropic events, updates the span with + * streaming attributes and (optionally) the aggregated output text, and yields + * each event from the input stream unchanged. + */ +export async function* instrumentStream( + stream: AsyncIterable, + span: Span, + recordOutputs: boolean, +): AsyncGenerator { + const state: StreamingState = { + responseTexts: [], + finishReasons: [], + responseId: '', + responseModel: '', + promptTokens: undefined, + completionTokens: undefined, + cacheCreationInputTokens: undefined, + cacheReadInputTokens: undefined, + }; + + try { + for await (const event of stream) { + processEvent(event, state, recordOutputs, span); + yield event; + } + } finally { + // Set common response attributes if available + if (state.responseId) { + span.setAttributes({ + [GEN_AI_RESPONSE_ID_ATTRIBUTE]: state.responseId, + }); + } + if (state.responseModel) { + span.setAttributes({ + [GEN_AI_RESPONSE_MODEL_ATTRIBUTE]: state.responseModel, + }); + } + + setTokenUsageAttributes( + span, + state.promptTokens, + state.completionTokens, + state.cacheCreationInputTokens, + state.cacheReadInputTokens, + ); + + span.setAttributes({ + [GEN_AI_RESPONSE_STREAMING_ATTRIBUTE]: true, + }); + + if (state.finishReasons.length > 0) { + span.setAttributes({ + [GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]: JSON.stringify(state.finishReasons), + }); + } + + if (recordOutputs && state.responseTexts.length > 0) { + span.setAttributes({ + [GEN_AI_RESPONSE_TEXT_ATTRIBUTE]: state.responseTexts.join(''), + }); + } + + span.end(); + } +} diff --git a/packages/core/src/utils/anthropic-ai/types.ts b/packages/core/src/utils/anthropic-ai/types.ts index 566e9588d56f..fd533b6795bc 100644 --- a/packages/core/src/utils/anthropic-ai/types.ts +++ b/packages/core/src/utils/anthropic-ai/types.ts @@ -61,3 +61,44 @@ export interface AnthropicAiIntegration { } export type AnthropicAiInstrumentedMethod = (typeof ANTHROPIC_AI_INSTRUMENTED_METHODS)[number]; + +/** + * Message type for Anthropic AI + */ +export type AnthropicAiMessage = { + id: string; + type: 'message'; + role: string; + model: string; + content: unknown[]; + stop_reason: string | null; + stop_sequence: number | null; + usage?: { + input_tokens: number; + cache_creation_input_tokens?: number; + cache_read_input_tokens?: number; + cache_creation?: unknown; + output_tokens?: number; // Not final; do not treat as total. Use `message_delta.usage.output_tokens` for the final total. + service_tier?: string; + }; +}; + +/** + * Streaming event type for Anthropic AI + */ +export type AnthropicAiStreamingEvent = { + type: 'message_delta' | 'content_block_start' | 'content_block_delta' | 'content_block_stop' | 'error'; + error?: { + type: string; + message: string; + }; + index?: number; + delta?: { + type: unknown; + text?: string; + }; + usage?: { + output_tokens: number; // Final total output tokens; emitted on the last `message_delta` event + }; + message?: AnthropicAiMessage; +}; From dfc411b48f0abe3652509f7b3acbe699ae94108c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:47:41 +0200 Subject: [PATCH 13/14] feat(deps): bump @opentelemetry/instrumentation-dataloader from 0.21.0 to 0.21.1 (#17457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@opentelemetry/instrumentation-dataloader](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-dataloader) from 0.21.0 to 0.21.1.
Release notes

Sourced from @​opentelemetry/instrumentation-dataloader's releases.

instrumentation-dataloader: v0.21.1

0.21.1 (2025-08-13)

Bug Fixes

  • instrumentation-dataloader: support ESM imports of dataloader module (#2973) (16979f6)
Changelog

Sourced from @​opentelemetry/instrumentation-dataloader's changelog.

0.21.1 (2025-08-13)

Bug Fixes

  • instrumentation-dataloader: support ESM imports of dataloader module (#2973) (16979f6)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@opentelemetry/instrumentation-dataloader&package-manager=npm_and_yarn&previous-version=0.21.0&new-version=0.21.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrei Borza --- .../suites/tracing/dataloader/test.ts | 20 +++++++------------ packages/node/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts b/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts index 1a653dc6496a..5bfb6ff72a39 100644 --- a/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/dataloader/test.ts @@ -32,17 +32,11 @@ describe('dataloader auto-instrumentation', () => { ]), }; - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument.mjs', - (createRunner, test) => { - test('should auto-instrument `dataloader` package.', async () => { - const runner = createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start(); - runner.makeRequest('get', '/'); - await runner.completed(); - }); - }, - { failsOnEsm: true }, - ); + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('should auto-instrument `dataloader` package.', async () => { + const runner = createRunner().expect({ transaction: EXPECTED_TRANSACTION }).start(); + runner.makeRequest('get', '/'); + await runner.completed(); + }); + }); }); diff --git a/packages/node/package.json b/packages/node/package.json index 82a3475e62e9..b9ebec0768ab 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -71,7 +71,7 @@ "@opentelemetry/instrumentation": "^0.203.0", "@opentelemetry/instrumentation-amqplib": "0.50.0", "@opentelemetry/instrumentation-connect": "0.47.0", - "@opentelemetry/instrumentation-dataloader": "0.21.0", + "@opentelemetry/instrumentation-dataloader": "0.21.1", "@opentelemetry/instrumentation-express": "0.52.0", "@opentelemetry/instrumentation-fs": "0.23.0", "@opentelemetry/instrumentation-generic-pool": "0.47.0", diff --git a/yarn.lock b/yarn.lock index 0421ff01e101..a7660fbc1e84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5896,10 +5896,10 @@ "@opentelemetry/semantic-conventions" "^1.27.0" "@types/connect" "3.4.38" -"@opentelemetry/instrumentation-dataloader@0.21.0": - version "0.21.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.0.tgz#19202a85000cae9612f74bc689005ed3164e30a4" - integrity sha512-Xu4CZ1bfhdkV3G6iVHFgKTgHx8GbKSqrTU01kcIJRGHpowVnyOPEv1CW5ow+9GU2X4Eki8zoNuVUenFc3RluxQ== +"@opentelemetry/instrumentation-dataloader@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.21.1.tgz#46fbbe59d9d6796980707768cf733225d43adea5" + integrity sha512-hNAm/bwGawLM8VDjKR0ZUDJ/D/qKR3s6lA5NV+btNaPVm2acqhPcT47l2uCVi+70lng2mywfQncor9v8/ykuyw== dependencies: "@opentelemetry/instrumentation" "^0.203.0" From 0496bbe48ebdb9cc5024bfb81dc9a5bb6ab596a5 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 27 Aug 2025 16:52:00 +0200 Subject: [PATCH 14/14] meta(changelog): Update changelog for 10.7.0 --- .size-limit.js | 2 +- CHANGELOG.md | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 24df3d3017c8..3ea2bdf80703 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -233,7 +233,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '149 KB', + limit: '150 KB', }, { name: '@sentry/node - without tracing', diff --git a/CHANGELOG.md b/CHANGELOG.md index 036e1408e300..2a88fc83987f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,11 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.7.0 + ### Important Changes -- feat(cloudflare): Add `instrumentPrototypeMethods` option to instrument RPC methods for DurableObjects ([#17424](https://github.com/getsentry/sentry-javascript/pull/17424)) +- **feat(cloudflare): Add `instrumentPrototypeMethods` option to instrument RPC methods for DurableObjects ([#17424](https://github.com/getsentry/sentry-javascript/pull/17424))** By default, `Sentry.instrumentDurableObjectWithSentry` will not wrap any RPC methods on the prototype. To enable wrapping for RPC methods, set `instrumentPrototypeMethods` to `true` or, if performance is a concern, a list of only the methods you want to instrument: @@ -35,6 +37,27 @@ export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry( ); ``` +## Other Changes + +- feat(aws): Add support for streaming handlers ([#17463](https://github.com/getsentry/sentry-javascript/pull/17463)) +- feat(core): Stream responses Anthropic AI ([#17460](https://github.com/getsentry/sentry-javascript/pull/17460)) +- feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.56.0 to 0.57.0 ([#17455](https://github.com/getsentry/sentry-javascript/pull/17455)) +- feat(deps): bump @opentelemetry/instrumentation-dataloader from 0.21.0 to 0.21.1 ([#17457](https://github.com/getsentry/sentry-javascript/pull/17457)) +- feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.12.0 to 0.13.0 ([#17469](https://github.com/getsentry/sentry-javascript/pull/17469)) +- feat(deps): bump @opentelemetry/instrumentation-mysql2 from 0.49.0 to 0.50.0 ([#17459](https://github.com/getsentry/sentry-javascript/pull/17459)) +- feat(deps): bump @prisma/instrumentation from 6.13.0 to 6.14.0 ([#17466](https://github.com/getsentry/sentry-javascript/pull/17466)) +- feat(deps): bump @sentry/cli from 2.51.1 to 2.52.0 ([#17458](https://github.com/getsentry/sentry-javascript/pull/17458)) +- feat(deps): bump @sentry/rollup-plugin from 4.1.0 to 4.1.1 ([#17456](https://github.com/getsentry/sentry-javascript/pull/17456)) +- feat(deps): bump @sentry/webpack-plugin from 4.1.0 to 4.1.1 ([#17467](https://github.com/getsentry/sentry-javascript/pull/17467)) +- feat(replay): Add option to skip `requestAnimationFrame` for canvas snapshots ([#17380](https://github.com/getsentry/sentry-javascript/pull/17380)) + +
+ Internal Changes + +- test(aws): Run E2E tests in all supported Node versions ([#17446](https://github.com/getsentry/sentry-javascript/pull/17446)) + +
+ ## 10.6.0 ### Important Changes