From 60ecb6efad7f0041d7aa641d04ec693cce4bb88f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 14 Oct 2025 20:42:51 +0200 Subject: [PATCH 01/16] feat(node): Allow selective tracking of `pino` loggers --- .../suites/pino/instrument-auto-off.mjs | 8 ++ .../suites/pino/scenario-track.mjs | 23 ++++++ .../suites/pino/scenario.mjs | 5 ++ .../suites/pino/test.ts | 50 ++++++++++++ packages/node-core/src/integrations/pino.ts | 77 +++++++++++++++++-- 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs create mode 100644 dev-packages/node-integration-tests/suites/pino/scenario-track.mjs diff --git a/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs b/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs new file mode 100644 index 000000000000..d2a0408eebcd --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/instrument-auto-off.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: '1.0', + enableLogs: true, + integrations: [Sentry.pinoIntegration({ autoInstrument: false })], +}); diff --git a/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs new file mode 100644 index 000000000000..2e968444a74f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs @@ -0,0 +1,23 @@ +import * as Sentry from '@sentry/node'; +import pino from 'pino'; + +const logger = pino({ name: 'myapp' }); +Sentry.pinoIntegration.trackLogger(logger); + +const loggerIgnore = pino({ name: 'ignore' }); + +loggerIgnore.info('this should be ignored'); + +Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'startup' }, () => { + logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world'); + }); +}); + +setTimeout(() => { + Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'later' }, () => { + logger.error(new Error('oh no')); + }); + }); +}, 1000); diff --git a/dev-packages/node-integration-tests/suites/pino/scenario.mjs b/dev-packages/node-integration-tests/suites/pino/scenario.mjs index ea8dc5e223d0..beb080ac3c42 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario.mjs @@ -3,6 +3,11 @@ import pino from 'pino'; const logger = pino({ name: 'myapp' }); +const ignoredLogger = pino({ name: 'ignored' }); +Sentry.pinoIntegration.untrackLogger(ignoredLogger); + +ignoredLogger.info('this will not be tracked'); + Sentry.withIsolationScope(() => { Sentry.startSpan({ name: 'startup' }, () => { logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world'); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index cc88f650203b..1982c8d686fc 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -173,4 +173,54 @@ conditionalTest({ min: 20 })('Pino integration', () => { .start() .completed(); }); + + test('captures logs when autoInstrument is false and logger is tracked', async () => { + const instrumentPath = join(__dirname, 'instrument-auto-off.mjs'); + + await createRunner(__dirname, 'scenario-track.mjs') + .withMockSentryServer() + .withInstrument(instrumentPath) + .expect({ + log: { + items: [ + { + timestamp: expect.any(Number), + level: 'info', + body: 'hello world', + trace_id: expect.any(String), + severity_number: 9, + attributes: expect.objectContaining({ + 'pino.logger.name': { value: 'myapp', type: 'string' }, + 'pino.logger.level': { value: 30, type: 'integer' }, + user: { value: 'user-id', type: 'string' }, + something: { + type: 'string', + value: '{"more":3,"complex":"nope"}', + }, + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + { + timestamp: expect.any(Number), + level: 'error', + body: 'oh no', + trace_id: expect.any(String), + severity_number: 17, + attributes: expect.objectContaining({ + 'pino.logger.name': { value: 'myapp', type: 'string' }, + 'pino.logger.level': { value: 50, type: 'integer' }, + err: { value: '{}', type: 'string' }, + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + ], + }, + }) + .start() + .completed(); + }); }); diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 6b78bcdb4386..d069f371ed6b 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -1,5 +1,5 @@ import { tracingChannel } from 'node:diagnostics_channel'; -import type { IntegrationFn, LogSeverityLevel } from '@sentry/core'; +import type { Integration, IntegrationFn, LogSeverityLevel } from '@sentry/core'; import { _INTERNAL_captureLog, addExceptionMechanism, @@ -11,6 +11,8 @@ import { } from '@sentry/core'; import { addInstrumentationConfig } from '../sdk/injectLoader'; +const SENTRY_TRACK_SYMBOL = Symbol('sentry-track-pino-logger'); + type LevelMapping = { // Fortunately pino uses the same levels as Sentry labels: { [level: number]: LogSeverityLevel }; @@ -18,6 +20,7 @@ type LevelMapping = { type Pino = { levels: LevelMapping; + [SENTRY_TRACK_SYMBOL]?: 'track' | 'ignore'; }; type MergeObject = { @@ -28,6 +31,17 @@ type MergeObject = { type PinoHookArgs = [MergeObject, string, number]; type PinoOptions = { + /** + * Automatically instrument all Pino loggers. + * + * When set to `false`, only loggers marked with `pinoIntegration.trackLogger(logger)` will be captured. + * + * @default true + */ + autoInstrument: boolean; + /** + * Options to enable capturing of error events. + */ error: { /** * Levels that trigger capturing of events. @@ -43,6 +57,9 @@ type PinoOptions = { */ handled: boolean; }; + /** + * Options to enable capturing of logs. + */ log: { /** * Levels that trigger capturing of logs. Logs are only captured if @@ -55,6 +72,7 @@ type PinoOptions = { }; const DEFAULT_OPTIONS: PinoOptions = { + autoInstrument: true, error: { levels: [], handled: true }, log: { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] }, }; @@ -63,18 +81,18 @@ type DeepPartial = { [P in keyof T]?: T[P] extends object ? Partial : T[P]; }; -/** - * Integration for Pino logging library. - * Captures Pino logs as Sentry logs and optionally captures some log levels as events. - * - * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0 - */ -export const pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { +const _pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { const options: PinoOptions = { + autoInstrument: 'autoInstrument' in userOptions ? !!userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument, error: { ...DEFAULT_OPTIONS.error, ...userOptions.error }, log: { ...DEFAULT_OPTIONS.log, ...userOptions.log }, }; + function shouldTrackLogger(logger: Pino): boolean { + const override = logger[SENTRY_TRACK_SYMBOL]; + return override === 'track' || (override !== 'ignore' && options.autoInstrument); + } + return { name: 'Pino', setup: client => { @@ -95,6 +113,10 @@ export const pinoIntegration = defineIntegration((userOptions: DeepPartial): Integration; + /** + * Marks a Pino logger to be tracked by the Pino integration. + * + * @param logger A Pino logger instance. + */ + trackLogger(logger: unknown): void; + /** + * Marks a Pino logger to be ignored by the Pino integration. + * + * @param logger A Pino logger instance. + */ + untrackLogger(logger: unknown): void; +} + +/** + * Integration for Pino logging library. + * Captures Pino logs as Sentry logs and optionally captures some log levels as events. + * + * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.untrackLogger(logger)`. + * + * If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`. + * + * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0 + */ +export const pinoIntegration = Object.assign(_pinoIntegration, { + trackLogger(logger: unknown): void { + if (logger && typeof logger === 'object' && 'levels' in logger) { + (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'track'; + } + }, + untrackLogger(logger: unknown): void { + if (logger && typeof logger === 'object' && 'levels' in logger) { + (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'ignore'; + } + }, +}) as PinoIntegrationFunction; From abdce676ccdee8b3544718d1da0656534ed96d1f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 00:34:41 +0200 Subject: [PATCH 02/16] fix(node): `pino` child loggers --- .../suites/pino/scenario.mjs | 3 +- .../suites/pino/test.ts | 41 +++++++++---------- packages/node-core/src/integrations/pino.ts | 36 ++++++++++------ 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/scenario.mjs b/dev-packages/node-integration-tests/suites/pino/scenario.mjs index beb080ac3c42..55966552a07f 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario.mjs @@ -17,7 +17,8 @@ Sentry.withIsolationScope(() => { setTimeout(() => { Sentry.withIsolationScope(() => { Sentry.startSpan({ name: 'later' }, () => { - logger.error(new Error('oh no')); + const child = logger.child({ module: 'authentication' }); + child.error(new Error('oh no')); }); }); }, 1000); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index 1982c8d686fc..fc19dbd95aa6 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -45,7 +45,6 @@ conditionalTest({ min: 20 })('Pino integration', () => { function: '?', in_app: true, module: 'scenario', - context_line: " logger.error(new Error('oh no'));", }), ]), }, @@ -63,8 +62,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'hello world', trace_id: expect.any(String), severity_number: 9, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, 'pino.logger.level': { value: 30, type: 'integer' }, user: { value: 'user-id', type: 'string' }, something: { @@ -74,7 +73,7 @@ conditionalTest({ min: 20 })('Pino integration', () => { 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, { timestamp: expect.any(Number), @@ -82,14 +81,14 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'oh no', trace_id: expect.any(String), severity_number: 17, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, + module: { value: 'authentication', type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, - err: { value: '{}', type: 'string' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, ], }, @@ -139,8 +138,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'hello world', trace_id: expect.any(String), severity_number: 9, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, 'pino.logger.level': { value: 30, type: 'integer' }, user: { value: 'user-id', type: 'string' }, something: { @@ -150,7 +149,7 @@ conditionalTest({ min: 20 })('Pino integration', () => { 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, { timestamp: expect.any(Number), @@ -158,14 +157,13 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'oh no', trace_id: expect.any(String), severity_number: 17, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, - err: { value: '{}', type: 'string' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, ], }, @@ -189,8 +187,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'hello world', trace_id: expect.any(String), severity_number: 9, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, 'pino.logger.level': { value: 30, type: 'integer' }, user: { value: 'user-id', type: 'string' }, something: { @@ -200,7 +198,7 @@ conditionalTest({ min: 20 })('Pino integration', () => { 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, { timestamp: expect.any(Number), @@ -208,14 +206,13 @@ conditionalTest({ min: 20 })('Pino integration', () => { body: 'oh no', trace_id: expect.any(String), severity_number: 17, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, + attributes: { + name: { value: 'myapp', type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, - err: { value: '{}', type: 'string' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), + }, }, ], }, diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index d069f371ed6b..dce35d3f3b14 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -81,6 +81,20 @@ type DeepPartial = { [P in keyof T]?: T[P] extends object ? Partial : T[P]; }; +type PinoResult = { + level?: string; + time?: string; + pid?: number; + hostname?: string; + err?: Error; +} & Record; + +function stripIgnoredFields(result: PinoResult): PinoResult { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { level, time, pid, hostname, err, ...rest } = result; + return rest; +} + const _pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { const options: PinoOptions = { autoInstrument: 'autoInstrument' in userOptions ? !!userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument, @@ -112,27 +126,23 @@ const _pinoIntegration = defineIntegration((userOptions: DeepPartial = { - ...obj, + ...resultObj, 'sentry.origin': 'auto.logging.pino', 'pino.logger.level': levelNumber, }; - const parsedResult = JSON.parse(result) as { name?: string }; - - if (parsedResult.name) { - attributes['pino.logger.name'] = parsedResult.name; - } - _INTERNAL_captureLog({ level, message, attributes }); } @@ -153,8 +163,8 @@ const _pinoIntegration = defineIntegration((userOptions: DeepPartial { const { self, arguments: args, result } = data as { self: Pino; arguments: PinoHookArgs; result: string }; - onPinoStart(self, args, result); + onPinoStart(self, args, JSON.parse(result)); }); integratedChannel.end.subscribe(data => { @@ -174,7 +184,7 @@ const _pinoIntegration = defineIntegration((userOptions: DeepPartial Date: Wed, 15 Oct 2025 12:44:53 +0200 Subject: [PATCH 03/16] Fix logic --- packages/node-core/src/integrations/pino.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index dce35d3f3b14..b636d0ad9b7c 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -97,7 +97,7 @@ function stripIgnoredFields(result: PinoResult): PinoResult { const _pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { const options: PinoOptions = { - autoInstrument: 'autoInstrument' in userOptions ? !!userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument, + autoInstrument: userOptions.autoInstrument === false ? userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument, error: { ...DEFAULT_OPTIONS.error, ...userOptions.error }, log: { ...DEFAULT_OPTIONS.log, ...userOptions.log }, }; From 4308b37a34cc1bd0db9a10b2c54b8c4626a91755 Mon Sep 17 00:00:00 2001 From: Guillaume M Date: Tue, 14 Oct 2025 09:32:59 +0200 Subject: [PATCH 04/16] docs: Update supported Angular version in README --- packages/angular/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/README.md b/packages/angular/README.md index 384c4c2d48c8..f686fb266b23 100644 --- a/packages/angular/README.md +++ b/packages/angular/README.md @@ -16,7 +16,7 @@ ## Angular Version Compatibility -This SDK officially supports Angular 14 to 19. +This SDK officially supports Angular 14 to 20. If you're using an older Angular version please check the [compatibility table in the docs](https://docs.sentry.io/platforms/javascript/guides/angular/#angular-version-compatibility). From 908e4eca3e3e0f9454417a1466ed1728c9adf611 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 15 Oct 2025 12:00:30 +0200 Subject: [PATCH 05/16] fix(browser): Ignore React 19.2+ component render measure entries (#17905) With 19.2, React introduced [custom perfomance tracks](https://react.dev/blog/2025/10/01/react-19-2#performance-tracks) in chrome dev tools. This track is populated by collecting `performance.measure` entries for every component (re-)render. Sounds good in theory but in reality this causes a massive performance degradation when using the Sentry SDK because we collect spans from `PerformanceMeasure` entries. In our Sentry UI, this caused 10+ second long blocks because we created thousands of spans from these render entries. This patch fixes this performance drop by inspecting the measure entries' `detail` object which we can use to _fairly well_ distinguish React's entries from users' entries. Not 100% bulletproof but I think good enough. --- .../src/metrics/browserMetrics.ts | 23 +++++++ .../test/browser/browserMetrics.test.ts | 69 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 2c61408c1d76..ec7213ae4ff6 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -425,6 +425,24 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries _measurements = {}; } +/** + * React 19.2+ creates performance.measure entries for component renders. + * We can identify them by the `detail.devtools.track` property being set to 'Components ⚛'. + * see: https://react.dev/reference/dev-tools/react-performance-tracks + * see: https://github.com/facebook/react/blob/06fcc8f380c6a905c7bc18d94453f623cf8cbc81/packages/react-reconciler/src/ReactFiberPerformanceTrack.js#L454-L473 + */ +function isReact19MeasureEntry(entry: PerformanceEntry | null): boolean | void { + if (entry?.entryType !== 'measure') { + return; + } + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return (entry as PerformanceMeasure).detail.devtools.track === 'Components ⚛'; + } catch { + return; + } +} + /** * Create measure related spans. * Exported only for tests. @@ -437,6 +455,10 @@ export function _addMeasureSpans( timeOrigin: number, ignorePerformanceApiSpans: AddPerformanceEntriesOptions['ignorePerformanceApiSpans'], ): void { + if (isReact19MeasureEntry(entry)) { + return; + } + if ( ['mark', 'measure'].includes(entry.entryType) && stringMatchesSomePattern(entry.name, ignorePerformanceApiSpans) @@ -445,6 +467,7 @@ export function _addMeasureSpans( } const navEntry = getNavigationEntry(false); + const requestTime = msToSec(navEntry ? navEntry.requestStart : 0); // Because performance.measure accepts arbitrary timestamps it can produce // spans that happen before the browser even makes a request for the page. diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index c734ec326b47..6d6e03fa0643 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -186,6 +186,75 @@ describe('_addMeasureSpans', () => { ]), ); }); + + it('ignores React 19.2+ measure spans', () => { + const pageloadSpan = new SentrySpan({ op: 'pageload', name: '/', sampled: true }); + const spans: Span[] = []; + + getClient()?.on('spanEnd', span => { + spans.push(span); + }); + + const entries: PerformanceMeasure[] = [ + { + entryType: 'measure', + name: '\u200bLayout', + duration: 0.3, + startTime: 12, + detail: { + devtools: { + track: 'Components ⚛', + }, + }, + toJSON: () => ({ foo: 'bar' }), + }, + { + entryType: 'measure', + name: '\u200bButton', + duration: 0.1, + startTime: 13, + detail: { + devtools: { + track: 'Components ⚛', + }, + }, + toJSON: () => ({}), + }, + { + entryType: 'measure', + name: 'Unmount', + duration: 0.1, + startTime: 14, + detail: { + devtools: { + track: 'Components ⚛', + }, + }, + toJSON: () => ({}), + }, + { + entryType: 'measure', + name: 'my-measurement', + duration: 0, + startTime: 12, + detail: null, + toJSON: () => ({}), + }, + ]; + + const timeOrigin = 100; + const startTime = 23; + const duration = 356; + + entries.forEach(e => { + _addMeasureSpans(pageloadSpan, e, startTime, duration, timeOrigin, []); + }); + + expect(spans).toHaveLength(1); + expect(spans.map(spanToJSON)).toEqual( + expect.arrayContaining([expect.objectContaining({ description: 'my-measurement', op: 'measure' })]), + ); + }); }); describe('_addResourceSpans', () => { From 318ce6738c6a49a84a4b1cbf87570cc800c8846f Mon Sep 17 00:00:00 2001 From: Daniel Sanchez <4277756+thedanchez@users.noreply.github.com> Date: Wed, 15 Oct 2025 07:17:36 -0400 Subject: [PATCH 06/16] feat(solid): Add support for TanStack Router Solid (#17735) # Summary This PR adds support for TanStack Router Solid. It follows the same outline as the existing implementation of TanStack Router React for Sentry as both TanStack Router flavors are built on the same agnostic foundation. --------- Co-authored-by: Andrei Borza --- .../solid-solidrouter/README.md | 40 ---- .../solid-solidrouter/index.html | 15 -- .../solid-solidrouter/postcss.config.js | 6 - .../solid-solidrouter/src/errors/404.tsx | 8 - .../solid-solidrouter/src/index.css | 3 - .../solid-solidrouter/src/index.tsx | 22 -- .../solid-solidrouter/src/pageroot.tsx | 28 --- .../src/pages/errorboundaryexample.tsx | 24 --- .../solid-solidrouter/src/pages/home.tsx | 39 ---- .../solid-solidrouter/src/pages/user.tsx | 6 - .../solid-solidrouter/src/routes.ts | 23 -- .../solid-solidrouter/tailwind.config.ts | 11 - .../tests/errorboundary.test.ts | 75 ------- .../solid-solidrouter/tests/errors.test.ts | 28 --- .../tests/performance.test.ts | 91 -------- .../solid-solidrouter/tsconfig.json | 14 -- .../solid-solidrouter/vite.config.ts | 10 - .../solid-tanstack-router/.cta.json | 11 + .../.gitignore | 0 .../.npmrc | 0 .../solid-tanstack-router/README.md | 165 ++++++++++++++ .../solid-tanstack-router/index.html | 20 ++ .../package.json | 32 +-- .../playwright.config.mjs | 0 .../solid-tanstack-router/public/favicon.ico | Bin 0 -> 3870 bytes .../solid-tanstack-router/public/logo192.png | Bin 0 -> 5347 bytes .../solid-tanstack-router/public/logo512.png | Bin 0 -> 9664 bytes .../public/manifest.json | 25 +++ .../solid-tanstack-router/public/robots.txt | 3 + .../solid-tanstack-router/src/App.tsx | 20 ++ .../solid-tanstack-router/src/logo.svg | 120 +++++++++++ .../solid-tanstack-router/src/main.tsx | 99 +++++++++ .../solid-tanstack-router/src/styles.css | 14 ++ .../start-event-proxy.mjs | 2 +- .../tests/routing-instrumentation.test.ts | 72 +++++++ .../solid-tanstack-router/tsconfig.json | 25 +++ .../solid-tanstack-router/vite.config.ts | 12 ++ packages/solid/README.md | 37 ++++ packages/solid/package.json | 21 +- packages/solid/rollup.npm.config.mjs | 2 +- packages/solid/src/tanstackrouter.ts | 126 +++++++++++ ...types.json => tsconfig.routers-types.json} | 2 +- packages/solid/tsconfig.types.json | 4 +- yarn.lock | 202 +++++++++++++++++- 44 files changed, 984 insertions(+), 473 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/README.md delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/postcss.config.js delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/errors/404.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.css delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pageroot.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/errorboundaryexample.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/home.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/user.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/src/routes.ts delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/tailwind.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errorboundary.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errors.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/performance.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/solid-solidrouter/vite.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/.cta.json rename dev-packages/e2e-tests/test-applications/{solid-solidrouter => solid-tanstack-router}/.gitignore (100%) rename dev-packages/e2e-tests/test-applications/{solid-solidrouter => solid-tanstack-router}/.npmrc (100%) create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/README.md create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/index.html rename dev-packages/e2e-tests/test-applications/{solid-solidrouter => solid-tanstack-router}/package.json (52%) rename dev-packages/e2e-tests/test-applications/{solid-solidrouter => solid-tanstack-router}/playwright.config.mjs (100%) create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/favicon.ico create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/logo192.png create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/logo512.png create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/manifest.json create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/robots.txt create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/App.tsx create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/logo.svg create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/styles.css rename dev-packages/e2e-tests/test-applications/{solid-solidrouter => solid-tanstack-router}/start-event-proxy.mjs (71%) create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/tests/routing-instrumentation.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/vite.config.ts create mode 100644 packages/solid/src/tanstackrouter.ts rename packages/solid/{tsconfig.solidrouter-types.json => tsconfig.routers-types.json} (85%) diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/README.md b/dev-packages/e2e-tests/test-applications/solid-solidrouter/README.md deleted file mode 100644 index 81e5eb6c2d40..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/README.md +++ /dev/null @@ -1,40 +0,0 @@ -## Usage - -Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. - -This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely -be removed once you clone a template. - -```bash -$ npm install # or pnpm install or yarn install -``` - -## Exploring the template - -This template's goal is to showcase the routing features of Solid. It also showcase how the router and Suspense work -together to parallelize data fetching tied to a route via the `.data.ts` pattern. - -You can learn more about it on the [`@solidjs/router` repository](https://github.com/solidjs/solid-router) - -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` or `npm start` - -Runs the app in the development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
- -### `npm run build` - -Builds the app for production to the `dist` folder.
It correctly bundles Solid in production mode and optimizes the -build for the best performance. - -The build is minified and the filenames include the hashes.
Your app is ready to be deployed! - -## Deployment - -You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/index.html b/dev-packages/e2e-tests/test-applications/solid-solidrouter/index.html deleted file mode 100644 index 1905a0429019..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - Solid App - - - -
- - - - diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/postcss.config.js b/dev-packages/e2e-tests/test-applications/solid-solidrouter/postcss.config.js deleted file mode 100644 index 12a703d900da..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/errors/404.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/errors/404.tsx deleted file mode 100644 index 56e5ad5e3be0..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/errors/404.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function NotFound() { - return ( -
-

404: Not Found

-

It's gone 😞

-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.css b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.css deleted file mode 100644 index b5c61c956711..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.tsx deleted file mode 100644 index 66773f009d1e..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* @refresh reload */ -import * as Sentry from '@sentry/solid'; -import { solidRouterBrowserTracingIntegration, withSentryRouterRouting } from '@sentry/solid/solidrouter'; -import { Router } from '@solidjs/router'; -import { render } from 'solid-js/web'; -import './index.css'; -import PageRoot from './pageroot'; -import { routes } from './routes'; - -Sentry.init({ - dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, - debug: true, - environment: 'qa', // dynamic sampling bias to keep transactions - integrations: [solidRouterBrowserTracingIntegration()], - release: 'e2e-test', - tunnel: 'http://localhost:3031/', // proxy server - tracesSampleRate: 1.0, -}); - -const SentryRouter = withSentryRouterRouting(Router); - -render(() => {routes}, document.getElementById('root')); diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pageroot.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pageroot.tsx deleted file mode 100644 index 0919c0e362db..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pageroot.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { A } from '@solidjs/router'; - -export default function PageRoot(props) { - return ( - <> - -
{props.children}
- - ); -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/errorboundaryexample.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/errorboundaryexample.tsx deleted file mode 100644 index b4cb4e93a02f..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/errorboundaryexample.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as Sentry from '@sentry/solid'; -import { ErrorBoundary } from 'solid-js'; - -const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary); - -export default function ErrorBoundaryExample() { - return ( - ( -
-

Error Boundary Fallback

-
- {error.message} -
- -
- )} - > - -
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/home.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/home.tsx deleted file mode 100644 index 08e92728762c..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/home.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { A } from '@solidjs/router'; -import { createSignal } from 'solid-js'; - -export default function Home() { - const [count, setCount] = createSignal(0); - - return ( -
-

Home

-

This is the home page.

- -
- - - Count: {count()} - - -
-
- - - User 5 - -
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/user.tsx b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/user.tsx deleted file mode 100644 index 639ab0be8118..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/pages/user.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useParams } from '@solidjs/router'; - -export default function User() { - const params = useParams(); - return
User ID: {params.id}
; -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/routes.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/routes.ts deleted file mode 100644 index 96b78e113ef5..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/src/routes.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { lazy } from 'solid-js'; - -import ErrorBoundaryExample from './pages/errorboundaryexample'; -import Home from './pages/home'; - -export const routes = [ - { - path: '/', - component: Home, - }, - { - path: '/user/:id', - component: lazy(() => import('./pages/user')), - }, - { - path: '/error-boundary-example', - component: ErrorBoundaryExample, - }, - { - path: '**', - component: lazy(() => import('./errors/404')), - }, -]; diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tailwind.config.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/tailwind.config.ts deleted file mode 100644 index f69a95185570..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tailwind.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Config } from 'tailwindcss'; - -const config: Config = { - content: ['./src/**/*.{js,jsx,ts,tsx}'], - theme: { - extend: {}, - }, - plugins: [], -}; - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errorboundary.test.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errorboundary.test.ts deleted file mode 100644 index 14396feb2334..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errorboundary.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -test('captures an exception', async ({ page }) => { - const errorEventPromise = waitForError('solid', errorEvent => { - return !errorEvent.type && errorEvent.transaction === '/error-boundary-example'; - }); - - const [, errorEvent] = await Promise.all([page.goto('/error-boundary-example'), errorEventPromise]); - - expect(errorEvent).toMatchObject({ - exception: { - values: [ - { - type: 'ReferenceError', - value: 'NonExistentComponent is not defined', - mechanism: { - type: 'auto.function.solid.error_boundary', - handled: true, - }, - }, - ], - }, - transaction: '/error-boundary-example', - }); -}); - -test('captures a second exception after resetting the boundary', async ({ page }) => { - const firstErrorEventPromise = waitForError('solid', errorEvent => { - return !errorEvent.type && errorEvent.transaction === '/error-boundary-example'; - }); - - const [, firstErrorEvent] = await Promise.all([page.goto('/error-boundary-example'), firstErrorEventPromise]); - - expect(firstErrorEvent).toMatchObject({ - exception: { - values: [ - { - type: 'ReferenceError', - value: 'NonExistentComponent is not defined', - mechanism: { - type: 'auto.function.solid.error_boundary', - handled: true, - }, - }, - ], - }, - transaction: '/error-boundary-example', - }); - - const secondErrorEventPromise = waitForError('solid', errorEvent => { - return !errorEvent.type && errorEvent.transaction === '/error-boundary-example'; - }); - - const [, secondErrorEvent] = await Promise.all([ - page.locator('#errorBoundaryResetBtn').click(), - await secondErrorEventPromise, - ]); - - expect(secondErrorEvent).toMatchObject({ - exception: { - values: [ - { - type: 'ReferenceError', - value: 'NonExistentComponent is not defined', - mechanism: { - type: 'auto.function.solid.error_boundary', - handled: true, - }, - }, - ], - }, - transaction: '/error-boundary-example', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errors.test.ts deleted file mode 100644 index a77f107af624..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/errors.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -test('sends an error', async ({ page }) => { - const errorPromise = waitForError('solid', async errorEvent => { - return !errorEvent.type && errorEvent.exception?.values?.[0]?.value === 'Error thrown from Solid E2E test app'; - }); - - await Promise.all([page.goto(`/`), page.locator('#errorBtn').click()]); - - const error = await errorPromise; - - expect(error).toMatchObject({ - exception: { - values: [ - { - type: 'Error', - value: 'Error thrown from Solid E2E test app', - mechanism: { - type: 'auto.browser.global_handlers.onerror', - handled: false, - }, - }, - ], - }, - transaction: '/', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/performance.test.ts deleted file mode 100644 index f73ff4940527..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tests/performance.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('sends a pageload transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('solid', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; - }); - - const [, pageloadTransaction] = await Promise.all([page.goto('/'), transactionPromise]); - - expect(pageloadTransaction).toMatchObject({ - contexts: { - trace: { - op: 'pageload', - origin: 'auto.pageload.browser', - }, - }, - transaction: '/', - transaction_info: { - source: 'url', - }, - }); -}); - -test('sends a navigation transaction', async ({ page }) => { - const transactionPromise = waitForTransaction('solid', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; - }); - - await page.goto(`/`); - - const [, navigationTransaction] = await Promise.all([page.locator('#navLink').click(), transactionPromise]); - - expect(navigationTransaction).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solid.solidrouter', - }, - }, - transaction: '/user/5', - transaction_info: { - source: 'url', - }, - }); -}); - -test('updates the transaction when using the back button', async ({ page }) => { - // Solid Router sends a `-1` navigation when using the back button. - // The sentry solidRouterBrowserTracingIntegration tries to update such - // transactions with the proper name once the `useLocation` hook triggers. - const navigationTxnPromise = waitForTransaction('solid', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; - }); - - await page.goto(`/`); - - const [, navigationTxn] = await Promise.all([page.locator('#navLink').click(), navigationTxnPromise]); - - expect(navigationTxn).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solid.solidrouter', - }, - }, - transaction: '/user/5', - transaction_info: { - source: 'url', - }, - }); - - const backNavigationTxnPromise = waitForTransaction('solid', async transactionEvent => { - return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; - }); - - const [, backNavigationTxn] = await Promise.all([page.goBack(), backNavigationTxnPromise]); - - expect(backNavigationTxn).toMatchObject({ - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.solid.solidrouter', - }, - }, - transaction: '/', - transaction_info: { - source: 'url', - }, - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tsconfig.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/tsconfig.json deleted file mode 100644 index 5d2faf0af117..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - "types": ["vite/client"], - "noEmit": true, - "isolatedModules": true - } -} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/vite.config.ts b/dev-packages/e2e-tests/test-applications/solid-solidrouter/vite.config.ts deleted file mode 100644 index d1835ee1b8ff..000000000000 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/vite.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'vite'; -import solidPlugin from 'vite-plugin-solid'; - -export default defineConfig({ - plugins: [solidPlugin()], - build: { - target: 'esnext', - }, - envPrefix: 'PUBLIC_', -}); diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/.cta.json b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/.cta.json new file mode 100644 index 000000000000..3b9146f46e52 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/.cta.json @@ -0,0 +1,11 @@ +{ + "projectName": "solid-tanstack-router", + "mode": "code-router", + "typescript": true, + "tailwind": true, + "packageManager": "pnpm", + "git": true, + "version": 1, + "framework": "solid", + "chosenAddOns": [] +} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/.gitignore b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/solid-solidrouter/.gitignore rename to dev-packages/e2e-tests/test-applications/solid-tanstack-router/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/.npmrc b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/solid-solidrouter/.npmrc rename to dev-packages/e2e-tests/test-applications/solid-tanstack-router/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/README.md b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/README.md new file mode 100644 index 000000000000..cde052fb5212 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/README.md @@ -0,0 +1,165 @@ +Welcome to your new TanStack app! + +# Getting Started + +To run this application: + +```bash +pnpm install +pnpm start +``` + +# Building For Production + +To build this application for production: + +```bash +pnpm build +``` + +## Styling + +This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. + +## Routing + +This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a code based router. Which means that the routes are defined in code (in the `./src/main.tsx` file). If you like you can also use a file based routing setup by following the [File Based Routing](https://tanstack.com/router/latest/docs/framework/solid/guide/file-based-routing) guide. + +### Adding A Route + +To add a new route to your application just add another `createRoute` call to the `./src/main.tsx` file. The example below adds a new `/about`route to the root route. + +```tsx +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + component: () =>

About

, +}); +``` + +You will also need to add the route to the `routeTree` in the `./src/main.tsx` file. + +```tsx +const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]); +``` + +With this set up you should be able to navigate to `/about` and see the about page. + +Of course you don't need to implement the About page in the `main.tsx` file. You can create that component in another file and import it into the `main.tsx` file, then use it in the `component` property of the `createRoute` call, like so: + +```tsx +import About from './components/About.tsx'; + +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + component: About, +}); +``` + +That is how we have the `App` component set up with the home page. + +For more information on the options you have when you are creating code based routes check out the [Code Based Routing](https://tanstack.com/router/latest/docs/framework/solid/guide/code-based-routing) documentation. + +Now that you have two routes you can use a `Link` component to navigate between them. + +### Adding Links + +To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/solid-router`. + +```tsx +import { Link } from '@tanstack/solid-router'; +``` + +Then anywhere in your JSX you can use it like so: + +```tsx +About +``` + +This will create a link that will navigate to the `/about` route. + +More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/solid/api/router/linkComponent). + +### Using A Layout + +Layouts can be used to wrap the contents of the routes in menus, headers, footers, etc. + +There is already a layout in the `src/main.tsx` file: + +```tsx +const rootRoute = createRootRoute({ + component: () => ( + <> + + + + ), +}); +``` + +You can use the Soliid component specified in the `component` property of the `rootRoute` to wrap the contents of the routes. The `` component is used to render the current route within the body of the layout. For example you could add a header to the layout like so: + +```tsx +import { Link } from '@tanstack/solid-router'; + +const rootRoute = createRootRoute({ + component: () => ( + <> +
+ +
+ + + + ), +}); +``` + +The `` component is not required so you can remove it if you don't want it in your layout. + +More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/routing-concepts#layouts). + +## Data Fetching + +There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. + +For example: + +```tsx +const peopleRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/people', + loader: async () => { + const response = await fetch('https://swapi.dev/api/people'); + return response.json() as Promise<{ + results: { + name: string; + }[]; + }>; + }, + component: () => { + const data = peopleRoute.useLoaderData(); + return ( +
    + {data.results.map(person => ( +
  • {person.name}
  • + ))} +
+ ); + }, +}); +``` + +Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/solid/guide/data-loading#loader-parameters). + +# Demo files + +Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. + +# Learn More + +You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/index.html b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/index.html new file mode 100644 index 000000000000..e1b9457f30b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + Create TanStack App - app-ts + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json similarity index 52% rename from dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json rename to dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json index ada4d06624ad..5dc35acaf095 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/package.json @@ -1,32 +1,32 @@ { - "name": "solid-solidrouter", - "version": "0.0.0", - "description": "", + "name": "solid-tanstack-router", + "private": true, + "type": "module", "scripts": { "build": "vite build", "clean": "npx rimraf node_modules pnpm-lock.yaml dist", "dev": "vite", + "start": "vite preview", "preview": "vite preview", - "start": "vite", "test:prod": "TEST_ENV=production playwright test", "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test:prod" }, - "license": "MIT", + "dependencies": { + "@sentry/solid": "latest || *", + "@tailwindcss/vite": "^4.0.6", + "@tanstack/solid-router": "^1.132.25", + "@tanstack/solid-router-devtools": "^1.132.25", + "@tanstack/solid-start": "^1.132.25", + "solid-js": "^1.9.5", + "tailwindcss": "^4.0.6" + }, "devDependencies": { "@playwright/test": "~1.53.2", "@sentry-internal/test-utils": "link:../../../test-utils", - "autoprefixer": "^10.4.17", - "postcss": "^8.4.33", - "solid-devtools": "^0.29.2", - "tailwindcss": "^3.4.1", - "vite": "^5.4.11", - "vite-plugin-solid": "^2.11.6" - }, - "dependencies": { - "@solidjs/router": "^0.13.5", - "solid-js": "^1.8.18", - "@sentry/solid": "latest || *" + "typescript": "^5.7.2", + "vite": "^7.1.7", + "vite-plugin-solid": "^2.11.2" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/playwright.config.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/solid-solidrouter/playwright.config.mjs rename to dev-packages/e2e-tests/test-applications/solid-tanstack-router/playwright.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/favicon.ico b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/logo192.png b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/manifest.json b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/manifest.json new file mode 100644 index 000000000000..078ef5011624 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "TanStack App", + "name": "Create TanStack App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/robots.txt b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/robots.txt new file mode 100644 index 000000000000..e9e57dc4d41b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/App.tsx b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/App.tsx new file mode 100644 index 000000000000..a584b2d83600 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/App.tsx @@ -0,0 +1,20 @@ +import logo from './logo.svg'; + +function App() { + return ( + + ); +} + +export default App; diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/logo.svg b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/logo.svg new file mode 100644 index 000000000000..21159f9fc0eb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/logo.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx new file mode 100644 index 000000000000..4580fa6e8a90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/main.tsx @@ -0,0 +1,99 @@ +import { Link, Outlet, RouterProvider, createRootRoute, createRoute, createRouter } from '@tanstack/solid-router'; +import * as Sentry from '@sentry/solid'; +import { tanstackRouterBrowserTracingIntegration } from '@sentry/solid/tanstackrouter'; +import { render } from 'solid-js/web'; + +import './styles.css'; + +import App from './App.tsx'; + +const rootRoute = createRootRoute({ + component: () => ( + <> +
    +
  • + Home +
  • +
  • + + Post 1 + +
  • +
  • + + Post 2 + +
  • +
+
+ + + ), +}); + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: App, +}); + +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts/', +}); + +const postIdRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', + shouldReload() { + return true; + }, + loader: ({ params }) => { + return Sentry.startSpan({ name: `loading-post-${params.postId}` }, async () => { + await new Promise(resolve => setTimeout(resolve, 1000)); + }); + }, + component: function Post() { + const params = postIdRoute.useParams(); + return
Post ID: {params().postId}
; + }, +}); + +const routeTree = rootRoute.addChildren([indexRoute, postsRoute.addChildren([postIdRoute])]); + +const router = createRouter({ + routeTree, + defaultPreload: 'intent', + scrollRestoration: true, +}); + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router; + } +} + +declare const __APP_DSN__: string; + +Sentry.init({ + dsn: __APP_DSN__, + debug: true, + environment: 'qa', // dynamic sampling bias to keep transactions + integrations: [tanstackRouterBrowserTracingIntegration(router)], + release: 'e2e-test', + tunnel: 'http://localhost:3031/', // proxy server + tracesSampleRate: 1.0, +}); + +function MainApp() { + return ( + <> + + + ); +} + +const rootElement = document.getElementById('app'); +if (rootElement) { + render(() => , rootElement); +} diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/styles.css b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/styles.css new file mode 100644 index 000000000000..9dbc2a933202 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/src/styles.css @@ -0,0 +1,14 @@ +@import 'tailwindcss'; + +body { + @apply m-0; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', + 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +} diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/start-event-proxy.mjs similarity index 71% rename from dev-packages/e2e-tests/test-applications/solid-solidrouter/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/solid-tanstack-router/start-event-proxy.mjs index 075d4dcb5cf5..496ea15d6c2a 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/start-event-proxy.mjs @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, - proxyServerName: 'solid', + proxyServerName: 'solid-tanstack-router', }); diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tests/routing-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tests/routing-instrumentation.test.ts new file mode 100644 index 000000000000..7119c7e76b99 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tests/routing-instrumentation.test.ts @@ -0,0 +1,72 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto(`/posts/456`); + + const rootSpan = await transactionPromise; + + expect(rootSpan).toMatchObject({ + contexts: { + trace: { + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.pageload.solid.tanstack_router', + 'sentry.op': 'pageload', + 'url.path.parameter.postId': '456', + }, + op: 'pageload', + origin: 'auto.pageload.solid.tanstack_router', + }, + }, + transaction: '/posts/$postId', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + const pageloadTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('solid-tanstack-router', async transactionEvent => { + return ( + !!transactionEvent?.transaction && + transactionEvent.transaction === '/posts/$postId' && + transactionEvent.contexts?.trace?.op === 'navigation' + ); + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + await page.waitForTimeout(5000); + await page.locator('#nav-link').click(); + + const navigationTxn = await navigationTxnPromise; + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.navigation.solid.tanstack_router', + 'sentry.op': 'navigation', + 'url.path.parameter.postId': '2', + }, + op: 'navigation', + origin: 'auto.navigation.solid.tanstack_router', + }, + }, + transaction: '/posts/$postId', + transaction_info: { + source: 'route', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tsconfig.json b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tsconfig.json new file mode 100644 index 000000000000..0ce9a7b137af --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/tsconfig.json @@ -0,0 +1,25 @@ +{ + "include": ["**/*.ts", "**/*.tsx"], + "compilerOptions": { + "target": "ES2022", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/vite.config.ts b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/vite.config.ts new file mode 100644 index 000000000000..bd612e95fbb8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; + +import solidPlugin from 'vite-plugin-solid'; +import tailwindcss from '@tailwindcss/vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + define: { + __APP_DSN__: JSON.stringify(process.env.E2E_TEST_DSN), + }, + plugins: [solidPlugin(), tailwindcss()], +}); diff --git a/packages/solid/README.md b/packages/solid/README.md index e5ddd2186c02..58fa5c75c345 100644 --- a/packages/solid/README.md +++ b/packages/solid/README.md @@ -52,6 +52,43 @@ render( ); ``` +### Tanstack Router + +The Tanstack Router instrumentation uses the Tanstack Router library to create navigation spans to ensure you collect +meaningful performance data about the health of your page loads and associated requests. + +Add `tanstackRouterBrowserTracingIntegration` instead of the regular `Sentry.browserTracingIntegration`. + +Make sure `tanstackRouterBrowserTracingIntegration` is initialized by your `Sentry.init` call. Otherwise, the routing +instrumentation will not work properly. + +Pass your router instance from `createRouter` to the integration. + +```js +import * as Sentry from '@sentry/solid'; +import { tanstackRouterBrowserTracingIntegration } from '@sentry/solid/tanstackrouter'; +import { Route, Router } from '@solidjs/router'; + +const router = createRouter({ + // your router config + // ... +}); + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router; + } +} + +Sentry.init({ + dsn: '__PUBLIC_DSN__', + integrations: [tanstackRouterBrowserTracingIntegration(router)], + tracesSampleRate: 1.0, // Capture 100% of the transactions +}); + +render(() => , document.getElementById('root')); +``` + # Solid ErrorBoundary To automatically capture exceptions from inside a component tree and render a fallback component, wrap the native Solid diff --git a/packages/solid/package.json b/packages/solid/package.json index 8a614ca120a2..5fdde9a0c97a 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -38,6 +38,16 @@ "types": "./solidrouter.d.ts", "default": "./build/cjs/solidrouter.js" } + }, + "./tanstackrouter": { + "import": { + "types": "./tanstackrouter.d.ts", + "default": "./build/esm/tanstackrouter.js" + }, + "require": { + "types": "./tanstackrouter.d.ts", + "default": "./build/cjs/tanstackrouter.js" + } } }, "publishConfig": { @@ -49,15 +59,20 @@ }, "peerDependencies": { "@solidjs/router": "^0.13.4", + "@tanstack/solid-router": "^1.132.27", "solid-js": "^1.8.4" }, "peerDependenciesMeta": { "@solidjs/router": { "optional": true + }, + "@tanstack/solid-router": { + "optional": true } }, "devDependencies": { "@solidjs/router": "^0.13.4", + "@tanstack/solid-router": "^1.132.27", "@solidjs/testing-library": "0.8.5", "@testing-library/dom": "^7.21.4", "@testing-library/jest-dom": "^6.4.5", @@ -70,15 +85,15 @@ "build": "run-p build:transpile build:types", "build:dev": "yarn build", "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:solidrouter", + "build:types": "run-s build:types:core build:types:routers", "build:types:core": "tsc -p tsconfig.types.json", - "build:types:solidrouter": "tsc -p tsconfig.solidrouter-types.json", + "build:types:routers": "tsc -p tsconfig.routers-types.json", "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "yarn build:watch", "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.ts && madge --circular src/solidrouter.ts", + "circularDepCheck": "madge --circular src/index.ts && madge --circular src/solidrouter.ts && madge --circular src/tanstackrouter.ts", "clean": "rimraf build coverage sentry-solid-*.tgz ./*.d.ts ./*.d.ts.map", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", diff --git a/packages/solid/rollup.npm.config.mjs b/packages/solid/rollup.npm.config.mjs index b044fda38c75..4da78623cb50 100644 --- a/packages/solid/rollup.npm.config.mjs +++ b/packages/solid/rollup.npm.config.mjs @@ -2,6 +2,6 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollu export default makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.ts', 'src/solidrouter.ts'], + entrypoints: ['src/index.ts', 'src/solidrouter.ts', 'src/tanstackrouter.ts'], }), ); diff --git a/packages/solid/src/tanstackrouter.ts b/packages/solid/src/tanstackrouter.ts new file mode 100644 index 000000000000..09790530e822 --- /dev/null +++ b/packages/solid/src/tanstackrouter.ts @@ -0,0 +1,126 @@ +import { + browserTracingIntegration as originalBrowserTracingIntegration, + startBrowserTracingNavigationSpan, + startBrowserTracingPageLoadSpan, + WINDOW, +} from '@sentry/browser'; +import type { Integration } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, +} from '@sentry/core'; +import type { AnyRouter } from '@tanstack/solid-router'; + +type RouteMatch = ReturnType[number]; + +/** + * A custom browser tracing integration for TanStack Router. + * + * The minimum compatible version of `@tanstack/solid-router` is `1.64.0 + * + * @param router A TanStack Router `Router` instance that should be used for routing instrumentation. + * @param options Sentry browser tracing configuration. + */ +export function tanstackRouterBrowserTracingIntegration( + router: R, + options: Parameters[0] = {}, +): Integration { + const browserTracingIntegrationInstance = originalBrowserTracingIntegration({ + ...options, + instrumentNavigation: false, + instrumentPageLoad: false, + }); + + const { instrumentPageLoad = true, instrumentNavigation = true } = options; + + return { + ...browserTracingIntegrationInstance, + afterAllSetup(client) { + browserTracingIntegrationInstance.afterAllSetup(client); + + const initialWindowLocation = WINDOW.location; + if (instrumentPageLoad && initialWindowLocation) { + const matchedRoutes = router.matchRoutes( + initialWindowLocation.pathname, + router.options.parseSearch(initialWindowLocation.search), + { preload: false, throwOnError: false }, + ); + + const lastMatch = matchedRoutes[matchedRoutes.length - 1]; + + startBrowserTracingPageLoadSpan(client, { + name: lastMatch ? lastMatch.routeId : initialWindowLocation.pathname, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.solid.tanstack_router', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: lastMatch ? 'route' : 'url', + ...routeMatchToParamSpanAttributes(lastMatch), + }, + }); + } + + if (instrumentNavigation) { + // The onBeforeNavigate hook is called at the very beginning of a navigation and is only called once per navigation, even when the user is redirected + router.subscribe('onBeforeNavigate', onBeforeNavigateArgs => { + // onBeforeNavigate is called during pageloads. We can avoid creating navigation spans by comparing the states of the to and from arguments. + if (onBeforeNavigateArgs.toLocation.state === onBeforeNavigateArgs.fromLocation?.state) { + return; + } + + const onResolvedMatchedRoutes = router.matchRoutes( + onBeforeNavigateArgs.toLocation.pathname, + onBeforeNavigateArgs.toLocation.search, + { preload: false, throwOnError: false }, + ); + + const onBeforeNavigateLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + + const navigationLocation = WINDOW.location; + const navigationSpan = startBrowserTracingNavigationSpan(client, { + name: onBeforeNavigateLastMatch ? onBeforeNavigateLastMatch.routeId : navigationLocation.pathname, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solid.tanstack_router', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: onBeforeNavigateLastMatch ? 'route' : 'url', + }, + }); + + // In case the user is redirected during navigation we want to update the span with the right value. + const unsubscribeOnResolved = router.subscribe('onResolved', onResolvedArgs => { + unsubscribeOnResolved(); + if (navigationSpan) { + const onResolvedMatchedRoutes = router.matchRoutes( + onResolvedArgs.toLocation.pathname, + onResolvedArgs.toLocation.search, + { preload: false, throwOnError: false }, + ); + + const onResolvedLastMatch = onResolvedMatchedRoutes[onResolvedMatchedRoutes.length - 1]; + + if (onResolvedLastMatch) { + navigationSpan.updateName(onResolvedLastMatch.routeId); + navigationSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + navigationSpan.setAttributes(routeMatchToParamSpanAttributes(onResolvedLastMatch)); + } + } + }); + }); + } + }, + }; +} + +function routeMatchToParamSpanAttributes(match: RouteMatch | undefined): Record { + if (!match) { + return {}; + } + + const paramAttributes: Record = {}; + Object.entries(match.params as Record).forEach(([key, value]) => { + paramAttributes[`url.path.parameter.${key}`] = value; + paramAttributes[`params.${key}`] = value; // params.[key] is an alias + }); + + return paramAttributes; +} diff --git a/packages/solid/tsconfig.solidrouter-types.json b/packages/solid/tsconfig.routers-types.json similarity index 85% rename from packages/solid/tsconfig.solidrouter-types.json rename to packages/solid/tsconfig.routers-types.json index 055ad82a187a..e173ebc0eb87 100644 --- a/packages/solid/tsconfig.solidrouter-types.json +++ b/packages/solid/tsconfig.routers-types.json @@ -9,7 +9,7 @@ }, "//": "This type is built separately because it is for a subpath export, which has problems if it is not in the root", - "include": ["src/solidrouter.ts"], + "include": ["src/solidrouter.ts", "src/tanstackrouter.ts"], "//": "Without this, we cannot output into the root dir", "exclude": [] } diff --git a/packages/solid/tsconfig.types.json b/packages/solid/tsconfig.types.json index fa96a3ccc08b..510f8c4fae3f 100644 --- a/packages/solid/tsconfig.types.json +++ b/packages/solid/tsconfig.types.json @@ -8,6 +8,6 @@ "outDir": "build/types" }, - "//": "This is built separately in tsconfig.solidrouter-types.json", - "exclude": ["src/solidrouter.ts"] + "//": "This is built separately in tsconfig.routers-types.json", + "exclude": ["src/solidrouter.ts", "src/tanstackrouter.ts"] } diff --git a/yarn.lock b/yarn.lock index 5e9edc00f1df..70c9e3d80b73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5217,6 +5217,11 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" +"@nothing-but/utils@~0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@nothing-but/utils/-/utils-0.17.0.tgz#eab601990c71ef29053ffc484909f2d1f26d88d8" + integrity sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ== + "@npmcli/fs@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" @@ -7630,6 +7635,136 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== +"@solid-devtools/debugger@^0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@solid-devtools/debugger/-/debugger-0.28.1.tgz#5c2e9d533ef65ac9debb4b1c3a625c6494f811c6" + integrity sha512-6qIUI6VYkXoRnL8oF5bvh2KgH71qlJ18hNw/mwSyY6v48eb80ZR48/5PDXufUa3q+MBSuYa1uqTMwLewpay9eg== + dependencies: + "@nothing-but/utils" "~0.17.0" + "@solid-devtools/shared" "^0.20.0" + "@solid-primitives/bounds" "^0.1.1" + "@solid-primitives/event-listener" "^2.4.1" + "@solid-primitives/keyboard" "^1.3.1" + "@solid-primitives/rootless" "^1.5.1" + "@solid-primitives/scheduled" "^1.5.1" + "@solid-primitives/static-store" "^0.1.1" + "@solid-primitives/utils" "^6.3.1" + +"@solid-devtools/logger@^0.9.4": + version "0.9.11" + resolved "https://registry.yarnpkg.com/@solid-devtools/logger/-/logger-0.9.11.tgz#a94d8ec640df8887eca7a0aaf8b2788adb244228" + integrity sha512-THbiY1iQlieL6vdgJc4FIsLe7V8a57hod/Thm8zdKrTkWL88UPZjkBBfM+mVNGusd4OCnAN20tIFBhNnuT1Dew== + dependencies: + "@nothing-but/utils" "~0.17.0" + "@solid-devtools/debugger" "^0.28.1" + "@solid-devtools/shared" "^0.20.0" + "@solid-primitives/utils" "^6.3.1" + +"@solid-devtools/shared@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@solid-devtools/shared/-/shared-0.20.0.tgz#1dff1573ee3bc43acd6d2dc3a2ed3765b20dcd3c" + integrity sha512-o5TACmUOQsxpzpOKCjbQqGk8wL8PMi+frXG9WNu4Lh3PQVUB6hs95Kl/S8xc++zwcMguUKZJn8h5URUiMOca6Q== + dependencies: + "@nothing-but/utils" "~0.17.0" + "@solid-primitives/event-listener" "^2.4.1" + "@solid-primitives/media" "^2.3.1" + "@solid-primitives/refs" "^1.1.1" + "@solid-primitives/rootless" "^1.5.1" + "@solid-primitives/scheduled" "^1.5.1" + "@solid-primitives/static-store" "^0.1.1" + "@solid-primitives/styles" "^0.1.1" + "@solid-primitives/utils" "^6.3.1" + +"@solid-primitives/bounds@^0.1.1": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@solid-primitives/bounds/-/bounds-0.1.3.tgz#6c7cca6fb969281a1ee103efc982cb190358f81c" + integrity sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q== + dependencies: + "@solid-primitives/event-listener" "^2.4.3" + "@solid-primitives/resize-observer" "^2.1.3" + "@solid-primitives/static-store" "^0.1.2" + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/event-listener@^2.4.1", "@solid-primitives/event-listener@^2.4.3": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz#e09380222e38ed1b27f3d93bc72e85ba8507b3c0" + integrity sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg== + dependencies: + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/keyboard@^1.3.1": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@solid-primitives/keyboard/-/keyboard-1.3.3.tgz#d51ab3c66308c2551d47452ff3dbdbbc0f25c546" + integrity sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA== + dependencies: + "@solid-primitives/event-listener" "^2.4.3" + "@solid-primitives/rootless" "^1.5.2" + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/media@^2.3.1": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@solid-primitives/media/-/media-2.3.3.tgz#74d669b6814c30a8308a468cfd7412133ea7d16e" + integrity sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA== + dependencies: + "@solid-primitives/event-listener" "^2.4.3" + "@solid-primitives/rootless" "^1.5.2" + "@solid-primitives/static-store" "^0.1.2" + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/refs@^1.0.8", "@solid-primitives/refs@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/refs/-/refs-1.1.2.tgz#1a37a825754bc8fe7f8845fc0c7664683646288e" + integrity sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg== + dependencies: + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/resize-observer@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@solid-primitives/resize-observer/-/resize-observer-2.1.3.tgz#459db96f9c4a3d98a194d940c6e69f3ad4b2dad8" + integrity sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ== + dependencies: + "@solid-primitives/event-listener" "^2.4.3" + "@solid-primitives/rootless" "^1.5.2" + "@solid-primitives/static-store" "^0.1.2" + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/rootless@^1.5.1", "@solid-primitives/rootless@^1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/rootless/-/rootless-1.5.2.tgz#0a9243a977672169cb8ed43bf4eba0c4d8eb5ac5" + integrity sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ== + dependencies: + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/scheduled@^1.5.1": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/scheduled/-/scheduled-1.5.2.tgz#616def57b9250bc0e4415c604b31ab5a71465632" + integrity sha512-/j2igE0xyNaHhj6kMfcUQn5rAVSTLbAX+CDEBm25hSNBmNiHLu2lM7Usj2kJJ5j36D67bE8wR1hBNA8hjtvsQA== + +"@solid-primitives/static-store@^0.1.1", "@solid-primitives/static-store@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/static-store/-/static-store-0.1.2.tgz#acdbecee75f17a5b64416859082fca67eefbaaaa" + integrity sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw== + dependencies: + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/styles@^0.1.1": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/styles/-/styles-0.1.2.tgz#282fbf8d37add03873fd67b692b2e03ad412581e" + integrity sha512-7iX5K+J5b1PRrbgw3Ki92uvU2LgQ0Kd/QMsrAZxDg5dpUBwMyTijZkA3bbs1ikZsT1oQhS41bTyKbjrXeU0Awg== + dependencies: + "@solid-primitives/rootless" "^1.5.2" + "@solid-primitives/utils" "^6.3.2" + +"@solid-primitives/utils@^6.3.1", "@solid-primitives/utils@^6.3.2": + version "6.3.2" + resolved "https://registry.yarnpkg.com/@solid-primitives/utils/-/utils-6.3.2.tgz#13d6126fc5a498965d7c45dd41c052e42dcfd7e1" + integrity sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ== + +"@solidjs/meta@^0.29.4": + version "0.29.4" + resolved "https://registry.yarnpkg.com/@solidjs/meta/-/meta-0.29.4.tgz#28a444db5200d1c9e4e62d8762ea808d3e8beffd" + integrity sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g== + "@solidjs/router@^0.13.4": version "0.13.6" resolved "https://registry.yarnpkg.com/@solidjs/router/-/router-0.13.6.tgz#210ca2761d4bf294f06ac0f9e25c16fafdabefac" @@ -7793,6 +7928,56 @@ dependencies: defer-to-connect "^1.0.1" +"@tanstack/history@1.132.21": + version "1.132.21" + resolved "https://registry.yarnpkg.com/@tanstack/history/-/history-1.132.21.tgz#09ae649b0c0c2d1093f0b1e34b9ab0cd3b2b1d2f" + integrity sha512-5ziPz3YarKU5cBJoEJ4muV8cy+5W4oWdJMqW7qosMrK5fb9Qfm+QWX+kO3emKJMu4YOUofVu3toEuuD3x1zXKw== + +"@tanstack/router-core@1.132.27": + version "1.132.27" + resolved "https://registry.yarnpkg.com/@tanstack/router-core/-/router-core-1.132.27.tgz#8869e98d10ea42338cb115af45bdcbc10eaf2b7f" + integrity sha512-mNx+nba7mXc7sJdX+kYH4rSW8f7Jx/+0hPOkX4XAnqiq7I1ng3gGqmGuf4+2BYTG2aD+aTSPExUPczy9VNgRfQ== + dependencies: + "@tanstack/history" "1.132.21" + "@tanstack/store" "^0.7.0" + cookie-es "^2.0.0" + seroval "^1.3.2" + seroval-plugins "^1.3.2" + tiny-invariant "^1.3.3" + tiny-warning "^1.0.3" + +"@tanstack/solid-router@^1.132.27": + version "1.132.27" + resolved "https://registry.yarnpkg.com/@tanstack/solid-router/-/solid-router-1.132.27.tgz#cafa331a8190fb6775f3cd3b88f31adce82e8cc8" + integrity sha512-d1JfRvl53wJpoOsqStSX5ATCWegSWo7ygrwT+uRvXIebG3fsriGHWkL0u39U515fIYX9Br3PU2iKNk5eShCgtA== + dependencies: + "@solid-devtools/logger" "^0.9.4" + "@solid-primitives/refs" "^1.0.8" + "@solidjs/meta" "^0.29.4" + "@tanstack/history" "1.132.21" + "@tanstack/router-core" "1.132.27" + "@tanstack/solid-store" "0.7.0" + isbot "^5.1.22" + tiny-invariant "^1.3.3" + tiny-warning "^1.0.3" + +"@tanstack/solid-store@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@tanstack/solid-store/-/solid-store-0.7.0.tgz#4fd8172bb8ba6f8438ddaa5e1ed1fc8afa14a5a1" + integrity sha512-uDQYkUuH3MppitiduZLTEcItkTr8vEJ33jzp2rH2VvlNRMGbuU54GQcqf3dLIlTbZ1/Z2TtIBtBjjl+N/OhwRg== + dependencies: + "@tanstack/store" "0.7.0" + +"@tanstack/store@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.7.0.tgz#afef29b06c6b592e93181cee9baa62fe77454459" + integrity sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg== + +"@tanstack/store@^0.7.0": + version "0.7.7" + resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.7.7.tgz#2c8b1d8c094f3614ae4e0483253239abd0e14488" + integrity sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ== + "@testing-library/dom@^7.21.4": version "7.31.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" @@ -19847,6 +20032,11 @@ isbinaryfile@^5.0.0: resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.0.tgz#034b7e54989dab8986598cbcea41f66663c65234" integrity sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg== +isbot@^5.1.22: + version "5.1.31" + resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.31.tgz#ecbab171da577002c66f9123fe180c1e795e4e4e" + integrity sha512-DPgQshehErHAqSCKDb3rNW03pa2wS/v5evvUqtxt6TTnHRqAG8FdzcSSJs9656pK6Y+NT7K9R4acEYXLHYfpUQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -27534,12 +27724,12 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1, serialize-javascript@^ dependencies: randombytes "^2.1.0" -seroval-plugins@^1.0.2, seroval-plugins@~1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.3.2.tgz#4200b538d699853c9bf5c3b7155c498c7c263a6a" - integrity sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ== +seroval-plugins@^1.0.2, seroval-plugins@^1.3.2, seroval-plugins@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.3.3.tgz#51bcacf09e5384080d7ea4002b08fd9f6166daf5" + integrity sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w== -seroval@^1.0.2, seroval@~1.3.0: +seroval@^1.0.2, seroval@^1.3.2, seroval@~1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.3.2.tgz#7e5be0dc1a3de020800ef013ceae3a313f20eca7" integrity sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ== @@ -29359,7 +29549,7 @@ tiny-lr@^2.0.0: object-assign "^4.1.0" qs "^6.4.0" -tiny-warning@^1.0.0: +tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== From 715f12f447bf6450a8715aff69c54e66598d82e3 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 15 Oct 2025 13:32:03 +0200 Subject: [PATCH 07/16] chore(ci): Update Next.js canary testing (#17939) next@canary now resolves to next 16 which is why we need to update the testing strategy here --- .github/workflows/canary.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index fbf476c369a4..29814ffea09c 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -76,11 +76,8 @@ jobs: build-command: 'test:build-canary' label: 'create-react-app (canary)' - test-application: 'nextjs-app-dir' - build-command: 'test:build-canary' - label: 'nextjs-app-dir (canary)' - - test-application: 'nextjs-app-dir' - build-command: 'test:build-latest' - label: 'nextjs-app-dir (latest)' + build-command: 'test:build-15' + label: 'nextjs-app-dir (next@15)' - test-application: 'nextjs-13' build-command: 'test:build-latest' label: 'nextjs-13 (latest)' @@ -90,12 +87,15 @@ jobs: - test-application: 'nextjs-14' build-command: 'test:build-latest' label: 'nextjs-14 (latest)' - - test-application: 'nextjs-15' - build-command: 'test:build-canary' - label: 'nextjs-15 (canary)' - test-application: 'nextjs-15' build-command: 'test:build-latest' label: 'nextjs-15 (latest)' + - test-application: 'nextjs-16' + build-command: 'test:build-canary' + label: 'nextjs-16 (canary)' + - test-application: 'nextjs-16' + build-command: 'test:build-canary-webpack' + label: 'nextjs-16 (canary-webpack)' - test-application: 'nextjs-turbo' build-command: 'test:build-canary' label: 'nextjs-turbo (canary)' From 1c4c62ba6be7c4c6e567dfb86f255c51578c56bb Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 15 Oct 2025 13:36:26 +0200 Subject: [PATCH 08/16] chore: Bump size limit (#17941) --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 5ccf34d416c0..5b8374f81615 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -183,7 +183,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.min.js'), gzip: false, brotli: false, - limit: '123 KB', + limit: '124 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed', From eb5a5a86d56f8b50596ad9652bd115e346fac16c Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Wed, 15 Oct 2025 13:38:18 +0200 Subject: [PATCH 09/16] chore: Add external contributor to CHANGELOG.md (#17940) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #17735 --------- Co-authored-by: andreiborza <168741329+andreiborza@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1415d2a3941c..1bfc1edf75a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @seoyeon9888 and @madhuchavva. Thank you for your contributions! +Work in this release was contributed by @seoyeon9888, @madhuchavva and @thedanchez . Thank you for your contributions! ## 10.19.0 From e53277e3245c37be0fe735a29db89a7d97cb086d Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 15 Oct 2025 12:51:24 +0100 Subject: [PATCH 10/16] fix(react): Add `POP` guard for long-running `pageload` spans (#17867) This resolves the issue that occurs when an extra `navigation` transaction is created after a prematurely ended `pageload` transaction in React Router lazy routes. This apparently occurs when there's a long-running pageload with lazy-routes (after fetching assets, there are multiple potentially long-running API calls happening). This causes the `pageload` transaction to prematurely end, even before the fully parameterized transaction name is resolved. The reason is that there can be a `POP` event emitted, which we subscribe to create a `navigation` transaction. This ends the ongoing `pageload` transaction before its name is updated with a resolved parameterized route path, and starts a `navigation` transaction, which contains the remaining spans that were supposed to be a part of the `pageload` transaction. This fix makes sure the initial `POP` events are not necessarily treated as `navigation` pointers, which should fix both: - Duplicate / extra `navigation` transactions having a part of `pageload` spans. - Remaining wildcards in the `pageload` transaction names --- .../react-router-7-lazy-routes/src/index.tsx | 6 + .../src/pages/Index.tsx | 4 + .../src/pages/LongRunningLazyRoutes.tsx | 49 +++++++ .../tests/transactions.test.ts | 83 +++++++++++ .../instrumentation.tsx | 134 ++++++++++++++---- 5 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/LongRunningLazyRoutes.tsx diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx index 2c960db9c16b..521048fd18f4 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/index.tsx @@ -55,6 +55,12 @@ const router = sentryCreateBrowserRouter( lazyChildren: () => import('./pages/AnotherLazyRoutes').then(module => module.anotherNestedRoutes), }, }, + { + path: '/long-running', + handle: { + lazyChildren: () => import('./pages/LongRunningLazyRoutes').then(module => module.longRunningNestedRoutes), + }, + }, { path: '/static', element: <>Hello World, diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx index aefa39d63811..3053aa57b887 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/Index.tsx @@ -15,6 +15,10 @@ const Index = () => { Navigate to Another Deep Lazy Route +
+ + Navigate to Long Running Lazy Route + ); }; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/LongRunningLazyRoutes.tsx b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/LongRunningLazyRoutes.tsx new file mode 100644 index 000000000000..416fb1e162f8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/src/pages/LongRunningLazyRoutes.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react'; +import { Link, useParams } from 'react-router-dom'; + +// Component that simulates a long-running component load +// This is used to test the POP guard during long-running pageloads +const SlowLoadingComponent = () => { + const { id } = useParams<{ id: string }>(); + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // Simulate a component that takes time to initialize + // This extends the pageload duration to create a window where POP events might occur + setTimeout(() => { + setData(`Data loaded for ID: ${id}`); + setIsLoading(false); + }, 1000); + }, [id]); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
{data}
+ + Go Home + +
+ ); +}; + +export const longRunningNestedRoutes = [ + { + path: 'slow', + children: [ + { + path: ':id', + element: , + loader: async () => { + // Simulate slow data fetching in the loader + await new Promise(resolve => setTimeout(resolve, 2000)); + return null; + }, + }, + ], + }, +]; diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts index 34e5105f8f9d..59d43c14ae95 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/tests/transactions.test.ts @@ -294,3 +294,86 @@ test('Does not send any duplicate navigation transaction names browsing between '/lazy/inner/:id/:anotherId', ]); }); + +test('Does not create premature navigation transaction during long-running lazy route pageload', async ({ page }) => { + const navigationPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => { + return ( + !!transactionEvent?.transaction && + transactionEvent.contexts?.trace?.op === 'navigation' && + transactionEvent.transaction.includes('long-running') + ); + }); + + const pageloadPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => { + return ( + !!transactionEvent?.transaction && + transactionEvent.contexts?.trace?.op === 'pageload' && + transactionEvent.transaction === '/long-running/slow/:id' + ); + }); + + await page.goto('/long-running/slow/12345'); + + const pageloadEvent = await pageloadPromise; + + expect(pageloadEvent.transaction).toBe('/long-running/slow/:id'); + expect(pageloadEvent.contexts?.trace?.op).toBe('pageload'); + + const slowLoadingContent = page.locator('id=slow-loading-content'); + await expect(slowLoadingContent).toBeVisible({ timeout: 5000 }); + + const result = await Promise.race([ + navigationPromise.then(() => 'navigation'), + new Promise<'timeout'>(resolve => setTimeout(() => resolve('timeout'), 2000)), + ]); + + // Should timeout, meaning no unwanted navigation transaction was created + expect(result).toBe('timeout'); +}); + +test('Allows legitimate POP navigation (back/forward) after pageload completes', async ({ page }) => { + await page.goto('/'); + + const navigationToLongRunning = page.locator('id=navigation-to-long-running'); + await expect(navigationToLongRunning).toBeVisible(); + + const firstNavigationPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => { + return ( + !!transactionEvent?.transaction && + transactionEvent.contexts?.trace?.op === 'navigation' && + transactionEvent.transaction === '/long-running/slow/:id' + ); + }); + + await navigationToLongRunning.click(); + + const slowLoadingContent = page.locator('id=slow-loading-content'); + await expect(slowLoadingContent).toBeVisible({ timeout: 5000 }); + + const firstNavigationEvent = await firstNavigationPromise; + + expect(firstNavigationEvent.transaction).toBe('/long-running/slow/:id'); + expect(firstNavigationEvent.contexts?.trace?.op).toBe('navigation'); + + // Now navigate back using browser back button (POP event) + // This should create a navigation transaction since pageload is complete + const backNavigationPromise = waitForTransaction('react-router-7-lazy-routes', async transactionEvent => { + return ( + !!transactionEvent?.transaction && + transactionEvent.contexts?.trace?.op === 'navigation' && + transactionEvent.transaction === '/' + ); + }); + + await page.goBack(); + + // Verify we're back at home + const homeLink = page.locator('id=navigation'); + await expect(homeLink).toBeVisible(); + + const backNavigationEvent = await backNavigationPromise; + + // Validate that the back navigation (POP) was properly tracked + expect(backNavigationEvent.transaction).toBe('/'); + expect(backNavigationEvent.contexts?.trace?.op).toBe('navigation'); +}); diff --git a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx index 10db32231195..bf57fdbd74dc 100644 --- a/packages/react/src/reactrouter-compat-utils/instrumentation.tsx +++ b/packages/react/src/reactrouter-compat-utils/instrumentation.tsx @@ -241,6 +241,12 @@ export function createV6CompatibleWrapCreateBrowserRouter< const activeRootSpan = getActiveRootSpan(); + // Track whether we've completed the initial pageload to properly distinguish + // between POPs that occur during pageload vs. legitimate back/forward navigation. + let isInitialPageloadComplete = false; + let hasSeenPageloadSpan = !!activeRootSpan && spanToJSON(activeRootSpan).op === 'pageload'; + let hasSeenPopAfterPageload = false; + // The initial load ends when `createBrowserRouter` is called. // This is the earliest convenient time to update the transaction name. // Callbacks to `router.subscribe` are not called for the initial load. @@ -255,20 +261,31 @@ export function createV6CompatibleWrapCreateBrowserRouter< } router.subscribe((state: RouterState) => { - if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - // Wait for the next render if loading an unsettled route - if (state.navigation.state !== 'idle') { - requestAnimationFrame(() => { - handleNavigation({ - location: state.location, - routes, - navigationType: state.historyAction, - version, - basename, - allRoutes: Array.from(allRoutes), - }); - }); - } else { + // Track pageload completion to distinguish POPs during pageload from legitimate back/forward navigation + if (!isInitialPageloadComplete) { + const currentRootSpan = getActiveRootSpan(); + const isCurrentlyInPageload = currentRootSpan && spanToJSON(currentRootSpan).op === 'pageload'; + + if (isCurrentlyInPageload) { + hasSeenPageloadSpan = true; + } else if (hasSeenPageloadSpan) { + // Pageload span was active but is now gone - pageload has completed + if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) { + // Pageload ended: ignore the first POP after pageload + hasSeenPopAfterPageload = true; + } else { + // Pageload ended: either non-POP action or subsequent POP + isInitialPageloadComplete = true; + } + } + // If we haven't seen a pageload span yet, keep waiting (don't mark as complete) + } + + const shouldHandleNavigation = + state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete); + + if (shouldHandleNavigation) { + const navigationHandler = (): void => { handleNavigation({ location: state.location, routes, @@ -277,6 +294,13 @@ export function createV6CompatibleWrapCreateBrowserRouter< basename, allRoutes: Array.from(allRoutes), }); + }; + + // Wait for the next render if loading an unsettled route + if (state.navigation.state !== 'idle') { + requestAnimationFrame(navigationHandler); + } else { + navigationHandler(); } } }); @@ -327,7 +351,6 @@ export function createV6CompatibleWrapCreateMemoryRouter< const router = createRouterFunction(routes, wrappedOpts); const basename = opts?.basename; - const activeRootSpan = getActiveRootSpan(); let initialEntry = undefined; const initialEntries = opts?.initialEntries; @@ -348,21 +371,68 @@ export function createV6CompatibleWrapCreateMemoryRouter< : initialEntry : router.state.location; - if (router.state.historyAction === 'POP' && activeRootSpan) { - updatePageloadTransaction({ activeRootSpan, location, routes, basename, allRoutes: Array.from(allRoutes) }); + const memoryActiveRootSpan = getActiveRootSpan(); + + if (router.state.historyAction === 'POP' && memoryActiveRootSpan) { + updatePageloadTransaction({ + activeRootSpan: memoryActiveRootSpan, + location, + routes, + basename, + allRoutes: Array.from(allRoutes), + }); } + // Track whether we've completed the initial pageload to properly distinguish + // between POPs that occur during pageload vs. legitimate back/forward navigation. + let isInitialPageloadComplete = false; + let hasSeenPageloadSpan = !!memoryActiveRootSpan && spanToJSON(memoryActiveRootSpan).op === 'pageload'; + let hasSeenPopAfterPageload = false; + router.subscribe((state: RouterState) => { + // Track pageload completion to distinguish POPs during pageload from legitimate back/forward navigation + if (!isInitialPageloadComplete) { + const currentRootSpan = getActiveRootSpan(); + const isCurrentlyInPageload = currentRootSpan && spanToJSON(currentRootSpan).op === 'pageload'; + + if (isCurrentlyInPageload) { + hasSeenPageloadSpan = true; + } else if (hasSeenPageloadSpan) { + // Pageload span was active but is now gone - pageload has completed + if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) { + // Pageload ended: ignore the first POP after pageload + hasSeenPopAfterPageload = true; + } else { + // Pageload ended: either non-POP action or subsequent POP + isInitialPageloadComplete = true; + } + } + // If we haven't seen a pageload span yet, keep waiting (don't mark as complete) + } + const location = state.location; - if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { - handleNavigation({ - location, - routes, - navigationType: state.historyAction, - version, - basename, - allRoutes: Array.from(allRoutes), - }); + + const shouldHandleNavigation = + state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete); + + if (shouldHandleNavigation) { + const navigationHandler = (): void => { + handleNavigation({ + location, + routes, + navigationType: state.historyAction, + version, + basename, + allRoutes: Array.from(allRoutes), + }); + }; + + // Wait for the next render if loading an unsettled route + if (state.navigation.state !== 'idle') { + requestAnimationFrame(navigationHandler); + } else { + navigationHandler(); + } } }); @@ -532,8 +602,16 @@ function wrapPatchRoutesOnNavigation( // Update navigation span after routes are patched const activeRootSpan = getActiveRootSpan(); if (activeRootSpan && (spanToJSON(activeRootSpan) as { op?: string }).op === 'navigation') { - // For memory routers, we should not access window.location; use targetPath only - const pathname = isMemoryRouter ? targetPath : targetPath || WINDOW.location?.pathname; + // Determine pathname based on router type + let pathname: string | undefined; + if (isMemoryRouter) { + // For memory routers, only use targetPath + pathname = targetPath; + } else { + // For browser routers, use targetPath or fall back to window.location + pathname = targetPath || WINDOW.location?.pathname; + } + if (pathname) { updateNavigationSpan( activeRootSpan, From d03fc614adb880c5bc2b7e58eeb8831cfcab68d7 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 15 Oct 2025 14:02:05 +0200 Subject: [PATCH 11/16] fix(tracemetrics): Send boolean for internal replay attribute (#17908) --- packages/core/src/metrics/internal.ts | 2 +- packages/core/test/lib/metrics/internal.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index f16352523700..676814f4d4e6 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -172,7 +172,7 @@ export function _INTERNAL_captureMetric(beforeMetric: Metric, options?: Internal if (replayId && replay?.getRecordingMode() === 'buffer') { // We send this so we can identify cases where the replayId is attached but the replay itself might not have been sent to Sentry - setMetricAttribute(processedMetricAttributes, 'sentry._internal.replay_is_buffering', replayId); + setMetricAttribute(processedMetricAttributes, 'sentry._internal.replay_is_buffering', true); } const metric: Metric = { diff --git a/packages/core/test/lib/metrics/internal.test.ts b/packages/core/test/lib/metrics/internal.test.ts index 33f5bb0de3ae..bb2ddcc413c3 100644 --- a/packages/core/test/lib/metrics/internal.test.ts +++ b/packages/core/test/lib/metrics/internal.test.ts @@ -450,8 +450,8 @@ describe('_INTERNAL_captureMetric', () => { type: 'string', }, 'sentry._internal.replay_is_buffering': { - value: 'buffer-replay-id', - type: 'string', + value: true, + type: 'boolean', }, }); }); @@ -577,8 +577,8 @@ describe('_INTERNAL_captureMetric', () => { type: 'string', }, 'sentry._internal.replay_is_buffering': { - value: 'buffer-replay-id', - type: 'string', + value: true, + type: 'boolean', }, }); }); @@ -736,8 +736,8 @@ describe('_INTERNAL_captureMetric', () => { type: 'string', }, 'sentry._internal.replay_is_buffering': { - value: 'buffer-replay-id', - type: 'string', + value: true, + type: 'boolean', }, }); }); From 6abafe3688232dd7f5714ed916913ad1e616f8c5 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 15 Oct 2025 16:04:11 +0200 Subject: [PATCH 12/16] meta(changelog): Update changelog for 10.20.0 --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bfc1edf75a8..a14433358d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,44 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.20.0 + +### Important Changes + +- **feat(flags): Add Growthbook integration ([#17440](https://github.com/getsentry/sentry-javascript/pull/17440))** + + Adds a new Growthbook integration for feature flag support. + +- **feat(solid): Add support for TanStack Router Solid ([#17735](https://github.com/getsentry/sentry-javascript/pull/17735))** + + Adds support for TanStack Router in the Solid SDK, enabling better routing instrumentation for Solid applications. + +- **feat(nextjs): Support native debugIds in turbopack ([#17853](https://github.com/getsentry/sentry-javascript/pull/17853))** + + Adds support for native Debug IDs in Turbopack, improving source map resolution and error tracking for Next.js applications using Turbopack. Native Debug ID generation will be enabled automatically for compatible versions. + +### Other Changes + +- feat(nextjs): Prepare for next 16 bundler default ([#17868](https://github.com/getsentry/sentry-javascript/pull/17868)) +- feat(node): Capture `pino` logger name ([#17930](https://github.com/getsentry/sentry-javascript/pull/17930)) +- fix(browser): Ignore React 19.2+ component render measure entries ([#17905](https://github.com/getsentry/sentry-javascript/pull/17905)) +- fix(nextjs): Fix createRouteManifest with basePath ([#17838](https://github.com/getsentry/sentry-javascript/pull/17838)) +- fix(react): Add `POP` guard for long-running `pageload` spans ([#17867](https://github.com/getsentry/sentry-javascript/pull/17867)) +- fix(tracemetrics): Send boolean for internal replay attribute ([#17908](https://github.com/getsentry/sentry-javascript/pull/17908)) +- ref(core): Add weight tracking logic to browser logs/metrics ([#17901](https://github.com/getsentry/sentry-javascript/pull/17901)) + +
+ Internal Changes +- chore(nextjs): Add Next.js 16 peer dependency ([#17925](https://github.com/getsentry/sentry-javascript/pull/17925)) +- chore(ci): Update Next.js canary testing ([#17939](https://github.com/getsentry/sentry-javascript/pull/17939)) +- chore: Bump size limit ([#17941](https://github.com/getsentry/sentry-javascript/pull/17941)) +- test(nextjs): Add next@16 e2e test ([#17922](https://github.com/getsentry/sentry-javascript/pull/17922)) +- test(nextjs): Update next 15 tests ([#17919](https://github.com/getsentry/sentry-javascript/pull/17919)) +- chore: Add external contributor to CHANGELOG.md ([#17915](https://github.com/getsentry/sentry-javascript/pull/17915)) +- chore: Add external contributor to CHANGELOG.md ([#17928](https://github.com/getsentry/sentry-javascript/pull/17928)) +- chore: Add external contributor to CHANGELOG.md ([#17940](https://github.com/getsentry/sentry-javascript/pull/17940)) +
+ Work in this release was contributed by @seoyeon9888, @madhuchavva and @thedanchez . Thank you for your contributions! ## 10.19.0 From 8eb3787d3e335515628023aeab8765184a067d93 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 15 Oct 2025 17:07:22 +0200 Subject: [PATCH 13/16] Child tests --- CHANGELOG.md | 38 ------------------- .../suites/pino/scenario-track.mjs | 13 ++++++- .../suites/pino/scenario.mjs | 2 +- .../suites/pino/test.ts | 3 ++ packages/angular/README.md | 2 +- packages/node-core/src/integrations/pino.ts | 8 ++-- 6 files changed, 21 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14433358d0c..1bfc1edf75a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,44 +4,6 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -## 10.20.0 - -### Important Changes - -- **feat(flags): Add Growthbook integration ([#17440](https://github.com/getsentry/sentry-javascript/pull/17440))** - - Adds a new Growthbook integration for feature flag support. - -- **feat(solid): Add support for TanStack Router Solid ([#17735](https://github.com/getsentry/sentry-javascript/pull/17735))** - - Adds support for TanStack Router in the Solid SDK, enabling better routing instrumentation for Solid applications. - -- **feat(nextjs): Support native debugIds in turbopack ([#17853](https://github.com/getsentry/sentry-javascript/pull/17853))** - - Adds support for native Debug IDs in Turbopack, improving source map resolution and error tracking for Next.js applications using Turbopack. Native Debug ID generation will be enabled automatically for compatible versions. - -### Other Changes - -- feat(nextjs): Prepare for next 16 bundler default ([#17868](https://github.com/getsentry/sentry-javascript/pull/17868)) -- feat(node): Capture `pino` logger name ([#17930](https://github.com/getsentry/sentry-javascript/pull/17930)) -- fix(browser): Ignore React 19.2+ component render measure entries ([#17905](https://github.com/getsentry/sentry-javascript/pull/17905)) -- fix(nextjs): Fix createRouteManifest with basePath ([#17838](https://github.com/getsentry/sentry-javascript/pull/17838)) -- fix(react): Add `POP` guard for long-running `pageload` spans ([#17867](https://github.com/getsentry/sentry-javascript/pull/17867)) -- fix(tracemetrics): Send boolean for internal replay attribute ([#17908](https://github.com/getsentry/sentry-javascript/pull/17908)) -- ref(core): Add weight tracking logic to browser logs/metrics ([#17901](https://github.com/getsentry/sentry-javascript/pull/17901)) - -
- Internal Changes -- chore(nextjs): Add Next.js 16 peer dependency ([#17925](https://github.com/getsentry/sentry-javascript/pull/17925)) -- chore(ci): Update Next.js canary testing ([#17939](https://github.com/getsentry/sentry-javascript/pull/17939)) -- chore: Bump size limit ([#17941](https://github.com/getsentry/sentry-javascript/pull/17941)) -- test(nextjs): Add next@16 e2e test ([#17922](https://github.com/getsentry/sentry-javascript/pull/17922)) -- test(nextjs): Update next 15 tests ([#17919](https://github.com/getsentry/sentry-javascript/pull/17919)) -- chore: Add external contributor to CHANGELOG.md ([#17915](https://github.com/getsentry/sentry-javascript/pull/17915)) -- chore: Add external contributor to CHANGELOG.md ([#17928](https://github.com/getsentry/sentry-javascript/pull/17928)) -- chore: Add external contributor to CHANGELOG.md ([#17940](https://github.com/getsentry/sentry-javascript/pull/17940)) -
- Work in this release was contributed by @seoyeon9888, @madhuchavva and @thedanchez . Thank you for your contributions! ## 10.19.0 diff --git a/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs index 2e968444a74f..d555bf4ecf75 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs @@ -17,7 +17,18 @@ Sentry.withIsolationScope(() => { setTimeout(() => { Sentry.withIsolationScope(() => { Sentry.startSpan({ name: 'later' }, () => { - logger.error(new Error('oh no')); + // This child should be captured as we marked the parent logger to be tracked + const child = logger.child({ module: 'authentication' }); + child.error(new Error('oh no')); + + // This child should be ignored + const child2 = logger.child({ module: 'authentication.v2' }); + Sentry.pinoIntegration.ignoreLogger(child2); + child2.error(new Error('oh no v2')); + + // This should also be ignored as the parent is ignored + const child3 = child2.child({ module: 'authentication.v3' }); + child3.error(new Error('oh no v3')); }); }); }, 1000); diff --git a/dev-packages/node-integration-tests/suites/pino/scenario.mjs b/dev-packages/node-integration-tests/suites/pino/scenario.mjs index 55966552a07f..9588e50e6e4f 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario.mjs @@ -4,7 +4,7 @@ import pino from 'pino'; const logger = pino({ name: 'myapp' }); const ignoredLogger = pino({ name: 'ignored' }); -Sentry.pinoIntegration.untrackLogger(ignoredLogger); +Sentry.pinoIntegration.ignoreLogger(ignoredLogger); ignoredLogger.info('this will not be tracked'); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index fc19dbd95aa6..f9cdb143ddff 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -195,6 +195,7 @@ conditionalTest({ min: 20 })('Pino integration', () => { type: 'string', value: '{"more":3,"complex":"nope"}', }, + msg: { value: 'hello world', type: 'string' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, @@ -208,6 +209,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { severity_number: 17, attributes: { name: { value: 'myapp', type: 'string' }, + module: { value: 'authentication', type: 'string' }, + msg: { value: 'oh no', type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, diff --git a/packages/angular/README.md b/packages/angular/README.md index f686fb266b23..384c4c2d48c8 100644 --- a/packages/angular/README.md +++ b/packages/angular/README.md @@ -16,7 +16,7 @@ ## Angular Version Compatibility -This SDK officially supports Angular 14 to 20. +This SDK officially supports Angular 14 to 19. If you're using an older Angular version please check the [compatibility table in the docs](https://docs.sentry.io/platforms/javascript/guides/angular/#angular-version-compatibility). diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index b636d0ad9b7c..846d38eb1bcb 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -97,7 +97,7 @@ function stripIgnoredFields(result: PinoResult): PinoResult { const _pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { const options: PinoOptions = { - autoInstrument: userOptions.autoInstrument === false ? userOptions.autoInstrument : DEFAULT_OPTIONS.autoInstrument, + autoInstrument: userOptions.autoInstrument !== false, error: { ...DEFAULT_OPTIONS.error, ...userOptions.error }, log: { ...DEFAULT_OPTIONS.log, ...userOptions.log }, }; @@ -203,14 +203,14 @@ interface PinoIntegrationFunction { * * @param logger A Pino logger instance. */ - untrackLogger(logger: unknown): void; + ignoreLogger(logger: unknown): void; } /** * Integration for Pino logging library. * Captures Pino logs as Sentry logs and optionally captures some log levels as events. * - * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.untrackLogger(logger)`. + * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.ignoreLogger(logger)`. * * If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`. * @@ -222,7 +222,7 @@ export const pinoIntegration = Object.assign(_pinoIntegration, { (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'track'; } }, - untrackLogger(logger: unknown): void { + ignoreLogger(logger: unknown): void { if (logger && typeof logger === 'object' && 'levels' in logger) { (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'ignore'; } From 1bc5b4fdd04ed14b5caf591d8347bfcf6d13c01d Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 16 Oct 2025 13:30:33 +0200 Subject: [PATCH 14/16] No breaking changes! --- .../node-integration-tests/suites/pino/scenario-track.mjs | 2 +- .../node-integration-tests/suites/pino/scenario.mjs | 2 +- packages/angular/README.md | 2 +- packages/node-core/src/integrations/pino.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs index d555bf4ecf75..e55f11f9c00c 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario-track.mjs @@ -23,7 +23,7 @@ setTimeout(() => { // This child should be ignored const child2 = logger.child({ module: 'authentication.v2' }); - Sentry.pinoIntegration.ignoreLogger(child2); + Sentry.pinoIntegration.untrackLogger(child2); child2.error(new Error('oh no v2')); // This should also be ignored as the parent is ignored diff --git a/dev-packages/node-integration-tests/suites/pino/scenario.mjs b/dev-packages/node-integration-tests/suites/pino/scenario.mjs index 9588e50e6e4f..55966552a07f 100644 --- a/dev-packages/node-integration-tests/suites/pino/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/pino/scenario.mjs @@ -4,7 +4,7 @@ import pino from 'pino'; const logger = pino({ name: 'myapp' }); const ignoredLogger = pino({ name: 'ignored' }); -Sentry.pinoIntegration.ignoreLogger(ignoredLogger); +Sentry.pinoIntegration.untrackLogger(ignoredLogger); ignoredLogger.info('this will not be tracked'); diff --git a/packages/angular/README.md b/packages/angular/README.md index 384c4c2d48c8..f686fb266b23 100644 --- a/packages/angular/README.md +++ b/packages/angular/README.md @@ -16,7 +16,7 @@ ## Angular Version Compatibility -This SDK officially supports Angular 14 to 19. +This SDK officially supports Angular 14 to 20. If you're using an older Angular version please check the [compatibility table in the docs](https://docs.sentry.io/platforms/javascript/guides/angular/#angular-version-compatibility). diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 846d38eb1bcb..7c3e0c2c813f 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -203,14 +203,14 @@ interface PinoIntegrationFunction { * * @param logger A Pino logger instance. */ - ignoreLogger(logger: unknown): void; + untrackLogger(logger: unknown): void; } /** * Integration for Pino logging library. * Captures Pino logs as Sentry logs and optionally captures some log levels as events. * - * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.ignoreLogger(logger)`. + * By default, all Pino loggers will be captured. To ignore a specific logger, use `pinoIntegration.untrackLogger(logger)`. * * If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`. * @@ -222,7 +222,7 @@ export const pinoIntegration = Object.assign(_pinoIntegration, { (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'track'; } }, - ignoreLogger(logger: unknown): void { + untrackLogger(logger: unknown): void { if (logger && typeof logger === 'object' && 'levels' in logger) { (logger as Pino)[SENTRY_TRACK_SYMBOL] = 'ignore'; } From b25e47ddb20ba0788027798a12db9bbb47398898 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 16 Oct 2025 13:36:04 +0200 Subject: [PATCH 15/16] remove duplicate test --- .../suites/pino/test.ts | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index 7ab203bd9563..f9cdb143ddff 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -223,54 +223,4 @@ conditionalTest({ min: 20 })('Pino integration', () => { .start() .completed(); }); - - test('captures logs when autoInstrument is false and logger is tracked', async () => { - const instrumentPath = join(__dirname, 'instrument-auto-off.mjs'); - - await createRunner(__dirname, 'scenario-track.mjs') - .withMockSentryServer() - .withInstrument(instrumentPath) - .expect({ - log: { - items: [ - { - timestamp: expect.any(Number), - level: 'info', - body: 'hello world', - trace_id: expect.any(String), - severity_number: 9, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, - 'pino.logger.level': { value: 30, type: 'integer' }, - user: { value: 'user-id', type: 'string' }, - something: { - type: 'string', - value: '{"more":3,"complex":"nope"}', - }, - 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, - 'sentry.release': { value: '1.0', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), - }, - { - timestamp: expect.any(Number), - level: 'error', - body: 'oh no', - trace_id: expect.any(String), - severity_number: 17, - attributes: expect.objectContaining({ - 'pino.logger.name': { value: 'myapp', type: 'string' }, - 'pino.logger.level': { value: 50, type: 'integer' }, - err: { value: '{}', type: 'string' }, - 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, - 'sentry.release': { value: '1.0', type: 'string' }, - 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, - }), - }, - ], - }, - }) - .start() - .completed(); - }); }); From df50a2a5031505f0667b6e4d19bf5676c6c1881e Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 22 Oct 2025 13:55:57 +0200 Subject: [PATCH 16/16] fix(node): Pino capture serialized `err` --- dev-packages/node-integration-tests/suites/pino/test.ts | 5 +++++ packages/node-core/src/integrations/pino.ts | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index f9cdb143ddff..19fb5d80387a 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -84,6 +84,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { attributes: { name: { value: 'myapp', type: 'string' }, module: { value: 'authentication', type: 'string' }, + msg: { value: 'oh no', type: 'string' }, + err: { value: expect.any(String), type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, @@ -159,6 +161,8 @@ conditionalTest({ min: 20 })('Pino integration', () => { severity_number: 17, attributes: { name: { value: 'myapp', type: 'string' }, + msg: { value: 'oh no', type: 'string' }, + err: { value: expect.any(String), type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, @@ -211,6 +215,7 @@ conditionalTest({ min: 20 })('Pino integration', () => { name: { value: 'myapp', type: 'string' }, module: { value: 'authentication', type: 'string' }, msg: { value: 'oh no', type: 'string' }, + err: { value: expect.any(String), type: 'string' }, 'pino.logger.level': { value: 50, type: 'integer' }, 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.release': { value: '1.0', type: 'string' }, diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 7c3e0c2c813f..68c17ded1abe 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -86,12 +86,11 @@ type PinoResult = { time?: string; pid?: number; hostname?: string; - err?: Error; } & Record; function stripIgnoredFields(result: PinoResult): PinoResult { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { level, time, pid, hostname, err, ...rest } = result; + const { level, time, pid, hostname, ...rest } = result; return rest; }
+ logo +

+ Edit src/App.tsx and save to reload. +

+
+ Learn Solid + + + Learn TanStack + +