diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 626955ac2718..38102ff204c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1168,7 +1168,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v4 with: - node-version-file: 'dev-packages/e2e-tests/package.json' + node-version: 22 - name: Restore caches uses: ./.github/actions/restore-cache with: diff --git a/.github/workflows/project-automation.yml b/.github/workflows/project-automation.yml new file mode 100644 index 000000000000..ce57e279dcf5 --- /dev/null +++ b/.github/workflows/project-automation.yml @@ -0,0 +1,98 @@ +name: "Automation: Update GH Project" +on: + pull_request: + types: + - closed + - opened + - reopened + - ready_for_review + - converted_to_draft + +jobs: + # Check if PR is in project + check_project: + name: Check if PR is in project + runs-on: ubuntu-latest + steps: + - name: Check if PR is in project + continue-on-error: true + id: check_project + uses: github/update-project-action@f980378bc179626af5b4e20ec05ec39c7f7a6f6d + with: + github_token: ${{ secrets.GH_PROJECT_AUTOMATION }} + organization: getsentry + project_number: 31 + content_id: ${{ github.event.pull_request.node_id }} + field: Status + operation: read + + - name: If project field is read, set is_in_project to 1 + if: steps.check_project.outputs.field_read_value + id: is_in_project + run: echo "is_in_project=1" >> "$GITHUB_OUTPUT" + + outputs: + is_in_project: ${{ steps.is_in_project.outputs.is_in_project || '0' }} + + # When a PR is a draft, it should go into "In Progress" + mark_as_in_progress: + name: "Mark as In Progress" + needs: check_project + if: | + needs.check_project.outputs.is_in_project == '1' + && (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'converted_to_draft') + && github.event.pull_request.draft == true + runs-on: ubuntu-latest + steps: + - name: Update status to in_progress + uses: github/update-project-action@f980378bc179626af5b4e20ec05ec39c7f7a6f6d + with: + github_token: ${{ secrets.GH_PROJECT_AUTOMATION }} + organization: getsentry + project_number: 31 + content_id: ${{ github.event.pull_request.node_id }} + field: Status + value: "🏗 In Progress" + + # When a PR is not a draft, it should go into "In Review" + mark_as_in_review: + name: "Mark as In Review" + needs: check_project + if: | + needs.check_project.outputs.is_in_project == '1' + && (github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'ready_for_review') + && github.event.pull_request.draft == false + runs-on: ubuntu-latest + steps: + - name: Update status to in_review + id: update_status + uses: github/update-project-action@f980378bc179626af5b4e20ec05ec39c7f7a6f6d + with: + github_token: ${{ secrets.GH_PROJECT_AUTOMATION }} + organization: getsentry + project_number: 31 + content_id: ${{ github.event.pull_request.node_id }} + field: Status + value: "👀 In Review" + + # By default, closed PRs go into "Ready for Release" + # But if they are closed without merging, they should go into "Done" + mark_as_done: + name: "Mark as Done" + needs: check_project + if: | + needs.check_project.outputs.is_in_project == '1' + && github.event.action == 'closed' && github.event.pull_request.merged == false + runs-on: ubuntu-latest + steps: + - name: Update status to done + id: update_status + uses: github/update-project-action@f980378bc179626af5b4e20ec05ec39c7f7a6f6d + with: + github_token: ${{ secrets.GH_PROJECT_AUTOMATION }} + organization: getsentry + project_number: 31 + content_id: ${{ github.event.pull_request.node_id }} + field: Status + value: "✅ Done" + diff --git a/CHANGELOG.md b/CHANGELOG.md index bc50808b4f11..7bf504ce80b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,38 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.30.0 + +### Important Changes + +- _feat(node): Add `kafkajs` integration (#13528)_ + +This release adds a new integration that instruments `kafkajs` library with spans and traces. This integration is +automatically enabled by default, but can be included with the `Sentry.kafkaIntegration()` import. + +```js +Sentry.init({ + integrations: [Sentry.kafkaIntegration()], +}); +``` + +### Other Changes + +- feat(core): Allow adding measurements without global client (#13612) +- feat(deps): Bump @opentelemetry/instrumentation-undici from 0.5.0 to 0.6.0 (#13622) +- feat(deps): Bump @sentry/cli from 2.33.0 to 2.35.0 (#13624) +- feat(node): Use `@opentelemetry/instrumentation-undici` for fetch tracing (#13485) +- feat(nuxt): Add server config to root folder (#13583) +- feat(otel): Upgrade @opentelemetry/semantic-conventions to 1.26.0 (#13631) +- fix(browser): check supportedEntryTypes before caling the function (#13541) +- fix(browser): Ensure Standalone CLS span timestamps are correct (#13649) +- fix(nextjs): Widen removal of 404 transactions (#13628) +- fix(node): Remove ambiguity and race conditions when matching local variables to exceptions (#13501) +- fix(node): Update OpenTelemetry instrumentation package for solidstart and opentelemetry (#13640) +- fix(node): Update OpenTelemetry instrumentation package for solidstart and opentelemetry (#13642) +- fix(vue): Ensure Vue `trackComponents` list matches components with or without `<>` (#13543) +- ref(profiling): Conditionally shim cjs globals (#13267) + Work in this release was contributed by @Zen-cronic and @odanado. Thank you for your contributions! ## 8.29.0 @@ -34,7 +66,7 @@ import * as Sentry from '@sentry/node'; Sentry.init({ dsn: '__PUBLIC_DSN__', - registerEsmLoaderHooks: { onlyHookedModules: true }, + registerEsmLoaderHooks: { onlyIncludeInstrumentedModules: true }, }); ``` diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 7125fd14bb09..719dba1eece3 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "8.29.0", + "version": "8.30.0", "main": "index.js", "license": "MIT", "engines": { @@ -43,7 +43,7 @@ "@babel/preset-typescript": "^7.16.7", "@playwright/test": "^1.44.1", "@sentry-internal/rrweb": "2.11.0", - "@sentry/browser": "8.29.0", + "@sentry/browser": "8.30.0", "axios": "1.6.7", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts index cdf1e6837ef4..6defe804e665 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts @@ -453,3 +453,44 @@ sentryTest("doesn't send further CLS after the first page hide", async ({ getLoc // a timeout or something similar. await navigationTxnPromise; }); + +sentryTest('CLS span timestamps are set correctly', async ({ getLocalTestPath, page }) => { + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.type).toBe('transaction'); + expect(eventData.contexts?.trace?.op).toBe('pageload'); + expect(eventData.timestamp).toBeDefined(); + + const pageloadEndTimestamp = eventData.timestamp!; + + const spanEnvelopePromise = getMultipleSentryEnvelopeRequests( + page, + 1, + { envelopeType: 'span' }, + properFullEnvelopeRequestParser, + ); + + await triggerAndWaitForLayoutShift(page); + + await hidePage(page); + + const spanEnvelope = (await spanEnvelopePromise)[0]; + const spanEnvelopeItem = spanEnvelope[1][0][1]; + + expect(spanEnvelopeItem.start_timestamp).toBeDefined(); + expect(spanEnvelopeItem.timestamp).toBeDefined(); + + const clsSpanStartTimestamp = spanEnvelopeItem.start_timestamp!; + const clsSpanEndTimestamp = spanEnvelopeItem.timestamp!; + + // CLS performance entries have no duration ==> start and end timestamp should be the same + expect(clsSpanStartTimestamp).toEqual(clsSpanEndTimestamp); + + // We don't really care that they are very close together but rather about the order of magnitude + // Previously, we had a bug where the timestamps would be significantly off (by multiple hours) + // so we only ensure that this bug is fixed. 60 seconds should be more than enough. + expect(clsSpanStartTimestamp - pageloadEndTimestamp).toBeLessThan(60); + expect(clsSpanStartTimestamp).toBeGreaterThan(pageloadEndTimestamp); +}); diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json index 403858bd7b2c..8fe2b4bdd739 100644 --- a/dev-packages/bundle-analyzer-scenarios/package.json +++ b/dev-packages/bundle-analyzer-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/bundle-analyzer-scenarios", - "version": "8.29.0", + "version": "8.30.0", "description": "Scenarios to test bundle analysis with", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios", diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index 97a2dc33f3f7..d53b62f14c9e 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/clear-cache-gh-action", "description": "An internal Github Action to clear GitHub caches.", - "version": "8.29.0", + "version": "8.30.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 966dab87a6bc..05734797034c 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "8.29.0", + "version": "8.30.0", "license": "MIT", "private": true, "scripts": { @@ -14,7 +14,7 @@ "test:prepare": "ts-node prepare.ts", "test:validate": "run-s test:validate-configuration test:validate-test-app-setups", "clean": "rimraf tmp node_modules pnpm-lock.yaml && yarn clean:test-applications", - "clean:test-applications": "rimraf test-applications/**/{node_modules,dist,build,.next,.sveltekit,pnpm-lock.yaml} .last-run.json && pnpm store prune" + "clean:test-applications": "rimraf --glob test-applications/**/{node_modules,dist,build,.next,.sveltekit,pnpm-lock.yaml} .last-run.json && pnpm store prune" }, "devDependencies": { "@types/glob": "8.0.0", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/404.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/404.test.ts new file mode 100644 index 000000000000..4c09bce36b4a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/404.test.ts @@ -0,0 +1,23 @@ +import { test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('should create a transaction for a CJS pages router API endpoint', async ({ page }) => { + let received404Transaction = false; + waitForTransaction('nextjs-13', async transactionEvent => { + return transactionEvent.transaction === 'GET /404' || transactionEvent.transaction === 'GET /_not-found'; + }).then(() => { + received404Transaction = true; + }); + + await page.goto('/page-that-doesnt-exist'); + + await new Promise((resolve, reject) => { + setTimeout(() => { + if (received404Transaction) { + reject(new Error('received 404 transaction')); + } else { + resolve(); + } + }, 5_000); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts index 303582ec1b24..bf3eca58a307 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/tests/generation-functions.test.ts @@ -17,7 +17,7 @@ test('Should emit a span for a generateMetadata() function invokation', async ({ expect(transaction.spans).toContainEqual( expect.objectContaining({ description: 'generateMetadata /generation-functions/page', - origin: 'manual', + origin: 'auto', parent_span_id: expect.any(String), span_id: expect.any(String), status: 'ok', @@ -74,7 +74,7 @@ test('Should send a transaction event for a generateViewport() function invokati expect((await transactionPromise).spans).toContainEqual( expect.objectContaining({ description: 'generateViewport /generation-functions/page', - origin: 'manual', + origin: 'auto', parent_span_id: expect.any(String), span_id: expect.any(String), status: 'ok', diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/build.shimmed.mjs b/dev-packages/e2e-tests/test-applications/node-profiling/build.shimmed.mjs new file mode 100644 index 000000000000..c45e30539bc0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling/build.shimmed.mjs @@ -0,0 +1,29 @@ +// Because bundlers can now predetermine a static set of binaries we need to ensure those binaries +// actually exists, else we risk a compile time error when bundling the package. This could happen +// if we added a new binary in cpu_profiler.ts, but forgot to prebuild binaries for it. Because CI +// only runs integration and unit tests, this change would be missed and could end up in a release. +// Therefor, once all binaries are precompiled in CI and tests pass, run esbuild with bundle:true +// which will copy all binaries to the outfile folder and throw if any of them are missing. +import esbuild from 'esbuild'; + +console.log('Running build using esbuild version', esbuild.version); + +esbuild.buildSync({ + platform: 'node', + entryPoints: ['./index.ts'], + outfile: './dist/index.shimmed.mjs', + target: 'esnext', + format: 'esm', + bundle: true, + loader: { '.node': 'copy' }, + banner: { + js: ` + import { dirname } from 'node:path'; + import { fileURLToPath } from 'node:url'; + import { createRequire } from 'node:module'; + const require = createRequire(import.meta.url); + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + `, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/node-profiling/package.json b/dev-packages/e2e-tests/test-applications/node-profiling/package.json index 94ec4926f2f6..a4c4bf1284fe 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling/package.json +++ b/dev-packages/e2e-tests/test-applications/node-profiling/package.json @@ -4,9 +4,9 @@ "private": true, "scripts": { "typecheck": "tsc --noEmit", - "build": "node build.mjs", - "test": "npm run build && node dist/index.js", - "clean": "npx rimraf node_modules", + "build": "node build.mjs && node build.shimmed.mjs", + "test": "node dist/index.js && node --experimental-require-module dist/index.js && node dist/index.shimmed.mjs", + "clean": "npx rimraf node_modules dist", "test:build": "npm run typecheck && npm run build", "test:assert": "npm run test" }, diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index 02cf1f75d1d6..0950b4ffadfc 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/external-contributor-gh-action", "description": "An internal Github Action to add external contributors to the CHANGELOG.md file.", - "version": "8.29.0", + "version": "8.30.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 08c6f5c989d4..e954fb631c97 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "8.29.0", + "version": "8.30.0", "license": "MIT", "engines": { "node": ">=14.18" @@ -31,10 +31,10 @@ "@nestjs/core": "^10.3.3", "@nestjs/platform-express": "^10.3.3", "@prisma/client": "5.9.1", - "@sentry/aws-serverless": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/aws-serverless": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", @@ -49,6 +49,7 @@ "graphql": "^16.3.0", "http-terminator": "^3.2.0", "ioredis": "^5.4.1", + "kafkajs": "2.2.4", "mongodb": "^3.7.3", "mongodb-memory-server-global": "^7.6.3", "mongoose": "^5.13.22", diff --git a/dev-packages/node-integration-tests/suites/tracing/kafkajs/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/kafkajs/docker-compose.yml new file mode 100644 index 000000000000..f744bfe6d50c --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/kafkajs/docker-compose.yml @@ -0,0 +1,7 @@ +services: + db: + image: apache/kafka:latest + restart: always + container_name: integration-tests-kafka + ports: + - '9092:9092' diff --git a/dev-packages/node-integration-tests/suites/tracing/kafkajs/scenario.js b/dev-packages/node-integration-tests/suites/tracing/kafkajs/scenario.js new file mode 100644 index 000000000000..d4541aa3a7de --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/kafkajs/scenario.js @@ -0,0 +1,63 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const { Kafka } = require('kafkajs'); + +async function run() { + const kafka = new Kafka({ + clientId: 'my-app', + brokers: ['localhost:9092'], + }); + + const admin = kafka.admin(); + await admin.connect(); + + const producer = kafka.producer(); + await producer.connect(); + + await admin.createTopics({ + topics: [{ topic: 'test-topic' }], + }); + + const consumer = kafka.consumer({ + groupId: 'test-group', + }); + + await consumer.connect(); + await consumer.subscribe({ topic: 'test-topic', fromBeginning: true }); + + consumer.run({ + eachMessage: async ({ message }) => { + // eslint-disable-next-line no-console + console.debug('Received message', message.value.toString()); + }, + }); + + // Wait for the consumer to be ready + await new Promise(resolve => setTimeout(resolve, 4000)); + + await producer.send({ + topic: 'test-topic', + messages: [ + { + value: 'TEST_MESSAGE', + }, + ], + }); + + // Wait for the message to be received + await new Promise(resolve => setTimeout(resolve, 5000)); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts new file mode 100644 index 000000000000..f818af1d676a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts @@ -0,0 +1,55 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +// When running docker compose, we need a larger timeout, as this takes some time... +jest.setTimeout(60_000); + +describe('kafkajs', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('traces producers and consumers', done => { + createRunner(__dirname, 'scenario.js') + .withDockerCompose({ + workingDirectory: [__dirname], + readyMatches: ['9092'], + }) + .expect({ + transaction: { + transaction: 'test-topic', + contexts: { + trace: expect.objectContaining({ + op: 'message', + status: 'ok', + data: expect.objectContaining({ + 'messaging.system': 'kafka', + 'messaging.destination': 'test-topic', + 'otel.kind': 'PRODUCER', + 'sentry.op': 'message', + 'sentry.origin': 'auto.kafkajs.otel.producer', + }), + }), + }, + }, + }) + .expect({ + transaction: { + transaction: 'test-topic', + contexts: { + trace: expect.objectContaining({ + op: 'message', + status: 'ok', + data: expect.objectContaining({ + 'messaging.system': 'kafka', + 'messaging.destination': 'test-topic', + 'otel.kind': 'CONSUMER', + 'sentry.op': 'message', + 'sentry.origin': 'auto.kafkajs.otel.consumer', + }), + }), + }, + }, + }) + .start(done); + }); +}); diff --git a/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json index 2913206356c2..0791424c2186 100644 --- a/dev-packages/overhead-metrics/package.json +++ b/dev-packages/overhead-metrics/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "8.29.0", + "version": "8.30.0", "name": "@sentry-internal/overhead-metrics", "main": "index.js", "author": "Sentry", diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 20c56a8ac745..97473e7258a2 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/rollup-utils", - "version": "8.29.0", + "version": "8.30.0", "description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils", diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index 2ff02558bf5c..7d3202115fb9 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/size-limit-gh-action", "description": "An internal Github Action to compare the current size of a PR against the one on develop.", - "version": "8.29.0", + "version": "8.30.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 00c8d663fe5e..e816681692ea 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "8.29.0", + "version": "8.30.0", "name": "@sentry-internal/test-utils", "author": "Sentry", "license": "MIT", @@ -45,8 +45,8 @@ }, "devDependencies": { "@playwright/test": "^1.44.1", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "volta": { "extends": "../../package.json" diff --git a/lerna.json b/lerna.json index 6fb31cf1edc2..8d9a29e631da 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "8.29.0", + "version": "8.30.0", "npmClient": "yarn" } diff --git a/packages/angular/package.json b/packages/angular/package.json index 04b8d96e22de..23245a7b999c 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/angular", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", @@ -21,10 +21,10 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "tslib": "^2.4.1" }, "devDependencies": { diff --git a/packages/astro/package.json b/packages/astro/package.json index 49904343b022..42792e7554e7 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/astro", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Astro", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro", @@ -56,11 +56,11 @@ "astro": ">=3.x || >=4.0.0-beta" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@sentry/vite-plugin": "^2.22.3" }, "devDependencies": { diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 747870da3014..2645151a9ede 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -65,6 +65,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + kafkaIntegration, koaIntegration, lastEventId, linkedErrorsIntegration, diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 317d0bfe5a8e..3fc6a162e05e 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/aws-serverless", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless", @@ -66,10 +66,10 @@ "dependencies": { "@opentelemetry/instrumentation-aws-lambda": "0.44.0", "@opentelemetry/instrumentation-aws-sdk": "0.44.0", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@types/aws-lambda": "^8.10.62" }, "devDependencies": { diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index f648dba045ec..19c90e3aef3f 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -89,6 +89,7 @@ export { fsIntegration, genericPoolIntegration, graphqlIntegration, + kafkaIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index 53926f4e060a..a2190d272edb 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-utils", - "version": "8.29.0", + "version": "8.30.0", "description": "Browser Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/browser-utils/src/metrics/cls.ts b/packages/browser-utils/src/metrics/cls.ts index aa25a54754a1..e1d13286f5f9 100644 --- a/packages/browser-utils/src/metrics/cls.ts +++ b/packages/browser-utils/src/metrics/cls.ts @@ -84,8 +84,7 @@ export function trackClsAsStandaloneSpan(): void { function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) { DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`); - const startTime = msToSec(browserPerformanceTimeOrigin as number) + (entry?.startTime || 0); - const duration = msToSec(entry?.duration || 0); + const startTime = msToSec((browserPerformanceTimeOrigin || 0) + (entry?.startTime || 0)); const routeName = getCurrentScope().getScopeData().transactionName; const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : 'Layout shift'; @@ -110,7 +109,9 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: clsValue, }); - span?.end(startTime + duration); + // LayoutShift performance entries always have a duration of 0, so we don't need to add `entry.duration` here + // see: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/duration + span?.end(startTime); } function supportsLayoutShift(): boolean { diff --git a/packages/browser-utils/src/metrics/utils.ts b/packages/browser-utils/src/metrics/utils.ts index 5f9d0de4d4ab..70327aeca838 100644 --- a/packages/browser-utils/src/metrics/utils.ts +++ b/packages/browser-utils/src/metrics/utils.ts @@ -86,7 +86,7 @@ export function startStandaloneWebVitalSpan(options: StandaloneWebVitalSpanOptio const user = scope.getUser(); const userDisplay = user !== undefined ? user.email || user.id || user.ip_address : undefined; - let profileId: string | undefined = undefined; + let profileId: string | undefined; try { // @ts-expect-error skip optional chaining to save bundle size with try catch profileId = scope.getScopeData().contexts.profile.profile_id; diff --git a/packages/browser/package.json b/packages/browser/package.json index dbab2cfaf5e0..1682b8f805c4 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/browser", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", @@ -39,16 +39,16 @@ "access": "public" }, "dependencies": { - "@sentry-internal/browser-utils": "8.29.0", - "@sentry-internal/feedback": "8.29.0", - "@sentry-internal/replay": "8.29.0", - "@sentry-internal/replay-canvas": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/browser-utils": "8.30.0", + "@sentry-internal/feedback": "8.30.0", + "@sentry-internal/replay": "8.30.0", + "@sentry-internal/replay-canvas": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { - "@sentry-internal/integration-shims": "8.29.0", + "@sentry-internal/integration-shims": "8.30.0", "fake-indexeddb": "^4.0.1" }, "scripts": { diff --git a/packages/bun/package.json b/packages/bun/package.json index dd7ae20c7a7e..510efd7a4a6e 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/bun", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for bun", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun", @@ -39,11 +39,11 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { "bun-types": "latest" diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 1d8b02c33568..fcb3d1331f46 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -110,6 +110,7 @@ export { setupConnectErrorHandler, genericPoolIntegration, graphqlIntegration, + kafkaIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 8522b4928fc6..96a99593b9a1 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cloudflare", - "version": "8.29.0", + "version": "8.30.0", "description": "Offical Sentry SDK for Cloudflare Workers and Pages", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "optionalDependencies": { "@cloudflare/workers-types": "^4.x" diff --git a/packages/core/package.json b/packages/core/package.json index e17b0bb8d208..c47bbdcdad61 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/core", - "version": "8.29.0", + "version": "8.30.0", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/deno/package.json b/packages/deno/package.json index aae4341defb1..1515981206bf 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/deno", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Deno", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno", @@ -24,9 +24,9 @@ "/build" ], "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { "@rollup/plugin-typescript": "^11.1.5", diff --git a/packages/ember/package.json b/packages/ember/package.json index 20fe85129779..0e03abbaa906 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -33,10 +33,10 @@ "dependencies": { "@babel/core": "^7.24.4", "@embroider/macros": "^1.16.0", - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "ember-auto-import": "^2.7.2", "ember-cli-babel": "^8.2.0", "ember-cli-htmlbars": "^6.1.1", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 9ea374afd7f3..d3f95709b052 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -22,8 +22,8 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "8.29.0", - "@sentry-internal/typescript": "8.29.0", + "@sentry-internal/eslint-plugin-sdk": "8.30.0", + "@sentry-internal/typescript": "8.30.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index 21ce55d15660..64637ce0110f 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", diff --git a/packages/feedback/package.json b/packages/feedback/package.json index 14cfcb071c0c..97d67baa7404 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/feedback", - "version": "8.29.0", + "version": "8.30.0", "description": "Sentry SDK integration for user feedback", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { "preact": "^10.19.4" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index a2fdbe56c655..5bceb2b813ba 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/gatsby", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Gatsby.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby", @@ -45,10 +45,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/react": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/react": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@sentry/webpack-plugin": "2.22.3" }, "peerDependencies": { diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 158080a3969a..e2df4b5e8af7 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/google-cloud-serverless", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Google Cloud Functions", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud", @@ -48,10 +48,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@types/express": "^4.17.14" }, "devDependencies": { diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 463a0c5c1246..14aa0996cb7c 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -89,6 +89,7 @@ export { fastifyIntegration, genericPoolIntegration, graphqlIntegration, + kafkaIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index af5afeb6d981..f9d6b7e0bd00 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/integration-shims", - "version": "8.29.0", + "version": "8.30.0", "description": "Shims for integrations in Sentry SDK.", "main": "build/cjs/index.js", "module": "build/esm/index.js", @@ -55,9 +55,9 @@ "url": "https://github.com/getsentry/sentry-javascript/issues" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "engines": { "node": ">=14.18" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 9d1ea5552c19..a88c18d35c63 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nestjs", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for NestJS", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs", @@ -44,10 +44,10 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 9880e0ffb28e..c401e82890dc 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nextjs", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Next.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs", @@ -69,15 +69,15 @@ }, "dependencies": { "@opentelemetry/instrumentation-http": "0.53.0", - "@opentelemetry/semantic-conventions": "^1.25.1", + "@opentelemetry/semantic-conventions": "^1.27.0", "@rollup/plugin-commonjs": "26.0.1", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/react": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", - "@sentry/vercel-edge": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/react": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", + "@sentry/vercel-edge": "8.30.0", "@sentry/webpack-plugin": "2.22.3", "chalk": "3.0.0", "resolve": "1.22.8", diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 1132a6e1eed2..e787f978cf22 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,4 +1,5 @@ import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, applySdkMetadata, getClient, @@ -10,7 +11,12 @@ import { getDefaultIntegrations, init as nodeInit } from '@sentry/node'; import type { NodeClient, NodeOptions } from '@sentry/node'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; -import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_ROUTE, SEMATTRS_HTTP_TARGET } from '@opentelemetry/semantic-conventions'; +import { + ATTR_HTTP_REQUEST_METHOD, + ATTR_HTTP_ROUTE, + SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_TARGET, +} from '@opentelemetry/semantic-conventions'; import type { EventProcessor } from '@sentry/types'; import { DEBUG_BUILD } from '../common/debug-build'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; @@ -150,8 +156,11 @@ export function init(options: NodeOptions): NodeClient | undefined { // because we didn't get the chance to do `suppressTracing`, since this happens outside of userland. // We need to drop these spans. if ( + // eslint-disable-next-line deprecation/deprecation typeof spanAttributes[SEMATTRS_HTTP_TARGET] === 'string' && + // eslint-disable-next-line deprecation/deprecation spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_key') && + // eslint-disable-next-line deprecation/deprecation spanAttributes[SEMATTRS_HTTP_TARGET].includes('sentry_client') ) { samplingDecision.decision = false; @@ -168,8 +177,12 @@ export function init(options: NodeOptions): NodeClient | undefined { const rootSpanAttributes = spanToJSON(rootSpan).data; // Only hoist the http.route attribute if the transaction doesn't already have it - if (rootSpanAttributes?.[SEMATTRS_HTTP_METHOD] && !rootSpanAttributes?.[SEMATTRS_HTTP_ROUTE]) { - rootSpan.setAttribute(SEMATTRS_HTTP_ROUTE, spanAttributes['next.route']); + if ( + // eslint-disable-next-line deprecation/deprecation + (rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) && + !rootSpanAttributes?.[ATTR_HTTP_ROUTE] + ) { + rootSpan.setAttribute(ATTR_HTTP_ROUTE, spanAttributes['next.route']); } } @@ -177,6 +190,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // with patterns (e.g. http.server spans) that will produce confusing data. if (spanAttributes?.['next.span_type'] !== undefined) { span.setAttribute('sentry.skip_span_data_inference', true); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); } // We want to rename these spans because they look like "GET /path/to/route" and we already emit spans that look @@ -219,8 +233,14 @@ export function init(options: NodeOptions): NodeClient | undefined { return null; } - // Filter out /404 transactions for pages-router which seem to be created excessively - if (event.transaction === '/404') { + // Filter out /404 transactions which seem to be created excessively + if ( + // Pages router + event.transaction === '/404' || + // App router (could be "GET /404", "POST /404", ...) + event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/404$/) || + event.transaction === 'GET /_not-found' + ) { return null; } @@ -268,6 +288,28 @@ export function init(options: NodeOptions): NodeClient | undefined { ), ); + getGlobalScope().addEventProcessor( + Object.assign( + (event => { + // Sometimes, the HTTP integration will not work, causing us not to properly set an op for spans generated by + // Next.js that are actually more or less correct server HTTP spans, so we are backfilling the op here. + if ( + event.type === 'transaction' && + event.transaction?.match(/^(RSC )?GET /) && + event.contexts?.trace?.data?.['sentry.rsc'] === true && + !event.contexts.trace.op + ) { + event.contexts.trace.data = event.contexts.trace.data || {}; + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; + event.contexts.trace.op = 'http.server'; + } + + return event; + }) satisfies EventProcessor, + { id: 'NextjsTransactionEnhancer' }, + ), + ); + if (process.env.NODE_ENV === 'development') { getGlobalScope().addEventProcessor(devErrorSymbolicationEventProcessor); } diff --git a/packages/node/package.json b/packages/node/package.json index ceb8eef2ea36..d80869cd9251 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node", - "version": "8.29.0", + "version": "8.30.0", "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node", @@ -79,6 +79,7 @@ "@opentelemetry/instrumentation-hapi": "0.41.0", "@opentelemetry/instrumentation-http": "0.53.0", "@opentelemetry/instrumentation-ioredis": "0.43.0", + "@opentelemetry/instrumentation-kafkajs": "0.3.0", "@opentelemetry/instrumentation-koa": "0.43.0", "@opentelemetry/instrumentation-mongodb": "0.47.0", "@opentelemetry/instrumentation-mongoose": "0.42.0", @@ -88,14 +89,14 @@ "@opentelemetry/instrumentation-pg": "0.44.0", "@opentelemetry/instrumentation-redis-4": "0.42.0", "@opentelemetry/instrumentation-undici": "0.6.0", - "@opentelemetry/resources": "^1.25.1", - "@opentelemetry/sdk-trace-base": "^1.25.1", - "@opentelemetry/semantic-conventions": "^1.25.1", + "@opentelemetry/resources": "^1.26.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0", "@prisma/instrumentation": "5.19.1", - "@sentry/core": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "import-in-the-middle": "^1.11.0" }, "devDependencies": { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 065e8423bb02..f3c945f5316d 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -14,6 +14,7 @@ export { anrIntegration } from './integrations/anr'; export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express'; export { fastifyIntegration, setupFastifyErrorHandler } from './integrations/tracing/fastify'; export { graphqlIntegration } from './integrations/tracing/graphql'; +export { kafkaIntegration } from './integrations/tracing/kafka'; export { mongoIntegration } from './integrations/tracing/mongo'; export { mongooseIntegration } from './integrations/tracing/mongoose'; export { mysqlIntegration } from './integrations/tracing/mysql'; diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 0d5b2d4814d1..d9e5e671b702 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -165,6 +165,10 @@ export const instrumentHttp = Object.assign( isolationScope.setTransactionName(bestEffortTransactionName); + if (isKnownPrefetchRequest(req)) { + span.setAttribute('sentry.http.prefetch', true); + } + _httpOptions.instrumentation?.requestHook?.(span, req); }, responseHook: (span, res) => { @@ -275,3 +279,11 @@ function getBreadcrumbData(request: ClientRequest): Partial; export type RateLimitIncrement = () => void; +/** + * The key used to store the local variables on the error object. + */ +export const LOCAL_VARIABLES_KEY = '__SENTRY_ERROR_LOCAL_VARIABLES__'; + /** * Creates a rate limiter that will call the disable callback when the rate limit is reached and the enable callback * when a timeout has occurred. @@ -55,6 +59,7 @@ export type PausedExceptionEvent = Debugger.PausedEventDataType & { data: { // This contains error.stack description: string; + objectId?: string; }; }; @@ -68,28 +73,6 @@ export function functionNamesMatch(a: string | undefined, b: string | undefined) return a === b || (isAnonymous(a) && isAnonymous(b)); } -/** Creates a unique hash from stack frames */ -export function hashFrames(frames: StackFrame[] | undefined): string | undefined { - if (frames === undefined) { - return; - } - - // Only hash the 10 most recent frames (ie. the last 10) - return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, ''); -} - -/** - * We use the stack parser to create a unique hash from the exception stack trace - * This is used to lookup vars when the exception passes through the event processor - */ -export function hashFromStack(stackParser: StackParser, stack: string | undefined): string | undefined { - if (stack === undefined) { - return undefined; - } - - return hashFrames(stackParser(stack, 1)); -} - export interface FrameVariables { function: string; vars?: Variables; diff --git a/packages/node/src/integrations/local-variables/inspector.d.ts b/packages/node/src/integrations/local-variables/inspector.d.ts index 9ac6b857dcc0..5cfd496f7626 100644 --- a/packages/node/src/integrations/local-variables/inspector.d.ts +++ b/packages/node/src/integrations/local-variables/inspector.d.ts @@ -20,6 +20,14 @@ declare module 'node:inspector/promises' { method: 'Runtime.getProperties', params: Runtime.GetPropertiesParameterType, ): Promise; + public post( + method: 'Runtime.callFunctionOn', + params: Runtime.CallFunctionOnParameterType, + ): Promise; + public post( + method: 'Runtime.releaseObject', + params: Runtime.ReleaseObjectParameterType, + ): Promise; public on( event: 'Debugger.paused', diff --git a/packages/node/src/integrations/local-variables/local-variables-async.ts b/packages/node/src/integrations/local-variables/local-variables-async.ts index 86ce9359a95e..d46fa224019f 100644 --- a/packages/node/src/integrations/local-variables/local-variables-async.ts +++ b/packages/node/src/integrations/local-variables/local-variables-async.ts @@ -1,11 +1,12 @@ import { Worker } from 'node:worker_threads'; import { defineIntegration } from '@sentry/core'; -import type { Event, Exception, IntegrationFn } from '@sentry/types'; -import { LRUMap, logger } from '@sentry/utils'; +import type { Event, EventHint, Exception, IntegrationFn } from '@sentry/types'; +import { logger } from '@sentry/utils'; import type { NodeClient } from '../../sdk/client'; import type { FrameVariables, LocalVariablesIntegrationOptions, LocalVariablesWorkerArgs } from './common'; -import { functionNamesMatch, hashFrames } from './common'; +import { LOCAL_VARIABLES_KEY } from './common'; +import { functionNamesMatch } from './common'; // This string is a placeholder that gets overwritten with the worker code. export const base64WorkerScript = '###LocalVariablesWorkerScript###'; @@ -20,23 +21,7 @@ function log(...args: unknown[]): void { export const localVariablesAsyncIntegration = defineIntegration((( integrationOptions: LocalVariablesIntegrationOptions = {}, ) => { - const cachedFrames: LRUMap = new LRUMap(20); - - function addLocalVariablesToException(exception: Exception): void { - const hash = hashFrames(exception?.stacktrace?.frames); - - if (hash === undefined) { - return; - } - - // Check if we have local variables for an exception that matches the hash - // remove is identical to get but also removes the entry from the cache - const cachedFrame = cachedFrames.remove(hash); - - if (cachedFrame === undefined) { - return; - } - + function addLocalVariablesToException(exception: Exception, localVariables: FrameVariables[]): void { // Filter out frames where the function name is `new Promise` since these are in the error.stack frames // but do not appear in the debugger call frames const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise'); @@ -45,32 +30,41 @@ export const localVariablesAsyncIntegration = defineIntegration((( // Sentry frames are in reverse order const frameIndex = frames.length - i - 1; - const cachedFrameVariable = cachedFrame[i]; - const frameVariable = frames[frameIndex]; + const frameLocalVariables = localVariables[i]; + const frame = frames[frameIndex]; - if (!frameVariable || !cachedFrameVariable) { + if (!frame || !frameLocalVariables) { // Drop out if we run out of frames to match up break; } if ( // We need to have vars to add - cachedFrameVariable.vars === undefined || + frameLocalVariables.vars === undefined || // We're not interested in frames that are not in_app because the vars are not relevant - frameVariable.in_app === false || + frame.in_app === false || // The function names need to match - !functionNamesMatch(frameVariable.function, cachedFrameVariable.function) + !functionNamesMatch(frame.function, frameLocalVariables.function) ) { continue; } - frameVariable.vars = cachedFrameVariable.vars; + frame.vars = frameLocalVariables.vars; } } - function addLocalVariablesToEvent(event: Event): Event { - for (const exception of event.exception?.values || []) { - addLocalVariablesToException(exception); + function addLocalVariablesToEvent(event: Event, hint: EventHint): Event { + if ( + hint.originalException && + typeof hint.originalException === 'object' && + LOCAL_VARIABLES_KEY in hint.originalException && + Array.isArray(hint.originalException[LOCAL_VARIABLES_KEY]) + ) { + for (const exception of event.exception?.values || []) { + addLocalVariablesToException(exception, hint.originalException[LOCAL_VARIABLES_KEY]); + } + + hint.originalException[LOCAL_VARIABLES_KEY] = undefined; } return event; @@ -96,10 +90,6 @@ export const localVariablesAsyncIntegration = defineIntegration((( worker.terminate(); }); - worker.on('message', ({ exceptionHash, frames }) => { - cachedFrames.set(exceptionHash, frames); - }); - worker.once('error', (err: Error) => { log('Worker error', err); }); @@ -139,8 +129,8 @@ export const localVariablesAsyncIntegration = defineIntegration((( }, ); }, - processEvent(event: Event): Event { - return addLocalVariablesToEvent(event); + processEvent(event: Event, hint: EventHint): Event { + return addLocalVariablesToEvent(event, hint); }, }; }) satisfies IntegrationFn); diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts index bf7aaa26cbf3..d3203614330f 100644 --- a/packages/node/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts @@ -1,6 +1,6 @@ import type { Debugger, InspectorNotification, Runtime, Session } from 'node:inspector'; import { defineIntegration, getClient } from '@sentry/core'; -import type { Event, Exception, IntegrationFn, StackParser } from '@sentry/types'; +import type { Event, Exception, IntegrationFn, StackFrame, StackParser } from '@sentry/types'; import { LRUMap, logger } from '@sentry/utils'; import { NODE_MAJOR } from '../../nodeVersion'; @@ -12,7 +12,29 @@ import type { RateLimitIncrement, Variables, } from './common'; -import { createRateLimiter, functionNamesMatch, hashFrames, hashFromStack } from './common'; +import { createRateLimiter, functionNamesMatch } from './common'; + +/** Creates a unique hash from stack frames */ +export function hashFrames(frames: StackFrame[] | undefined): string | undefined { + if (frames === undefined) { + return; + } + + // Only hash the 10 most recent frames (ie. the last 10) + return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, ''); +} + +/** + * We use the stack parser to create a unique hash from the exception stack trace + * This is used to lookup vars when the exception passes through the event processor + */ +export function hashFromStack(stackParser: StackParser, stack: string | undefined): string | undefined { + if (stack === undefined) { + return undefined; + } + + return hashFrames(stackParser(stack, 1)); +} type OnPauseEvent = InspectorNotification; export interface DebugSession { diff --git a/packages/node/src/integrations/local-variables/worker.ts b/packages/node/src/integrations/local-variables/worker.ts index 5bee22a84c29..eb4fee87947c 100644 --- a/packages/node/src/integrations/local-variables/worker.ts +++ b/packages/node/src/integrations/local-variables/worker.ts @@ -1,16 +1,12 @@ import type { Debugger, InspectorNotification, Runtime } from 'node:inspector'; import { Session } from 'node:inspector/promises'; -import { parentPort, workerData } from 'node:worker_threads'; -import type { StackParser } from '@sentry/types'; -import { createStackParser, nodeStackLineParser } from '@sentry/utils'; -import { createGetModuleFromFilename } from '../../utils/module'; +import { workerData } from 'node:worker_threads'; import type { LocalVariablesWorkerArgs, PausedExceptionEvent, RateLimitIncrement, Variables } from './common'; -import { createRateLimiter, hashFromStack } from './common'; +import { LOCAL_VARIABLES_KEY } from './common'; +import { createRateLimiter } from './common'; const options: LocalVariablesWorkerArgs = workerData; -const stackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename(options.basePath))); - function log(...args: unknown[]): void { if (options.debug) { // eslint-disable-next-line no-console @@ -88,19 +84,15 @@ let rateLimiter: RateLimitIncrement | undefined; async function handlePaused( session: Session, - stackParser: StackParser, - { reason, data, callFrames }: PausedExceptionEvent, -): Promise { + { reason, data: { objectId }, callFrames }: PausedExceptionEvent, +): Promise { if (reason !== 'exception' && reason !== 'promiseRejection') { return; } rateLimiter?.(); - // data.description contains the original error.stack - const exceptionHash = hashFromStack(stackParser, data?.description); - - if (exceptionHash == undefined) { + if (objectId == undefined) { return; } @@ -123,7 +115,15 @@ async function handlePaused( } } - parentPort?.postMessage({ exceptionHash, frames }); + // We write the local variables to a property on the error object. These can be read by the integration as the error + // event pass through the SDK event pipeline + await session.post('Runtime.callFunctionOn', { + functionDeclaration: `function() { this.${LOCAL_VARIABLES_KEY} = ${JSON.stringify(frames)}; }`, + silent: true, + objectId, + }); + + return objectId; } async function startDebugger(): Promise { @@ -141,13 +141,23 @@ async function startDebugger(): Promise { session.on('Debugger.paused', (event: InspectorNotification) => { isPaused = true; - handlePaused(session, stackParser, event.params as PausedExceptionEvent).then( - () => { + handlePaused(session, event.params as PausedExceptionEvent).then( + async objectId => { // After the pause work is complete, resume execution! - return isPaused ? session.post('Debugger.resume') : Promise.resolve(); + if (isPaused) { + await session.post('Debugger.resume'); + } + + if (objectId) { + // The object must be released after the debugger has resumed or we get a memory leak. + // For node v20, setImmediate is enough here but for v22 a longer delay is required + setTimeout(async () => { + await session.post('Runtime.releaseObject', { objectId }); + }, 1_000); + } }, _ => { - // ignore + // ignore any errors }, ); }); diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 0858d7b307d3..edf8ed5c52c2 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -8,6 +8,7 @@ import { fastifyIntegration, instrumentFastify } from './fastify'; import { genericPoolIntegration, instrumentGenericPool } from './genericPool'; import { graphqlIntegration, instrumentGraphql } from './graphql'; import { hapiIntegration, instrumentHapi } from './hapi'; +import { instrumentKafka, kafkaIntegration } from './kafka'; import { instrumentKoa, koaIntegration } from './koa'; import { instrumentMongo, mongoIntegration } from './mongo'; import { instrumentMongoose, mongooseIntegration } from './mongoose'; @@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { connectIntegration(), genericPoolIntegration(), dataloaderIntegration(), + kafkaIntegration(), ]; } @@ -55,6 +57,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentConnect, instrumentFastify, instrumentHapi, + instrumentKafka, instrumentKoa, instrumentNest, instrumentMongo, diff --git a/packages/node/src/integrations/tracing/kafka.ts b/packages/node/src/integrations/tracing/kafka.ts new file mode 100644 index 000000000000..7bdab00459e1 --- /dev/null +++ b/packages/node/src/integrations/tracing/kafka.ts @@ -0,0 +1,37 @@ +import { KafkaJsInstrumentation } from '@opentelemetry/instrumentation-kafkajs'; + +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; +import { addOriginToSpan } from '../../utils/addOriginToSpan'; + +const INTEGRATION_NAME = 'Kafka'; + +export const instrumentKafka = generateInstrumentOnce( + INTEGRATION_NAME, + () => + new KafkaJsInstrumentation({ + consumerHook(span) { + addOriginToSpan(span, 'auto.kafkajs.otel.consumer'); + }, + producerHook(span) { + addOriginToSpan(span, 'auto.kafkajs.otel.producer'); + }, + }), +); + +const _kafkaIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentKafka(); + }, + }; +}) satisfies IntegrationFn; + +/** + * KafkaJs integration + * + * Capture tracing data for KafkaJs. + */ +export const kafkaIntegration = defineIntegration(_kafkaIntegration); diff --git a/packages/node/src/integrations/tracing/koa.ts b/packages/node/src/integrations/tracing/koa.ts index 7db0225b6fc8..15ddc4658fa9 100644 --- a/packages/node/src/integrations/tracing/koa.ts +++ b/packages/node/src/integrations/tracing/koa.ts @@ -1,5 +1,5 @@ import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa'; -import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -29,7 +29,7 @@ export const instrumentKoa = generateInstrumentOnce( return; } const attributes = spanToJSON(span).data; - const route = attributes && attributes[SEMATTRS_HTTP_ROUTE]; + const route = attributes && attributes[ATTR_HTTP_ROUTE]; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const method: string = info?.context?.request?.method?.toUpperCase() || 'GET'; if (route) { diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 37b94ebc439f..c5ec5367f68c 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -3,9 +3,9 @@ import { DiagLogLevel, diag } from '@opentelemetry/api'; import { Resource } from '@opentelemetry/resources'; import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import { - SEMRESATTRS_SERVICE_NAME, + ATTR_SERVICE_NAME, + ATTR_SERVICE_VERSION, SEMRESATTRS_SERVICE_NAMESPACE, - SEMRESATTRS_SERVICE_VERSION, } from '@opentelemetry/semantic-conventions'; import { SDK_VERSION } from '@sentry/core'; import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry'; @@ -130,9 +130,10 @@ export function setupOtel(client: NodeClient): BasicTracerProvider { const provider = new BasicTracerProvider({ sampler: new SentrySampler(client), resource: new Resource({ - [SEMRESATTRS_SERVICE_NAME]: 'node', + [ATTR_SERVICE_NAME]: 'node', + // eslint-disable-next-line deprecation/deprecation [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', - [SEMRESATTRS_SERVICE_VERSION]: SDK_VERSION, + [ATTR_SERVICE_VERSION]: SDK_VERSION, }), forceFlushTimeoutMillis: 500, }); diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 25ff6de8bf97..cca5c304a672 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nuxt", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Nuxt (EXPERIMENTAL)", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nuxt", @@ -43,15 +43,15 @@ }, "dependencies": { "@nuxt/kit": "^3.12.2", - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", "@sentry/rollup-plugin": "2.22.3", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@sentry/vite-plugin": "2.22.3", - "@sentry/vue": "8.29.0" + "@sentry/vue": "8.30.0" }, "devDependencies": { "@nuxt/module-builder": "0.8.1", diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 99fd7e84a6c8..ab9edb2a7e8f 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/opentelemetry", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry utilities for OpenTelemetry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry", @@ -39,23 +39,23 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1", - "@opentelemetry/instrumentation": "^0.52.1", - "@opentelemetry/sdk-trace-base": "^1.25.1", - "@opentelemetry/semantic-conventions": "^1.25.1" + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "devDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.25.1", "@opentelemetry/core": "^1.25.1", - "@opentelemetry/sdk-trace-base": "^1.25.1", - "@opentelemetry/semantic-conventions": "^1.25.1" + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 4ed5a15532d2..387943cf9cf0 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -3,9 +3,8 @@ import { INVALID_TRACEID } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; import { propagation, trace } from '@opentelemetry/api'; import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; -import { SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { ATTR_URL_FULL, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; import type { continueTrace } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_URL_FULL } from '@sentry/core'; import { hasTracingEnabled } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; @@ -294,7 +293,9 @@ function getExistingBaggage(carrier: unknown): string | undefined { */ function getCurrentURL(span: Span): string | undefined { const spanData = spanToJSON(span).data; - const urlAttribute = spanData?.[SEMATTRS_HTTP_URL] || spanData?.[SEMANTIC_ATTRIBUTE_URL_FULL]; + // `ATTR_URL_FULL` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_URL`, for now. + // eslint-disable-next-line deprecation/deprecation + const urlAttribute = spanData?.[SEMATTRS_HTTP_URL] || spanData?.[ATTR_URL_FULL]; if (urlAttribute) { return urlAttribute; } diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 03d47989ae1d..3438b4b6bbca 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -5,10 +5,8 @@ import { TraceState } from '@opentelemetry/core'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; import { - SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, - SEMANTIC_ATTRIBUTE_URL_FULL, hasTracingEnabled, sampleSpan, } from '@sentry/core'; @@ -16,7 +14,12 @@ import type { Client, SpanAttributes } from '@sentry/types'; import { logger } from '@sentry/utils'; import { SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, SENTRY_TRACE_STATE_URL } from './constants'; -import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { + ATTR_HTTP_REQUEST_METHOD, + ATTR_URL_FULL, + SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_URL, +} from '@opentelemetry/semantic-conventions'; import { DEBUG_BUILD } from './debug-build'; import { getPropagationContextFromSpan } from './propagator'; import { getSamplingDecision } from './utils/getSamplingDecision'; @@ -52,13 +55,13 @@ export class SentrySampler implements Sampler { return wrapSamplingDecision({ decision: undefined, context, spanAttributes }); } + // `ATTR_HTTP_REQUEST_METHOD` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_METHOD`, for now. + // eslint-disable-next-line deprecation/deprecation + const maybeSpanHttpMethod = spanAttributes[SEMATTRS_HTTP_METHOD] || spanAttributes[ATTR_HTTP_REQUEST_METHOD]; + // If we have a http.client span that has no local parent, we never want to sample it // but we want to leave downstream sampling decisions up to the server - if ( - spanKind === SpanKind.CLIENT && - (spanAttributes[SEMATTRS_HTTP_METHOD] || spanAttributes[SEMANTIC_ATTRIBUTE_HTTP_REQUEST_METHOD]) && - (!parentSpan || parentContext?.isRemote) - ) { + if (spanKind === SpanKind.CLIENT && maybeSpanHttpMethod && (!parentSpan || parentContext?.isRemote)) { return wrapSamplingDecision({ decision: undefined, context, spanAttributes }); } @@ -109,7 +112,7 @@ export class SentrySampler implements Sampler { [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: sampleRate, }; - const method = `${spanAttributes[SEMATTRS_HTTP_METHOD]}`.toUpperCase(); + const method = `${maybeSpanHttpMethod}`.toUpperCase(); if (method === 'OPTIONS' || method === 'HEAD') { DEBUG_BUILD && logger.log(`[Tracing] Not sampling span because HTTP method is '${method}' for ${spanName}`); @@ -198,7 +201,9 @@ function getBaseTraceState(context: Context, spanAttributes: SpanAttributes): Tr let traceState = parentContext?.traceState || new TraceState(); // We always keep the URL on the trace state, so we can access it in the propagator - const url = spanAttributes[SEMATTRS_HTTP_URL] || spanAttributes[SEMANTIC_ATTRIBUTE_URL_FULL]; + // `ATTR_URL_FULL` is the new attribute, but we still support the old one, `ATTR_HTTP_URL`, for now. + // eslint-disable-next-line deprecation/deprecation + const url = spanAttributes[SEMATTRS_HTTP_URL] || spanAttributes[ATTR_URL_FULL]; if (url && typeof url === 'string') { traceState = traceState.set(SENTRY_TRACE_STATE_URL, url); } diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 5714a3d93970..18c935863b75 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -1,7 +1,7 @@ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_STATUS_CODE } from '@opentelemetry/semantic-conventions'; +import { ATTR_HTTP_RESPONSE_STATUS_CODE, SEMATTRS_HTTP_STATUS_CODE } from '@opentelemetry/semantic-conventions'; import { captureEvent, getCapturedScopesOnSpan, @@ -345,6 +345,7 @@ function removeSentryAttributes(data: Record): Record { data['otel.kind'] = SpanKind[span.kind]; } - if (attributes[SEMATTRS_HTTP_STATUS_CODE]) { - const statusCode = attributes[SEMATTRS_HTTP_STATUS_CODE] as string; - data['http.response.status_code'] = statusCode; + // eslint-disable-next-line deprecation/deprecation + const maybeHttpStatusCodeAttribute = attributes[SEMATTRS_HTTP_STATUS_CODE]; + if (maybeHttpStatusCodeAttribute) { + data[ATTR_HTTP_RESPONSE_STATUS_CODE] = maybeHttpStatusCodeAttribute as string; } const requestData = getRequestSpanData(span); diff --git a/packages/opentelemetry/src/utils/getRequestSpanData.ts b/packages/opentelemetry/src/utils/getRequestSpanData.ts index 8ce4419c925d..ee723b3ca335 100644 --- a/packages/opentelemetry/src/utils/getRequestSpanData.ts +++ b/packages/opentelemetry/src/utils/getRequestSpanData.ts @@ -1,6 +1,11 @@ import type { Span } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; -import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; +import { + ATTR_HTTP_REQUEST_METHOD, + ATTR_URL_FULL, + SEMATTRS_HTTP_METHOD, + SEMATTRS_HTTP_URL, +} from '@opentelemetry/semantic-conventions'; import type { SanitizedRequestData } from '@sentry/types'; import { getSanitizedUrlString, parseUrl } from '@sentry/utils'; @@ -15,9 +20,17 @@ export function getRequestSpanData(span: Span | ReadableSpan): Partial = { - url: span.attributes[SEMATTRS_HTTP_URL] as string | undefined, - 'http.method': span.attributes[SEMATTRS_HTTP_METHOD] as string | undefined, + url: maybeUrlAttribute, + // eslint-disable-next-line deprecation/deprecation + 'http.method': (span.attributes[ATTR_HTTP_REQUEST_METHOD] || span.attributes[SEMATTRS_HTTP_METHOD]) as + | string + | undefined, }; // Default to GET if URL is set but method is not @@ -26,9 +39,8 @@ export function getRequestSpanData(span: Span | ReadableSpan): Partial { [SEMATTRS_HTTP_METHOD]: 'GET', [SEMATTRS_HTTP_URL]: 'https://www.example.com/my-path/123', [SEMATTRS_HTTP_TARGET]: '/my-path/123', - [SEMATTRS_HTTP_ROUTE]: '/my-path/:id', + [ATTR_HTTP_ROUTE]: '/my-path/:id', }, 'test name', SpanKind.CLIENT, @@ -312,7 +313,7 @@ describe('getSanitizedUrl', () => { [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', [SEMATTRS_HTTP_METHOD]: 'GET', [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_ROUTE]: '/my-route', + [ATTR_HTTP_ROUTE]: '/my-route', [SEMATTRS_HTTP_HOST]: 'example.com:80', [SEMATTRS_HTTP_STATUS_CODE]: 200, }, @@ -384,7 +385,7 @@ describe('getSanitizedUrl', () => { [SEMATTRS_HTTP_URL]: 'http://example.com/?what=true', [SEMATTRS_HTTP_METHOD]: 'GET', [SEMATTRS_HTTP_TARGET]: '/?what=true', - [SEMATTRS_HTTP_ROUTE]: '/my-route', + [ATTR_HTTP_ROUTE]: '/my-route', [SEMATTRS_HTTP_HOST]: 'example.com:80', [SEMATTRS_HTTP_STATUS_CODE]: 200, }, diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index 02b9fdaa7263..0cf4f6c378e8 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/profiling-node", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Node.js Profiling", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/profiling-node", @@ -75,10 +75,10 @@ "test": "cross-env SENTRY_PROFILER_BINARY_DIR=lib jest --config jest.config.js" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "detect-libc": "^2.0.2", "node-abi": "^3.61.0" }, diff --git a/packages/profiling-node/rollup.npm.config.mjs b/packages/profiling-node/rollup.npm.config.mjs index 1cc4f0936954..12492b7c83e8 100644 --- a/packages/profiling-node/rollup.npm.config.mjs +++ b/packages/profiling-node/rollup.npm.config.mjs @@ -1,12 +1,49 @@ import commonjs from '@rollup/plugin-commonjs'; -import esmshim from '@rollup/plugin-esm-shim'; import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; -export default makeNPMConfigVariants( +export const ESMShim = ` +import cjsUrl from 'node:url'; +import cjsPath from 'node:path'; +import cjsModule from 'node:module'; + +if(typeof __filename === 'undefined'){ + globalThis.__filename = cjsUrl.fileURLToPath(import.meta.url); +} + +if(typeof __dirname === 'undefined'){ + globalThis.__dirname = cjsPath.dirname(__filename); +} + +if(typeof require === 'undefined'){ + globalThis.require = cjsModule.createRequire(import.meta.url); +} +`; + +function makeESMShimPlugin(shim) { + return { + transform(code) { + const SHIM_REGEXP = /\/\/ #START_SENTRY_ESM_SHIM[\s\S]*?\/\/ #END_SENTRY_ESM_SHIM/; + return code.replace(SHIM_REGEXP, shim); + }, + }; +} + +const variants = makeNPMConfigVariants( makeBaseNPMConfig({ packageSpecificConfig: { output: { dir: 'lib', preserveModules: false }, - plugins: [commonjs(), esmshim()], + plugins: [commonjs()], }, }), ); + +for (const variant of variants) { + if (variant.output.format === 'esm') { + variant.plugins.push(makeESMShimPlugin(ESMShim)); + } else { + // Remove the ESM shim comment + variant.plugins.push(makeESMShimPlugin('')); + } +} + +export default variants; diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts index 9ab470e2ca70..fb739a939e77 100644 --- a/packages/profiling-node/src/cpu_profiler.ts +++ b/packages/profiling-node/src/cpu_profiler.ts @@ -15,6 +15,12 @@ import type { } from './types'; import type { ProfileFormat } from './types'; +// #START_SENTRY_ESM_SHIM +// When building for ESM, we shim require to use createRequire and __dirname. +// We need to do this because .node extensions in esm are not supported. +// The comment below this line exists as a placeholder for where to insert the shim. +// #END_SENTRY_ESM_SHIM + const stdlib = familySync(); const platform = process.env['BUILD_PLATFORM'] || _platform(); const arch = process.env['BUILD_ARCH'] || _arch(); diff --git a/packages/react/package.json b/packages/react/package.json index 9a9125fb2f40..25933bb98511 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for React.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { diff --git a/packages/remix/package.json b/packages/remix/package.json index 3e9151629322..ada7991ead3c 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/remix", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Remix", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix", @@ -55,12 +55,12 @@ "@opentelemetry/instrumentation-http": "0.53.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.35.0", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/react": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/react": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "glob": "^10.3.4", "opentelemetry-instrumentation-remix": "0.7.1", "yargs": "^17.6.0" diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 457dcb9f8685..37161b41715e 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -67,6 +67,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + kafkaIntegration, koaIntegration, lastEventId, linkedErrorsIntegration, diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index e936346db22d..3c1934663de7 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-canvas", - "version": "8.29.0", + "version": "8.30.0", "description": "Replay canvas integration", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -68,10 +68,10 @@ "@sentry-internal/rrweb": "2.26.0" }, "dependencies": { - "@sentry-internal/replay": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/replay": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "engines": { "node": ">=14.18" diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index f06aa881e3ab..8a565e19a162 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay", - "version": "8.29.0", + "version": "8.30.0", "description": "User replays for Sentry", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -68,7 +68,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/replay-worker": "8.29.0", + "@sentry-internal/replay-worker": "8.30.0", "@sentry-internal/rrweb": "2.26.0", "@sentry-internal/rrweb-snapshot": "2.26.0", "fflate": "^0.8.1", @@ -76,10 +76,10 @@ "jsdom-worker": "^0.2.1" }, "dependencies": { - "@sentry-internal/browser-utils": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/browser-utils": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "engines": { "node": ">=14.18" diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index b48ac787543b..06f81b6982c6 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -377,7 +377,19 @@ export class ReplayContainer implements ReplayContainerInterface { // When running in error sampling mode, we need to overwrite `checkoutEveryNms` // Without this, it would record forever, until an error happens, which we don't want // instead, we'll always keep the last 60 seconds of replay before an error happened - ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }), + ...(this.recordingMode === 'buffer' + ? { checkoutEveryNms: BUFFER_CHECKOUT_TIME } + : // Otherwise, use experimental option w/ min checkout time of 6 minutes + // This is to improve playback seeking as there could potentially be + // less mutations to process in the worse cases. + // + // checkout by "N" events is probably ideal, but means we have less + // control about the number of checkouts we make (which generally + // increases replay size) + this._options._experiments.continuousCheckout && { + // Minimum checkout time is 6 minutes + checkoutEveryNms: Math.max(360_000, this._options._experiments.continuousCheckout), + }), emit: getHandleRecordingEmit(this), onMutation: this._onMutationHandler, ...(canvasOptions @@ -1226,27 +1238,29 @@ export class ReplayContainer implements ReplayContainerInterface { // TODO FN: Evaluate if we want to stop here, or remove this again? } - // this._flushLock acts as a lock so that future calls to `_flush()` - // will be blocked until this promise resolves + const _flushInProgress = !!this._flushLock; + + // this._flushLock acts as a lock so that future calls to `_flush()` will + // be blocked until current flush is finished (i.e. this promise resolves) if (!this._flushLock) { this._flushLock = this._runFlush(); - await this._flushLock; - this._flushLock = undefined; - return; } - // Wait for previous flush to finish, then call the debounced `_flush()`. - // It's possible there are other flush requests queued and waiting for it - // to resolve. We want to reduce all outstanding requests (as well as any - // new flush requests that occur within a second of the locked flush - // completing) into a single flush. - try { await this._flushLock; } catch (err) { - DEBUG_BUILD && logger.error(err); + this.handleException(err); } finally { - this._debouncedFlush(); + this._flushLock = undefined; + + if (_flushInProgress) { + // Wait for previous flush to finish, then call the debounced + // `_flush()`. It's possible there are other flush requests queued and + // waiting for it to resolve. We want to reduce all outstanding + // requests (as well as any new flush requests that occur within a + // second of the locked flush completing) into a single flush. + this._debouncedFlush(); + } } }; diff --git a/packages/replay-internal/src/types/replay.ts b/packages/replay-internal/src/types/replay.ts index 1e510e2bc519..0605ba97449a 100644 --- a/packages/replay-internal/src/types/replay.ts +++ b/packages/replay-internal/src/types/replay.ts @@ -232,6 +232,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions { _experiments: Partial<{ captureExceptions: boolean; traceInternals: boolean; + continuousCheckout: number; }>; } diff --git a/packages/replay-internal/src/util/handleRecordingEmit.ts b/packages/replay-internal/src/util/handleRecordingEmit.ts index 6b87845d793f..0467edefa9a2 100644 --- a/packages/replay-internal/src/util/handleRecordingEmit.ts +++ b/packages/replay-internal/src/util/handleRecordingEmit.ts @@ -58,9 +58,14 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa return false; } + const session = replay.session; + // Additionally, create a meta event that will capture certain SDK settings. // In order to handle buffer mode, this needs to either be done when we - // receive checkout events or at flush time. + // receive checkout events or at flush time. We have an experimental mode + // to perform multiple checkouts a session (the idea is to improve + // seeking during playback), so also only include if segmentId is 0 + // (handled in `addSettingsEvent`). // // `isCheckout` is always true, but want to be explicit that it should // only be added for checkouts @@ -72,22 +77,22 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa // of the previous session. Do not immediately flush in this case // to avoid capturing only the checkout and instead the replay will // be captured if they perform any follow-up actions. - if (replay.session && replay.session.previousSessionId) { + if (session && session.previousSessionId) { return true; } // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer // this should usually be the timestamp of the checkout event, but to be safe... - if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) { + if (replay.recordingMode === 'buffer' && session && replay.eventBuffer) { const earliestEvent = replay.eventBuffer.getEarliestTimestamp(); if (earliestEvent) { DEBUG_BUILD && logger.info(`Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`); - replay.session.started = earliestEvent; + session.started = earliestEvent; if (replay.getOptions().stickySession) { - saveSession(replay.session); + saveSession(session); } } } diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index ffc0a83bb141..52654fa909d3 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -493,6 +493,64 @@ describe('Integration | flush', () => { await replay.start(); }); + it('resets flush lock if runFlush rejects/throws', async () => { + mockRunFlush.mockImplementation( + () => + new Promise((resolve, reject) => { + reject(new Error('runFlush')); + }), + ); + try { + await replay['_flush'](); + } catch { + // do nothing + } + expect(replay['_flushLock']).toBeUndefined(); + }); + + it('resets flush lock when flush is called multiple times before it resolves', async () => { + let _resolve; + mockRunFlush.mockImplementation( + () => + new Promise(resolve => { + _resolve = resolve; + }), + ); + const mockDebouncedFlush: MockedFunction = vi.spyOn(replay, '_debouncedFlush'); + mockDebouncedFlush.mockImplementation(vi.fn); + mockDebouncedFlush.cancel = vi.fn(); + + const results = [replay['_flush'](), replay['_flush']()]; + expect(replay['_flushLock']).not.toBeUndefined(); + + _resolve && _resolve(); + await Promise.all(results); + expect(replay['_flushLock']).toBeUndefined(); + mockDebouncedFlush.mockRestore(); + }); + + it('resets flush lock when flush is called multiple times before it rejects', async () => { + let _reject; + mockRunFlush.mockImplementation( + () => + new Promise((_, reject) => { + _reject = reject; + }), + ); + const mockDebouncedFlush: MockedFunction = vi.spyOn(replay, '_debouncedFlush'); + mockDebouncedFlush.mockImplementation(vi.fn); + mockDebouncedFlush.cancel = vi.fn(); + expect(replay['_flushLock']).toBeUndefined(); + replay['_flush'](); + const result = replay['_flush'](); + expect(replay['_flushLock']).not.toBeUndefined(); + + _reject && _reject(new Error('Throw runFlush')); + await result; + expect(replay['_flushLock']).toBeUndefined(); + mockDebouncedFlush.mockRestore(); + }); + /** * Assuming the user wants to record a session * when calling flush() without replay being enabled diff --git a/packages/replay-internal/test/integration/rrweb.test.ts b/packages/replay-internal/test/integration/rrweb.test.ts index 863baab45bce..4327ddb21de1 100644 --- a/packages/replay-internal/test/integration/rrweb.test.ts +++ b/packages/replay-internal/test/integration/rrweb.test.ts @@ -46,4 +46,44 @@ describe('Integration | rrweb', () => { } `); }); + + it('calls rrweb.record with checkoutEveryNms', async () => { + const { mockRecord } = await resetSdkMock({ + replayOptions: { + _experiments: { + continuousCheckout: 1, + }, + }, + sentryOptions: { + replaysOnErrorSampleRate: 0.0, + replaysSessionSampleRate: 1.0, + }, + }); + + expect(mockRecord.mock.calls[0]?.[0]).toMatchInlineSnapshot(` + { + "blockSelector": ".sentry-block,[data-sentry-block],base[href="/"],img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]", + "checkoutEveryNms": 360000, + "collectFonts": true, + "emit": [Function], + "errorHandler": [Function], + "ignoreSelector": ".sentry-ignore,[data-sentry-ignore],input[type="file"]", + "inlineImages": false, + "inlineStylesheet": true, + "maskAllInputs": true, + "maskAllText": true, + "maskAttributeFn": [Function], + "maskInputFn": undefined, + "maskInputOptions": { + "password": true, + }, + "maskTextFn": undefined, + "maskTextSelector": ".sentry-mask,[data-sentry-mask]", + "onMutation": [Function], + "slimDOMOptions": "all", + "unblockSelector": "", + "unmaskTextSelector": "", + } + `); + }); }); diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index 803c5e4fb7d0..565e1d63e485 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-worker", - "version": "8.29.0", + "version": "8.30.0", "description": "Worker for @sentry-internal/replay", "main": "build/esm/index.js", "module": "build/esm/index.js", diff --git a/packages/solid/package.json b/packages/solid/package.json index 41dc8a30a1b1..25d261c89fc9 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solid", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Solid", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solid", @@ -44,10 +44,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "peerDependencies": { "@solidjs/router": "^0.13.4", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index bfbafd7a0232..ed3936a44a72 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solidstart", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Solid Start", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solidstart", @@ -66,13 +66,13 @@ } }, "dependencies": { - "@opentelemetry/instrumentation": "^0.52.1", - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/solid": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/solid": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@sentry/vite-plugin": "2.22.3" }, "devDependencies": { diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index 995f58d057e3..794106eca715 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -58,6 +58,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + kafkaIntegration, koaIntegration, lastEventId, linkedErrorsIntegration, diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 5f08a093dda0..e860f2884a4b 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/svelte", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Svelte", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "magic-string": "^0.30.0" }, "peerDependencies": { diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 865f151fea41..32446d0b2246 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/sveltekit", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for SvelteKit", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit", @@ -40,12 +40,12 @@ } }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/node": "8.29.0", - "@sentry/opentelemetry": "8.29.0", - "@sentry/svelte": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0", + "@sentry/core": "8.30.0", + "@sentry/node": "8.30.0", + "@sentry/opentelemetry": "8.30.0", + "@sentry/svelte": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0", "@sentry/vite-plugin": "2.22.3", "magic-string": "0.30.7", "magicast": "0.2.8", diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index d57ec35bd7cc..e2902afb400b 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -60,6 +60,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + kafkaIntegration, koaIntegration, lastEventId, linkedErrorsIntegration, diff --git a/packages/types/package.json b/packages/types/package.json index 9e3527b53bde..d30fe8aee0aa 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/types", - "version": "8.29.0", + "version": "8.30.0", "description": "Types for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", diff --git a/packages/typescript/package.json b/packages/typescript/package.json index a184dce7e083..a8cc376a4c2c 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/typescript", - "version": "8.29.0", + "version": "8.30.0", "description": "Typescript configuration used at Sentry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript", diff --git a/packages/utils/package.json b/packages/utils/package.json index c0e579b3aeea..ba097a67668b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/utils", - "version": "8.29.0", + "version": "8.30.0", "description": "Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/utils", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.30.0" }, "devDependencies": { "@types/array.prototype.flat": "^1.2.1", diff --git a/packages/utils/src/version.ts b/packages/utils/src/version.ts index 1563ae1ecacf..a6851e289b1d 100644 --- a/packages/utils/src/version.ts +++ b/packages/utils/src/version.ts @@ -1 +1 @@ -export const SDK_VERSION = '8.29.0'; +export const SDK_VERSION = '8.30.0'; diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 6a486415c6fa..0e211e5de086 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vercel-edge", - "version": "8.29.0", + "version": "8.30.0", "description": "Offical Sentry SDK for the Vercel Edge Runtime", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "devDependencies": { "@edge-runtime/types": "3.0.1" diff --git a/packages/vue/package.json b/packages/vue/package.json index 49d6940f4fec..757033553f6c 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vue", - "version": "8.29.0", + "version": "8.30.0", "description": "Official Sentry SDK for Vue.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "peerDependencies": { "vue": "2.x || 3.x" diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 739bfb05c858..737839b12235 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/wasm", - "version": "8.29.0", + "version": "8.30.0", "description": "Support for WASM.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm", @@ -39,10 +39,10 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/browser": "8.30.0", + "@sentry/core": "8.30.0", + "@sentry/types": "8.30.0", + "@sentry/utils": "8.30.0" }, "scripts": { "build": "run-p build:transpile build:bundle build:types", diff --git a/yarn.lock b/yarn.lock index 48f27630308e..692681043c70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7176,6 +7176,14 @@ "@opentelemetry/redis-common" "^0.36.2" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-kafkajs@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz#6687bce4dac8b90ef8ccbf1b662d5d1e95a34414" + integrity sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation-koa@0.43.0": version "0.43.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz#963fd192a1b5f6cbae5dabf4ec82e3105cbb23b1" @@ -7280,7 +7288,7 @@ semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0", "@opentelemetry/instrumentation@^0.52.1": +"@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0": version "0.52.1" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz#2e7e46a38bd7afbf03cf688c862b0b43418b7f48" integrity sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw== @@ -7337,7 +7345,7 @@ "@opentelemetry/core" "1.25.0" "@opentelemetry/semantic-conventions" "1.25.0" -"@opentelemetry/resources@1.25.1", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.25.1", "@opentelemetry/resources@^1.8.0": +"@opentelemetry/resources@1.25.1", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.8.0": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.25.1.tgz#bb9a674af25a1a6c30840b755bc69da2796fefbb" integrity sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ== @@ -7345,6 +7353,14 @@ "@opentelemetry/core" "1.25.1" "@opentelemetry/semantic-conventions" "1.25.1" +"@opentelemetry/resources@1.26.0", "@opentelemetry/resources@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" + integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/resources@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-0.12.0.tgz#5eb287c3032a2bebb2bb9f69b44bd160d2a7d591" @@ -7388,7 +7404,7 @@ "@opentelemetry/resources" "1.23.0" "@opentelemetry/semantic-conventions" "1.23.0" -"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.23.0", "@opentelemetry/sdk-trace-base@^1.25.1": +"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.23.0": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz#cbc1e60af255655d2020aa14cde17b37bd13df37" integrity sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw== @@ -7397,6 +7413,15 @@ "@opentelemetry/resources" "1.25.1" "@opentelemetry/semantic-conventions" "1.25.1" +"@opentelemetry/sdk-trace-base@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" + integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/semantic-conventions@1.23.0": version "1.23.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.23.0.tgz#627f2721b960fe586b7f72a07912cb7699f06eef" @@ -7407,7 +7432,7 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.0.tgz#390eb4d42a29c66bdc30066af9035645e9bb7270" integrity sha512-M+kkXKRAIAiAP6qYyesfrC5TOmDpDVtsxuGfPcqd9B/iBrac+E14jYwrgm0yZBUIbIP2OnqC3j+UgkXLm1vxUQ== -"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.23.0", "@opentelemetry/semantic-conventions@^1.25.1": +"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.23.0": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz#0deecb386197c5e9c2c28f2f89f51fb8ae9f145e" integrity sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ== @@ -22409,6 +22434,11 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" +kafkajs@2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-2.2.4.tgz#59e6e16459d87fdf8b64be73970ed5aa42370a5b" + integrity sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA== + kareem@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93"