From 0d79b51e7ecbe26b1b235ed92410d2d07febdcde Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:20:10 +0200 Subject: [PATCH 01/16] fix(node): Update OpenTelemetry instrumentation package for solidstart and opentelemetry (#13640) Looks like we overlooked two packages when updating deps previously in #13587. See: https://github.com/getsentry/sentry-javascript/pull/13587#issuecomment-2339419127 Closes: #13219 --- packages/opentelemetry/package.json | 2 +- packages/solidstart/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 99fd7e84a6c8..688c5e01f2b6 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -46,7 +46,7 @@ "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1", - "@opentelemetry/instrumentation": "^0.52.1", + "@opentelemetry/instrumentation": "^0.53.0", "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/semantic-conventions": "^1.25.1" }, diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index bfbafd7a0232..3a56d6d2b9e7 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -66,7 +66,7 @@ } }, "dependencies": { - "@opentelemetry/instrumentation": "^0.52.1", + "@opentelemetry/instrumentation": "^0.53.0", "@sentry/core": "8.29.0", "@sentry/node": "8.29.0", "@sentry/opentelemetry": "8.29.0", From 0c0c7f6a5e68fd1cb495d903adac663586be6ce5 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 10 Sep 2024 09:28:33 +0200 Subject: [PATCH 02/16] fix: incorrect property name in `CHANGELOG.md` (#13635) I failed to update the description in my PR when we improved the property name after some discussion in the PR, so the wrong property name was used in the changelog: https://github.com/getsentry/sentry-javascript/blob/bcf571d9954094be76a99edbb12c23eff7f7b5dc/packages/node/src/types.ts#L20 Thanks to @torickjdavis for reporting this [here](https://github.com/getsentry/sentry-javascript/issues/12414#issuecomment-2339236336)! --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc50808b4f11..da1f91d499bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ import * as Sentry from '@sentry/node'; Sentry.init({ dsn: '__PUBLIC_DSN__', - registerEsmLoaderHooks: { onlyHookedModules: true }, + registerEsmLoaderHooks: { onlyIncludeInstrumentedModules: true }, }); ``` From 703b5d4e42e6df70a5a676ab0d5b5ef2a38df1b0 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:37:08 +0200 Subject: [PATCH 03/16] fix(node): Update OpenTelemetry instrumentation package for solidstart and opentelemetry (#13642) Part 2 because I forgot to update the lockfile in the [previous PR](https://github.com/getsentry/sentry-javascript/pull/13640). --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 86f0c70cbdbd..69c732db9498 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7273,7 +7273,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== From 7fa366ffc4ae849358747607d154eb36f67861ac Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 10 Sep 2024 05:05:07 -0400 Subject: [PATCH 04/16] ref(profiling): Conditionally shim cjs globals (#13267) The shims should only be applied if the globals are not present, else it results in double decl and a runtime error. The profiling SDK should gracefully handle env where the shims are already provided. I couldn't find a way to modify the shim as it is hardcoded in the plugin we are using so I went with the replace plugin approach and a placeholder value #poormansmacros. --- .github/workflows/build.yml | 2 +- .../node-profiling/build.shimmed.mjs | 29 +++++++++++++ .../node-profiling/package.json | 6 +-- packages/profiling-node/rollup.npm.config.mjs | 43 +++++++++++++++++-- packages/profiling-node/src/cpu_profiler.ts | 6 +++ 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling/build.shimmed.mjs 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/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/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(); From 4c6dd808f221b4f64e5505f1d545bdad1d0820e7 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 10 Sep 2024 11:08:00 +0200 Subject: [PATCH 05/16] fix(node): Remove ambiguity and race conditions when matching local variables to exceptions (#13501) Closes #13415 This PR only modifies the async version of this integration which is used for Node > v19. I tried applying similar changes to the sync integration and I cannot get it to work without causing memory leaks. @Bruno-DaSilva has been helping me explore different ways to fix a few fundamental issues with the local variables integration. Bruno found a way to [write to the error object](https://github.com/getsentry/sentry-javascript/issues/13415#issuecomment-2313754556) from the debugger which removes any ambiguity over which variables go with which exception. This allows us to remove the stack parsing and hashing which we were using previously to match up exceptions. Rather than write the `objectId` to the error, I have used this to write the entire local variables array directly to the error object. This completely negates the need to post the local variables from the worker thread which removes any possibility of race conditions. We then later pull the local variables directly from `hint.originalException.__SENTRY_ERROR_LOCAL_VARIABLES__`. --- .../integrations/local-variables/common.ts | 29 ++------- .../local-variables/inspector.d.ts | 8 +++ .../local-variables/local-variables-async.ts | 62 ++++++++----------- .../local-variables/local-variables-sync.ts | 26 +++++++- .../integrations/local-variables/worker.ts | 48 ++++++++------ 5 files changed, 93 insertions(+), 80 deletions(-) diff --git a/packages/node/src/integrations/local-variables/common.ts b/packages/node/src/integrations/local-variables/common.ts index 67c8d6d43d81..58ccea70d6de 100644 --- a/packages/node/src/integrations/local-variables/common.ts +++ b/packages/node/src/integrations/local-variables/common.ts @@ -1,10 +1,14 @@ import type { Debugger } from 'node:inspector'; -import type { StackFrame, StackParser } from '@sentry/types'; export type Variables = Record; 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 }, ); }); From 017e8f95be53b85792132f095ac722458d0676cc Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 10 Sep 2024 11:08:26 +0200 Subject: [PATCH 06/16] fix(nextjs): Widen removal of 404 transactions (#13628) On app router, transactions like `GET /404` get created that we don't like. --- .../nextjs-13/tests/server/404.test.ts | 23 +++++++++++++++++++ packages/nextjs/src/server/index.ts | 10 ++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/404.test.ts 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/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 1132a6e1eed2..2348ba203021 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -219,8 +219,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; } From cef6986afc85f3eaacc244829982c3d588131f1b Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 10 Sep 2024 11:27:28 +0200 Subject: [PATCH 07/16] feat(otel): Upgrade @opentelemetry/semantic-conventions to 1.26.0 (#13631) resolves https://github.com/getsentry/sentry-javascript/issues/13627 In 1.26.0 otel-js has updated the deprecations for the attributes based on the new changes to semantic conventions. They also changed the name of some exports, for example: `SEMATTRS_HTTP_ROUTE` -> `ATTR_HTTP_ROUTE`. Some exports names were not able to be changed because they are imported from a subpath export @opentelemetry/semantic-conventions/incubating. This subpath breaks some bundling setups, so we are unable to use it. --- packages/nextjs/package.json | 2 +- packages/nextjs/src/server/index.ts | 18 ++++++++++--- packages/node/package.json | 6 ++--- packages/node/src/integrations/tracing/koa.ts | 4 +-- packages/node/src/sdk/initOtel.ts | 9 ++++--- packages/opentelemetry/package.json | 8 +++--- packages/opentelemetry/src/propagator.ts | 7 +++--- packages/opentelemetry/src/sampler.ts | 25 +++++++++++-------- packages/opentelemetry/src/spanExporter.ts | 9 ++++--- .../src/utils/getRequestSpanData.ts | 24 +++++++++++++----- .../src/utils/isSentryRequest.ts | 8 +++--- packages/opentelemetry/src/utils/mapStatus.ts | 10 ++++++-- .../src/utils/parseSpanDescription.ts | 25 +++++++++++-------- .../opentelemetry/test/helpers/initOtel.ts | 9 ++++--- packages/opentelemetry/test/trace.test.ts | 1 + .../test/utils/getRequestSpanData.test.ts | 1 + .../test/utils/mapStatus.test.ts | 1 + .../test/utils/parseSpanDescription.test.ts | 9 ++++--- yarn.lock | 23 ++++++++++++++--- 19 files changed, 133 insertions(+), 66 deletions(-) diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 9880e0ffb28e..8e4fd27370da 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -69,7 +69,7 @@ }, "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", diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 2348ba203021..96c97371df24 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -10,7 +10,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 +155,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 +176,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']); } } diff --git a/packages/node/package.json b/packages/node/package.json index a77c1b56b215..63c6f719a2d4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -87,9 +87,9 @@ "@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", 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/opentelemetry/package.json b/packages/opentelemetry/package.json index 688c5e01f2b6..cedb64423433 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -47,15 +47,15 @@ "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.53.0", - "@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" }, "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..d00319ec2c98 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, @@ -358,9 +358,10 @@ function getData(span: ReadableSpan): 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/yarn.lock b/yarn.lock index 69c732db9498..22770c583774 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7330,7 +7330,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== @@ -7338,6 +7338,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" @@ -7381,7 +7389,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== @@ -7390,6 +7398,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" @@ -7400,7 +7417,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== From 1285e4bd41ca21cffcf2ca42e060170f4014ec74 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 10 Sep 2024 12:39:27 +0300 Subject: [PATCH 08/16] feat(node): Add `kafkajs` integration (#13528) --- .../node-integration-tests/package.json | 1 + .../suites/tracing/kafkajs/docker-compose.yml | 7 ++ .../suites/tracing/kafkajs/scenario.js | 63 +++++++++++++++ .../suites/tracing/kafkajs/test.ts | 55 +++++++++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/package.json | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/tracing/index.ts | 3 + .../node/src/integrations/tracing/kafka.ts | 37 +++++++++ packages/remix/src/index.server.ts | 1 + packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + yarn.lock | 77 +++++-------------- 16 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/kafkajs/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/kafkajs/scenario.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/kafkajs/test.ts create mode 100644 packages/node/src/integrations/tracing/kafka.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 486b93bba24f..65c204a30dd8 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -48,6 +48,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/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/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/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/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/node/package.json b/packages/node/package.json index 63c6f719a2d4..9deef7bc4fe3 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -78,6 +78,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", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 6ce3c325e3ff..d4cbcb9544a9 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/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 46a9f79e4caa..69ffc24a8be2 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -7,6 +7,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'; @@ -39,6 +40,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { koaIntegration(), connectIntegration(), genericPoolIntegration(), + kafkaIntegration(), ]; } @@ -53,6 +55,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/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/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/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/yarn.lock b/yarn.lock index 22770c583774..a9e1df0710b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7169,6 +7169,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" @@ -9685,17 +9693,8 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": + name "@types/history-4" version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -10023,15 +10022,7 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14": - version "5.1.14" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" - integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -22431,6 +22422,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" @@ -28434,7 +28430,8 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0": +"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: + name react-router-6 version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -28449,13 +28446,6 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -30934,16 +30924,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31055,14 +31036,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -34031,16 +34005,7 @@ wrangler@^3.67.1: optionalDependencies: fsevents "~2.3.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From a2d1b2c37f4f69a1dfc4778b2dbe52643a43e14b Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 10 Sep 2024 15:07:37 +0200 Subject: [PATCH 09/16] fix(browser): Ensure Standalone CLS span timestamps are correct (#13649) Fix a bug in the initial experimental CLS standalone span implementation. Previously we'd add the CLS start timestamp value in ms to the performance time origin timestamp which was already converted to seconds. Ensure that we first add time origin and the CLS start timestamp and then convert to seconds --------- Co-authored-by: Abhijeet Prasad --- .../web-vitals-cls-standalone-spans/test.ts | 41 +++++++++++++++++++ packages/browser-utils/src/metrics/cls.ts | 7 ++-- packages/browser-utils/src/metrics/utils.ts | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) 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/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; From ae4451df8b9de6776a285765b730738097a66b3e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 10 Sep 2024 15:11:54 +0200 Subject: [PATCH 10/16] ci: Add some additional GH project automation (#13629) Follow up to https://github.com/getsentry/sentry-javascript/pull/13608, after some tweaks and a rebase! * When a PR is opened in draft mode, move to "In Progress" on the board * When a PR is opened for review, move to "In Review" on the board * When a PR is closed but not merged, move it directly to "Done" (instead of "Ready for Release") Note that for now, this only applies to PRs that are on the board themselves, not to PRs linked to an issue. --- .github/workflows/project-automation.yml | 98 ++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/project-automation.yml 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" + From aba68f7e0baf65e3326b6af5afd1e1d711ca96c9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 10 Sep 2024 13:58:35 +0200 Subject: [PATCH 11/16] meta: Update CHANGELOG for 8.30.0 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da1f91d499bb..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 From c4fe3378a5c0f17afcf7bdc20d0901f52e450c87 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 10 Sep 2024 13:57:52 +0000 Subject: [PATCH 12/16] release: 8.30.0 --- .../browser-integration-tests/package.json | 4 ++-- .../bundle-analyzer-scenarios/package.json | 2 +- .../clear-cache-gh-action/package.json | 2 +- dev-packages/e2e-tests/package.json | 2 +- .../package.json | 2 +- .../node-integration-tests/package.json | 10 +++++----- dev-packages/overhead-metrics/package.json | 2 +- dev-packages/rollup-utils/package.json | 2 +- dev-packages/size-limit-gh-action/package.json | 2 +- dev-packages/test-utils/package.json | 6 +++--- lerna.json | 2 +- packages/angular/package.json | 10 +++++----- packages/astro/package.json | 12 ++++++------ packages/aws-serverless/package.json | 10 +++++----- packages/browser-utils/package.json | 8 ++++---- packages/browser/package.json | 18 +++++++++--------- packages/bun/package.json | 12 ++++++------ packages/cloudflare/package.json | 8 ++++---- packages/core/package.json | 6 +++--- packages/deno/package.json | 8 ++++---- packages/ember/package.json | 10 +++++----- packages/eslint-config-sdk/package.json | 6 +++--- packages/eslint-plugin-sdk/package.json | 2 +- packages/feedback/package.json | 8 ++++---- packages/gatsby/package.json | 10 +++++----- packages/google-cloud-serverless/package.json | 10 +++++----- packages/integration-shims/package.json | 8 ++++---- packages/nestjs/package.json | 10 +++++----- packages/nextjs/package.json | 16 ++++++++-------- packages/node/package.json | 10 +++++----- packages/nuxt/package.json | 16 ++++++++-------- packages/opentelemetry/package.json | 8 ++++---- packages/profiling-node/package.json | 10 +++++----- packages/react/package.json | 10 +++++----- packages/remix/package.json | 14 +++++++------- packages/replay-canvas/package.json | 10 +++++----- packages/replay-internal/package.json | 12 ++++++------ packages/replay-worker/package.json | 2 +- packages/solid/package.json | 10 +++++----- packages/solidstart/package.json | 14 +++++++------- packages/svelte/package.json | 10 +++++----- packages/sveltekit/package.json | 14 +++++++------- packages/types/package.json | 2 +- packages/typescript/package.json | 2 +- packages/utils/package.json | 4 ++-- packages/utils/src/version.ts | 2 +- packages/vercel-edge/package.json | 8 ++++---- packages/vue/package.json | 10 +++++----- packages/wasm/package.json | 10 +++++----- 49 files changed, 193 insertions(+), 193 deletions(-) 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/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..49d28c745d8f 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": { 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 65c204a30dd8..8c5a7dfe1bc3 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", 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/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/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/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/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/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 8e4fd27370da..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", @@ -71,13 +71,13 @@ "@opentelemetry/instrumentation-http": "0.53.0", "@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/node/package.json b/packages/node/package.json index 9deef7bc4fe3..dace1125f2e4 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", @@ -92,10 +92,10 @@ "@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/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 cedb64423433..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,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" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", 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/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/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-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 3a56d6d2b9e7..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", @@ -67,12 +67,12 @@ }, "dependencies": { "@opentelemetry/instrumentation": "^0.53.0", - "@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", + "@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/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/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", From 02290d1974f0cf2c1df961c9594bd0d67bdb57a4 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 10 Sep 2024 17:19:59 +0200 Subject: [PATCH 13/16] feat(nextjs): Improve Next.js serverside span data quality (#13652) Sometimes we were sending faulty `default` ops for Next.js transactions. This seemed to happen when the HTTP integration couldn't get a handle of the incoming requests (yet to figure out why). This PR: - Backfills the op for faulty transactions - Sets the origin of spans generated by Next.js to `auto` (I couldn't come up with a more specific origin because the second part of it is an op and none are satisfactory) - Remove the `sentry.skip_span_data_inference` which is only used internally. This change is hard to test because it seems to happen flakily that the http integration isn't working. Fixes https://github.com/getsentry/sentry-javascript/issues/13598 --- .../tests/generation-functions.test.ts | 4 ++-- packages/nextjs/src/server/index.ts | 24 +++++++++++++++++++ packages/opentelemetry/src/spanExporter.ts | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) 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/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 96c97371df24..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, @@ -189,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 @@ -286,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/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index d00319ec2c98..18c935863b75 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -345,6 +345,7 @@ function removeSentryAttributes(data: Record): Record Date: Tue, 10 Sep 2024 17:20:18 +0200 Subject: [PATCH 14/16] feat(nextjs): Give app router prefetch requests a `http.server.prefetch` op (#13600) Ref (not complete fix) https://github.com/getsentry/sentry-javascript/issues/13596 This gives Next.js prefetch requests a `http.server.prefetch` op, when a `Next-Router-Prefetch: 1` header is present. In some situations Next.js doesn't seem to attach the header for prefetch requests. Seems like it is only attached when the current route is a dynamic route. --- dev-packages/e2e-tests/package.json | 2 +- packages/node/src/integrations/http.ts | 12 ++++++++++++ .../opentelemetry/src/utils/parseSpanDescription.ts | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 966dab87a6bc..0a375d118749 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -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/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 Date: Tue, 10 Sep 2024 16:08:29 -0230 Subject: [PATCH 15/16] fix(replay): Fixes potential out-of-order segments (#13609) This fixes a potential issue where segments can come in out of order due to our flush "lock" was not being respected in two cases: 1) No current flush in progress, but flush throws an error (this should be rare as the common errors that get thrown should stop the replay completely) 2) Flush is in progress, which skips the code block that releases lock and then calls debouncedFlush. This leaves the lock always set to a resolved (or rejected) promise. This ultimately should not change too much as the flush calls are debounced anyway, but this cleans up the code a bit and also logs any exceptions that may occur. However this can fix issues where segments can come in out of order depending on how long the send request takes. e.g. ![image](https://github.com/user-attachments/assets/ea304892-1c72-4e96-acc6-c714d263980c) where ideally it looks like ![image](https://github.com/user-attachments/assets/8c3e706c-d3b2-43bd-a970-561b32b05458) --- packages/replay-internal/src/replay.ts | 28 ++++----- .../test/integration/flush.test.ts | 58 +++++++++++++++++++ 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index b48ac787543b..cfeb26841911 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -1226,27 +1226,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/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 From e944daa3d71272f87fc324ae3f0b540ed0645b8e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 10 Sep 2024 18:06:44 -0230 Subject: [PATCH 16/16] feat(replay): Add experimental option to allow for a checkout every 6 minutes (#13069) Including more checkouts will improve replayer scrubbing since it will reduce the number of mutations that need to be processed (especially for longer replays). The downside is that it will increase the size of replays since we will have up to 9 more snapshots per replay (max replay duration is 60 minutes / 6 minute checkouts). --- packages/replay-internal/src/replay.ts | 14 ++++++- packages/replay-internal/src/types/replay.ts | 1 + .../src/util/handleRecordingEmit.ts | 15 ++++--- .../test/integration/rrweb.test.ts | 40 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index cfeb26841911..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 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/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": "", + } + `); + }); });